From a5cc11c7f4c66bb76f3135059b2c28b1a734c2e0 Mon Sep 17 00:00:00 2001 From: OHaiiBuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Sun, 22 Jan 2023 03:26:26 +0700 Subject: [PATCH 01/92] PlayChain keychain emulation support (#65) * feat: Apple Keychain Emulation (beta) * feat: Make PlayChain optional * fix: Store Keychains in PlayCover directories * fix: remove more logging when debug isn't enabled * fix: trailing space after keychainFolder * fix: cleanup logging functions * fix: use debugLogger from PlayKeychain instead of redefining it * fix: correctly return NotFound for non supported types * fix: Nicer keychainFolder * Revert "fix: Nicer keychainFolder" This reverts commit 992e860320661b5904fabbe474f715fbb300f626 due to it storing the keychain inside the app container instead of PlayCover's * fix: team IDs and logging * fix: indentations * fix: tighter checks for Update operations * fix: remove Package.resolved --- PlayTools.xcodeproj/project.pbxproj | 12 ++ PlayTools/MysticRunes/PlayedApple.swift | 167 ++++++++++++++++++++++++ PlayTools/PlayLoader.m | 64 +++++++++ PlayTools/PlaySettings.swift | 6 + 4 files changed, 249 insertions(+) create mode 100644 PlayTools/MysticRunes/PlayedApple.swift diff --git a/PlayTools.xcodeproj/project.pbxproj b/PlayTools.xcodeproj/project.pbxproj index 86e76384..e25d9dc2 100644 --- a/PlayTools.xcodeproj/project.pbxproj +++ b/PlayTools.xcodeproj/project.pbxproj @@ -45,6 +45,7 @@ AA719870287A81A000623C15 /* UIEvent+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = AA719842287A81A000623C15 /* UIEvent+Private.h */; }; AA719872287A81A000623C15 /* UITouch+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = AA719844287A81A000623C15 /* UITouch+Private.h */; }; AA818CB9287ABFB1000BEE9D /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA818CB8287ABFB1000BEE9D /* IOKit.framework */; }; + ABCECEE629750BA600746595 /* PlayedApple.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABCECEE529750BA600746595 /* PlayedApple.swift */; }; B127172228817AB90025112B /* SwordRPC in Frameworks */ = {isa = PBXBuildFile; productRef = B127172128817AB90025112B /* SwordRPC */; }; B127172528817C040025112B /* DiscordIPC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B127172428817C040025112B /* DiscordIPC.swift */; }; B1271729288284BE0025112B /* DiscordActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1271728288284BE0025112B /* DiscordActivity.swift */; }; @@ -107,6 +108,7 @@ AA719844287A81A000623C15 /* UITouch+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITouch+Private.h"; sourceTree = ""; }; AA818CB8287ABFB1000BEE9D /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/System/Library/Frameworks/IOKit.framework; sourceTree = DEVELOPER_DIR; }; AA818CBA287ABFD5000BEE9D /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/System/iOSSupport/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; + ABCECEE529750BA600746595 /* PlayedApple.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayedApple.swift; sourceTree = ""; }; B127172428817C040025112B /* DiscordIPC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscordIPC.swift; sourceTree = ""; }; B1271728288284BE0025112B /* DiscordActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscordActivity.swift; sourceTree = ""; }; B1E8CF8928BBE2AB004340D3 /* Keymapping.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Keymapping.swift; sourceTree = ""; }; @@ -165,6 +167,7 @@ AA7196DA287A447700623C15 /* PlayTools */ = { isa = PBXGroup; children = ( + ABCECEE729750BB100746595 /* MysticRunes */, B127172328817AC70025112B /* DiscordActivity */, AA719721287A480C00623C15 /* Controls */, AA719799287A481500623C15 /* Keymap */, @@ -262,6 +265,14 @@ name = Frameworks; sourceTree = ""; }; + ABCECEE729750BB100746595 /* MysticRunes */ = { + isa = PBXGroup; + children = ( + ABCECEE529750BA600746595 /* PlayedApple.swift */, + ); + path = MysticRunes; + sourceTree = ""; + }; B127172328817AC70025112B /* DiscordActivity */ = { isa = PBXGroup; children = ( @@ -447,6 +458,7 @@ AA7197A9287A481500623C15 /* PlayInfo.swift in Sources */, AA71986A287A81A000623C15 /* PTFakeMetaTouch.m in Sources */, AA71970C287A44D200623C15 /* PlayScreen.swift in Sources */, + ABCECEE629750BA600746595 /* PlayedApple.swift in Sources */, AA71986C287A81A000623C15 /* NSObject+Swizzle.m in Sources */, AA71970F287A44D200623C15 /* PlayCover.swift in Sources */, B127172528817C040025112B /* DiscordIPC.swift in Sources */, diff --git a/PlayTools/MysticRunes/PlayedApple.swift b/PlayTools/MysticRunes/PlayedApple.swift new file mode 100644 index 00000000..f7c13379 --- /dev/null +++ b/PlayTools/MysticRunes/PlayedApple.swift @@ -0,0 +1,167 @@ +// +// PlayWeRuinedIt.swift +// PlayTools +// +// Created by Venti on 16/01/2023. +// + +import Foundation +import Security + +// Implementation for PlayKeychain +// World's Most Advanced Keychain Replacement Solution:tm: +// This is a joke, don't take it seriously + +public class PlayKeychain: NSObject { + static let shared = PlayKeychain() + + private static func getKeychainDirectory() -> URL? { + let bundleID = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String ?? "" + let keychainFolder = URL(fileURLWithPath: "/Users/\(NSUserName())/Library/Containers/io.playcover.PlayCover") + .appendingPathComponent("PlayChain") + .appendingPathComponent(bundleID) + + // Create the keychain folder if it doesn't exist + if !FileManager.default.fileExists(atPath: keychainFolder.path) { + do { + try FileManager.default.createDirectory(at: keychainFolder, + withIntermediateDirectories: true, + attributes: nil) + } catch { + debugLogger("Failed to create keychain folder") + } + } + + return keychainFolder + } + + private static func keychainPath(_ attributes: NSDictionary) -> URL { + let keychainFolder = getKeychainDirectory() + // Generate a key path based on the key attributes + let accountName = attributes[kSecAttrAccount as String] as? String ?? "" + let serviceName = attributes[kSecAttrService as String] as? String ?? "" + let classType = attributes[kSecClass as String] as? String ?? "" + return keychainFolder! + .appendingPathComponent("\(serviceName)-\(accountName)-\(classType).plist") + } + + @objc public static func debugLogger(_ logContent: String) { + if PlaySettings.shared.settingsData.playChainDebugging { + NSLog("PC-DEBUG: \(logContent)") + } + } + // Emulates SecItemAdd, SecItemUpdate, SecItemDelete and SecItemCopyMatching + // Store the entire dictionary as a plist + // SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result) + @objc static public func add(_ attributes: NSDictionary, result: UnsafeMutablePointer?) -> OSStatus { + let keychainPath = keychainPath(attributes) + // Check if the keychain file already exists + if FileManager.default.fileExists(atPath: keychainPath.path) { + debugLogger("Keychain file already exists") + return errSecDuplicateItem + } + // Write the dictionary to the keychain file + do { + try attributes.write(to: keychainPath) + debugLogger("Wrote keychain file to \(keychainPath)") + } catch { + debugLogger("Failed to write keychain file") + return errSecIO + } + // Place v_data in the result + if let v_data = attributes["v_data"] { + result?.pointee = v_data as CFTypeRef + } + return errSecSuccess + } + + // SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate) + @objc static public func update(_ query: NSDictionary, attributesToUpdate: NSDictionary) -> OSStatus { + // Get the path to the keychain file + let keychainPath = keychainPath(query) + // Read the dictionary from the keychain file + let keychainDict = NSDictionary(contentsOf: keychainPath) + debugLogger("Read keychain file from \(keychainPath)") + // Check if the file exist + if keychainDict == nil { + debugLogger("Keychain file not found at \(keychainPath)") + return errSecItemNotFound + } + // Reconstruct the dictionary (subscripting won't work as assignment is not allowed) + let newKeychainDict = NSMutableDictionary() + for (key, value) in keychainDict! { + newKeychainDict.setValue(value, forKey: key as! String) // swiftlint:disable:this force_cast + } + // Update the dictionary + for (key, value) in attributesToUpdate { + newKeychainDict.setValue(value, forKey: key as! String) // swiftlint:disable:this force_cast + } + // Write the dictionary to the keychain file + do { + try newKeychainDict.write(to: keychainPath) + debugLogger("Wrote keychain file to \(keychainPath)") + } catch { + debugLogger("Failed to write keychain file") + return errSecIO + } + + return errSecSuccess + } + + // SecItemDelete(CFDictionaryRef query) + @objc static public func delete(_ query: NSDictionary) -> OSStatus { + // Get the path to the keychain file + let keychainPath = keychainPath(query) + // Delete the keychain file + do { + try FileManager.default.removeItem(at: keychainPath) + debugLogger("Deleted keychain file at \(keychainPath)") + } catch { + debugLogger("Failed to delete keychain file") + return errSecIO + } + return errSecSuccess + } + + // SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result) + @objc static public func copyMatching(_ query: NSDictionary, result: UnsafeMutablePointer?) + -> OSStatus { + // Get the path to the keychain file + let keychainPath = keychainPath(query) + // Read the dictionary from the keychain file + let keychainDict = NSDictionary(contentsOf: keychainPath) + // Check the `r_Attributes` key. If it is set to 1 in the query + // DROP, NOT IMPLEMENTED + let classType = query[kSecClass as String] as? String ?? "" + if query["r_Attributes"] as? Int == 1 { + return errSecItemNotFound + } + // If the keychain file doesn't exist, return errSecItemNotFound + if keychainDict == nil { + debugLogger("Keychain file not found at \(keychainPath)") + return errSecItemNotFound + } + // Return v_Data if it exists + if let vData = keychainDict!["v_Data"] { + debugLogger("Read keychain file from \(keychainPath)") + // Check the class type, if it is a key we need to return the data + // as SecKeyRef, otherwise we can return it as a CFTypeRef + if classType == "keys" { + // kSecAttrKeyType is stored as `type` in the dictionary + // kSecAttrKeyClass is stored as `kcls` in the dictionary + let keyAttributes = [ + kSecAttrKeyType: keychainDict!["type"] as! CFString, // swiftlint:disable:this force_cast + kSecAttrKeyClass: keychainDict!["kcls"] as! CFString // swiftlint:disable:this force_cast + ] + let keyData = vData as! Data // swiftlint:disable:this force_cast + let key = SecKeyCreateWithData(keyData as CFData, keyAttributes as CFDictionary, nil) + result?.pointee = key + return errSecSuccess + } + result?.pointee = vData as CFTypeRef + return errSecSuccess + } + + return errSecItemNotFound + } +} diff --git a/PlayTools/PlayLoader.m b/PlayTools/PlayLoader.m index 61a66c78..94764a68 100644 --- a/PlayTools/PlayLoader.m +++ b/PlayTools/PlayLoader.m @@ -91,6 +91,70 @@ static int pt_sysctlbyname(const char *name, void *oldp, size_t *oldlenp, void * DYLD_INTERPOSE(pt_sysctlbyname, sysctlbyname) DYLD_INTERPOSE(pt_sysctl, sysctl) +// Interpose Apple Keychain functions (SecItemCopyMatching, SecItemAdd, SecItemUpdate, SecItemDelete) +// This allows us to intercept keychain requests and return our own data + +// Use the implementations from PlayKeychain +static OSStatus pt_SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result) { + OSStatus retval; + if ([[PlaySettings shared] playChain]) { + retval = [PlayKeychain copyMatching:(__bridge NSDictionary * _Nonnull)(query) result:result]; + } else { + retval = SecItemCopyMatching(query, result); + } + if (result != NULL) { + [PlayKeychain debugLogger:[NSString stringWithFormat:@"SecItemCopyMatching: %@", query]]; + [PlayKeychain debugLogger:[NSString stringWithFormat:@"SecItemCopyMatching result: %@", *result]]; + } + return retval; +} + +static OSStatus pt_SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result) { + OSStatus retval; + if ([[PlaySettings shared] playChain]) { + retval = [PlayKeychain add:(__bridge NSDictionary * _Nonnull)(attributes) result:result]; + } else { + retval = SecItemAdd(attributes, result); + } + if (result != NULL) { + [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemAdd: %@", attributes]]; + [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemAdd result: %@", *result]]; + } + return retval; +} + +static OSStatus pt_SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate) { + OSStatus retval; + if ([[PlaySettings shared] playChain]) { + retval = [PlayKeychain update:(__bridge NSDictionary * _Nonnull)(query) attributesToUpdate:(__bridge NSDictionary * _Nonnull)(attributesToUpdate)]; + } else { + retval = SecItemUpdate(query, attributesToUpdate); + } + if (attributesToUpdate != NULL) { + [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemUpdate: %@", query]]; + [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemUpdate attributesToUpdate: %@", attributesToUpdate]]; + } + return retval; + +} + +static OSStatus pt_SecItemDelete(CFDictionaryRef query) { + OSStatus retval; + if ([[PlaySettings shared] playChain]) { + retval = [PlayKeychain delete:(__bridge NSDictionary * _Nonnull)(query)]; + } else { + retval = SecItemDelete(query); + } + [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemDelete: %@", query]]; + return retval; +} + +DYLD_INTERPOSE(pt_SecItemCopyMatching, SecItemCopyMatching) +DYLD_INTERPOSE(pt_SecItemAdd, SecItemAdd) +DYLD_INTERPOSE(pt_SecItemUpdate, SecItemUpdate) +DYLD_INTERPOSE(pt_SecItemDelete, SecItemDelete) + + @implementation PlayLoader static void __attribute__((constructor)) initialize(void) { diff --git a/PlayTools/PlaySettings.swift b/PlayTools/PlaySettings.swift index ef6fc181..0f95eab6 100644 --- a/PlayTools/PlaySettings.swift +++ b/PlayTools/PlaySettings.swift @@ -59,6 +59,10 @@ let settings = PlaySettings.shared return "J320xAP" } }() + + @objc lazy var playChain = settingsData.playChain + + @objc lazy var playChainDebugging = settingsData.playChainDebugging } struct AppSettingsData: Codable { @@ -76,4 +80,6 @@ struct AppSettingsData: Codable { var bypass = false var discordActivity = DiscordActivity() var version = "2.0.0" + var playChain = false + var playChainDebugging = false } From 2138fc8aace739c2998fe8c1b87d65a3a3eec8be Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Sat, 4 Feb 2023 09:48:38 +0800 Subject: [PATCH 02/92] refactor button handler --- PlayTools/Controls/PlayAction.swift | 39 ++----------------------- PlayTools/Controls/PlayInput.swift | 42 +++++++++++++++++++++++---- PlayTools/Controls/PlayMice.swift | 44 ++++++++--------------------- 3 files changed, 49 insertions(+), 76 deletions(-) diff --git a/PlayTools/Controls/PlayAction.swift b/PlayTools/Controls/PlayAction.swift index a346578e..c2b6de93 100644 --- a/PlayTools/Controls/PlayAction.swift +++ b/PlayTools/Controls/PlayAction.swift @@ -19,16 +19,6 @@ extension GCKeyboard { class ButtonAction: Action { func invalidate() { Toucher.touchcam(point: point, phase: UITouch.Phase.ended, tid: &id) - if let gcKey = GCKeyboard.coalesced?.keyboardInput?.button(forKeyCode: keyCode) { - gcKey.pressedChangedHandler = nil - - } else if let gcControllerElement = GCController.current?.extendedGamepad?.elements[keyName] { - - if let gcControllerButton = gcControllerElement as? GCControllerButtonInput { - gcControllerButton.pressedChangedHandler = nil - } - - } } let keyCode: GCKeyCode @@ -36,37 +26,12 @@ class ButtonAction: Action { let point: CGPoint var id: Int? - private func getChangedHandler(handler: ((T1, Float, Bool) -> Void)?) -> (T1, Float, Bool) -> Void { - return { button, value, pressed in - if !PlayInput.cmdPressed() { - self.update(pressed: pressed) - } - if let previous = handler { - previous(button, value, pressed) - } - } - } - init(keyCode: GCKeyCode, keyName: String, point: CGPoint) { self.keyCode = keyCode self.keyName = keyName self.point = point - if PlayMice.shared.setMiceButtons(keyCode.rawValue, action: self) { - // No more work to do for mouse buttons - } else if let gcKey = GCKeyboard.coalesced!.keyboardInput!.button(forKeyCode: keyCode) { - let handler = gcKey.pressedChangedHandler - gcKey.pressedChangedHandler = getChangedHandler(handler: handler) - - } else if let gcControllerElement = GCController.current?.extendedGamepad?.elements[keyName] { - - if let gcControllerButton = gcControllerElement as? GCControllerButtonInput { - let handler = gcControllerButton.pressedChangedHandler - gcControllerButton.pressedChangedHandler = getChangedHandler(handler: handler) - } - - } else { - Toast.showOver(msg: "failed to map button at point \(point)") - } + // TODO: set both key names in draggable button, so as to depracate key code + PlayInput.registerButton(key: KeyCodeNames.keyCodes[keyCode.rawValue]!, handler: self.update) } convenience init(data: Button) { diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 0698c746..26937322 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -10,11 +10,41 @@ class PlayInput { static private var lCmdPressed = false static private var rCmdPressed = false + static public var buttonHandlers: [String: [(Bool) -> Void]] = [:] + func invalidate() { PlayMice.shared.stop() for action in self.actions { action.invalidate() } + PlayInput.buttonHandlers.removeAll(keepingCapacity: true) + GCKeyboard.coalesced!.keyboardInput!.keyChangedHandler = nil + GCController.current?.extendedGamepad?.valueChangedHandler = nil + } + + static public func registerButton(key: String, handler: @escaping (Bool) -> Void) { + if PlayInput.buttonHandlers[key] == nil { + PlayInput.buttonHandlers[key] = [] + } + PlayInput.buttonHandlers[key]!.append(handler) + } + + func keyboardHandler(_: GCKeyboardInput, _: GCControllerButtonInput, keyCode: GCKeyCode, pressed: Bool) { + if PlayInput.cmdPressed() { return } + guard let handlers = PlayInput.buttonHandlers[KeyCodeNames.keyCodes[keyCode.rawValue]!] else { return } + for handler in handlers { + handler(pressed) + } + } + + func controllerButtonHandler(_: GCExtendedGamepad, element: GCControllerElement) { + guard let buttonElement = element as? GCControllerButtonInput else { return } + // TODO: handle analog input here too + let name: String = element.aliases.first! + guard let handlers = PlayInput.buttonHandlers[name] else { return } + for handler in handlers { + handler(buttonElement.isPressed) + } } func parseKeymap() { @@ -56,21 +86,21 @@ class PlayInput { } } if let controller = GCController.current?.extendedGamepad { + // TODO: direction pad is analog controller.valueChangedHandler = { _, element in // This is the index of controller buttons, which is String, not Int let alias: String! = element.aliases.first EditorController.shared.setKey(alias) } } - } else { - GCKeyboard.coalesced!.keyboardInput!.keyChangedHandler = nil - GCController.current?.extendedGamepad?.valueChangedHandler = nil } } func setup() { parseKeymap() + GCKeyboard.coalesced!.keyboardInput!.keyChangedHandler = keyboardHandler + GCController.current?.extendedGamepad?.valueChangedHandler = controllerButtonHandler for mouse in GCMouse.mice() { if settings.mouseMapping { mouse.mouseInput?.mouseMovedHandler = PlayMice.shared.handleMouseMoved @@ -118,7 +148,7 @@ class PlayInput { return screen.window?.rootViewController } - func setupShortcuts() { + func setupHotkeys() { if let keyboard = GCKeyboard.coalesced?.keyboardInput { keyboard.button(forKeyCode: .leftGUI)?.pressedChangedHandler = { _, _, pressed in PlayInput.lCmdPressed = pressed @@ -144,7 +174,7 @@ class PlayInput { let main = OperationQueue.main centre.addObserver(forName: NSNotification.Name.GCKeyboardDidConnect, object: nil, queue: main) { _ in - self.setupShortcuts() + self.setupHotkeys() if !mode.visible { self.setup() } @@ -162,7 +192,7 @@ class PlayInput { } } - setupShortcuts() + setupHotkeys() // Fix beep sound AKInterface.shared! diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index 5fec34b6..a15f6d76 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -142,42 +142,28 @@ public class PlayMice { } public func stop() { - mouseActions.keys.forEach { key in - mouseActions[key] = [] - } for mouse in GCMouse.mice() { mouse.mouseInput?.mouseMovedHandler = { _, _, _ in} } } - func setMiceButtons(_ keyId: Int, action: ButtonAction) -> Bool { - if (-3 ... -1).contains(keyId) { - setMiceButton(keyId, action: action) - return true - } - return false - } - - var mouseActions: [Int: [ButtonAction]] = [2: [], 8: [], 33554432: []] + // TODO: get rid of this shit + var buttonIndex: [Int: Int] = [2: -1, 8: -2, 33554432: -3] private func setupMouseButton(_up: Int, _down: Int) { AKInterface.shared!.setupMouseButton(_up, _down, dontIgnore(_:_:_:)) } - private func dontIgnore(_ actionIndex: Int, _ state: Bool, _ isEventWindow: Bool) -> Bool { + private func dontIgnore(_ actionIndex: Int, _ pressed: Bool, _ isEventWindow: Bool) -> Bool { if EditorController.shared.editorMode { - if state { - if actionIndex == 8 { - EditorController.shared.setKey(-2) - } else if actionIndex == 33554432 { - EditorController.shared.setKey(-3) - } + if pressed && actionIndex != 2 { + EditorController.shared.setKey(buttonIndex[actionIndex]!) } return true } if self.acceptMouseEvents { let curPos = self.cursorPos() - if state { + if pressed { if !self.fakedMousePressed // For traffic light buttons when not fullscreen && curPos.y > 0 @@ -197,23 +183,15 @@ public class PlayMice { return true } if !mode.visible { - self.mouseActions[actionIndex]!.forEach({ buttonAction in - buttonAction.update(pressed: state) - }) + if let handlers = PlayInput.buttonHandlers[KeyCodeNames.keyCodes[buttonIndex[actionIndex]!]!] { + for handler in handlers { + handler(pressed) + } + } return false } return true } - - private func setMiceButton(_ keyId: Int, action: ButtonAction) { - switch keyId { - case -1: mouseActions[2]!.append(action) - case -2: mouseActions[8]!.append(action) - case -3: mouseActions[33554432]!.append(action) - default: - mouseActions[2]!.append(action) - } - } } class CameraAction: Action { From 44acd1d664c87443db210078b6c9654f13c15579 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Tue, 7 Feb 2023 01:19:56 +0800 Subject: [PATCH 03/92] refactor controller input handling --- .../Controls/PTFakeTouch/PTFakeMetaTouch.m | 2 +- PlayTools/Controls/PlayAction.swift | 51 ++---- PlayTools/Controls/PlayInput.swift | 48 ++++-- PlayTools/Controls/PlayMice.swift | 160 ++++++++++-------- PlayTools/Keymap/ControlModel.swift | 2 + PlayTools/Keymap/KeyCodeNames.swift | 1 + PlayTools/Keymap/Keymapping.swift | 3 +- 7 files changed, 150 insertions(+), 117 deletions(-) diff --git a/PlayTools/Controls/PTFakeTouch/PTFakeMetaTouch.m b/PlayTools/Controls/PTFakeTouch/PTFakeMetaTouch.m index cfcd3c5b..b3e76150 100644 --- a/PlayTools/Controls/PTFakeTouch/PTFakeMetaTouch.m +++ b/PlayTools/Controls/PTFakeTouch/PTFakeMetaTouch.m @@ -65,7 +65,7 @@ + (NSInteger)fakeTouchId: (NSInteger)pointId AtPoint: (CGPoint)point withTouchPh touch = toStationarify; toStationarify = NULL; if(touch.phase == UITouchPhaseBegan) { - [touch setPhaseAndUpdateTimestamp:UITouchPhaseStationary]; + [touch setPhaseAndUpdateTimestamp:UITouchPhaseMoved]; } } // respect the semantics of touch phase, allocate new touch on touch began. diff --git a/PlayTools/Controls/PlayAction.swift b/PlayTools/Controls/PlayAction.swift index c2b6de93..9db0181f 100644 --- a/PlayTools/Controls/PlayAction.swift +++ b/PlayTools/Controls/PlayAction.swift @@ -30,8 +30,10 @@ class ButtonAction: Action { self.keyCode = keyCode self.keyName = keyName self.point = point + let code = keyCode.rawValue // TODO: set both key names in draggable button, so as to depracate key code - PlayInput.registerButton(key: KeyCodeNames.keyCodes[keyCode.rawValue]!, handler: self.update) + PlayInput.registerButton(key: code == KeyCodeNames.defaultCode ? keyName: KeyCodeNames.keyCodes[code]!, + handler: self.update) } convenience init(data: Button) { @@ -59,7 +61,6 @@ class DraggableButtonAction: ButtonAction { override init(keyCode: GCKeyCode, keyName: String, point: CGPoint) { self.releasePoint = point super.init(keyCode: keyCode, keyName: keyName, point: point) - _ = PlayMice.shared.setupThumbstickChangedHandler(name: keyName) } override func update(pressed: Bool) { @@ -101,10 +102,10 @@ class ContinuousJoystickAction: Action { self.key = data.keyName position = center self.sensitivity = data.transform.size.absoluteSize / 4 - if PlayMice.shared.setupThumbstickChangedHandler(name: key) { - PlayMice.shared.joystickHandler[key] = thumbstickUpdate + if key == PlayMice.elementName { + PlayMice.shared.joystickHandler[key] = self.mouseUpdate } else { - PlayMice.shared.joystickHandler[key] = mouseUpdate + PlayMice.shared.joystickHandler[key] = self.thumbstickUpdate } } @@ -151,16 +152,8 @@ class JoystickAction: Action { self.keys = keys self.center = center self.shift = shift / 2 - if let keyboard = GCKeyboard.coalesced?.keyboardInput { - for key in keys { - let handler = keyboard.button(forKeyCode: key)?.pressedChangedHandler - keyboard.button(forKeyCode: key)?.pressedChangedHandler = { button, value, pressed in - Toucher.touchQueue.async(execute: self.update) - if let previous = handler { - previous(button, value, pressed) - } - } - } + for key in keys { + PlayInput.registerButton(key: KeyCodeNames.keyCodes[key.rawValue]!, handler: self.update) } } @@ -181,28 +174,19 @@ class JoystickAction: Action { func invalidate() { Toucher.touchcam(point: center, phase: UITouch.Phase.ended, tid: &id) self.moving = false - if let keyboard = GCKeyboard.coalesced?.keyboardInput { - for key in keys { - keyboard.button(forKeyCode: key)?.pressedChangedHandler = nil - } - } } - func update() { - if mode.visible { - return - } + func update(_: Bool) { var touch = center - var start = center if GCKeyboard.pressed(key: keys[0]) { - touch.y -= shift / 3 + touch.y -= shift / 2 } else if GCKeyboard.pressed(key: keys[1]) { - touch.y += shift / 3 + touch.y += shift / 2 } if GCKeyboard.pressed(key: keys[2]) { - touch.x -= shift / 3 + touch.x -= shift / 2 } else if GCKeyboard.pressed(key: keys[3]) { - touch.x += shift / 3 + touch.x += shift / 2 } if moving { if touch.equalTo(center) { @@ -213,15 +197,8 @@ class JoystickAction: Action { } } else { if !touch.equalTo(center) { - start.x += (touch.x - start.x) / 8 - start.y += (touch.y - start.y) / 8 moving = true - Toucher.touchcam(point: start, phase: UITouch.Phase.began, tid: &id) - Toucher.touchQueue.asyncAfter(deadline: .now() + 0.04) { - if self.moving { - Toucher.touchcam(point: touch, phase: UITouch.Phase.moved, tid: &self.id) - } // end if - } // end closure + Toucher.touchcam(point: touch, phase: UITouch.Phase.began, tid: &id) } // end if } // end else } diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 26937322..cbc36df5 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -29,21 +29,30 @@ class PlayInput { PlayInput.buttonHandlers[key]!.append(handler) } - func keyboardHandler(_: GCKeyboardInput, _: GCControllerButtonInput, keyCode: GCKeyCode, pressed: Bool) { + func keyboardHandler(_: GCKeyboardInput, _: GCControllerButtonInput, _ keyCode: GCKeyCode, _ pressed: Bool) { if PlayInput.cmdPressed() { return } - guard let handlers = PlayInput.buttonHandlers[KeyCodeNames.keyCodes[keyCode.rawValue]!] else { return } + guard let handlers = PlayInput.buttonHandlers[KeyCodeNames.keyCodes[keyCode.rawValue]!] else { + // TODO: usage hint of disabling keymapping is planned to be added here + return + } for handler in handlers { handler(pressed) } } - func controllerButtonHandler(_: GCExtendedGamepad, element: GCControllerElement) { - guard let buttonElement = element as? GCControllerButtonInput else { return } - // TODO: handle analog input here too + func controllerButtonHandler(_ profile: GCExtendedGamepad, _ element: GCControllerElement) { let name: String = element.aliases.first! - guard let handlers = PlayInput.buttonHandlers[name] else { return } - for handler in handlers { - handler(buttonElement.isPressed) + if let buttonElement = element as? GCControllerButtonInput { +// Toast.showOver(msg: "recognised controller button: \(name)") + guard let handlers = PlayInput.buttonHandlers[name] else { return } + Toast.showOver(msg: name + ": \(buttonElement.isPressed)") + for handler in handlers { + handler(buttonElement.isPressed) + } + } else if let dpadElement = element as? GCControllerDirectionPad { + PlayMice.shared.handleControllerDirectionPad(profile, dpadElement) + } else { + Toast.showOver(msg: "unrecognised controller element input happens") } } @@ -86,10 +95,25 @@ class PlayInput { } } if let controller = GCController.current?.extendedGamepad { - // TODO: direction pad is analog controller.valueChangedHandler = { _, element in // This is the index of controller buttons, which is String, not Int - let alias: String! = element.aliases.first + var alias: String = element.aliases.first! + if alias == "Direction Pad" { + guard let dpadElement = element as? GCControllerDirectionPad else { + Toast.showOver(msg: "cannot map direction pad: element type not recognizable") + return + } + if dpadElement.xAxis.value > 0 { + alias = dpadElement.right.aliases.first! + } else if dpadElement.xAxis.value < 0 { + alias = dpadElement.left.aliases.first! + } + if dpadElement.yAxis.value > 0 { + alias = dpadElement.down.aliases.first! + } else if dpadElement.yAxis.value < 0 { + alias = dpadElement.up.aliases.first! + } + } EditorController.shared.setKey(alias) } } @@ -162,6 +186,7 @@ class PlayInput { keyboard.button(forKeyCode: .rightAlt)?.pressedChangedHandler = { _, _, pressed in self.swapMode(pressed) } + // TODO: set a timeout to display usage guide of Option and Keymapping menu in turn } } @@ -190,6 +215,9 @@ class PlayInput { if !mode.visible { self.setup() } + if EditorController.shared.editorMode { + self.toggleEditor(show: true) + } } setupHotkeys() diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index a15f6d76..8fabe3a1 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -28,7 +28,10 @@ public class PlayMice { var fakedMouseTouchPointId: Int? var fakedMousePressed: Bool {fakedMouseTouchPointId != nil} - private var thumbstickVelocity: CGVector = CGVector.zero + private var directionPadXValue: Float = 0, + directionPadYValue: Float = 0, + thumbstickCursorControl: [String: (((CGFloat, CGFloat) -> Void)?, CGFloat, CGFloat) -> Void] + = ["Left Thumbstick": ThumbstickCursorControl().update, "Right Thumbstick": ThumbstickCursorControl().update] public var draggableHandler: [String: (CGFloat, CGFloat) -> Void] = [:], cameraMoveHandler: [String: (CGFloat, CGFloat) -> Void] = [:], cameraScaleHandler: [String: (CGFloat, CGFloat) -> Void] = [:], @@ -58,10 +61,6 @@ public class PlayMice { return point } - static private func isVectorSignificant(_ vector: CGVector) -> Bool { - return vector.dx.magnitude + vector.dy.magnitude > 0.2 - } - public func setupScrollWheelHandler() { AKInterface.shared!.setupScrollWheel({deltaX, deltaY in if let cameraScale = self.cameraScaleHandler[PlayMice.elementName] { @@ -73,53 +72,31 @@ public class PlayMice { }) } - public func setupThumbstickChangedHandler(name: String) -> Bool { - if let thumbstick = GCController.current?.extendedGamepad?.elements[name] as? GCControllerDirectionPad { - thumbstick.valueChangedHandler = { _, deltaX, deltaY in - if !PlayMice.isVectorSignificant(self.thumbstickVelocity) { - if let closure = self.thumbstickPoll(name) { - DispatchQueue.main.async(execute: closure) - } - } - self.thumbstickVelocity.dx = CGFloat(deltaX * 6) - self.thumbstickVelocity.dy = CGFloat(deltaY * 6) -// Toast.showOver(msg: "thumbstick") - if let joystickUpdate = self.joystickHandler[name] { - joystickUpdate(self.thumbstickVelocity.dx, self.thumbstickVelocity.dy) - } + public func handleControllerDirectionPad(_ profile: GCExtendedGamepad, _ dpad: GCControllerDirectionPad) { + let name = dpad.aliases.first! + let xAxis = dpad.xAxis, yAxis = dpad.yAxis + if name == "Direction Pad" { + if (xAxis.value > 0) != (directionPadXValue > 0) { + PlayInput.shared.controllerButtonHandler(profile, dpad.right) } - return true - } - return false - } - - private func thumbstickPoll(_ name: String) -> (() -> Void)? { -// DispatchQueue.main.async { -// Toast.showOver(msg: "polling") -// } - let draggableUpdate = self.draggableHandler[name] - let cameraUpdate = self.cameraMoveHandler[name] - if draggableUpdate == nil && cameraUpdate == nil { - return nil - } - return { - if PlayMice.isVectorSignificant(self.thumbstickVelocity) { - var captured = false - if let draggableUpdate = self.draggableHandler[name] { - draggableUpdate(self.thumbstickVelocity.dx, self.thumbstickVelocity.dy) - captured = true - } - if !captured { - if let cameraUpdate = self.cameraMoveHandler[name] { - cameraUpdate(self.thumbstickVelocity.dx, self.thumbstickVelocity.dy) - } - } - if let closure = self.thumbstickPoll(name) { - DispatchQueue.main.asyncAfter( - deadline: DispatchTime.now() + 0.017, execute: closure) - } + if (xAxis.value < 0) != (directionPadXValue < 0) { + PlayInput.shared.controllerButtonHandler(profile, dpad.left) + } + if (yAxis.value > 0) != (directionPadYValue > 0) { + PlayInput.shared.controllerButtonHandler(profile, dpad.up) + } + if (yAxis.value < 0) != (directionPadYValue < 0) { + PlayInput.shared.controllerButtonHandler(profile, dpad.down) } + directionPadXValue = xAxis.value + directionPadYValue = yAxis.value + return } + let deltaX = xAxis.value, deltaY = yAxis.value + let cgDx = CGFloat(deltaX) + let cgDy = CGFloat(deltaY) + thumbstickCursorControl[name]!(draggableHandler[name] ?? cameraMoveHandler[name], cgDx * 6, cgDy * 6) + joystickHandler[name]?(cgDx, cgDy) } public func handleFakeMouseMoved(_: GCMouseInput, deltaX: Float, deltaY: Float) { @@ -148,7 +125,7 @@ public class PlayMice { } // TODO: get rid of this shit - var buttonIndex: [Int: Int] = [2: -1, 8: -2, 33554432: -3] + let buttonIndex: [Int: Int] = [2: -1, 8: -2, 33554432: -3] private func setupMouseButton(_up: Int, _down: Int) { AKInterface.shared!.setupMouseButton(_up, _down, dontIgnore(_:_:_:)) @@ -194,8 +171,46 @@ public class PlayMice { } } +class ThumbstickCursorControl { + private var thumbstickVelocity: CGVector = CGVector.zero, + thumbstickPolling: Bool = false, + eventHandler: ((CGFloat, CGFloat) -> Void)! + + static private func isVectorSignificant(_ vector: CGVector) -> Bool { + return vector.dx.magnitude + vector.dy.magnitude > 0.2 + } + + public func update(handler: ((CGFloat, CGFloat) -> Void)?, velocityX: CGFloat, velocityY: CGFloat) { + guard let hdlr = handler else { + if thumbstickPolling { + self.thumbstickVelocity.dx = 0 + self.thumbstickVelocity.dy = 0 + } + return + } + self.eventHandler = hdlr + self.thumbstickVelocity.dx = velocityX + self.thumbstickVelocity.dy = velocityY + if !thumbstickPolling { + DispatchQueue.main.async(execute: self.thumbstickPoll) + self.thumbstickPolling = true + } + } + + private func thumbstickPoll() { + if !ThumbstickCursorControl.isVectorSignificant(self.thumbstickVelocity) { + self.thumbstickPolling = false + return + } + self.eventHandler(self.thumbstickVelocity.dx, self.thumbstickVelocity.dy) + DispatchQueue.main.asyncAfter( + deadline: DispatchTime.now() + 0.017, execute: self.thumbstickPoll) + } +} + class CameraAction: Action { var swipeMove, swipeScale1, swipeScale2: SwipeAction + static var swipeDrag = SwipeAction() var key: String! var center: CGPoint var distance1: CGFloat = 100, distance2: CGFloat = 100 @@ -207,7 +222,6 @@ class CameraAction: Action { swipeMove = SwipeAction() swipeScale1 = SwipeAction() swipeScale2 = SwipeAction() - _ = PlayMice.shared.setupThumbstickChangedHandler(name: key) PlayMice.shared.cameraMoveHandler[key] = self.moveUpdated PlayMice.shared.cameraScaleHandler[PlayMice.elementName] = self.scaleUpdated } @@ -234,22 +248,32 @@ class CameraAction: Action { // Event handlers SHOULD be SMALL // DO NOT check things like mode.visible in an event handler // change the handler itself instead - func dragUpdated(_ deltaX: CGFloat, _ deltaY: CGFloat) { - swipeMove.move(from: PlayMice.shared.cursorPos, deltaX: deltaX * 4, deltaY: -deltaY * 4) + static func dragUpdated(_ deltaX: CGFloat, _ deltaY: CGFloat) { + swipeDrag.move(from: PlayMice.shared.cursorPos, deltaX: deltaX * 4, deltaY: -deltaY * 4) } func invalidate() { PlayMice.shared.cameraMoveHandler.removeValue(forKey: key) - PlayMice.shared.cameraScaleHandler[PlayMice.elementName] = self.dragUpdated + PlayMice.shared.cameraScaleHandler[PlayMice.elementName] = CameraAction.dragUpdated + swipeMove.invalidate() + swipeScale1.invalidate() + swipeScale2.invalidate() } } class SwipeAction: Action { var location: CGPoint = CGPoint.zero var id: Int? + let timer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.main) init() { // in rare cases the cooldown reset task is lost by the dispatch queue self.cooldown = false + // TODO: camera mode switch: Flexibility v.s. Precision + // TODO: find reasonable ways to promote fake touch priority + timer.schedule(deadline: DispatchTime.now() + 1, repeating: 0.3, leeway: DispatchTimeInterval.never) + timer.setEventHandler(qos: DispatchQoS.background, handler: self.checkEnded) + timer.activate() + timer.suspend() } func delay(_ delay: Double, closure: @escaping () -> Void) { @@ -261,23 +285,21 @@ class SwipeAction: Action { var counter = 0 // if should wait before beginning next touch var cooldown = false - // in how many tests has this been identified as stationary - var stationaryCount = 0 - let stationaryThreshold = 2 + var lastCounter = 0 func checkEnded() { - // if been stationary for enough time - if self.stationaryCount < self.stationaryThreshold || (self.stationaryCount < 20 - self.counter) { - self.stationaryCount += 1 - self.delay(0.04, closure: self.checkEnded) - return + if self.counter == self.lastCounter { + if self.counter < 24 { + counter += 24 + } else { + timer.suspend() + self.doLiftOff() + } } - self.doLiftOff() + self.lastCounter = self.counter } public func move(from: () -> CGPoint, deltaX: CGFloat, deltaY: CGFloat) { - // count touch duration - counter += 1 if id == nil { if cooldown { return @@ -285,12 +307,13 @@ class SwipeAction: Action { counter = 0 location = from() Toucher.touchcam(point: location, phase: UITouch.Phase.began, tid: &id) - delay(0.01, closure: checkEnded) + timer.resume() } + // count touch duration + counter += 1 self.location.x += deltaX self.location.y -= deltaY Toucher.touchcam(point: self.location, phase: UITouch.Phase.moved, tid: &id) - stationaryCount = 0 } public func doLiftOff() { @@ -308,6 +331,7 @@ class SwipeAction: Action { } func invalidate() { - // pass + timer.cancel() + self.doLiftOff() } } diff --git a/PlayTools/Keymap/ControlModel.swift b/PlayTools/Keymap/ControlModel.swift index b5ea7dc4..28fc7f42 100644 --- a/PlayTools/Keymap/ControlModel.swift +++ b/PlayTools/Keymap/ControlModel.swift @@ -1,5 +1,7 @@ import GameController +// Data structure definition should match those in +// https://github.com/PlayCover/PlayCover/blob/develop/PlayCover/Model/Keymapping.swift class ControlData { var keyCodes: [Int] var keyName: String diff --git a/PlayTools/Keymap/KeyCodeNames.swift b/PlayTools/Keymap/KeyCodeNames.swift index 8df24d67..5c2c888e 100644 --- a/PlayTools/Keymap/KeyCodeNames.swift +++ b/PlayTools/Keymap/KeyCodeNames.swift @@ -1,3 +1,4 @@ +// Should match https://github.com/PlayCover/PlayCover/blob/develop/PlayCover/Model/KeyCodeNames.swift exactly class KeyCodeNames { public static let defaultCode = -10 diff --git a/PlayTools/Keymap/Keymapping.swift b/PlayTools/Keymap/Keymapping.swift index 5fb866de..af3fb26b 100644 --- a/PlayTools/Keymap/Keymapping.swift +++ b/PlayTools/Keymap/Keymapping.swift @@ -63,7 +63,8 @@ class Keymapping { } } } - +// Data structure definition should match those in +// https://github.com/PlayCover/PlayCover/blob/develop/PlayCover/Model/Keymapping.swift struct KeyModelTransform: Codable { var size: CGFloat var xCoord: CGFloat From f640c406a63b2c7b71d0f3d2025f20a60e0b06f1 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 7 Feb 2023 17:36:22 +0700 Subject: [PATCH 04/92] fix: Prevent ObjC object dereferencing if debugging is not enabled --- PlayTools/PlayLoader.m | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/PlayTools/PlayLoader.m b/PlayTools/PlayLoader.m index 94764a68..f4089391 100644 --- a/PlayTools/PlayLoader.m +++ b/PlayTools/PlayLoader.m @@ -103,8 +103,10 @@ static OSStatus pt_SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result) retval = SecItemCopyMatching(query, result); } if (result != NULL) { - [PlayKeychain debugLogger:[NSString stringWithFormat:@"SecItemCopyMatching: %@", query]]; - [PlayKeychain debugLogger:[NSString stringWithFormat:@"SecItemCopyMatching result: %@", *result]]; + if ([[PlaySettings shared] playChainDebugging]) { + [PlayKeychain debugLogger:[NSString stringWithFormat:@"SecItemCopyMatching: %@", query]]; + [PlayKeychain debugLogger:[NSString stringWithFormat:@"SecItemCopyMatching result: %@", *result]]; + } } return retval; } @@ -117,8 +119,10 @@ static OSStatus pt_SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result) { retval = SecItemAdd(attributes, result); } if (result != NULL) { - [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemAdd: %@", attributes]]; - [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemAdd result: %@", *result]]; + if ([[PlaySettings shared] playChainDebugging]) { + [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemAdd: %@", attributes]]; + [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemAdd result: %@", *result]]; + } } return retval; } @@ -131,8 +135,10 @@ static OSStatus pt_SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attribut retval = SecItemUpdate(query, attributesToUpdate); } if (attributesToUpdate != NULL) { - [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemUpdate: %@", query]]; - [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemUpdate attributesToUpdate: %@", attributesToUpdate]]; + if ([[PlaySettings shared] playChainDebugging]) { + [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemUpdate: %@", query]]; + [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemUpdate attributesToUpdate: %@", attributesToUpdate]]; + } } return retval; @@ -145,7 +151,9 @@ static OSStatus pt_SecItemDelete(CFDictionaryRef query) { } else { retval = SecItemDelete(query); } - [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemDelete: %@", query]]; + if ([[PlaySettings shared] playChainDebugging]) { + [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemDelete: %@", query]]; + } return retval; } From 197a3c808c60016ec41e30ea0d0a6c51be05d0f7 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Thu, 9 Feb 2023 01:58:01 +0800 Subject: [PATCH 05/92] add new hint toast --- PlayTools/Controls/ControlMode.swift | 12 ++++ PlayTools/Controls/PlayInput.swift | 15 ++++ PlayTools/Keymap/EditorController.swift | 7 +- PlayTools/Utils/Toast.swift | 92 +++++++++++++++++++++++++ 4 files changed, 124 insertions(+), 2 deletions(-) diff --git a/PlayTools/Controls/ControlMode.swift b/PlayTools/Controls/ControlMode.swift index 35661efb..df298807 100644 --- a/PlayTools/Controls/ControlMode.swift +++ b/PlayTools/Controls/ControlMode.swift @@ -16,6 +16,8 @@ public class ControlMode { if !editor.editorMode { if show { if !visible { + NotificationCenter.default.post(name: NSNotification.Name.playtoolsKeymappingWillDisable, + object: nil, userInfo: [:]) if screen.fullscreen { screen.switchDock(true) } @@ -26,6 +28,8 @@ public class ControlMode { } } else { if visible { + NotificationCenter.default.post(name: NSNotification.Name.playtoolsKeymappingWillEnable, + object: nil, userInfo: [:]) if PlaySettings.shared.mouseMapping { AKInterface.shared!.hideCursor() } @@ -40,3 +44,11 @@ public class ControlMode { } } } + +extension NSNotification.Name { + public static let playtoolsKeymappingWillEnable: NSNotification.Name + = NSNotification.Name("playtools.keymappingWillEnable") + + public static let playtoolsKeymappingWillDisable: NSNotification.Name + = NSNotification.Name("playtools.keymappingWillDisable") +} diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 0698c746..6b031afd 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -163,6 +163,21 @@ class PlayInput { } setupShortcuts() + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5) { + if !settings.mouseMapping || !mode.visible { + return + } + Toast.showHint(title: "Keymapping Disabled", text: ["Press ", "option ⌥", " to enable keymapping"], + notification: NSNotification.Name.playtoolsKeymappingWillEnable) + let center = NotificationCenter.default + var token: NSObjectProtocol? + token = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillEnable, + object: nil, queue: OperationQueue.main) { _ in + center.removeObserver(token!) + Toast.showHint(title: "Keymapping Enabled", text: ["Press ", "option ⌥", " to disable keymapping"], + notification: NSNotification.Name.playtoolsKeymappingWillDisable) + } + } // Fix beep sound AKInterface.shared! diff --git a/PlayTools/Keymap/EditorController.swift b/PlayTools/Keymap/EditorController.swift index 1fe47c66..c6f7075e 100644 --- a/PlayTools/Keymap/EditorController.swift +++ b/PlayTools/Keymap/EditorController.swift @@ -60,14 +60,17 @@ class EditorController { previousWindow?.makeKeyAndVisible() PlayInput.shared.toggleEditor(show: false) focusedControl = nil - Toast.showOver(msg: "Keymapping saved") + Toast.showHint(title: "Keymap Saved") } else { PlayInput.shared.toggleEditor(show: true) previousWindow = screen.keyWindow editorWindow = initWindow() editorWindow?.makeKeyAndVisible() showButtons() - Toast.showOver(msg: "Click to start keymmaping edit") + Toast.showHint(title: "Keymapping Editor", + text: ["Click a button to edit its position or key bind\n" + + "Click an empty area to open input menu"], + notification: NSNotification.Name.playtoolsKeymappingWillEnable) } // Toast.showOver(msg: "\(UIApplication.shared.windows.count)") lock.unlock() diff --git a/PlayTools/Utils/Toast.swift b/PlayTools/Utils/Toast.swift index 4bf90244..f6540a87 100644 --- a/PlayTools/Utils/Toast.swift +++ b/PlayTools/Utils/Toast.swift @@ -12,6 +12,98 @@ class Toast { Toast.show(message: msg, parent: parent) } } + static var hintView: [UIView] = [] + + private static let gap: CGFloat = 40 + + public static func hideHint(hint: UIView) { + let id = hintView.firstIndex(of: hint)! + for index in 0.. id { + hintView[index-1] = hintView[index] + } + } + hintView.removeLast() + UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveEaseOut, animations: { + hint.alpha = 0.0 + }, completion: {_ in + hint.removeFromSuperview() + }) + } + + private static func getAttributedString(title: String, text: [String]) -> NSMutableAttributedString { + var heading = title + if !text.isEmpty { + heading += "\n" + } + let txt = NSMutableAttributedString(string: text.reduce(into: heading, { result, string in + result += string + })) + var messageLength = 0 + var highlight = false + for msg in text { + txt.addAttribute(.foregroundColor, value: highlight ? UIColor.cyan: UIColor.white, + range: NSRange(location: heading.count + messageLength, length: msg.count)) + highlight = !highlight + messageLength += msg.count + } + let style = NSMutableParagraphStyle() + style.alignment = .center + txt.addAttribute(.paragraphStyle, value: style, + range: NSRange(location: 0, length: heading.count + messageLength)) + txt.addAttribute(.font, value: UIFont.systemFont(ofSize: 28, weight: .bold), + range: NSRange(location: 0, length: heading.count)) + txt.addAttribute(.foregroundColor, value: UIColor.white, + range: NSRange(location: 0, length: heading.count)) + txt.addAttribute(.font, value: UIFont.systemFont(ofSize: 28), + range: NSRange(location: heading.count, length: messageLength)) + return txt + } + + public static func showHint(title: String, text: [String] = [], timeout: Double = 3, + notification: NSNotification.Name? = nil) { + let parent = screen.keyWindow! + + // Width and height here serve as an upper limit. + // Text would fill width first, then wrap, then fill height, then scroll + let messageLabel = UITextView(frame: CGRect(x: 0, y: 0, width: 800, height: 800)) + messageLabel.attributedText = getAttributedString(title: title, text: text) + messageLabel.backgroundColor = UIColor.black.withAlphaComponent(0.5) + messageLabel.alpha = 1.0 + messageLabel.clipsToBounds = true + messageLabel.isUserInteractionEnabled = false + messageLabel.frame.size = messageLabel.sizeThatFits(messageLabel.frame.size) + messageLabel.layer.cornerCurve = CALayerCornerCurve.continuous + messageLabel.layer.cornerRadius = messageLabel.frame.size.height / 4 + messageLabel.frame.size.width += messageLabel.layer.cornerRadius * 2 + messageLabel.center.x = parent.center.x + messageLabel.center.y = -messageLabel.frame.size.height / 2 + + hintView.append(messageLabel) + parent.addSubview(messageLabel) + + if let note = notification { + let center = NotificationCenter.default + var token: NSObjectProtocol? + token = center.addObserver(forName: note, object: nil, queue: OperationQueue.main) { _ in + center.removeObserver(token!) + hideHint(hint: messageLabel) + } + } else { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5 + timeout) { + hideHint(hint: messageLabel) + } + } + for view in hintView { + UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveEaseIn, animations: { + view.layer.position.y += messageLabel.frame.size.height + gap + }) + } + } // swiftlint:disable function_body_length From 833fd10b19f3f7797e90d44c4a4c4477c35293ab Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Fri, 10 Feb 2023 17:27:07 +0800 Subject: [PATCH 06/92] switch to NSEvent --- AKPlugin.swift | 53 +++++++++- PlayTools/Controls/PlayAction.swift | 54 ++++++---- PlayTools/Controls/PlayInput.swift | 66 +++++++----- PlayTools/Controls/PlayMice.swift | 18 ++-- PlayTools/Keymap/KeyCodeNames.swift | 158 +++++++++++++++++++++++++++- Plugin.swift | 3 +- 6 files changed, 288 insertions(+), 64 deletions(-) diff --git a/AKPlugin.swift b/AKPlugin.swift index 43003749..277903c9 100644 --- a/AKPlugin.swift +++ b/AKPlugin.swift @@ -51,12 +51,40 @@ class AKPlugin: NSObject, Plugin { NSApplication.shared.terminate(self) } - func eliminateRedundantKeyPressEvents(_ dontIgnore: @escaping() -> Bool) { + private var modifierFlag: UInt = 0 + private let flagMap: [UInt: UInt16] = [ + NSEvent.ModifierFlags.capsLock.rawValue >> 16: 57, + NSEvent.ModifierFlags.shift.rawValue >> 16: 56 + ] + func setupKeyboard(_ onChanged: @escaping(UInt16, Bool) -> Bool) { NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: { event in - if dontIgnore() { - return event + if event.isARepeat { + return nil } - return nil + let consumed = onChanged(event.keyCode, true) + if consumed { + return nil + } + return event + }) + NSEvent.addLocalMonitorForEvents(matching: .keyUp, handler: { event in + let consumed = onChanged(event.keyCode, false) + if consumed { + return nil + } + return event + }) + NSEvent.addLocalMonitorForEvents(matching: .flagsChanged, handler: { event in + let changed = (event.modifierFlags.rawValue ^ self.modifierFlag) + self.modifierFlag = event.modifierFlags.rawValue + // ignore lower 16 bit + guard let virtualCode = self.flagMap[changed >> 16] else {return event} + let pressed = (changed & event.modifierFlags.rawValue) > 0 + let consumed = onChanged(virtualCode, pressed) + if consumed { + return nil + } + return event }) } @@ -78,7 +106,22 @@ class AKPlugin: NSObject, Plugin { func setupScrollWheel(_ onMoved: @escaping(CGFloat, CGFloat) -> Bool) { NSEvent.addLocalMonitorForEvents(matching: NSEvent.EventTypeMask.scrollWheel, handler: { event in - let consumed = onMoved(event.scrollingDeltaX, event.scrollingDeltaY) + var deltaX = event.scrollingDeltaX, deltaY = event.scrollingDeltaY + if !event.hasPreciseScrollingDeltas { + deltaX *= 16 + deltaY *= 16 + } + let consumed = onMoved(deltaX, deltaY) + if consumed { + return nil + } + return event + }) + } + + func setupMouseMove(_ onMoved: @escaping(CGFloat, CGFloat) -> Bool) { + NSEvent.addLocalMonitorForEvents(matching: NSEvent.EventTypeMask.mouseMoved, handler: { event in + let consumed = onMoved(event.deltaX, event.deltaY) if consumed { return nil } diff --git a/PlayTools/Controls/PlayAction.swift b/PlayTools/Controls/PlayAction.swift index 9db0181f..d58f2dbe 100644 --- a/PlayTools/Controls/PlayAction.swift +++ b/PlayTools/Controls/PlayAction.swift @@ -21,23 +21,26 @@ class ButtonAction: Action { Toucher.touchcam(point: point, phase: UITouch.Phase.ended, tid: &id) } - let keyCode: GCKeyCode + let keyCode: Int let keyName: String let point: CGPoint var id: Int? - init(keyCode: GCKeyCode, keyName: String, point: CGPoint) { + init(keyCode: Int, keyName: String, point: CGPoint) { self.keyCode = keyCode self.keyName = keyName self.point = point - let code = keyCode.rawValue + let code = keyCode + guard let codeName = KeyCodeNames.keyCodes[code] else { + Toast.showOver(msg: keyName+"(\(keyCode)) cannot be mapped") + return + } // TODO: set both key names in draggable button, so as to depracate key code - PlayInput.registerButton(key: code == KeyCodeNames.defaultCode ? keyName: KeyCodeNames.keyCodes[code]!, - handler: self.update) + PlayInput.registerButton(key: code == KeyCodeNames.defaultCode ? keyName: codeName, handler: self.update) } convenience init(data: Button) { - let keyCode = GCKeyCode(rawValue: data.keyCode) + let keyCode = data.keyCode self.init( keyCode: keyCode, keyName: data.keyName, @@ -58,7 +61,7 @@ class ButtonAction: Action { class DraggableButtonAction: ButtonAction { var releasePoint: CGPoint - override init(keyCode: GCKeyCode, keyName: String, point: CGPoint) { + override init(keyCode: Int, keyName: String, point: CGPoint) { self.releasePoint = point super.init(keyCode: keyCode, keyName: keyName, point: point) } @@ -76,7 +79,6 @@ class DraggableButtonAction: ButtonAction { override func invalidate() { PlayMice.shared.draggableHandler.removeValue(forKey: keyName) - PlayMice.shared.stop() super.invalidate() } @@ -142,28 +144,31 @@ class ContinuousJoystickAction: Action { } class JoystickAction: Action { - let keys: [GCKeyCode] + let keys: [Int] let center: CGPoint let shift: CGFloat var id: Int? var moving = false + private var keyPressed = [Bool](repeating: false, count: 4) - init(keys: [GCKeyCode], center: CGPoint, shift: CGFloat) { + init(keys: [Int], center: CGPoint, shift: CGFloat) { self.keys = keys self.center = center self.shift = shift / 2 - for key in keys { - PlayInput.registerButton(key: KeyCodeNames.keyCodes[key.rawValue]!, handler: self.update) + for index in 0.. (Bool) -> Void { + return { pressed in + self.keyPressed[index] = pressed + self.update() + } + } + + func update() { var touch = center - if GCKeyboard.pressed(key: keys[0]) { + if keyPressed[0] { touch.y -= shift / 2 - } else if GCKeyboard.pressed(key: keys[1]) { + } else if keyPressed[1] { touch.y += shift / 2 } - if GCKeyboard.pressed(key: keys[2]) { + if keyPressed[2] { touch.x -= shift / 2 - } else if GCKeyboard.pressed(key: keys[3]) { + } else if keyPressed[3] { touch.x += shift / 2 } if moving { diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index cbc36df5..af6d82e5 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -6,19 +6,17 @@ class PlayInput { static let shared = PlayInput() var actions = [Action]() var timeoutForBind = true - + static private var whichShift: UInt16 = 60 static private var lCmdPressed = false static private var rCmdPressed = false static public var buttonHandlers: [String: [(Bool) -> Void]] = [:] func invalidate() { - PlayMice.shared.stop() for action in self.actions { action.invalidate() } PlayInput.buttonHandlers.removeAll(keepingCapacity: true) - GCKeyboard.coalesced!.keyboardInput!.keyChangedHandler = nil GCController.current?.extendedGamepad?.valueChangedHandler = nil } @@ -29,10 +27,12 @@ class PlayInput { PlayInput.buttonHandlers[key]!.append(handler) } - func keyboardHandler(_: GCKeyboardInput, _: GCControllerButtonInput, _ keyCode: GCKeyCode, _ pressed: Bool) { - if PlayInput.cmdPressed() { return } - guard let handlers = PlayInput.buttonHandlers[KeyCodeNames.keyCodes[keyCode.rawValue]!] else { - // TODO: usage hint of disabling keymapping is planned to be added here + func keyboardHandler(_ keyCode: UInt16, _ pressed: Bool) { + guard let name = KeyCodeNames.virtualCodes[keyCode] else { + Toast.showOver(msg: "keycode \(keyCode) is not recognized") + return + } + guard let handlers = PlayInput.buttonHandlers[name] else { return } for handler in handlers { @@ -90,6 +90,10 @@ class PlayInput { if !PlayInput.cmdPressed() && !PlayInput.FORBIDDEN.contains(keyCode) && self.isSafeToBind(keyboard) { + if KeyCodeNames.keyCodes[keyCode.rawValue] == nil { + Toast.showOver(msg: "This key cannot be mapped") + return + } EditorController.shared.setKey(keyCode.rawValue) } } @@ -122,17 +126,7 @@ class PlayInput { func setup() { parseKeymap() - - GCKeyboard.coalesced!.keyboardInput!.keyChangedHandler = keyboardHandler GCController.current?.extendedGamepad?.valueChangedHandler = controllerButtonHandler - for mouse in GCMouse.mice() { - if settings.mouseMapping { - mouse.mouseInput?.mouseMovedHandler = PlayMice.shared.handleMouseMoved - } else { - mouse.mouseInput?.mouseMovedHandler = PlayMice.shared.handleFakeMouseMoved - } - } - } static public func cmdPressed() -> Bool { @@ -222,12 +216,36 @@ class PlayInput { setupHotkeys() - // Fix beep sound - AKInterface.shared! - .eliminateRedundantKeyPressEvents(self.dontIgnore) - } - - func dontIgnore() -> Bool { - (mode.visible && !EditorController.shared.editorMode) || PlayInput.cmdPressed() + AKInterface.shared!.setupKeyboard {keycode, pressed in + let consumed = !mode.visible && !PlayInput.cmdPressed() + if !consumed { + return false + } + var code = keycode + if code == 56 { + if pressed { + if GCKeyboard.pressed(key: GCKeyCode(rawValue: 229)) { + // Actually right shift + code = 60 + } + PlayInput.whichShift = code + } else { + code = PlayInput.whichShift + } + } + self.keyboardHandler(code, pressed) + return consumed + } + AKInterface.shared!.setupMouseMove {deltaX, deltaY in + if mode.visible { + return false + } + if settings.mouseMapping { + PlayMice.shared.handleMouseMoved(deltaX: deltaX, deltaY: deltaY) + } else { + PlayMice.shared.handleFakeMouseMoved(deltaX: deltaX, deltaY: deltaY) + } + return true + } } } diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index 8fabe3a1..39fd182a 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -99,16 +99,16 @@ public class PlayMice { joystickHandler[name]?(cgDx, cgDy) } - public func handleFakeMouseMoved(_: GCMouseInput, deltaX: Float, deltaY: Float) { + public func handleFakeMouseMoved(deltaX: CGFloat, deltaY: CGFloat) { if self.fakedMousePressed { Toucher.touchcam(point: self.cursorPos(), phase: UITouch.Phase.moved, tid: &fakedMouseTouchPointId) } } - public func handleMouseMoved(_: GCMouseInput, deltaX: Float, deltaY: Float) { + public func handleMouseMoved(deltaX: CGFloat, deltaY: CGFloat) { let sensy = CGFloat(PlaySettings.shared.sensitivity) - let cgDx = CGFloat(deltaX) * sensy, - cgDy = CGFloat(deltaY) * sensy + let cgDx = deltaX * sensy * 0.5, + cgDy = -deltaY * sensy * 0.5 let name = PlayMice.elementName if let draggableUpdate = self.draggableHandler[name] { draggableUpdate(cgDx, cgDy) @@ -118,12 +118,6 @@ public class PlayMice { self.joystickHandler[name]?(cgDx, cgDy) } - public func stop() { - for mouse in GCMouse.mice() { - mouse.mouseInput?.mouseMovedHandler = { _, _, _ in} - } - } - // TODO: get rid of this shit let buttonIndex: [Int: Int] = [2: -1, 8: -2, 33554432: -3] @@ -270,7 +264,7 @@ class SwipeAction: Action { self.cooldown = false // TODO: camera mode switch: Flexibility v.s. Precision // TODO: find reasonable ways to promote fake touch priority - timer.schedule(deadline: DispatchTime.now() + 1, repeating: 0.3, leeway: DispatchTimeInterval.never) + timer.schedule(deadline: DispatchTime.now() + 1, repeating: 0.1, leeway: DispatchTimeInterval.never) timer.setEventHandler(qos: DispatchQoS.background, handler: self.checkEnded) timer.activate() timer.suspend() @@ -290,7 +284,7 @@ class SwipeAction: Action { func checkEnded() { if self.counter == self.lastCounter { if self.counter < 24 { - counter += 24 + counter += 12 } else { timer.suspend() self.doLiftOff() diff --git a/PlayTools/Keymap/KeyCodeNames.swift b/PlayTools/Keymap/KeyCodeNames.swift index 5c2c888e..dec53138 100644 --- a/PlayTools/Keymap/KeyCodeNames.swift +++ b/PlayTools/Keymap/KeyCodeNames.swift @@ -47,7 +47,7 @@ class KeyCodeNames { 67: "F10", 68: "F11", 69: "F12", - 100: "§", +// 100: "§", 30: "1", 31: "2", 32: "3", @@ -96,4 +96,160 @@ class KeyCodeNames { 55: ".", 56: "/" ] + public static let virtualCodes: [UInt16: String] = [ + 0: "A", + 11: "B", + 8: "C", + 2: "D", + 14: "E", + 3: "F", + 5: "G", + 4: "H", + 34: "I", + 38: "J", + 40: "K", + 37: "L", + 46: "M", + 45: "N", + 31: "O", + 35: "P", + 12: "Q", + 15: "R", + 1: "S", + 17: "T", + 32: "U", + 9: "V", + 13: "W", + 7: "X", + 16: "Y", + 6: "Z", + 18: "1", + 19: "2", + 20: "3", + 21: "4", + 23: "5", + 22: "6", + 26: "7", + 28: "8", + 25: "9", + 29: "0", + 36: "Enter", + 53: "Esc", + 51: "Del", + 48: "Tab", + 49: "Spc", + 27: "-", + 24: "=", + 33: "[", + 30: "]", + 42: "\\", + 41: ";", + 39: "'", + 50: "`", + 43: ",", + 47: ".", + 44: "/", + 57: "Caps", + 122: "F1", + 120: "F2", + 99: "F3", + 118: "F4", + 96: "F5", + 97: "F6", + 98: "F7", + 100: "F8", + 101: "F9", + 109: "F10", + 103: "F11", + 111: "F12", + 124: "Right", + 123: "Left", + 125: "Down", + 126: "Up", +// §: "§", + 56: "Lshft", + 58: "LOpt", + 55: "LCmd", + 60: "Rshft", + 61: "ROpt", + 54: "RCmd", +] +private static let toVirtual = [ + 41: 53, // Esc + 44: 49, + 225: 0x38, // "Lshft", + 57: 0x39, // "Caps", + 43: 48, + 227: 0x37, // "LCmd", + 226: 0x3A, // "LOpt", + 231: 0x36, // "RCmd", + 230: 0x3D, // "ROpt", + 40: 36, + 42: 51, + 229: 0x3C, // "Rshft", + 80: 123, + 79: 124, + 82: 126, + 81: 125, + 58: 122, // "F1", + 59: 120, // "F2", + 60: 99, // "F3", + 61: 118, // "F4", + 62: 96, // "F5", + 63: 97, // "F6", + 64: 98, // "F7", + 65: 100, // "F8", + 66: 101, + 67: 109, + 68: 0x67, // "F11", + 69: 111, // "F12", +// 100: "§", + 30: 18, // "1", + 31: 19, // "2", + 32: 20, // "3", + 33: 21, // "4", + 34: 23, // "5", + 35: 22, // "6", + 36: 26, // "7", + 37: 28, // "8", + 38: 25, // "9", + 39: 29, // -"0", + 45: 27, // "-", + 46: 24, // "=", + 20: 12, // "Q", + 26: 13, // "W", + 8: 14, // "E", + 21: 15, // "R", + 23: 17, // u"T", + 28: 16, // "Y", + 24: 32, // "U", + 12: 34, // "I", + 18: 31, // "O", + 19: 35, // "P", + 47: 33, // "[", + 48: 30, // "]", + 4: 0, // "A", + 22: 1, // "S", + 7: 2, // "D", + 9: 3, // "F", + 10: 5, // "G", + 11: 4, // "H", + 13: 38, // "J", + 14: 40, // "K", + 15: 37, // "L", + 51: 41, // ";", + 52: 39, // "'", + 49: 42, // "\\", + 29: 6, // "Z", + 53: 50, // "`", + 27: 7, // "X", + 6: 8, // "C", + 25: 9, // "V", + 5: 11, // "B", + 17: 45, // "N", + 16: 46, // "M", + 54: 43, // ",", + 55: 47, // ".", + 56: 44, // "/" + ] } diff --git a/Plugin.swift b/Plugin.swift index 6c2a7066..056fa36f 100644 --- a/Plugin.swift +++ b/Plugin.swift @@ -21,9 +21,10 @@ public protocol Plugin: NSObjectProtocol { func hideCursor() func unhideCursor() func terminateApplication() - func eliminateRedundantKeyPressEvents(_ dontIgnore: @escaping() -> Bool) + func setupKeyboard(_ onChanged: @escaping(UInt16, Bool) -> Bool) func setupMouseButton(_ _up: Int, _ _down: Int, _ dontIgnore: @escaping(Int, Bool, Bool) -> Bool) func setupScrollWheel(_ onMoved: @escaping(CGFloat, CGFloat) -> Bool) + func setupMouseMove(_ onMoved: @escaping(CGFloat, CGFloat) -> Bool) func urlForApplicationWithBundleIdentifier(_ value: String) -> URL? func setMenuBarVisible(_ value: Bool) } From 30fa58a41b9736aebb4cca176131c5fbbf796092 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Fri, 10 Feb 2023 22:50:16 +0800 Subject: [PATCH 07/92] warp mouse cursor to center --- AKPlugin.swift | 16 ++++++---------- PlayTools/Controls/PlayAction.swift | 7 ------- PlayTools/Controls/PlayInput.swift | 26 +++++++------------------- PlayTools/Controls/PlayMice.swift | 2 +- PlayTools/Keymap/KeyCodeNames.swift | 6 +++--- 5 files changed, 17 insertions(+), 40 deletions(-) diff --git a/AKPlugin.swift b/AKPlugin.swift index 277903c9..fb380595 100644 --- a/AKPlugin.swift +++ b/AKPlugin.swift @@ -40,6 +40,8 @@ class AKPlugin: NSObject, Plugin { func hideCursor() { NSCursor.hide() CGAssociateMouseAndMouseCursorPosition(0) + let frame = windowFrame + CGWarpMouseCursorPosition(CGPoint(x: frame.width / 2, y: frame.height / 2)) } func unhideCursor() { @@ -52,10 +54,6 @@ class AKPlugin: NSObject, Plugin { } private var modifierFlag: UInt = 0 - private let flagMap: [UInt: UInt16] = [ - NSEvent.ModifierFlags.capsLock.rawValue >> 16: 57, - NSEvent.ModifierFlags.shift.rawValue >> 16: 56 - ] func setupKeyboard(_ onChanged: @escaping(UInt16, Bool) -> Bool) { NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: { event in if event.isARepeat { @@ -75,12 +73,9 @@ class AKPlugin: NSObject, Plugin { return event }) NSEvent.addLocalMonitorForEvents(matching: .flagsChanged, handler: { event in - let changed = (event.modifierFlags.rawValue ^ self.modifierFlag) + let pressed = self.modifierFlag < event.modifierFlags.rawValue self.modifierFlag = event.modifierFlags.rawValue - // ignore lower 16 bit - guard let virtualCode = self.flagMap[changed >> 16] else {return event} - let pressed = (changed & event.modifierFlags.rawValue) > 0 - let consumed = onChanged(virtualCode, pressed) + let consumed = onChanged(event.keyCode, pressed) if consumed { return nil } @@ -120,7 +115,8 @@ class AKPlugin: NSObject, Plugin { } func setupMouseMove(_ onMoved: @escaping(CGFloat, CGFloat) -> Bool) { - NSEvent.addLocalMonitorForEvents(matching: NSEvent.EventTypeMask.mouseMoved, handler: { event in + let mask: NSEvent.EventTypeMask = [.leftMouseDragged, .otherMouseDragged, .rightMouseDragged, .mouseMoved] + NSEvent.addLocalMonitorForEvents(matching: mask, handler: { event in let consumed = onMoved(event.deltaX, event.deltaY) if consumed { return nil diff --git a/PlayTools/Controls/PlayAction.swift b/PlayTools/Controls/PlayAction.swift index d58f2dbe..a42e5ddc 100644 --- a/PlayTools/Controls/PlayAction.swift +++ b/PlayTools/Controls/PlayAction.swift @@ -4,18 +4,11 @@ // import Foundation -import GameController protocol Action { func invalidate() } -extension GCKeyboard { - static func pressed(key: GCKeyCode) -> Bool { - return GCKeyboard.coalesced?.keyboardInput?.button(forKeyCode: key)?.isPressed ?? false - } -} - class ButtonAction: Action { func invalidate() { Toucher.touchcam(point: point, phase: UITouch.Phase.ended, tid: &id) diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index af6d82e5..501dcec3 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -6,7 +6,7 @@ class PlayInput { static let shared = PlayInput() var actions = [Action]() var timeoutForBind = true - static private var whichShift: UInt16 = 60 + static private var lCmdPressed = false static private var rCmdPressed = false @@ -89,12 +89,11 @@ class PlayInput { keyboard.keyChangedHandler = { _, _, keyCode, _ in if !PlayInput.cmdPressed() && !PlayInput.FORBIDDEN.contains(keyCode) - && self.isSafeToBind(keyboard) { - if KeyCodeNames.keyCodes[keyCode.rawValue] == nil { - Toast.showOver(msg: "This key cannot be mapped") - return - } + && self.isSafeToBind(keyboard) + && KeyCodeNames.keyCodes[keyCode.rawValue] != nil { EditorController.shared.setKey(keyCode.rawValue) + } else { + Toast.showOver(msg: "This key (\(keyCode.rawValue)) cannot be mapped") } } } @@ -126,6 +125,7 @@ class PlayInput { func setup() { parseKeymap() + GCKeyboard.coalesced?.keyboardInput?.keyChangedHandler = nil GCController.current?.extendedGamepad?.valueChangedHandler = controllerButtonHandler } @@ -221,19 +221,7 @@ class PlayInput { if !consumed { return false } - var code = keycode - if code == 56 { - if pressed { - if GCKeyboard.pressed(key: GCKeyCode(rawValue: 229)) { - // Actually right shift - code = 60 - } - PlayInput.whichShift = code - } else { - code = PlayInput.whichShift - } - } - self.keyboardHandler(code, pressed) + self.keyboardHandler(keycode, pressed) return consumed } AKInterface.shared!.setupMouseMove {deltaX, deltaY in diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index 39fd182a..b7b0430a 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -284,7 +284,7 @@ class SwipeAction: Action { func checkEnded() { if self.counter == self.lastCounter { if self.counter < 24 { - counter += 12 + counter += 24 } else { timer.suspend() self.doLiftOff() diff --git a/PlayTools/Keymap/KeyCodeNames.swift b/PlayTools/Keymap/KeyCodeNames.swift index dec53138..73227d9b 100644 --- a/PlayTools/Keymap/KeyCodeNames.swift +++ b/PlayTools/Keymap/KeyCodeNames.swift @@ -96,7 +96,7 @@ class KeyCodeNames { 55: ".", 56: "/" ] - public static let virtualCodes: [UInt16: String] = [ +public static let virtualCodes: [UInt16: String] = [ 0: "A", 11: "B", 8: "C", @@ -172,7 +172,7 @@ class KeyCodeNames { 55: "LCmd", 60: "Rshft", 61: "ROpt", - 54: "RCmd", + 54: "RCmd" ] private static let toVirtual = [ 41: 53, // Esc @@ -250,6 +250,6 @@ private static let toVirtual = [ 16: 46, // "M", 54: 43, // ",", 55: 47, // ".", - 56: 44, // "/" + 56: 44 // "/" ] } From 784150feb4e1eaa16aad81b6a9d06d2719d7f3d4 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Sat, 11 Feb 2023 17:29:03 +0800 Subject: [PATCH 08/92] limit toast number to four --- PlayTools/Utils/Toast.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/PlayTools/Utils/Toast.swift b/PlayTools/Utils/Toast.swift index f6540a87..44fba46a 100644 --- a/PlayTools/Utils/Toast.swift +++ b/PlayTools/Utils/Toast.swift @@ -17,7 +17,7 @@ class Toast { private static let gap: CGFloat = 40 public static func hideHint(hint: UIView) { - let id = hintView.firstIndex(of: hint)! + guard let id = hintView.firstIndex(of: hint) else {return} for index in 0.. 4 { + hideHint(hint: hintView.first!) + } if let note = notification { let center = NotificationCenter.default var token: NSObjectProtocol? From e8aad2653a8c71c255f3fd733fdbcc5e86f17c4b Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Sun, 12 Feb 2023 03:39:55 +0800 Subject: [PATCH 09/92] add Control key to key code map --- AKPlugin.swift | 33 +++++++++++++++-------------- PlayTools/Controls/PlayAction.swift | 5 +---- PlayTools/Controls/PlayInput.swift | 21 +++++++++--------- PlayTools/Controls/PlayMice.swift | 4 ++-- PlayTools/Keymap/KeyCodeNames.swift | 14 ++++++++---- Plugin.swift | 4 ++-- 6 files changed, 43 insertions(+), 38 deletions(-) diff --git a/AKPlugin.swift b/AKPlugin.swift index fb380595..6b6a5cb8 100644 --- a/AKPlugin.swift +++ b/AKPlugin.swift @@ -40,8 +40,12 @@ class AKPlugin: NSObject, Plugin { func hideCursor() { NSCursor.hide() CGAssociateMouseAndMouseCursorPosition(0) + warpCursor() + } + + func warpCursor() { let frame = windowFrame - CGWarpMouseCursorPosition(CGPoint(x: frame.width / 2, y: frame.height / 2)) + CGWarpMouseCursorPosition(CGPoint(x: frame.midX, y: frame.midY)) } func unhideCursor() { @@ -54,19 +58,19 @@ class AKPlugin: NSObject, Plugin { } private var modifierFlag: UInt = 0 - func setupKeyboard(_ onChanged: @escaping(UInt16, Bool) -> Bool) { + func initialize(keyboard: @escaping(UInt16, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool) { NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: { event in if event.isARepeat { return nil } - let consumed = onChanged(event.keyCode, true) + let consumed = keyboard(event.keyCode, true) if consumed { return nil } return event }) NSEvent.addLocalMonitorForEvents(matching: .keyUp, handler: { event in - let consumed = onChanged(event.keyCode, false) + let consumed = keyboard(event.keyCode, false) if consumed { return nil } @@ -75,7 +79,15 @@ class AKPlugin: NSObject, Plugin { NSEvent.addLocalMonitorForEvents(matching: .flagsChanged, handler: { event in let pressed = self.modifierFlag < event.modifierFlags.rawValue self.modifierFlag = event.modifierFlags.rawValue - let consumed = onChanged(event.keyCode, pressed) + let consumed = keyboard(event.keyCode, pressed) + if consumed { + return nil + } + return event + }) + let mask: NSEvent.EventTypeMask = [.leftMouseDragged, .otherMouseDragged, .rightMouseDragged, .mouseMoved] + NSEvent.addLocalMonitorForEvents(matching: mask, handler: { event in + let consumed = mouseMoved(event.deltaX, event.deltaY) if consumed { return nil } @@ -114,17 +126,6 @@ class AKPlugin: NSObject, Plugin { }) } - func setupMouseMove(_ onMoved: @escaping(CGFloat, CGFloat) -> Bool) { - let mask: NSEvent.EventTypeMask = [.leftMouseDragged, .otherMouseDragged, .rightMouseDragged, .mouseMoved] - NSEvent.addLocalMonitorForEvents(matching: mask, handler: { event in - let consumed = onMoved(event.deltaX, event.deltaY) - if consumed { - return nil - } - return event - }) - } - func urlForApplicationWithBundleIdentifier(_ value: String) -> URL? { NSWorkspace.shared.urlForApplication(withBundleIdentifier: value) } diff --git a/PlayTools/Controls/PlayAction.swift b/PlayTools/Controls/PlayAction.swift index a42e5ddc..855f82c5 100644 --- a/PlayTools/Controls/PlayAction.swift +++ b/PlayTools/Controls/PlayAction.swift @@ -24,10 +24,7 @@ class ButtonAction: Action { self.keyName = keyName self.point = point let code = keyCode - guard let codeName = KeyCodeNames.keyCodes[code] else { - Toast.showOver(msg: keyName+"(\(keyCode)) cannot be mapped") - return - } + let codeName = KeyCodeNames.keyCodes[code] ?? "Btn" // TODO: set both key names in draggable button, so as to depracate key code PlayInput.registerButton(key: code == KeyCodeNames.defaultCode ? keyName: codeName, handler: self.update) } diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 501dcec3..4f21956c 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -28,10 +28,7 @@ class PlayInput { } func keyboardHandler(_ keyCode: UInt16, _ pressed: Bool) { - guard let name = KeyCodeNames.virtualCodes[keyCode] else { - Toast.showOver(msg: "keycode \(keyCode) is not recognized") - return - } + let name = KeyCodeNames.virtualCodes[keyCode] ?? "Btn" guard let handlers = PlayInput.buttonHandlers[name] else { return } @@ -92,8 +89,6 @@ class PlayInput { && self.isSafeToBind(keyboard) && KeyCodeNames.keyCodes[keyCode.rawValue] != nil { EditorController.shared.setKey(keyCode.rawValue) - } else { - Toast.showOver(msg: "This key (\(keyCode.rawValue)) cannot be mapped") } } } @@ -214,17 +209,23 @@ class PlayInput { } } + centre.addObserver(forName: NSNotification.Name(rawValue: "NSWindowDidBecomeKeyNotification"), object: nil, + queue: OperationQueue.main + ) { _ in + if !mode.visible && settings.mouseMapping { + AKInterface.shared!.warpCursor() + } + } setupHotkeys() - AKInterface.shared!.setupKeyboard {keycode, pressed in + AKInterface.shared!.initialize(keyboard: {keycode, pressed in let consumed = !mode.visible && !PlayInput.cmdPressed() if !consumed { return false } self.keyboardHandler(keycode, pressed) return consumed - } - AKInterface.shared!.setupMouseMove {deltaX, deltaY in + }, mouseMoved: {deltaX, deltaY in if mode.visible { return false } @@ -234,6 +235,6 @@ class PlayInput { PlayMice.shared.handleFakeMouseMoved(deltaX: deltaX, deltaY: deltaY) } return true - } + }) } } diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index b7b0430a..3cb3328e 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -283,8 +283,8 @@ class SwipeAction: Action { func checkEnded() { if self.counter == self.lastCounter { - if self.counter < 24 { - counter += 24 + if self.counter < 12 { + counter += 12 } else { timer.suspend() self.doLiftOff() diff --git a/PlayTools/Keymap/KeyCodeNames.swift b/PlayTools/Keymap/KeyCodeNames.swift index 73227d9b..4e62696b 100644 --- a/PlayTools/Keymap/KeyCodeNames.swift +++ b/PlayTools/Keymap/KeyCodeNames.swift @@ -26,6 +26,8 @@ class KeyCodeNames { 43: "Tab", 227: "LCmd", 226: "LOpt", + 224: "LCtrl", + 228: "RCtrl", 231: "RCmd", 230: "ROpt", 40: "Enter", @@ -172,9 +174,12 @@ public static let virtualCodes: [UInt16: String] = [ 55: "LCmd", 60: "Rshft", 61: "ROpt", - 54: "RCmd" + 54: "RCmd", + 59: "LCtrl", + 62: "RCtrl" ] -private static let toVirtual = [ +} +let mapGCKeyCodeRawValuetoNSEventVirtualCode = [ 41: 53, // Esc 44: 49, 225: 0x38, // "Lshft", @@ -250,6 +255,7 @@ private static let toVirtual = [ 16: 46, // "M", 54: 43, // ",", 55: 47, // ".", - 56: 44 // "/" + 56: 44, // "/" + 224: 59, // "LCtrl", + 228: 62 // "RCtrl", ] -} diff --git a/Plugin.swift b/Plugin.swift index 056fa36f..33193fba 100644 --- a/Plugin.swift +++ b/Plugin.swift @@ -19,12 +19,12 @@ public protocol Plugin: NSObjectProtocol { var isFullscreen: Bool { get } func hideCursor() + func warpCursor() func unhideCursor() func terminateApplication() - func setupKeyboard(_ onChanged: @escaping(UInt16, Bool) -> Bool) + func initialize(keyboard: @escaping(UInt16, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool) func setupMouseButton(_ _up: Int, _ _down: Int, _ dontIgnore: @escaping(Int, Bool, Bool) -> Bool) func setupScrollWheel(_ onMoved: @escaping(CGFloat, CGFloat) -> Bool) - func setupMouseMove(_ onMoved: @escaping(CGFloat, CGFloat) -> Bool) func urlForApplicationWithBundleIdentifier(_ value: String) -> URL? func setMenuBarVisible(_ value: Bool) } From 51ab649983e977fd378706e0037866851ba538b8 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Mon, 13 Feb 2023 01:14:54 +0800 Subject: [PATCH 10/92] Add new toast UI and operation hint message (#82) * add new hint toast * limit toast number to four --- PlayTools/Controls/ControlMode.swift | 12 ++++ PlayTools/Controls/PlayInput.swift | 15 ++++ PlayTools/Keymap/EditorController.swift | 7 +- PlayTools/Utils/Toast.swift | 95 +++++++++++++++++++++++++ 4 files changed, 127 insertions(+), 2 deletions(-) diff --git a/PlayTools/Controls/ControlMode.swift b/PlayTools/Controls/ControlMode.swift index 35661efb..df298807 100644 --- a/PlayTools/Controls/ControlMode.swift +++ b/PlayTools/Controls/ControlMode.swift @@ -16,6 +16,8 @@ public class ControlMode { if !editor.editorMode { if show { if !visible { + NotificationCenter.default.post(name: NSNotification.Name.playtoolsKeymappingWillDisable, + object: nil, userInfo: [:]) if screen.fullscreen { screen.switchDock(true) } @@ -26,6 +28,8 @@ public class ControlMode { } } else { if visible { + NotificationCenter.default.post(name: NSNotification.Name.playtoolsKeymappingWillEnable, + object: nil, userInfo: [:]) if PlaySettings.shared.mouseMapping { AKInterface.shared!.hideCursor() } @@ -40,3 +44,11 @@ public class ControlMode { } } } + +extension NSNotification.Name { + public static let playtoolsKeymappingWillEnable: NSNotification.Name + = NSNotification.Name("playtools.keymappingWillEnable") + + public static let playtoolsKeymappingWillDisable: NSNotification.Name + = NSNotification.Name("playtools.keymappingWillDisable") +} diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 0698c746..6b031afd 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -163,6 +163,21 @@ class PlayInput { } setupShortcuts() + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5) { + if !settings.mouseMapping || !mode.visible { + return + } + Toast.showHint(title: "Keymapping Disabled", text: ["Press ", "option ⌥", " to enable keymapping"], + notification: NSNotification.Name.playtoolsKeymappingWillEnable) + let center = NotificationCenter.default + var token: NSObjectProtocol? + token = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillEnable, + object: nil, queue: OperationQueue.main) { _ in + center.removeObserver(token!) + Toast.showHint(title: "Keymapping Enabled", text: ["Press ", "option ⌥", " to disable keymapping"], + notification: NSNotification.Name.playtoolsKeymappingWillDisable) + } + } // Fix beep sound AKInterface.shared! diff --git a/PlayTools/Keymap/EditorController.swift b/PlayTools/Keymap/EditorController.swift index 1fe47c66..c6f7075e 100644 --- a/PlayTools/Keymap/EditorController.swift +++ b/PlayTools/Keymap/EditorController.swift @@ -60,14 +60,17 @@ class EditorController { previousWindow?.makeKeyAndVisible() PlayInput.shared.toggleEditor(show: false) focusedControl = nil - Toast.showOver(msg: "Keymapping saved") + Toast.showHint(title: "Keymap Saved") } else { PlayInput.shared.toggleEditor(show: true) previousWindow = screen.keyWindow editorWindow = initWindow() editorWindow?.makeKeyAndVisible() showButtons() - Toast.showOver(msg: "Click to start keymmaping edit") + Toast.showHint(title: "Keymapping Editor", + text: ["Click a button to edit its position or key bind\n" + + "Click an empty area to open input menu"], + notification: NSNotification.Name.playtoolsKeymappingWillEnable) } // Toast.showOver(msg: "\(UIApplication.shared.windows.count)") lock.unlock() diff --git a/PlayTools/Utils/Toast.swift b/PlayTools/Utils/Toast.swift index 4bf90244..44fba46a 100644 --- a/PlayTools/Utils/Toast.swift +++ b/PlayTools/Utils/Toast.swift @@ -12,6 +12,101 @@ class Toast { Toast.show(message: msg, parent: parent) } } + static var hintView: [UIView] = [] + + private static let gap: CGFloat = 40 + + public static func hideHint(hint: UIView) { + guard let id = hintView.firstIndex(of: hint) else {return} + for index in 0.. id { + hintView[index-1] = hintView[index] + } + } + hintView.removeLast() + UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveEaseOut, animations: { + hint.alpha = 0.0 + }, completion: {_ in + hint.removeFromSuperview() + }) + } + + private static func getAttributedString(title: String, text: [String]) -> NSMutableAttributedString { + var heading = title + if !text.isEmpty { + heading += "\n" + } + let txt = NSMutableAttributedString(string: text.reduce(into: heading, { result, string in + result += string + })) + var messageLength = 0 + var highlight = false + for msg in text { + txt.addAttribute(.foregroundColor, value: highlight ? UIColor.cyan: UIColor.white, + range: NSRange(location: heading.count + messageLength, length: msg.count)) + highlight = !highlight + messageLength += msg.count + } + let style = NSMutableParagraphStyle() + style.alignment = .center + txt.addAttribute(.paragraphStyle, value: style, + range: NSRange(location: 0, length: heading.count + messageLength)) + txt.addAttribute(.font, value: UIFont.systemFont(ofSize: 28, weight: .bold), + range: NSRange(location: 0, length: heading.count)) + txt.addAttribute(.foregroundColor, value: UIColor.white, + range: NSRange(location: 0, length: heading.count)) + txt.addAttribute(.font, value: UIFont.systemFont(ofSize: 28), + range: NSRange(location: heading.count, length: messageLength)) + return txt + } + + public static func showHint(title: String, text: [String] = [], timeout: Double = 3, + notification: NSNotification.Name? = nil) { + let parent = screen.keyWindow! + + // Width and height here serve as an upper limit. + // Text would fill width first, then wrap, then fill height, then scroll + let messageLabel = UITextView(frame: CGRect(x: 0, y: 0, width: 800, height: 800)) + messageLabel.attributedText = getAttributedString(title: title, text: text) + messageLabel.backgroundColor = UIColor.black.withAlphaComponent(0.5) + messageLabel.alpha = 1.0 + messageLabel.clipsToBounds = true + messageLabel.isUserInteractionEnabled = false + messageLabel.frame.size = messageLabel.sizeThatFits(messageLabel.frame.size) + messageLabel.layer.cornerCurve = CALayerCornerCurve.continuous + messageLabel.layer.cornerRadius = messageLabel.frame.size.height / 4 + messageLabel.frame.size.width += messageLabel.layer.cornerRadius * 2 + messageLabel.center.x = parent.center.x + messageLabel.center.y = -messageLabel.frame.size.height / 2 + + hintView.append(messageLabel) + parent.addSubview(messageLabel) + + if hintView.count > 4 { + hideHint(hint: hintView.first!) + } + if let note = notification { + let center = NotificationCenter.default + var token: NSObjectProtocol? + token = center.addObserver(forName: note, object: nil, queue: OperationQueue.main) { _ in + center.removeObserver(token!) + hideHint(hint: messageLabel) + } + } else { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5 + timeout) { + hideHint(hint: messageLabel) + } + } + for view in hintView { + UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveEaseIn, animations: { + view.layer.position.y += messageLabel.frame.size.height + gap + }) + } + } // swiftlint:disable function_body_length From cdcf1220ff6d21e651a75bd424904bfae5e7fd1c Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 7 Feb 2023 18:15:21 +0700 Subject: [PATCH 11/92] fix: void out unavailable VSS calls --- PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m index 63873578..99e0012b 100644 --- a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m +++ b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m @@ -11,6 +11,7 @@ #import "UIKit/UIKit.h" #import #import "PTFakeMetaTouch.h" +#import __attribute__((visibility("hidden"))) @interface PTSwizzleLoader : NSObject @@ -82,6 +83,10 @@ - (double) hook_scale { return 2.0; } +- (void) hook_setCurrentSubscription:(VSSubscription *)currentSubscription { + // do nothing +} + bool menuWasCreated = false; - (id) initWithRootMenuHook:(id)rootMenu { @@ -136,6 +141,8 @@ + (void)load { [objc_getClass("_UIMenuBuilder") swizzleInstanceMethod:sel_getUid("initWithRootMenu:") withMethod:@selector(initWithRootMenuHook:)]; [objc_getClass("IOSViewController") swizzleInstanceMethod:@selector(prefersPointerLocked) withMethod:@selector(hook_prefersPointerLocked)]; + + [objc_getClass("VSSubscriptionRegistrationCenter") swizzleInstanceMethod:@selector(setCurrentSubscription:) withMethod:@selector(hook_setCurrentSubscription:)]; } @end From cf9bffbbc8b11804a4be6ffe2cbad0c898e01e7c Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Mon, 13 Feb 2023 21:25:18 +0800 Subject: [PATCH 12/92] switch option key handling to NSEvent --- AKPlugin.swift | 8 +++++++- PlayTools/Controls/PlayInput.swift | 21 ++++++--------------- Plugin.swift | 3 ++- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/AKPlugin.swift b/AKPlugin.swift index 6b6a5cb8..0e17c448 100644 --- a/AKPlugin.swift +++ b/AKPlugin.swift @@ -58,7 +58,8 @@ class AKPlugin: NSObject, Plugin { } private var modifierFlag: UInt = 0 - func initialize(keyboard: @escaping(UInt16, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool) { + func initialize(keyboard: @escaping(UInt16, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool, + swapMode: @escaping() -> Void) { NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: { event in if event.isARepeat { return nil @@ -78,7 +79,12 @@ class AKPlugin: NSObject, Plugin { }) NSEvent.addLocalMonitorForEvents(matching: .flagsChanged, handler: { event in let pressed = self.modifierFlag < event.modifierFlags.rawValue + let changed = self.modifierFlag ^ event.modifierFlags.rawValue self.modifierFlag = event.modifierFlags.rawValue + if pressed && NSEvent.ModifierFlags(rawValue: changed).contains(.option) { + swapMode() + return nil + } let consumed = keyboard(event.keyCode, pressed) if consumed { return nil diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 4f21956c..e541c285 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -145,16 +145,14 @@ class PlayInput { .printScreen ] - private func swapMode(_ pressed: Bool) { + private func swapMode() { if !settings.mouseMapping { return } - if pressed { - if !mode.visible { - self.invalidate() - } - mode.show(!mode.visible) + if !mode.visible { + self.invalidate() } + mode.show(!mode.visible) } var root: UIViewController? { @@ -169,12 +167,6 @@ class PlayInput { keyboard.button(forKeyCode: .rightGUI)?.pressedChangedHandler = { _, _, pressed in PlayInput.rCmdPressed = pressed } - keyboard.button(forKeyCode: .leftAlt)?.pressedChangedHandler = { _, _, pressed in - self.swapMode(pressed) - } - keyboard.button(forKeyCode: .rightAlt)?.pressedChangedHandler = { _, _, pressed in - self.swapMode(pressed) - } // TODO: set a timeout to display usage guide of Option and Keymapping menu in turn } } @@ -210,8 +202,7 @@ class PlayInput { } centre.addObserver(forName: NSNotification.Name(rawValue: "NSWindowDidBecomeKeyNotification"), object: nil, - queue: OperationQueue.main - ) { _ in + queue: main) { _ in if !mode.visible && settings.mouseMapping { AKInterface.shared!.warpCursor() } @@ -235,6 +226,6 @@ class PlayInput { PlayMice.shared.handleFakeMouseMoved(deltaX: deltaX, deltaY: deltaY) } return true - }) + }, swapMode: self.swapMode) } } diff --git a/Plugin.swift b/Plugin.swift index 33193fba..877c9db4 100644 --- a/Plugin.swift +++ b/Plugin.swift @@ -22,7 +22,8 @@ public protocol Plugin: NSObjectProtocol { func warpCursor() func unhideCursor() func terminateApplication() - func initialize(keyboard: @escaping(UInt16, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool) + func initialize(keyboard: @escaping(UInt16, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool, + swapMode: @escaping() -> Void) func setupMouseButton(_ _up: Int, _ _down: Int, _ dontIgnore: @escaping(Int, Bool, Bool) -> Bool) func setupScrollWheel(_ onMoved: @escaping(CGFloat, CGFloat) -> Bool) func urlForApplicationWithBundleIdentifier(_ value: String) -> URL? From 893f90d361ffd0a39cc2d617d34d3a106911f51b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Moreno?= <47700212+JoseMoreville@users.noreply.github.com> Date: Tue, 14 Feb 2023 19:02:12 -0700 Subject: [PATCH 13/92] Fix: Default app size now shows normally and other Adaptive fixes plus responsive fix on default (#77) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix for default screen * Code cleanup * setting @objc to scene control vars * PR to make default size work correctly (#1) * Reverting cleanup * Testing removing new scaling methods for default * Revert to new methods for testing * Setting orientation to 1 * 4 * commenting suspect stuff * 12 * sd * removing FBS * test * returning back to normal * Test * a * test * x * a * a * a * as * 1 * as * así * aa * as * Change names * testing something * testing 2 * more test * 12 * return * 123 * Testing defaults and auto fix * Testing c * is this ready? * test * 1 * returning adaptive * Delete PlayScreenDefault.swift * Update project.pbxproj Removing All references to PlayScreenDefault.swift * Adds fix for some apps with adaptive display enabled (#3) * testing changing to default bounds * Update project.pbxproj (#2) Removing All references to PlayScreenDefault.swift * test 2 * rest * test * as * asd * test * 123 * Adding inverseScreenValues to settings * Reduced code and added ternaries * added else when inverseScreenValues is false (might rework * Adds MacOSVersion to plist and a 'fix' for screen issue (#4) * testing changing to default bounds * Update project.pbxproj (#2) Removing All references to PlayScreenDefault.swift * test 2 * rest * test * as * asd * test * 123 * Adding inverseScreenValues to settings * Reduced code and added ternaries * added else when inverseScreenValues is false (might rework * Test to fix pre MacOS 13.2 * Check this out * Using float instead of string * v2 * test * lets see * adding print * NSLog(@"macOS version: %f", [[PlaySettings shared] macOSVersion]); * [[PlaySettings shared] macOSVersion] rounded * adding 00000 to every float * testing * return back to normal double * Forgot about floating point error * more than 13.1 * more * 13.199 * setting to latest macOS version * more floating error and macOS fixes * reduced lines and fix some errors * Update project.pbxproj * Update project.pbxproj * Update PlaySettings.swift * Enables responsiveness for some apps for default screen settings if toggle enabled (#5) * test disabling FBSDisplayMode * disabling FBS * Adding experimental fix toggle to fix responsiveness * adding an extra log * extra * moving the function to a better place * back to another place * added CheckResizabilityHasRun * Testing some stuff * name issue * added some comments to test * added fuction stop * True to yes * Changed yes to no * adding observer for resize * adding chess * test * back again * Removing extra check for a future commit * changed macos check to ios check * Remove macOSVersion --- PlayTools.xcodeproj/project.pbxproj | 8 +- .../Controls/PTFakeTouch/NSObject+Swizzle.m | 93 ++++++++++++++++--- PlayTools/PlayLoader.m | 2 + PlayTools/PlayScreen.swift | 40 +++++++- PlayTools/PlaySettings.swift | 7 +- 5 files changed, 129 insertions(+), 21 deletions(-) diff --git a/PlayTools.xcodeproj/project.pbxproj b/PlayTools.xcodeproj/project.pbxproj index e25d9dc2..8c455222 100644 --- a/PlayTools.xcodeproj/project.pbxproj +++ b/PlayTools.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 2847AE48298EFC0F00B0F983 /* PlayScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2847AE47298EFC0F00B0F983 /* PlayScreen.swift */; }; 6E76639B28D0FAE700DE4AF9 /* Plugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E76639A28D0FAE700DE4AF9 /* Plugin.swift */; }; 6E76639C28D0FAE700DE4AF9 /* Plugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E76639A28D0FAE700DE4AF9 /* Plugin.swift */; }; 6E7663A128D0FB5300DE4AF9 /* AKPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7663A028D0FB5300DE4AF9 /* AKPlugin.swift */; }; @@ -14,7 +15,6 @@ 6E84A14528D0F94E00BF7495 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA818CBA287ABFD5000BEE9D /* UIKit.framework */; }; 6E84A15028D0F97500BF7495 /* AKInterface.bundle in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6E84A14C28D0F96D00BF7495 /* AKInterface.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; AA71970B287A44D200623C15 /* PlayLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = AA719702287A44D200623C15 /* PlayLoader.m */; }; - AA71970C287A44D200623C15 /* PlayScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719703287A44D200623C15 /* PlayScreen.swift */; }; AA71970D287A44D200623C15 /* PlaySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719704287A44D200623C15 /* PlaySettings.swift */; }; AA71970E287A44D200623C15 /* PlayLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = AA719705287A44D200623C15 /* PlayLoader.h */; }; AA71970F287A44D200623C15 /* PlayCover.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719706287A44D200623C15 /* PlayCover.swift */; }; @@ -67,6 +67,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 2847AE47298EFC0F00B0F983 /* PlayScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayScreen.swift; sourceTree = ""; }; 6E76639628D0FA6200DE4AF9 /* AKInterface-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AKInterface-Bridging-Header.h"; sourceTree = ""; }; 6E76639A28D0FAE700DE4AF9 /* Plugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Plugin.swift; sourceTree = ""; }; 6E7663A028D0FB5300DE4AF9 /* AKPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AKPlugin.swift; sourceTree = ""; }; @@ -74,7 +75,6 @@ 6E84A14C28D0F96D00BF7495 /* AKInterface.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AKInterface.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; AA7196D8287A447700623C15 /* PlayTools.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PlayTools.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AA719702287A44D200623C15 /* PlayLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlayLoader.m; sourceTree = ""; }; - AA719703287A44D200623C15 /* PlayScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayScreen.swift; sourceTree = ""; }; AA719704287A44D200623C15 /* PlaySettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaySettings.swift; sourceTree = ""; }; AA719705287A44D200623C15 /* PlayLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlayLoader.h; sourceTree = ""; }; AA719706287A44D200623C15 /* PlayCover.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayCover.swift; sourceTree = ""; }; @@ -175,11 +175,11 @@ AA719791287A481500623C15 /* Utils */, AA71970A287A44D200623C15 /* Info.plist */, AA719706287A44D200623C15 /* PlayCover.swift */, - AA719703287A44D200623C15 /* PlayScreen.swift */, AA719704287A44D200623C15 /* PlaySettings.swift */, AA719705287A44D200623C15 /* PlayLoader.h */, AA719702287A44D200623C15 /* PlayLoader.m */, AA719708287A44D200623C15 /* PlayTools.h */, + 2847AE47298EFC0F00B0F983 /* PlayScreen.swift */, ); path = PlayTools; sourceTree = ""; @@ -445,6 +445,7 @@ AA71970D287A44D200623C15 /* PlaySettings.swift in Sources */, AA719759287A480D00623C15 /* PlayAction.swift in Sources */, 6E7663A528D0FEBE00DE4AF9 /* AKPluginLoader.swift in Sources */, + 2847AE48298EFC0F00B0F983 /* PlayScreen.swift in Sources */, AA7197A2287A481500623C15 /* CircleMenu.swift in Sources */, AA71978B287A480D00623C15 /* MenuController.swift in Sources */, AA7197AA287A481500623C15 /* Toast.swift in Sources */, @@ -457,7 +458,6 @@ AA7197A3287A481500623C15 /* CircleMenuButton.swift in Sources */, AA7197A9287A481500623C15 /* PlayInfo.swift in Sources */, AA71986A287A81A000623C15 /* PTFakeMetaTouch.m in Sources */, - AA71970C287A44D200623C15 /* PlayScreen.swift in Sources */, ABCECEE629750BA600746595 /* PlayedApple.swift in Sources */, AA71986C287A81A000623C15 /* NSObject+Swizzle.m in Sources */, AA71970F287A44D200623C15 /* PlayCover.swift in Sources */, diff --git a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m index 63873578..13b0de40 100644 --- a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m +++ b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m @@ -53,6 +53,23 @@ - (BOOL) hook_prefersPointerLocked { return false; } +- (CGRect) hook_frameDefault { + return [PlayScreen frameDefault:[self hook_frameDefault]]; +} + +- (CGRect) hook_boundsDefault { + return [PlayScreen boundsDefault:[self hook_boundsDefault]]; +} + +- (CGRect) hook_nativeBoundsDefault { + return [PlayScreen nativeBoundsDefault:[self hook_nativeBoundsDefault]]; +} + +- (CGSize) hook_sizeDelfault { + return [PlayScreen sizeAspectRatioDefault:[self hook_sizeDelfault]]; +} + + - (CGRect) hook_frame { return [PlayScreen frame:[self hook_frame]]; } @@ -70,6 +87,7 @@ - (CGSize) hook_size { } + - (long long) hook_orientation { return 0; } @@ -82,6 +100,16 @@ - (double) hook_scale { return 2.0; } +- (double) get_default_height { + return [[UIScreen mainScreen] bounds].size.height; + +} +- (double) get_default_width { + return [[UIScreen mainScreen] bounds].size.width; + +} + + bool menuWasCreated = false; - (id) initWithRootMenuHook:(id)rootMenu { @@ -119,21 +147,62 @@ -(CGRect) hook_frame { However, doing this would require generating @interface declarations (either with class-dump or by hand) which would add a lot of code and complexity. I'm not sure this trade-off is "worth it", at least at the time of writing. */ + @implementation PTSwizzleLoader + (void)load { - if ([[PlaySettings shared] adaptiveDisplay]) { - // This lines set external Scene (frame and those things) settings and other IOS10 Runtime services by swizzling - [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frame)]; - [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_bounds)]; - [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_size)]; - - // This actually fixes Apple mess at MacOS 13.2 - [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(orientation) withMethod:@selector(hook_orientation)]; - [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeBounds) withMethod:@selector(hook_nativeBounds)]; - [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeScale) withMethod:@selector(hook_nativeScale)]; - [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(scale) withMethod:@selector(hook_scale)]; + // This might need refactor soon + if(@available(iOS 16.3, *)) { + if ([[PlaySettings shared] adaptiveDisplay]) { + // This is an experimental fix + if ([[PlaySettings shared] inverseScreenValues]) { + // This lines set External Scene settings and other IOS10 Runtime services by swizzling + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frameDefault)]; + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_boundsDefault)]; + [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_sizeDelfault)]; + + // Fixes Apple mess at MacOS 13.2 + [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(orientation) withMethod:@selector(hook_orientation)]; + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeBounds) withMethod:@selector(hook_nativeBoundsDefault)]; + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeScale) withMethod:@selector(hook_nativeScale)]; + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(scale) withMethod:@selector(hook_scale)]; + } else { + // This acutally runs when adaptiveDisplay is normally triggered + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frame)]; + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_bounds)]; + [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_size)]; + + [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(orientation) withMethod:@selector(hook_orientation)]; + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeBounds) withMethod:@selector(hook_nativeBounds)]; + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeScale) withMethod:@selector(hook_nativeScale)]; + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(scale) withMethod:@selector(hook_scale)]; + } + } + else { + CGFloat newValueW = (CGFloat) [self get_default_width]; + [[PlaySettings shared] setValue:@(newValueW) forKey:@"windowSizeWidth"]; + + CGFloat newValueH = (CGFloat)[self get_default_height]; + [[PlaySettings shared] setValue:@(newValueH) forKey:@"windowSizeHeight"]; + if (![[PlaySettings shared] inverseScreenValues]) { + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frameDefault)]; + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_boundsDefault)]; + [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_sizeDelfault)]; + } + [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(orientation) withMethod:@selector(hook_orientation)]; + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeBounds) withMethod:@selector(hook_nativeBoundsDefault)]; + + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeScale) withMethod:@selector(hook_nativeScale)]; + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(scale) withMethod:@selector(hook_scale)]; + } + } + else { + if ([[PlaySettings shared] adaptiveDisplay]) { + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frame)]; + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_bounds)]; + [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_size)]; + } } - + [objc_getClass("_UIMenuBuilder") swizzleInstanceMethod:sel_getUid("initWithRootMenu:") withMethod:@selector(initWithRootMenuHook:)]; [objc_getClass("IOSViewController") swizzleInstanceMethod:@selector(prefersPointerLocked) withMethod:@selector(hook_prefersPointerLocked)]; } diff --git a/PlayTools/PlayLoader.m b/PlayTools/PlayLoader.m index f4089391..92887d97 100644 --- a/PlayTools/PlayLoader.m +++ b/PlayTools/PlayLoader.m @@ -27,6 +27,7 @@ static int pt_uname(struct utsname *uts) { return 0; } + // Update output of sysctl for key values hw.machine, hw.product and hw.target to match iOS output // This spoofs the device type to apps allowing us to report as any iOS device static int pt_sysctl(int *name, u_int types, void *buf, size_t *size, void *arg0, size_t arg1) { @@ -163,6 +164,7 @@ static OSStatus pt_SecItemDelete(CFDictionaryRef query) { DYLD_INTERPOSE(pt_SecItemDelete, SecItemDelete) + @implementation PlayLoader static void __attribute__((constructor)) initialize(void) { diff --git a/PlayTools/PlayScreen.swift b/PlayTools/PlayScreen.swift index cefacb51..47bc4ff6 100644 --- a/PlayTools/PlayScreen.swift +++ b/PlayTools/PlayScreen.swift @@ -2,13 +2,13 @@ // ScreenController.swift // PlayTools // - import Foundation import UIKit let screen = PlayScreen.shared -let mainScreenWidth = PlaySettings.shared.windowSizeWidth -let mainScreenHeight = PlaySettings.shared.windowSizeHeight +let isInvertFixEnabled = PlaySettings.shared.inverseScreenValues && PlaySettings.shared.adaptiveDisplay +let mainScreenWidth = !isInvertFixEnabled ? PlaySettings.shared.windowSizeWidth : PlaySettings.shared.windowSizeHeight +let mainScreenHeight = !isInvertFixEnabled ? PlaySettings.shared.windowSizeHeight : PlaySettings.shared.windowSizeWidth extension CGSize { func aspectRatio() -> CGFloat { @@ -30,6 +30,12 @@ extension CGSize { func toAspectRatioInternal() -> CGSize { return CGSize(width: mainScreenHeight, height: mainScreenWidth) } + func toAspectRatioDefault() -> CGSize { + return CGSize(width: mainScreenHeight, height: mainScreenWidth) + } + func toAspectRatioInternalDefault() -> CGSize { + return CGSize(width: mainScreenWidth, height: mainScreenHeight) + } } extension CGRect { @@ -48,6 +54,12 @@ extension CGRect { func toAspectRatioReversed() -> CGRect { return CGRect(x: minX, y: minY, width: mainScreenHeight, height: mainScreenWidth) } + func toAspectRatioDefault(_ multiplier: CGFloat = 1) -> CGRect { + return CGRect(x: minX, y: minY, width: mainScreenWidth * multiplier, height: mainScreenHeight * multiplier) + } + func toAspectRatioReversedDefault() -> CGRect { + return CGRect(x: minX, y: minY, width: mainScreenHeight, height: mainScreenWidth) + } } extension UIScreen { @@ -148,6 +160,28 @@ public class PlayScreen: NSObject { AKInterface.shared!.setMenuBarVisible(visible) } + // Default calculation + @objc public static func frameReversedDefault(_ rect: CGRect) -> CGRect { + return rect.toAspectRatioReversedDefault() + } + @objc public static func frameDefault(_ rect: CGRect) -> CGRect { + return rect.toAspectRatioDefault() + } + @objc public static func boundsDefault(_ rect: CGRect) -> CGRect { + return rect.toAspectRatioDefault() + } + + @objc public static func nativeBoundsDefault(_ rect: CGRect) -> CGRect { + return rect.toAspectRatioDefault(2) + } + + @objc public static func sizeAspectRatioDefault(_ size: CGSize) -> CGSize { + return size.toAspectRatioDefault() + } + @objc public static func frameInternalDefault(_ rect: CGRect) -> CGRect { + return rect.toAspectRatioDefault() + } + } extension CGFloat { diff --git a/PlayTools/PlaySettings.swift b/PlayTools/PlaySettings.swift index 0f95eab6..f425bb87 100644 --- a/PlayTools/PlaySettings.swift +++ b/PlayTools/PlaySettings.swift @@ -33,9 +33,11 @@ let settings = PlaySettings.shared lazy var sensitivity = settingsData.sensitivity / 100 - lazy var windowSizeHeight = CGFloat(settingsData.windowHeight) + @objc lazy var windowSizeHeight = CGFloat(settingsData.windowHeight) - lazy var windowSizeWidth = CGFloat(settingsData.windowWidth) + @objc lazy var windowSizeWidth = CGFloat(settingsData.windowWidth) + + @objc lazy var inverseScreenValues = settingsData.inverseScreenValues @objc lazy var adaptiveDisplay = settingsData.resolution == 0 ? false : true @@ -82,4 +84,5 @@ struct AppSettingsData: Codable { var version = "2.0.0" var playChain = false var playChainDebugging = false + var inverseScreenValues = false } From 98288d9cba2497410f5f0c0b515bfa869a9a20ff Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Wed, 15 Feb 2023 22:04:54 +0800 Subject: [PATCH 14/92] Localization --- PlayTools.xcodeproj/project.pbxproj | 19 ++++++++ PlayTools/Controls/ControlMode.swift | 2 +- PlayTools/Controls/MenuController.swift | 22 +++++++--- PlayTools/Controls/PlayInput.swift | 50 +++++++++++++++------- PlayTools/Keymap/EditorController.swift | 12 ++++-- PlayTools/en.lproj/Playtools.strings | Bin 0 -> 3000 bytes PlayTools/zh-Hans.lproj/Playtools.strings | Bin 0 -> 2924 bytes 7 files changed, 78 insertions(+), 27 deletions(-) create mode 100644 PlayTools/en.lproj/Playtools.strings create mode 100644 PlayTools/zh-Hans.lproj/Playtools.strings diff --git a/PlayTools.xcodeproj/project.pbxproj b/PlayTools.xcodeproj/project.pbxproj index 86e76384..45e0a16c 100644 --- a/PlayTools.xcodeproj/project.pbxproj +++ b/PlayTools.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 6E7663A528D0FEBE00DE4AF9 /* AKPluginLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7663A428D0FEBE00DE4AF9 /* AKPluginLoader.swift */; }; 6E84A14528D0F94E00BF7495 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA818CBA287ABFD5000BEE9D /* UIKit.framework */; }; 6E84A15028D0F97500BF7495 /* AKInterface.bundle in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6E84A14C28D0F96D00BF7495 /* AKInterface.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 951D8275299D097C00D35B20 /* Playtools.strings in Resources */ = {isa = PBXBuildFile; fileRef = 951D8277299D097C00D35B20 /* Playtools.strings */; }; AA71970B287A44D200623C15 /* PlayLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = AA719702287A44D200623C15 /* PlayLoader.m */; }; AA71970C287A44D200623C15 /* PlayScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719703287A44D200623C15 /* PlayScreen.swift */; }; AA71970D287A44D200623C15 /* PlaySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719704287A44D200623C15 /* PlaySettings.swift */; }; @@ -71,6 +72,8 @@ 6E7663A028D0FB5300DE4AF9 /* AKPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AKPlugin.swift; sourceTree = ""; }; 6E7663A428D0FEBE00DE4AF9 /* AKPluginLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AKPluginLoader.swift; sourceTree = ""; }; 6E84A14C28D0F96D00BF7495 /* AKInterface.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AKInterface.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; + 951D8276299D097C00D35B20 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Playtools.strings; sourceTree = ""; }; + 951D8278299D098000D35B20 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Playtools.strings"; sourceTree = ""; }; AA7196D8287A447700623C15 /* PlayTools.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PlayTools.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AA719702287A44D200623C15 /* PlayLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlayLoader.m; sourceTree = ""; }; AA719703287A44D200623C15 /* PlayScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayScreen.swift; sourceTree = ""; }; @@ -146,6 +149,7 @@ AA7196CE287A447700623C15 = { isa = PBXGroup; children = ( + 951D8277299D097C00D35B20 /* Playtools.strings */, AA7196DA287A447700623C15 /* PlayTools */, 6E76639928D0FA6F00DE4AF9 /* AKInterface */, AA7196D9287A447700623C15 /* Products */, @@ -358,6 +362,7 @@ knownRegions = ( en, Base, + "zh-Hans", ); mainGroup = AA7196CE287A447700623C15; packageReferences = ( @@ -385,6 +390,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 951D8275299D097C00D35B20 /* Playtools.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -458,6 +464,19 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXVariantGroup section */ + 951D8277299D097C00D35B20 /* Playtools.strings */ = { + isa = PBXVariantGroup; + children = ( + 951D8276299D097C00D35B20 /* en */, + 951D8278299D098000D35B20 /* zh-Hans */, + ); + name = Playtools.strings; + path = PlayTools; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + /* Begin XCBuildConfiguration section */ 6E84A14E28D0F96D00BF7495 /* Debug */ = { isa = XCBuildConfiguration; diff --git a/PlayTools/Controls/ControlMode.swift b/PlayTools/Controls/ControlMode.swift index df298807..deb68764 100644 --- a/PlayTools/Controls/ControlMode.swift +++ b/PlayTools/Controls/ControlMode.swift @@ -48,7 +48,7 @@ public class ControlMode { extension NSNotification.Name { public static let playtoolsKeymappingWillEnable: NSNotification.Name = NSNotification.Name("playtools.keymappingWillEnable") - + public static let playtoolsKeymappingWillDisable: NSNotification.Name = NSNotification.Name("playtools.keymappingWillDisable") } diff --git a/PlayTools/Controls/MenuController.swift b/PlayTools/Controls/MenuController.swift index 3ec96d13..a304b48c 100644 --- a/PlayTools/Controls/MenuController.swift +++ b/PlayTools/Controls/MenuController.swift @@ -61,12 +61,19 @@ extension UIViewController { struct CommandsList { static let KeymappingToolbox = "keymapping" } - -var keymapping = ["Open/Close Keymapping Editor", - "Delete selected element", - "Upsize selected element", - "Downsize selected element", - "Rotate display area"] +// have to use a customized name, in case it conflicts with the game's localization file +var keymapping = [ + NSLocalizedString("menu.keymapping.toggleEditor", tableName: "Playtools", + value: "Open/Close Keymapping Editor", comment: ""), + NSLocalizedString("menu.keymapping.deleteElement", tableName: "Playtools", + value: "Delete selected element", comment: ""), + NSLocalizedString("menu.keymapping.upsizeElement", tableName: "Playtools", + value: "Upsize selected element", comment: ""), + NSLocalizedString("menu.keymapping.downsizeElement", tableName: "Playtools", + value: "Downsize selected element", comment: ""), + NSLocalizedString("menu.keymapping.rotateDisplay", tableName: "Playtools", + value: "Rotate display area", comment: "") + ] var keymappingSelectors = [#selector(UIApplication.switchEditorMode(_:)), #selector(UIApplication.removeElement(_:)), #selector(UIApplication.upscaleElement(_:)), @@ -97,7 +104,8 @@ class MenuController { options: .displayInline, children: arrowKeyChildrenCommands) - return UIMenu(title: NSLocalizedString("Keymapping", comment: ""), + return UIMenu(title: NSLocalizedString("menu.keymapping", tableName: "Playtools", + value: "Keymapping", comment: ""), image: nil, identifier: .keymappingMenu, options: [], diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 6b031afd..b632667c 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -135,6 +135,40 @@ class PlayInput { } } + func initializeToasts() { + if !settings.mouseMapping || !mode.visible { + return + } + Toast.showHint(title: NSLocalizedString("hint.enableKeymapping.title", + tableName: "Playtools", + value: "Keymapping Disabled", comment: ""), + text: [NSLocalizedString("hint.enableKeymapping.content.before", + tableName: "Playtools", + value: "Press", comment: ""), + " option ⌥ ", + NSLocalizedString("hint.enableKeymapping.content.after", + tableName: "Playtools", + value: "to enable keymapping", comment: "")], + notification: NSNotification.Name.playtoolsKeymappingWillEnable) + let center = NotificationCenter.default + var token: NSObjectProtocol? + token = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillEnable, + object: nil, queue: OperationQueue.main) { _ in + center.removeObserver(token!) + Toast.showHint(title: NSLocalizedString("hint.disableKeymapping.title", + tableName: "Playtools", + value: "Keymapping Enabled", comment: ""), + text: [NSLocalizedString("hint.disableKeymapping.content.before", + tableName: "Playtools", + value: "Press", comment: ""), + " option ⌥ ", + NSLocalizedString("hint.disableKeymapping.content.after", + tableName: "Playtools", + value: "to disable keymapping", comment: "")], + notification: NSNotification.Name.playtoolsKeymappingWillDisable) + } + } + func initialize() { if !PlaySettings.shared.keymapping { return @@ -163,21 +197,7 @@ class PlayInput { } setupShortcuts() - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5) { - if !settings.mouseMapping || !mode.visible { - return - } - Toast.showHint(title: "Keymapping Disabled", text: ["Press ", "option ⌥", " to enable keymapping"], - notification: NSNotification.Name.playtoolsKeymappingWillEnable) - let center = NotificationCenter.default - var token: NSObjectProtocol? - token = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillEnable, - object: nil, queue: OperationQueue.main) { _ in - center.removeObserver(token!) - Toast.showHint(title: "Keymapping Enabled", text: ["Press ", "option ⌥", " to disable keymapping"], - notification: NSNotification.Name.playtoolsKeymappingWillDisable) - } - } + DispatchQueue.main.asyncAfter(deadline: .now() + 5, qos: .utility, execute: initializeToasts) // Fix beep sound AKInterface.shared! diff --git a/PlayTools/Keymap/EditorController.swift b/PlayTools/Keymap/EditorController.swift index c6f7075e..43af3eaf 100644 --- a/PlayTools/Keymap/EditorController.swift +++ b/PlayTools/Keymap/EditorController.swift @@ -60,16 +60,20 @@ class EditorController { previousWindow?.makeKeyAndVisible() PlayInput.shared.toggleEditor(show: false) focusedControl = nil - Toast.showHint(title: "Keymap Saved") + Toast.showHint(title: NSLocalizedString("hint.keymapSaved", + tableName: "Playtools", value: "Keymap Saved", comment: "")) } else { PlayInput.shared.toggleEditor(show: true) previousWindow = screen.keyWindow editorWindow = initWindow() editorWindow?.makeKeyAndVisible() showButtons() - Toast.showHint(title: "Keymapping Editor", - text: ["Click a button to edit its position or key bind\n" + - "Click an empty area to open input menu"], + Toast.showHint(title: NSLocalizedString("hint.keymappingEditor.title", + tableName: "Playtools", value: "Keymapping Editor", comment: ""), + text: [NSLocalizedString("hint.keymappingEditor.content", + tableName: "Playtools", + value: "Click a button to edit its position or key bind\n" + + "Click an empty area to open input menu", comment: "")], notification: NSNotification.Name.playtoolsKeymappingWillEnable) } // Toast.showOver(msg: "\(UIApplication.shared.windows.count)") diff --git a/PlayTools/en.lproj/Playtools.strings b/PlayTools/en.lproj/Playtools.strings new file mode 100644 index 0000000000000000000000000000000000000000..1e82629eb58fd99c06b0626d339ec84318dafe43 GIT binary patch literal 3000 zcmd6p&rZTX5XR@sQ*1bLfCn(1gp-LHjW;ia79?of&=!Laul{DsX17U|dVmtrW&h01 ze7pP2xBLAyk&zg=lZpZ0Xs_T4MTQdgijH)bSO)DYXQfF?}PM$Uuhj zYWqw2Vrqp%%z4A=mT!;m2(P)dSz)6QHC7#Q%;?g#ws zVpnI##p65Kod@9woL&P%f6!(k%T1&+n^g+FkC7^rcq9 zYu)EsVRLpmLPh-9JO(!IMdhr#)|@IA|7QFcJDyllDbUVjgPyG{Btl<=o|)`Way&tAidqaC4}Bx? zWgtU&)BOedBGhsTFlPm;JG?ur9bnZ=+a$1|NBXKfdKjJS)&?snxxqU`o7kGGDOoz! z8_G2>41lChdzu019DCT{Tkq;ow{t|OAE*nQNuf`w#^^dTB@>*OIn}5G^yG%|=R$NE zi@bfD_%Byw`_cYCUN=K7!#-ej@!~S{_G*4H9d#5@SFTUAB2XKsKTRE}8GH*yFEF}< zf&_SHQiA22m7-NC3zd!BSGvsc%`l$p*5Y7EFGh2?3bikiDaxh?t{Ow-GOS0X40M*$ zbLDG|5%Pn+Qq+k?9Td0hAeW9u(skahtfLN8abs-SXR3|-8FAerj#M9WOV}8d*cR>F z3DqrGf_pYHbFUOpTY&(}&xU5&vgJ6lc8bfYN8@}&gcV+?z>Y))s_4@Cd<2><{9 literal 0 HcmV?d00001 From 17d43d2c6c27c5913f8dfcfebac63bfec5868553 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Sat, 18 Feb 2023 20:12:48 +0800 Subject: [PATCH 15/92] remove rounded square mouse from circle menu --- PlayTools/Keymap/DragElementsView.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/PlayTools/Keymap/DragElementsView.swift b/PlayTools/Keymap/DragElementsView.swift index c1e0a378..24dfe7a1 100644 --- a/PlayTools/Keymap/DragElementsView.swift +++ b/PlayTools/Keymap/DragElementsView.swift @@ -20,7 +20,7 @@ class KeymapHolder: CircleMenuDelegate { frame: CGRect(x: 0, y: 0, width: 50, height: 50), normalIcon: "xmark.circle.fill", selectedIcon: "xmark.circle.fill", - buttonsCount: 4, + buttonsCount: 3, duration: 0.25, distance: 80) menu?.delegate = self @@ -57,7 +57,8 @@ class KeymapHolder: CircleMenuDelegate { case 2: EditorController.shared.addMouseArea(globalPoint!) default: - EditorController.shared.addMouseJoystick(globalPoint!) + Toast.showHint(title: "item \(atIndex) is not recognizable") +// EditorController.shared.addMouseJoystick(globalPoint!) } hideWithAnimation() } @@ -73,7 +74,7 @@ class KeymapHolder: CircleMenuDelegate { private let items: [String] = [ "circle.circle", "dpad", - "arrow.up.and.down.and.arrow.left.and.right", +// "arrow.up.and.down.and.arrow.left.and.right", // "rb.rectangle.roundedbottom.fill", // "lb.rectangle.roundedbottom", "computermouse" From 853bec569bd135a8e7cd1c788056362f8a61dcc9 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Sun, 19 Feb 2023 17:04:57 +0800 Subject: [PATCH 16/92] not show hint every launch --- PlayTools/Controls/PlayInput.swift | 26 +++++++++++++++++++++++--- PlayTools/Utils/Toast.swift | 10 +++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 6b031afd..a91b61ea 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -164,18 +164,38 @@ class PlayInput { setupShortcuts() DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5) { - if !settings.mouseMapping || !mode.visible { + self.parseKeymap() + if !settings.mouseMapping || !mode.visible || self.actions.count <= 0 { + return + } + let persistenceKeyname = "playtoolsKeymappingDisabledAt" + let lastUse = UserDefaults.standard.float(forKey: persistenceKeyname) + var thisUse = lastUse + if lastUse < 1 { + thisUse = 2 + } else { + thisUse = Float(Date.timeIntervalSinceReferenceDate) + } + var token2: NSObjectProtocol? + let center = NotificationCenter.default + token2 = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillDisable, + object: nil, queue: OperationQueue.main) { _ in + center.removeObserver(token2!) + UserDefaults.standard.set(thisUse, forKey: persistenceKeyname) + } + if lastUse > Float(Date.now.addingTimeInterval(-86400*14).timeIntervalSinceReferenceDate) { return } Toast.showHint(title: "Keymapping Disabled", text: ["Press ", "option ⌥", " to enable keymapping"], + timeout: 10, notification: NSNotification.Name.playtoolsKeymappingWillEnable) - let center = NotificationCenter.default var token: NSObjectProtocol? token = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillEnable, object: nil, queue: OperationQueue.main) { _ in center.removeObserver(token!) Toast.showHint(title: "Keymapping Enabled", text: ["Press ", "option ⌥", " to disable keymapping"], - notification: NSNotification.Name.playtoolsKeymappingWillDisable) + timeout: 10, + notification: NSNotification.Name.playtoolsKeymappingWillDisable) } } diff --git a/PlayTools/Utils/Toast.swift b/PlayTools/Utils/Toast.swift index 44fba46a..aafc0ad1 100644 --- a/PlayTools/Utils/Toast.swift +++ b/PlayTools/Utils/Toast.swift @@ -64,7 +64,7 @@ class Toast { return txt } - public static func showHint(title: String, text: [String] = [], timeout: Double = 3, + public static func showHint(title: String, text: [String] = [], timeout: Double = -3, notification: NSNotification.Name? = nil) { let parent = screen.keyWindow! @@ -89,6 +89,7 @@ class Toast { if hintView.count > 4 { hideHint(hint: hintView.first!) } + var life = timeout if let note = notification { let center = NotificationCenter.default var token: NSObjectProtocol? @@ -96,8 +97,11 @@ class Toast { center.removeObserver(token!) hideHint(hint: messageLabel) } - } else { - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5 + timeout) { + } else if life < 0 { + life = 3 + } + if life >= 0 { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5 + life) { hideHint(hint: messageLabel) } } From e7744f593b79d7f484a4ffc0869e1e495cccad8e Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Wed, 22 Feb 2023 03:29:53 +0800 Subject: [PATCH 17/92] automatic keyboard mapping --- AKPlugin.swift | 4 +- PlayTools/Controls/ControlMode.swift | 12 ++---- PlayTools/Controls/PlayInput.swift | 45 +++++++++++--------- PlayTools/Controls/PlayMice.swift | 62 ++++++++++++++-------------- PlayTools/Controls/Toucher.swift | 9 +--- PlayTools/PlaySettings.swift | 3 -- 6 files changed, 65 insertions(+), 70 deletions(-) diff --git a/AKPlugin.swift b/AKPlugin.swift index 0e17c448..2d1a76b3 100644 --- a/AKPlugin.swift +++ b/AKPlugin.swift @@ -18,11 +18,11 @@ class AKPlugin: NSObject, Plugin { } var mousePoint: CGPoint { - NSApplication.shared.windows.first!.mouseLocationOutsideOfEventStream as CGPoint + NSApplication.shared.windows.first?.mouseLocationOutsideOfEventStream ?? CGPoint() } var windowFrame: CGRect { - NSApplication.shared.windows.first!.frame as CGRect + NSApplication.shared.windows.first?.frame ?? CGRect() } var isMainScreenEqualToFirst: Bool { diff --git a/PlayTools/Controls/ControlMode.swift b/PlayTools/Controls/ControlMode.swift index 35661efb..e95842dd 100644 --- a/PlayTools/Controls/ControlMode.swift +++ b/PlayTools/Controls/ControlMode.swift @@ -10,7 +10,7 @@ let mode = ControlMode.mode public class ControlMode { static public let mode = ControlMode() - public var visible: Bool = PlaySettings.shared.mouseMapping + public var visible: Bool = true func show(_ show: Bool) { if !editor.editorMode { @@ -19,16 +19,12 @@ public class ControlMode { if screen.fullscreen { screen.switchDock(true) } - if PlaySettings.shared.mouseMapping { - AKInterface.shared!.unhideCursor() - } - PlayInput.shared.invalidate() + AKInterface.shared!.unhideCursor() +// PlayInput.shared.invalidate() } } else { if visible { - if PlaySettings.shared.mouseMapping { - AKInterface.shared!.hideCursor() - } + AKInterface.shared!.hideCursor() if screen.fullscreen { screen.switchDock(false) } diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index e541c285..27c192b2 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -5,7 +5,7 @@ import UIKit class PlayInput { static let shared = PlayInput() var actions = [Action]() - var timeoutForBind = true + static var keyboardMapped = true static private var lCmdPressed = false static private var rCmdPressed = false @@ -16,8 +16,6 @@ class PlayInput { for action in self.actions { action.invalidate() } - PlayInput.buttonHandlers.removeAll(keepingCapacity: true) - GCController.current?.extendedGamepad?.valueChangedHandler = nil } static public func registerButton(key: String, handler: @escaping (Bool) -> Void) { @@ -55,6 +53,7 @@ class PlayInput { func parseKeymap() { actions = [] + PlayInput.buttonHandlers.removeAll(keepingCapacity: true) for button in keymap.keymapData.buttonModels { actions.append(ButtonAction(data: button)) } @@ -64,9 +63,7 @@ class PlayInput { } for mouse in keymap.keymapData.mouseAreaModel { - if mouse.keyName.hasSuffix("tick") || settings.mouseMapping { - actions.append(CameraAction(data: mouse)) - } + actions.append(CameraAction(data: mouse)) } for joystick in keymap.keymapData.joystickModel { @@ -81,6 +78,7 @@ class PlayInput { public func toggleEditor(show: Bool) { mode.show(show) + PlayInput.keyboardMapped = !show if show { if let keyboard = GCKeyboard.coalesced!.keyboardInput { keyboard.keyChangedHandler = { _, _, keyCode, _ in @@ -115,11 +113,12 @@ class PlayInput { EditorController.shared.setKey(alias) } } + } else { + DispatchQueue.main.async(execute: parseKeymap) } } func setup() { - parseKeymap() GCKeyboard.coalesced?.keyboardInput?.keyChangedHandler = nil GCController.current?.extendedGamepad?.valueChangedHandler = controllerButtonHandler } @@ -146,12 +145,12 @@ class PlayInput { ] private func swapMode() { - if !settings.mouseMapping { - return - } - if !mode.visible { - self.invalidate() - } +// if !settings.mouseMapping { +// return +// } +// if !mode.visible { +// self.invalidate() +// } mode.show(!mode.visible) } @@ -200,30 +199,36 @@ class PlayInput { self.toggleEditor(show: true) } } - + parseKeymap() + centre.addObserver(forName: UIApplication.keyboardDidHideNotification, object: nil, queue: main) { _ in + PlayInput.keyboardMapped = true + } + centre.addObserver(forName: UIApplication.keyboardWillShowNotification, object: nil, queue: main) { _ in + PlayInput.keyboardMapped = false + } centre.addObserver(forName: NSNotification.Name(rawValue: "NSWindowDidBecomeKeyNotification"), object: nil, queue: main) { _ in - if !mode.visible && settings.mouseMapping { + if !mode.visible { AKInterface.shared!.warpCursor() } } setupHotkeys() AKInterface.shared!.initialize(keyboard: {keycode, pressed in - let consumed = !mode.visible && !PlayInput.cmdPressed() + let consumed = PlayInput.keyboardMapped && !PlayInput.cmdPressed() if !consumed { return false } self.keyboardHandler(keycode, pressed) return consumed }, mouseMoved: {deltaX, deltaY in - if mode.visible { + if !PlayInput.keyboardMapped { return false } - if settings.mouseMapping { - PlayMice.shared.handleMouseMoved(deltaX: deltaX, deltaY: deltaY) - } else { + if mode.visible { PlayMice.shared.handleFakeMouseMoved(deltaX: deltaX, deltaY: deltaY) + } else { + PlayMice.shared.handleMouseMoved(deltaX: deltaX, deltaY: deltaY) } return true }, swapMode: self.swapMode) diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index 3cb3328e..14ae316e 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -12,16 +12,12 @@ public class PlayMice { public static let elementName = "Mouse" private static var isInit = false - private var acceptMouseEvents = !PlaySettings.shared.mouseMapping - public init() { if !PlayMice.isInit { setupMouseButton(_up: 2, _down: 4) setupMouseButton(_up: 8, _down: 16) setupMouseButton(_up: 33554432, _down: 67108864) - if !acceptMouseEvents { - setupScrollWheelHandler() - } + setupScrollWheelHandler() PlayMice.isInit = true } } @@ -37,11 +33,14 @@ public class PlayMice { cameraScaleHandler: [String: (CGFloat, CGFloat) -> Void] = [:], joystickHandler: [String: (CGFloat, CGFloat) -> Void] = [:] - public func cursorPos() -> CGPoint { + public func cursorPos() -> CGPoint? { var point = CGPoint(x: 0, y: 0) point = AKInterface.shared!.mousePoint let rect = AKInterface.shared!.windowFrame let viewRect: CGRect = screen.screenRect + if rect.width < 1 || rect.height < 1 { + return nil + } let widthRate = viewRect.width / rect.width var rate = viewRect.height / rect.height if widthRate > rate { @@ -101,7 +100,9 @@ public class PlayMice { public func handleFakeMouseMoved(deltaX: CGFloat, deltaY: CGFloat) { if self.fakedMousePressed { - Toucher.touchcam(point: self.cursorPos(), phase: UITouch.Phase.moved, tid: &fakedMouseTouchPointId) + if let pos = self.cursorPos() { + Toucher.touchcam(point: pos, phase: UITouch.Phase.moved, tid: &fakedMouseTouchPointId) + } } } @@ -126,15 +127,8 @@ public class PlayMice { } private func dontIgnore(_ actionIndex: Int, _ pressed: Bool, _ isEventWindow: Bool) -> Bool { - if EditorController.shared.editorMode { - if pressed && actionIndex != 2 { - EditorController.shared.setKey(buttonIndex[actionIndex]!) - } - return true - } - if self.acceptMouseEvents { - let curPos = self.cursorPos() - if pressed { + if mode.visible && pressed && PlayInput.keyboardMapped { + if let curPos = self.cursorPos() { if !self.fakedMousePressed // For traffic light buttons when not fullscreen && curPos.y > 0 @@ -145,13 +139,12 @@ public class PlayMice { tid: &fakedMouseTouchPointId) return false } - } else { - if self.fakedMousePressed { - Toucher.touchcam(point: curPos, phase: UITouch.Phase.ended, tid: &fakedMouseTouchPointId) - return false - } } - return true + } else if self.fakedMousePressed { + if let curPos = self.cursorPos() { + Toucher.touchcam(point: curPos, phase: UITouch.Phase.ended, tid: &fakedMouseTouchPointId) + return false + } } if !mode.visible { if let handlers = PlayInput.buttonHandlers[KeyCodeNames.keyCodes[buttonIndex[actionIndex]!]!] { @@ -161,6 +154,11 @@ public class PlayMice { } return false } + if EditorController.shared.editorMode { + if pressed && actionIndex != 2 { + EditorController.shared.setKey(buttonIndex[actionIndex]!) + } + } return true } } @@ -217,7 +215,13 @@ class CameraAction: Action { swipeScale1 = SwipeAction() swipeScale2 = SwipeAction() PlayMice.shared.cameraMoveHandler[key] = self.moveUpdated - PlayMice.shared.cameraScaleHandler[PlayMice.elementName] = self.scaleUpdated + PlayMice.shared.cameraScaleHandler[PlayMice.elementName] = {deltaX, deltaY in + if mode.visible { + CameraAction.dragUpdated(deltaX, deltaY) + } else { + self.scaleUpdated(deltaX, deltaY) + } + } } func moveUpdated(_ deltaX: CGFloat, _ deltaY: CGFloat) { swipeMove.move(from: {return center}, deltaX: deltaX, deltaY: deltaY) @@ -239,16 +243,14 @@ class CameraAction: Action { return CGPoint(x: center.x, y: center.y + 100) }, deltaX: 0, deltaY: -moveY) } - // Event handlers SHOULD be SMALL - // DO NOT check things like mode.visible in an event handler - // change the handler itself instead + static func dragUpdated(_ deltaX: CGFloat, _ deltaY: CGFloat) { swipeDrag.move(from: PlayMice.shared.cursorPos, deltaX: deltaX * 4, deltaY: -deltaY * 4) } func invalidate() { PlayMice.shared.cameraMoveHandler.removeValue(forKey: key) - PlayMice.shared.cameraScaleHandler[PlayMice.elementName] = CameraAction.dragUpdated + PlayMice.shared.cameraScaleHandler[PlayMice.elementName] = nil swipeMove.invalidate() swipeScale1.invalidate() swipeScale2.invalidate() @@ -263,7 +265,6 @@ class SwipeAction: Action { // in rare cases the cooldown reset task is lost by the dispatch queue self.cooldown = false // TODO: camera mode switch: Flexibility v.s. Precision - // TODO: find reasonable ways to promote fake touch priority timer.schedule(deadline: DispatchTime.now() + 1, repeating: 0.1, leeway: DispatchTimeInterval.never) timer.setEventHandler(qos: DispatchQoS.background, handler: self.checkEnded) timer.activate() @@ -293,13 +294,14 @@ class SwipeAction: Action { self.lastCounter = self.counter } - public func move(from: () -> CGPoint, deltaX: CGFloat, deltaY: CGFloat) { + public func move(from: () -> CGPoint?, deltaX: CGFloat, deltaY: CGFloat) { if id == nil { if cooldown { return } + guard let start = from() else {return} + location = start counter = 0 - location = from() Toucher.touchcam(point: location, phase: UITouch.Phase.began, tid: &id) timer.resume() } diff --git a/PlayTools/Controls/Toucher.swift b/PlayTools/Controls/Toucher.swift index 60a27be5..46b4dc39 100644 --- a/PlayTools/Controls/Toucher.swift +++ b/PlayTools/Controls/Toucher.swift @@ -20,7 +20,8 @@ class Toucher { if phase == UITouch.Phase.began { tid = nextId nextId += 1 -// Toast.showOver(msg: tid!.description) + keyWindow = screen.keyWindow + keyView = keyWindow!.hitTest(point, with: nil) } guard let bigId = tid else { // sending other phases with empty id is no-op @@ -30,12 +31,6 @@ class Toucher { tid = nil } touchQueue.async { - if keyWindow == nil || keyView == nil { - keyWindow = screen.keyWindow - DispatchQueue.main.sync { - keyView = keyWindow?.hitTest(point, with: nil) - } - } var pointId: Int = 0 if phase != UITouch.Phase.began { guard let id = idMap.firstIndex(of: bigId) else { diff --git a/PlayTools/PlaySettings.swift b/PlayTools/PlaySettings.swift index ef6fc181..ef57303b 100644 --- a/PlayTools/PlaySettings.swift +++ b/PlayTools/PlaySettings.swift @@ -27,8 +27,6 @@ let settings = PlaySettings.shared lazy var keymapping = settingsData.keymapping - lazy var mouseMapping = settingsData.mouseMapping - lazy var notch = settingsData.notch lazy var sensitivity = settingsData.sensitivity / 100 @@ -63,7 +61,6 @@ let settings = PlaySettings.shared struct AppSettingsData: Codable { var keymapping = true - var mouseMapping = true var sensitivity: Float = 50 var disableTimeout = false From 5f87bdbdb1205eb0891caada5c0229489a00c662 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Fri, 24 Feb 2023 17:46:06 +0800 Subject: [PATCH 18/92] switch CMD key to NSEvent --- PlayTools/Controls/PlayInput.swift | 32 +++++++----------------------- PlayTools/Controls/PlayMice.swift | 1 - 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 043bc57f..9bd2b6dd 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -169,17 +169,6 @@ class PlayInput { return screen.window?.rootViewController } - func setupHotkeys() { - if let keyboard = GCKeyboard.coalesced?.keyboardInput { - keyboard.button(forKeyCode: .leftGUI)?.pressedChangedHandler = { _, _, pressed in - PlayInput.lCmdPressed = pressed - } - keyboard.button(forKeyCode: .rightGUI)?.pressedChangedHandler = { _, _, pressed in - PlayInput.rCmdPressed = pressed - } - } - } - func initialize() { if !PlaySettings.shared.keymapping { return @@ -188,19 +177,6 @@ class PlayInput { let centre = NotificationCenter.default let main = OperationQueue.main - centre.addObserver(forName: NSNotification.Name.GCKeyboardDidConnect, object: nil, queue: main) { _ in - self.setupHotkeys() - if !mode.visible { - self.setup() - } - } - - centre.addObserver(forName: NSNotification.Name.GCMouseDidConnect, object: nil, queue: main) { _ in - if !mode.visible { - self.setup() - } - } - centre.addObserver(forName: NSNotification.Name.GCControllerDidConnect, object: nil, queue: main) { _ in if !mode.visible { self.setup() @@ -222,7 +198,6 @@ class PlayInput { AKInterface.shared!.warpCursor() } } - setupHotkeys() DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5) { if !mode.visible || self.actions.count <= 0 || !PlayInput.shouldLockCursor { return @@ -259,6 +234,13 @@ class PlayInput { } AKInterface.shared!.initialize(keyboard: {keycode, pressed, isRepeat in + if keycode == 54 { + PlayInput.rCmdPressed = pressed + return false + } else if keycode == 55 { + PlayInput.lCmdPressed = pressed + return false + } if !PlayInput.keyboardMapped || PlayInput.cmdPressed() { return false } diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index bb511cd2..6fc7c595 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -282,7 +282,6 @@ class SwipeAction: Action { if self.counter < 12 { counter += 12 } else { - Toast.showHint(title: "\(id)") timer.suspend() self.doLiftOff() } From 80552d6d8362fe0df01d6210d2f79e85928d7c2d Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Sat, 25 Feb 2023 21:18:28 +0800 Subject: [PATCH 19/92] optimize mouse button event handler for branch prediction --- AKPlugin.swift | 31 +++++- .../Controls/PTFakeTouch/PTFakeMetaTouch.m | 63 +++++++++--- PlayTools/Controls/PlayInput.swift | 30 +++--- PlayTools/Controls/PlayMice.swift | 98 +++++++++---------- Plugin.swift | 3 +- 5 files changed, 141 insertions(+), 84 deletions(-) diff --git a/AKPlugin.swift b/AKPlugin.swift index 2995cafe..7a77331e 100644 --- a/AKPlugin.swift +++ b/AKPlugin.swift @@ -38,6 +38,8 @@ class AKPlugin: NSObject, Plugin { NSApplication.shared.windows.first!.styleMask.contains(.fullScreen) } + var cmdPressed: Bool = false + func hideCursor() { NSCursor.hide() CGAssociateMouseAndMouseCursorPosition(0) @@ -61,7 +63,19 @@ class AKPlugin: NSObject, Plugin { private var modifierFlag: UInt = 0 func initialize(keyboard: @escaping(UInt16, Bool, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool, swapMode: @escaping() -> Void) { + func checkCmd(modifier: NSEvent.ModifierFlags) -> Bool { + if modifier.contains(.command) { + self.cmdPressed = true + return true + } else if self.cmdPressed { + self.cmdPressed = false + } + return false + } NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: { event in + if checkCmd(modifier: event.modifierFlags) { + return event + } let consumed = keyboard(event.keyCode, true, event.isARepeat) if consumed { return nil @@ -69,6 +83,9 @@ class AKPlugin: NSObject, Plugin { return event }) NSEvent.addLocalMonitorForEvents(matching: .keyUp, handler: { event in + if checkCmd(modifier: event.modifierFlags) { + return event + } let consumed = keyboard(event.keyCode, false, false) if consumed { return nil @@ -76,6 +93,9 @@ class AKPlugin: NSObject, Plugin { return event }) NSEvent.addLocalMonitorForEvents(matching: .flagsChanged, handler: { event in + if checkCmd(modifier: event.modifierFlags) { + return event + } let pressed = self.modifierFlag < event.modifierFlags.rawValue let changed = self.modifierFlag ^ event.modifierFlags.rawValue self.modifierFlag = event.modifierFlags.rawValue @@ -99,16 +119,19 @@ class AKPlugin: NSObject, Plugin { }) } - func setupMouseButton(_ _up: Int, _ _down: Int, _ dontIgnore: @escaping(Int, Bool, Bool) -> Bool) { + func setupMouseButton(_ _up: Int, _ _down: Int, _ dontIgnore: @escaping(Int, Bool) -> Bool) { NSEvent.addLocalMonitorForEvents(matching: NSEvent.EventTypeMask(rawValue: UInt64(_up)), handler: { event in - let isEventWindow = event.window == NSApplication.shared.windows.first! - if dontIgnore(_up, true, isEventWindow) { + // For traffic light buttons when fullscreen + if event.window != NSApplication.shared.windows.first! { + return event + } + if dontIgnore(_up, true) { return event } return nil }) NSEvent.addLocalMonitorForEvents(matching: NSEvent.EventTypeMask(rawValue: UInt64(_down)), handler: { event in - if dontIgnore(_up, false, true) { + if dontIgnore(_up, false) { return event } return nil diff --git a/PlayTools/Controls/PTFakeTouch/PTFakeMetaTouch.m b/PlayTools/Controls/PTFakeTouch/PTFakeMetaTouch.m index 23720434..b3e76150 100644 --- a/PlayTools/Controls/PTFakeTouch/PTFakeMetaTouch.m +++ b/PlayTools/Controls/PTFakeTouch/PTFakeMetaTouch.m @@ -16,16 +16,58 @@ static NSMutableArray *livingTouchAry; uint64_t reusageMask = 0; +static CFRunLoopSourceRef source; + +static UITouch *toStationarify = NULL; +NSLock *lock; + +void eventSendCallback(void* info) { + UIEvent *event = [[UIApplication sharedApplication] _touchesEvent]; + [event _clearTouches]; + [lock lock]; + [livingTouchAry enumerateObjectsUsingBlock:^(UITouch *aTouch, NSUInteger idx, BOOL *stop) { + switch (aTouch.phase) { + case UITouchPhaseEnded: + case UITouchPhaseCancelled: + // set this bit to 0 + reusageMask |= 1ull< Void]] = [:] func invalidate() { @@ -35,7 +34,9 @@ class PlayInput { } var mapped = false for handler in handlers { - handler(pressed) + PlayInput.touchQueue.async(qos: .userInteractive, execute: { + handler(pressed) + }) mapped = true } return mapped @@ -137,7 +138,7 @@ class PlayInput { } static public func cmdPressed() -> Bool { - return lCmdPressed || rCmdPressed + return AKInterface.shared!.cmdPressed } private func isSafeToBind(_ input: GCKeyboardInput) -> Bool { @@ -234,14 +235,7 @@ class PlayInput { } AKInterface.shared!.initialize(keyboard: {keycode, pressed, isRepeat in - if keycode == 54 { - PlayInput.rCmdPressed = pressed - return false - } else if keycode == 55 { - PlayInput.lCmdPressed = pressed - return false - } - if !PlayInput.keyboardMapped || PlayInput.cmdPressed() { + if !PlayInput.keyboardMapped { return false } if isRepeat { @@ -253,11 +247,13 @@ class PlayInput { if !PlayInput.keyboardMapped { return false } - if mode.visible { - PlayMice.shared.handleFakeMouseMoved(deltaX: deltaX, deltaY: deltaY) - } else { - PlayMice.shared.handleMouseMoved(deltaX: deltaX, deltaY: deltaY) - } + PlayInput.touchQueue.async(qos: .userInteractive, execute: { + if mode.visible { + PlayMice.shared.handleFakeMouseMoved(deltaX: deltaX, deltaY: deltaY) + } else { + PlayMice.shared.handleMouseMoved(deltaX: deltaX, deltaY: deltaY) + } + }) return true }, swapMode: self.swapMode) PlayMice.shared.initialize() diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index 6fc7c595..fe46d425 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -30,38 +30,39 @@ public class PlayMice { joystickHandler: [String: (CGFloat, CGFloat) -> Void] = [:] public func mouseMovementMapped() -> Bool { - for handler in [draggableHandler, cameraMoveHandler, joystickHandler] { - if handler[PlayMice.elementName] != nil { - return true - } + for handler in [draggableHandler, cameraMoveHandler, joystickHandler] + where handler[PlayMice.elementName] != nil { + return true } return false } - + public func cursorPos() -> CGPoint? { - var point = CGPoint(x: 0, y: 0) - point = AKInterface.shared!.mousePoint + var point = AKInterface.shared!.mousePoint let rect = AKInterface.shared!.windowFrame - let viewRect: CGRect = screen.screenRect if rect.width < 1 || rect.height < 1 { return nil } + let viewRect: CGRect = screen.screenRect let widthRate = viewRect.width / rect.width var rate = viewRect.height / rect.height if widthRate > rate { // Keep aspect ratio rate = widthRate } - // Horizontally in center - point.x -= (rect.width - viewRect.width / rate)/2 - point.x *= rate if screen.fullscreen { // Vertically in center point.y -= (rect.height - viewRect.height / rate)/2 } point.y *= rate point.y = viewRect.height - point.y - + // For traffic light buttons when not fullscreen + if point.y < 0 { + return nil + } + // Horizontally in center + point.x -= (rect.width - viewRect.width / rate)/2 + point.x *= rate return point } @@ -112,9 +113,9 @@ public class PlayMice { } public func handleMouseMoved(deltaX: CGFloat, deltaY: CGFloat) { - let sensy = CGFloat(PlaySettings.shared.sensitivity) - let cgDx = deltaX * sensy * 0.6, - cgDy = -deltaY * sensy * 0.6 + let sensy = CGFloat(PlaySettings.shared.sensitivity * 0.6) + let cgDx = deltaX * sensy, + cgDy = -deltaY * sensy let name = PlayMice.elementName if let draggableUpdate = self.draggableHandler[name] { draggableUpdate(cgDx, cgDy) @@ -128,43 +129,38 @@ public class PlayMice { let buttonIndex: [Int: Int] = [2: -1, 8: -2, 33554432: -3] private func setupMouseButton(_up: Int, _down: Int) { - AKInterface.shared!.setupMouseButton(_up, _down, dontIgnore(_:_:_:)) + AKInterface.shared!.setupMouseButton(_up, _down, dontIgnore) } - private func dontIgnore(_ actionIndex: Int, _ pressed: Bool, _ isEventWindow: Bool) -> Bool { - if mode.visible && pressed && PlayInput.keyboardMapped { - if let curPos = self.cursorPos() { - if !self.fakedMousePressed - // For traffic light buttons when not fullscreen - && curPos.y > 0 - // For traffic light buttons when fullscreen - && isEventWindow { - Toucher.touchcam(point: curPos, - phase: UITouch.Phase.began, - tid: &fakedMouseTouchPointId) - return false - } - } - } else if self.fakedMousePressed { - if let curPos = self.cursorPos() { - Toucher.touchcam(point: curPos, phase: UITouch.Phase.ended, tid: &fakedMouseTouchPointId) - return false - } - } - if !mode.visible || !pressed { - if let handlers = PlayInput.buttonHandlers[KeyCodeNames.keyCodes[buttonIndex[actionIndex]!]!] { - for handler in handlers { - handler(pressed) - } - } - return false + private func dontIgnore(_ actionIndex: Int, _ pressed: Bool) -> Bool { + if !PlayInput.shouldLockCursor { + return true } if EditorController.shared.editorMode { if pressed && actionIndex != 2 { EditorController.shared.setKey(buttonIndex[actionIndex]!) } + return true } - return true + guard let curPos = self.cursorPos() else { return true } + PlayInput.touchQueue.async(qos: .userInteractive, execute: { + if self.fakedMousePressed { + Toucher.touchcam(point: curPos, phase: UITouch.Phase.ended, tid: &self.fakedMouseTouchPointId) + return + } + if mode.visible && pressed { + Toucher.touchcam(point: curPos, + phase: UITouch.Phase.began, + tid: &self.fakedMouseTouchPointId) + } else { + if let handlers = PlayInput.buttonHandlers[KeyCodeNames.keyCodes[self.buttonIndex[actionIndex]!]!] { + for handler in handlers { + handler(pressed) + } + } + } + }) + return false } } @@ -221,11 +217,13 @@ class CameraAction: Action { swipeScale2 = SwipeAction() PlayMice.shared.cameraMoveHandler[key] = self.moveUpdated PlayMice.shared.cameraScaleHandler[PlayMice.elementName] = {deltaX, deltaY in - if mode.visible { - CameraAction.dragUpdated(deltaX, deltaY) - } else { - self.scaleUpdated(deltaX, deltaY) - } + PlayInput.touchQueue.async(qos: .userInteractive, execute: { + if mode.visible { + CameraAction.dragUpdated(deltaX, deltaY) + } else { + self.scaleUpdated(deltaX, deltaY) + } + }) } } func moveUpdated(_ deltaX: CGFloat, _ deltaY: CGFloat) { @@ -265,7 +263,7 @@ class CameraAction: Action { class SwipeAction: Action { var location: CGPoint = CGPoint.zero var id: Int? - let timer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.main) + let timer = DispatchSource.makeTimerSource(flags: [], queue: PlayInput.touchQueue) init() { timer.schedule(deadline: DispatchTime.now() + 1, repeating: 0.1, leeway: DispatchTimeInterval.milliseconds(50)) timer.setEventHandler(qos: DispatchQoS.background, handler: self.checkEnded) diff --git a/Plugin.swift b/Plugin.swift index b52d4326..29d63283 100644 --- a/Plugin.swift +++ b/Plugin.swift @@ -17,6 +17,7 @@ public protocol Plugin: NSObjectProtocol { var mainScreenFrame: CGRect { get } var isMainScreenEqualToFirst: Bool { get } var isFullscreen: Bool { get } + var cmdPressed: Bool { get } func hideCursor() func warpCursor() @@ -24,7 +25,7 @@ public protocol Plugin: NSObjectProtocol { func terminateApplication() func initialize(keyboard: @escaping(UInt16, Bool, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool, swapMode: @escaping() -> Void) - func setupMouseButton(_ _up: Int, _ _down: Int, _ dontIgnore: @escaping(Int, Bool, Bool) -> Bool) + func setupMouseButton(_ _up: Int, _ _down: Int, _ dontIgnore: @escaping(Int, Bool) -> Bool) func setupScrollWheel(_ onMoved: @escaping(CGFloat, CGFloat) -> Bool) func urlForApplicationWithBundleIdentifier(_ value: String) -> URL? func setMenuBarVisible(_ value: Bool) From de848d447a7d3e65bcbe39665f21a2d226c972c7 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Sun, 26 Feb 2023 17:44:43 +0800 Subject: [PATCH 20/92] revert removing swipe cd --- PlayTools/Controls/PlayInput.swift | 4 +++- PlayTools/Controls/PlayMice.swift | 17 +++++++++++++++-- PlayTools/Utils/Toast.swift | 2 +- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index e7f1ab93..f77bb23e 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -188,7 +188,9 @@ class PlayInput { } parseKeymap() centre.addObserver(forName: UIApplication.keyboardDidHideNotification, object: nil, queue: main) { _ in - PlayInput.keyboardMapped = true + DispatchQueue.main.async(qos: .userInteractive, execute: { + PlayInput.keyboardMapped = true + }) } centre.addObserver(forName: UIApplication.keyboardWillShowNotification, object: nil, queue: main) { _ in PlayInput.keyboardMapped = false diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index fe46d425..b37b21cd 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -271,14 +271,20 @@ class SwipeAction: Action { timer.suspend() } + func delay(_ delay: Double, closure: @escaping () -> Void) { + let when = DispatchTime.now() + delay + PlayInput.touchQueue.asyncAfter(deadline: when, execute: closure) + } // Count swipe duration var counter = 0 + // if should wait before beginning next touch + var cooldown = false var lastCounter = 0 func checkEnded() { if self.counter == self.lastCounter { - if self.counter < 12 { - counter += 12 + if self.counter < 4 { + counter += 1 } else { timer.suspend() self.doLiftOff() @@ -289,6 +295,9 @@ class SwipeAction: Action { public func move(from: () -> CGPoint?, deltaX: CGFloat, deltaY: CGFloat) { if id == nil { + if cooldown { + return + } guard let start = from() else {return} location = start counter = 0 @@ -307,6 +316,10 @@ class SwipeAction: Action { return } Toucher.touchcam(point: self.location, phase: UITouch.Phase.ended, tid: &id) + delay(0.02) { + self.cooldown = false + } + cooldown = true } func invalidate() { diff --git a/PlayTools/Utils/Toast.swift b/PlayTools/Utils/Toast.swift index aafc0ad1..2832a3a5 100644 --- a/PlayTools/Utils/Toast.swift +++ b/PlayTools/Utils/Toast.swift @@ -101,7 +101,7 @@ class Toast { life = 3 } if life >= 0 { - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5 + life) { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5 + life, qos: .background) { hideHint(hint: messageLabel) } } From 59788d75479ce2dfa18c13a5a595c44026f2f281 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Tue, 28 Feb 2023 22:26:03 +0800 Subject: [PATCH 21/92] delay keyboard mapping toggling --- AKPlugin.swift | 4 +++- PlayTools/Controls/PlayInput.swift | 12 +++++++----- PlayTools/Controls/PlayMice.swift | 24 +++++++++++++++++------- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/AKPlugin.swift b/AKPlugin.swift index 7a77331e..17cbc810 100644 --- a/AKPlugin.swift +++ b/AKPlugin.swift @@ -47,8 +47,10 @@ class AKPlugin: NSObject, Plugin { } func warpCursor() { + guard let firstScreen = NSScreen.screens.first else {return} let frame = windowFrame - CGWarpMouseCursorPosition(CGPoint(x: frame.midX, y: frame.midY)) + // Convert from NS coordinates to CG coordinates + CGWarpMouseCursorPosition(CGPoint(x: frame.midX, y: firstScreen.frame.height - frame.midY)) } func unhideCursor() { diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index f77bb23e..7436c2e7 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -5,7 +5,8 @@ import UIKit class PlayInput { static let shared = PlayInput() var actions = [Action]() - static var keyboardMapped = true + static var keyboardMapped = false + private static var keyboardWillMap = true static var shouldLockCursor = true static var touchQueue = DispatchQueue.init(label: "playcover.toucher", qos: .userInteractive) @@ -188,12 +189,10 @@ class PlayInput { } parseKeymap() centre.addObserver(forName: UIApplication.keyboardDidHideNotification, object: nil, queue: main) { _ in - DispatchQueue.main.async(qos: .userInteractive, execute: { - PlayInput.keyboardMapped = true - }) + PlayInput.keyboardWillMap = true } centre.addObserver(forName: UIApplication.keyboardWillShowNotification, object: nil, queue: main) { _ in - PlayInput.keyboardMapped = false + PlayInput.keyboardWillMap = false } centre.addObserver(forName: NSNotification.Name(rawValue: "NSWindowDidBecomeKeyNotification"), object: nil, queue: main) { _ in @@ -237,6 +236,9 @@ class PlayInput { } AKInterface.shared!.initialize(keyboard: {keycode, pressed, isRepeat in + if PlayInput.keyboardWillMap != PlayInput.keyboardMapped && pressed { + PlayInput.keyboardMapped = PlayInput.keyboardWillMap + } if !PlayInput.keyboardMapped { return false } diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index b37b21cd..83ebfcaa 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -133,11 +133,8 @@ public class PlayMice { } private func dontIgnore(_ actionIndex: Int, _ pressed: Bool) -> Bool { - if !PlayInput.shouldLockCursor { - return true - } - if EditorController.shared.editorMode { - if pressed && actionIndex != 2 { + if !PlayInput.keyboardMapped { + if EditorController.shared.editorMode && actionIndex != 2 && pressed { EditorController.shared.setKey(buttonIndex[actionIndex]!) } return true @@ -258,15 +255,24 @@ class CameraAction: Action { swipeScale1.invalidate() swipeScale2.invalidate() } + + func debug() { + var count = 0 + for swipe in [swipeScale1, swipeScale2, swipeMove, CameraAction.swipeDrag] { + count += 1 + guard let id = swipe.getTouchId() else {continue} + Toast.showHint(title: "type:\(count), id:\(id)") + } + } } class SwipeAction: Action { var location: CGPoint = CGPoint.zero - var id: Int? + private var id: Int? let timer = DispatchSource.makeTimerSource(flags: [], queue: PlayInput.touchQueue) init() { timer.schedule(deadline: DispatchTime.now() + 1, repeating: 0.1, leeway: DispatchTimeInterval.milliseconds(50)) - timer.setEventHandler(qos: DispatchQoS.background, handler: self.checkEnded) + timer.setEventHandler(qos: .userInteractive, handler: self.checkEnded) timer.activate() timer.suspend() } @@ -326,4 +332,8 @@ class SwipeAction: Action { timer.cancel() self.doLiftOff() } + + public func getTouchId() -> Int? { + return id + } } From c21b2dc1f5979778c4331a4ba7af1f8ec7fa557d Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Mon, 6 Mar 2023 11:30:02 +0700 Subject: [PATCH 22/92] feat: adding an alternate window fix mode for apps that are able to handle display by themselves --- .../Controls/PTFakeTouch/NSObject+Swizzle.m | 33 +++++++++++-------- PlayTools/PlaySettings.swift | 4 +++ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m index 6c776520..039b76ab 100644 --- a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m +++ b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m @@ -182,21 +182,26 @@ + (void)load { } } else { - CGFloat newValueW = (CGFloat) [self get_default_width]; - [[PlaySettings shared] setValue:@(newValueW) forKey:@"windowSizeWidth"]; - - CGFloat newValueH = (CGFloat)[self get_default_height]; - [[PlaySettings shared] setValue:@(newValueH) forKey:@"windowSizeHeight"]; - if (![[PlaySettings shared] inverseScreenValues]) { - [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frameDefault)]; - [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_boundsDefault)]; - [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_sizeDelfault)]; + if ([[PlaySettings shared] windowFixMethod] == 1) { + // do nothing:tm: + } + else { + CGFloat newValueW = (CGFloat) [self get_default_width]; + [[PlaySettings shared] setValue:@(newValueW) forKey:@"windowSizeWidth"]; + + CGFloat newValueH = (CGFloat)[self get_default_height]; + [[PlaySettings shared] setValue:@(newValueH) forKey:@"windowSizeHeight"]; + if (![[PlaySettings shared] inverseScreenValues]) { + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frameDefault)]; + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_boundsDefault)]; + [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_sizeDelfault)]; + } + [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(orientation) withMethod:@selector(hook_orientation)]; + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeBounds) withMethod:@selector(hook_nativeBoundsDefault)]; + + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeScale) withMethod:@selector(hook_nativeScale)]; + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(scale) withMethod:@selector(hook_scale)]; } - [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(orientation) withMethod:@selector(hook_orientation)]; - [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeBounds) withMethod:@selector(hook_nativeBoundsDefault)]; - - [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeScale) withMethod:@selector(hook_nativeScale)]; - [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(scale) withMethod:@selector(hook_scale)]; } } else { diff --git a/PlayTools/PlaySettings.swift b/PlayTools/PlaySettings.swift index f425bb87..d2443171 100644 --- a/PlayTools/PlaySettings.swift +++ b/PlayTools/PlaySettings.swift @@ -65,6 +65,9 @@ let settings = PlaySettings.shared @objc lazy var playChain = settingsData.playChain @objc lazy var playChainDebugging = settingsData.playChainDebugging + + @objc lazy var windowFixMethod = settingsData.windowFixMethod + } struct AppSettingsData: Codable { @@ -85,4 +88,5 @@ struct AppSettingsData: Codable { var playChain = false var playChainDebugging = false var inverseScreenValues = false + var windowFixMethod = 0 } From a6c04e54b125882b96c01bb270d04feb067b5109 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Fri, 10 Mar 2023 01:13:55 +0800 Subject: [PATCH 23/92] fix option key consumed --- AKPlugin.swift | 8 +++++--- PlayTools/Controls/PlayInput.swift | 16 ++++++++-------- Plugin.swift | 2 +- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/AKPlugin.swift b/AKPlugin.swift index 17cbc810..3e12d5cf 100644 --- a/AKPlugin.swift +++ b/AKPlugin.swift @@ -64,7 +64,7 @@ class AKPlugin: NSObject, Plugin { private var modifierFlag: UInt = 0 func initialize(keyboard: @escaping(UInt16, Bool, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool, - swapMode: @escaping() -> Void) { + swapMode: @escaping() -> Bool) { func checkCmd(modifier: NSEvent.ModifierFlags) -> Bool { if modifier.contains(.command) { self.cmdPressed = true @@ -102,8 +102,10 @@ class AKPlugin: NSObject, Plugin { let changed = self.modifierFlag ^ event.modifierFlags.rawValue self.modifierFlag = event.modifierFlags.rawValue if pressed && NSEvent.ModifierFlags(rawValue: changed).contains(.option) { - swapMode() - return nil + if swapMode() { + return nil + } + return event } let consumed = keyboard(event.keyCode, pressed, false) if consumed { diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 7436c2e7..46128ecc 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -46,9 +46,8 @@ class PlayInput { func controllerButtonHandler(_ profile: GCExtendedGamepad, _ element: GCControllerElement) { let name: String = element.aliases.first! if let buttonElement = element as? GCControllerButtonInput { -// Toast.showOver(msg: "recognised controller button: \(name)") guard let handlers = PlayInput.buttonHandlers[name] else { return } - Toast.showOver(msg: name + ": \(buttonElement.isPressed)") +// Toast.showOver(msg: name + ": \(buttonElement.isPressed)") for handler in handlers { handler(buttonElement.isPressed) } @@ -129,7 +128,7 @@ class PlayInput { } else { setup() parseKeymap() - self.swapMode() + _ = self.swapMode() } } @@ -159,12 +158,13 @@ class PlayInput { .printScreen ] - private func swapMode() { - if !PlayInput.shouldLockCursor { - mode.show(true) - return + private func swapMode() -> Bool { + if PlayInput.shouldLockCursor { + mode.show(!mode.visible) + return true } - mode.show(!mode.visible) + mode.show(true) + return false } var root: UIViewController? { diff --git a/Plugin.swift b/Plugin.swift index 29d63283..291052b9 100644 --- a/Plugin.swift +++ b/Plugin.swift @@ -24,7 +24,7 @@ public protocol Plugin: NSObjectProtocol { func unhideCursor() func terminateApplication() func initialize(keyboard: @escaping(UInt16, Bool, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool, - swapMode: @escaping() -> Void) + swapMode: @escaping() -> Bool) func setupMouseButton(_ _up: Int, _ _down: Int, _ dontIgnore: @escaping(Int, Bool) -> Bool) func setupScrollWheel(_ onMoved: @escaping(CGFloat, CGFloat) -> Bool) func urlForApplicationWithBundleIdentifier(_ value: String) -> URL? From d8e7805153c47267dba65bee1aad634965155082 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Wed, 8 Mar 2023 11:12:44 +0700 Subject: [PATCH 24/92] feat: PlayShadow --- PlayTools.xcodeproj/project.pbxproj | 8 + .../Controls/PTFakeTouch/NSObject+Swizzle.m | 1 - PlayTools/MysticRunes/PlayShadow.h | 18 ++ PlayTools/MysticRunes/PlayShadow.m | 217 ++++++++++++++++++ 4 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 PlayTools/MysticRunes/PlayShadow.h create mode 100644 PlayTools/MysticRunes/PlayShadow.m diff --git a/PlayTools.xcodeproj/project.pbxproj b/PlayTools.xcodeproj/project.pbxproj index 8c455222..01f751fe 100644 --- a/PlayTools.xcodeproj/project.pbxproj +++ b/PlayTools.xcodeproj/project.pbxproj @@ -45,6 +45,8 @@ AA719870287A81A000623C15 /* UIEvent+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = AA719842287A81A000623C15 /* UIEvent+Private.h */; }; AA719872287A81A000623C15 /* UITouch+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = AA719844287A81A000623C15 /* UITouch+Private.h */; }; AA818CB9287ABFB1000BEE9D /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA818CB8287ABFB1000BEE9D /* IOKit.framework */; }; + AB7DA47529B85BFB0034ACB2 /* PlayShadow.m in Sources */ = {isa = PBXBuildFile; fileRef = AB7DA47429B85BFB0034ACB2 /* PlayShadow.m */; }; + AB7DA47729B8A78B0034ACB2 /* PlayShadow.h in Headers */ = {isa = PBXBuildFile; fileRef = AB7DA47629B8A78B0034ACB2 /* PlayShadow.h */; }; ABCECEE629750BA600746595 /* PlayedApple.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABCECEE529750BA600746595 /* PlayedApple.swift */; }; B127172228817AB90025112B /* SwordRPC in Frameworks */ = {isa = PBXBuildFile; productRef = B127172128817AB90025112B /* SwordRPC */; }; B127172528817C040025112B /* DiscordIPC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B127172428817C040025112B /* DiscordIPC.swift */; }; @@ -108,6 +110,8 @@ AA719844287A81A000623C15 /* UITouch+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITouch+Private.h"; sourceTree = ""; }; AA818CB8287ABFB1000BEE9D /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/System/Library/Frameworks/IOKit.framework; sourceTree = DEVELOPER_DIR; }; AA818CBA287ABFD5000BEE9D /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/System/iOSSupport/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; + AB7DA47429B85BFB0034ACB2 /* PlayShadow.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PlayShadow.m; sourceTree = ""; }; + AB7DA47629B8A78B0034ACB2 /* PlayShadow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PlayShadow.h; sourceTree = ""; }; ABCECEE529750BA600746595 /* PlayedApple.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayedApple.swift; sourceTree = ""; }; B127172428817C040025112B /* DiscordIPC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscordIPC.swift; sourceTree = ""; }; B1271728288284BE0025112B /* DiscordActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscordActivity.swift; sourceTree = ""; }; @@ -269,6 +273,8 @@ isa = PBXGroup; children = ( ABCECEE529750BA600746595 /* PlayedApple.swift */, + AB7DA47429B85BFB0034ACB2 /* PlayShadow.m */, + AB7DA47629B8A78B0034ACB2 /* PlayShadow.h */, ); path = MysticRunes; sourceTree = ""; @@ -290,6 +296,7 @@ buildActionMask = 2147483647; files = ( AA71986E287A81A000623C15 /* NSObject+Swizzle.h in Headers */, + AB7DA47729B8A78B0034ACB2 /* PlayShadow.h in Headers */, AA719846287A81A000623C15 /* IOHIDEvent+KIF.h in Headers */, AA719862287A81A000623C15 /* UITouch-KIFAdditions.h in Headers */, AA71986B287A81A000623C15 /* UIApplication+Private.h in Headers */, @@ -465,6 +472,7 @@ AA719850287A81A000623C15 /* UITouch-KIFAdditions.m in Sources */, AA71985F287A81A000623C15 /* IOHIDEvent+KIF.m in Sources */, B1E8CF8A28BBE2AB004340D3 /* Keymapping.swift in Sources */, + AB7DA47529B85BFB0034ACB2 /* PlayShadow.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m index 039b76ab..789fc498 100644 --- a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m +++ b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m @@ -114,7 +114,6 @@ - (void) hook_setCurrentSubscription:(VSSubscription *)currentSubscription { // do nothing } - bool menuWasCreated = false; - (id) initWithRootMenuHook:(id)rootMenu { self = [self initWithRootMenuHook:rootMenu]; diff --git a/PlayTools/MysticRunes/PlayShadow.h b/PlayTools/MysticRunes/PlayShadow.h new file mode 100644 index 00000000..53965479 --- /dev/null +++ b/PlayTools/MysticRunes/PlayShadow.h @@ -0,0 +1,18 @@ +// +// PlayMask.h +// PlayTools +// +// Created by Venti on 08/03/2023. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSObject (Swizzle) + +- (void)swizzleInstanceMethod:(SEL)origSelector withMethod:(SEL)newSelector; + +@end + +NS_ASSUME_NONNULL_END diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m new file mode 100644 index 00000000..11d838b8 --- /dev/null +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -0,0 +1,217 @@ +// +// PlayMask.m +// PlayTools +// +// Created by Venti on 08/03/2023. +// + +#import +#import "NSObject+Swizzle.h" +#import + +__attribute__((visibility("hidden"))) +@interface PlayShadowLoader : NSObject +@end + +@implementation NSObject (Swizzle) + +- (void) swizzleInstanceMethod:(SEL)origSelector withMethod:(SEL)newSelector +{ + Class cls = [self class]; + // If current class doesn't exist selector, then get super + Method originalMethod = class_getInstanceMethod(cls, origSelector); + Method swizzledMethod = class_getInstanceMethod(cls, newSelector); + + // Add selector if it doesn't exist, implement append with method + if (class_addMethod(cls, + origSelector, + method_getImplementation(swizzledMethod), + method_getTypeEncoding(swizzledMethod)) ) { + // Replace class instance method, added if selector not exist + // For class cluster, it always adds new selector here + class_replaceMethod(cls, + newSelector, + method_getImplementation(originalMethod), + method_getTypeEncoding(originalMethod)); + + } else { + // SwizzleMethod maybe belongs to super + class_replaceMethod(cls, + newSelector, + class_replaceMethod(cls, + origSelector, + method_getImplementation(swizzledMethod), + method_getTypeEncoding(swizzledMethod)), + method_getTypeEncoding(originalMethod)); + } +} + +- (NSInteger) hook_deviceType { + return 1; +} + +- (BOOL) pm_return_false { + NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return false; +} + +- (BOOL) pm_return_true { + NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return true; +} + +- (BOOL) pm_return_yes { + NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return YES; +} + +- (BOOL) pm_return_no { + NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return NO; +} + +- (int) pm_return_0 { + NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return 0; +} + +- (int) pm_return_1 { + NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return 1; +} +@end + +@implementation PlayShadowLoader + ++ (void) load { + // Swizzle NSProcessInfo to troll every app that tries to detect macCatalyst + // [objc_getClass("NSProcessInfo") swizzleInstanceMethod:@selector(isMacCatalystApp) withMethod:@selector(pm_return_false)]; + [objc_getClass("NSProcessInfo") swizzleInstanceMethod:@selector(isiOSAppOnMac) withMethod:@selector(pm_return_true)]; + + [objc_getClass("RNDeviceInfo") swizzleInstanceMethod:@selector(getDeviceType) withMethod:@selector(hook_deviceType)]; + + // Class: UIDevice + [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_no)]; + [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(isJailBreak) withMethod:@selector(pm_return_no)]; + [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(isJailBroken) withMethod:@selector(pm_return_no)]; + + // Class: JailbreakDetectionVC + [objc_getClass("JailbreakDetectionVC") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_no)]; + + // Class: DTTJailbreakDetection + [objc_getClass("DTTJailbreakDetection") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_no)]; + + // Class: ANSMetadata + [objc_getClass("ANSMetadata") swizzleInstanceMethod:@selector(computeIsJailbroken) withMethod:@selector(pm_return_no)]; + [objc_getClass("ANSMetadata") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_no)]; + + // Class: AppsFlyerUtils + [objc_getClass("AppsFlyerUtils") swizzleInstanceMethod:@selector(isJailBreakon) withMethod:@selector(pm_return_no)]; + [objc_getClass("AppsFlyerUtils") swizzleInstanceMethod:@selector(a) withMethod:@selector(pm_return_false)]; + + // Class: jailBreak + [objc_getClass("jailBreak") swizzleInstanceMethod:@selector(isJailBreak) withMethod:@selector(pm_return_false)]; + + // Class: GBDeviceInfo + [objc_getClass("GBDeviceInfo") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_no)]; + + // Class: CMARAppRestrictionsDelegate + [objc_getClass("CMARAppRestrictionsDelegate") swizzleInstanceMethod:@selector(isDeviceNonCompliant) withMethod:@selector(pm_return_false)]; + + // Class: ADYSecurityChecks + [objc_getClass("ADYSecurityChecks") swizzleInstanceMethod:@selector(isDeviceJailbroken) withMethod:@selector(pm_return_false)]; + + // Class: UBReportMetadataDevice + [objc_getClass("UBReportMetadataDevice") swizzleInstanceMethod:@selector(is_rooted) withMethod:@selector(pm_return_null)]; + + // Class: UtilitySystem + [objc_getClass("UtilitySystem") swizzleInstanceMethod:@selector(isJailbreak) withMethod:@selector(pm_return_false)]; + + // Class: GemaltoConfiguration + [objc_getClass("GemaltoConfiguration") swizzleInstanceMethod:@selector(isJailbreak) withMethod:@selector(pm_return_false)]; + + // Class: CPWRDeviceInfo + [objc_getClass("CPWRDeviceInfo") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; + + // Class: CPWRSessionInfo + [objc_getClass("CPWRSessionInfo") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; + + // Class: KSSystemInfo + [objc_getClass("KSSystemInfo") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; + + // Class: EMDSKPPConfiguration + [objc_getClass("EMDSKPPConfiguration") swizzleInstanceMethod:@selector(jailBroken) withMethod:@selector(pm_return_false)]; + + // Class: EnrollParameters + [objc_getClass("EnrollParameters") swizzleInstanceMethod:@selector(jailbroken) withMethod:@selector(pm_return_null)]; + + // Class: EMDskppConfigurationBuilder + [objc_getClass("EMDskppConfigurationBuilder") swizzleInstanceMethod:@selector(jailbreakStatus) withMethod:@selector(pm_return_false)]; + + // Class: FCRSystemMetadata + [objc_getClass("FCRSystemMetadata") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; + + // Class: v_VDMap + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isJailbrokenDetected) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isJailBrokenDetectedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isDFPHookedDetecedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isCodeInjectionDetectedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isDebuggerCheckDetectedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isAppSignerCheckDetectedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(v_checkAModified) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isRuntimeTamperingDetected) withMethod:@selector(pm_return_false)]; + + // Class: SDMUtils + [objc_getClass("SDMUtils") swizzleInstanceMethod:@selector(isJailBroken) withMethod:@selector(pm_return_no)]; + + // Class: OneSignalJailbreakDetection + [objc_getClass("OneSignalJailbreakDetection") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_no)]; + + // Class: DigiPassHandler + [objc_getClass("DigiPassHandler") swizzleInstanceMethod:@selector(rootedDeviceTestResult) withMethod:@selector(pm_return_no)]; + + // Class: AWMyDeviceGeneralInfo + [objc_getClass("AWMyDeviceGeneralInfo") swizzleInstanceMethod:@selector(isCompliant) withMethod:@selector(pm_return_true)]; + + // Class: DTXSessionInfo + [objc_getClass("DTXSessionInfo") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; + + // Class: DTXDeviceInfo + [objc_getClass("DTXDeviceInfo") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; + + // Class: JailbreakDetection + [objc_getClass("JailbreakDetection") swizzleInstanceMethod:@selector(jailbroken) withMethod:@selector(pm_return_false)]; + + // Class: jailBrokenJudge + [objc_getClass("jailBrokenJudge") swizzleInstanceMethod:@selector(isJailBreak) withMethod:@selector(pm_return_false)]; + [objc_getClass("jailBrokenJudge") swizzleInstanceMethod:@selector(isCydiaJailBreak) withMethod:@selector(pm_return_false)]; + [objc_getClass("jailBrokenJudge") swizzleInstanceMethod:@selector(isApplicationsJailBreak) withMethod:@selector(pm_return_false)]; + [objc_getClass("jailBrokenJudge") swizzleInstanceMethod:@selector(ischeckCydiaJailBreak) withMethod:@selector(pm_return_false)]; + [objc_getClass("jailBrokenJudge") swizzleInstanceMethod:@selector(isPathJailBreak) withMethod:@selector(pm_return_false)]; + [objc_getClass("jailBrokenJudge") swizzleInstanceMethod:@selector(boolIsjailbreak) withMethod:@selector(pm_return_false)]; + + // Class: FBAdBotDetector + [objc_getClass("FBAdBotDetector") swizzleInstanceMethod:@selector(isJailBrokenDevice) withMethod:@selector(pm_return_false)]; + + // Class: TNGDeviceTool + [objc_getClass("TNGDeviceTool") swizzleInstanceMethod:@selector(isJailBreak) withMethod:@selector(pm_return_false)]; + [objc_getClass("TNGDeviceTool") swizzleInstanceMethod:@selector(isJailBreak_file) withMethod:@selector(pm_return_false)]; + [objc_getClass("TNGDeviceTool") swizzleInstanceMethod:@selector(isJailBreak_cydia) withMethod:@selector(pm_return_false)]; + [objc_getClass("TNGDeviceTool") swizzleInstanceMethod:@selector(isJailBreak_appList) withMethod:@selector(pm_return_false)]; + [objc_getClass("TNGDeviceTool") swizzleInstanceMethod:@selector(isJailBreak_env) withMethod:@selector(pm_return_false)]; + + // Class: DTDeviceInfo + [objc_getClass("DTDeviceInfo") swizzleInstanceMethod:@selector(isJailbreak) withMethod:@selector(pm_return_false)]; + + // Class: SecVIDeviceUtil + [objc_getClass("SecVIDeviceUtil") swizzleInstanceMethod:@selector(isJailbreak) withMethod:@selector(pm_return_false)]; + + // Class: RVPBridgeExtension4Jailbroken + [objc_getClass("RVPBridgeExtension4Jailbroken") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; + + // Class: ZDetection + [objc_getClass("ZDetection") swizzleInstanceMethod:@selector(isRootedOrJailbroken) withMethod:@selector(pm_return_false)]; +} + +@end + From 2ce8827ba9d38b5a6f6bca6233e752e69cd779da Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Fri, 10 Mar 2023 00:29:01 +0700 Subject: [PATCH 25/92] fix: make PlayShadow less noisy --- PlayTools/MysticRunes/PlayShadow.m | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index 11d838b8..60b28ac4 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -51,32 +51,32 @@ - (NSInteger) hook_deviceType { } - (BOOL) pm_return_false { - NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); return false; } - (BOOL) pm_return_true { - NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); return true; } - (BOOL) pm_return_yes { - NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); return YES; } - (BOOL) pm_return_no { - NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); return NO; } - (int) pm_return_0 { - NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); return 0; } - (int) pm_return_1 { - NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); return 1; } @end @@ -86,7 +86,7 @@ @implementation PlayShadowLoader + (void) load { // Swizzle NSProcessInfo to troll every app that tries to detect macCatalyst // [objc_getClass("NSProcessInfo") swizzleInstanceMethod:@selector(isMacCatalystApp) withMethod:@selector(pm_return_false)]; - [objc_getClass("NSProcessInfo") swizzleInstanceMethod:@selector(isiOSAppOnMac) withMethod:@selector(pm_return_true)]; + // [objc_getClass("NSProcessInfo") swizzleInstanceMethod:@selector(isiOSAppOnMac) withMethod:@selector(pm_return_true)]; [objc_getClass("RNDeviceInfo") swizzleInstanceMethod:@selector(getDeviceType) withMethod:@selector(hook_deviceType)]; From 7a704340502084e242369981d8ebbc291cb86f6d Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Sat, 11 Mar 2023 01:00:58 +0700 Subject: [PATCH 26/92] feat: swizzle some device info classes --- PlayTools/MysticRunes/PlayShadow.m | 6 ++++++ PlayTools/MysticRunes/PlayedApple.swift | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index 60b28ac4..267c1b9e 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -79,6 +79,12 @@ - (int) pm_return_1 { // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); return 1; } + +- (NSString *) pm_return_empty { + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return @""; +} + @end @implementation PlayShadowLoader diff --git a/PlayTools/MysticRunes/PlayedApple.swift b/PlayTools/MysticRunes/PlayedApple.swift index f7c13379..7eae64d3 100644 --- a/PlayTools/MysticRunes/PlayedApple.swift +++ b/PlayTools/MysticRunes/PlayedApple.swift @@ -164,4 +164,4 @@ public class PlayKeychain: NSObject { return errSecItemNotFound } -} +} \ No newline at end of file From 505c6a87687d15d29aec72041aed2ad050819449 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Sat, 11 Mar 2023 09:15:56 +0700 Subject: [PATCH 27/92] feat: Togglable PlayShadow --- PlayTools/MysticRunes/PlayShadow.m | 9 +++++++++ PlayTools/PlaySettings.swift | 2 ++ 2 files changed, 11 insertions(+) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index 267c1b9e..041e4de9 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -8,6 +8,7 @@ #import #import "NSObject+Swizzle.h" #import +#import __attribute__((visibility("hidden"))) @interface PlayShadowLoader : NSObject @@ -90,10 +91,18 @@ - (NSString *) pm_return_empty { @implementation PlayShadowLoader + (void) load { + // NSLog(@"PC-DEBUG: [PlayMask] Loaded"); + if ([[PlaySettings shared] bypass]) [self loadJailbreakBypass]; +} + ++ (void) loadJailbreakBypass { // Swizzle NSProcessInfo to troll every app that tries to detect macCatalyst // [objc_getClass("NSProcessInfo") swizzleInstanceMethod:@selector(isMacCatalystApp) withMethod:@selector(pm_return_false)]; // [objc_getClass("NSProcessInfo") swizzleInstanceMethod:@selector(isiOSAppOnMac) withMethod:@selector(pm_return_true)]; + // Some device info class + [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(platform) withMethod:@selector(pm_return_empty)]; + [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(hwModel) withMethod:@selector(pm_return_empty)]; [objc_getClass("RNDeviceInfo") swizzleInstanceMethod:@selector(getDeviceType) withMethod:@selector(hook_deviceType)]; // Class: UIDevice diff --git a/PlayTools/PlaySettings.swift b/PlayTools/PlaySettings.swift index d2443171..b8ee75b9 100644 --- a/PlayTools/PlaySettings.swift +++ b/PlayTools/PlaySettings.swift @@ -33,6 +33,8 @@ let settings = PlaySettings.shared lazy var sensitivity = settingsData.sensitivity / 100 + @objc lazy var bypass = settingsData.bypass + @objc lazy var windowSizeHeight = CGFloat(settingsData.windowHeight) @objc lazy var windowSizeWidth = CGFloat(settingsData.windowWidth) From 3ab864ce0de3f07aaa05338f7f47f407a13ca4a0 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Sat, 11 Mar 2023 20:23:26 +0700 Subject: [PATCH 28/92] feat: attr for PlayChain --- PlayTools/MysticRunes/PlayedApple.swift | 95 +++++++++++++++++++++++-- PlayTools/PlayLoader.m | 2 - 2 files changed, 91 insertions(+), 6 deletions(-) diff --git a/PlayTools/MysticRunes/PlayedApple.swift b/PlayTools/MysticRunes/PlayedApple.swift index f7c13379..4204a14c 100644 --- a/PlayTools/MysticRunes/PlayedApple.swift +++ b/PlayTools/MysticRunes/PlayedApple.swift @@ -45,6 +45,87 @@ public class PlayKeychain: NSObject { .appendingPathComponent("\(serviceName)-\(accountName)-\(classType).plist") } + private static func createGenpAttr(_ query: NSDictionary, _ keychainPath: URL) -> NSDictionary? { + let currentTime = Date() + let keychainDict = NSDictionary(contentsOf: keychainPath) + + let attributes = NSMutableDictionary() + attributes[kSecAttrAccessControl as String] = + SecAccessControlCreateWithFlags(kCFAllocatorDefault, + kSecAttrAccessibleWhenUnlockedThisDeviceOnly, + .or, + nil) + attributes[kSecAttrAccount as String] = query["acct"] ?? keychainDict?["acct"] ?? "" + attributes[kSecAttrAccessGroup as String] = "*" + attributes[kSecAttrCreationDate as String] = String(describing: currentTime) + .replacingOccurrences(of: "UTC", with: "+0000") + attributes[kSecAttrModificationDate as String] = String(describing: currentTime) + .replacingOccurrences(of: "UTC", with: "+0000") + attributes["musr"] = Data() + attributes[kSecAttrPath as String] = keychainDict?["pdmn"] ?? "ak" + attributes["sha1"] = Data() + attributes[kSecAttrService as String] = query["svce"] ?? keychainDict?["svce"] ?? "" + attributes[kSecAttrSynchronizable as String] = query["sync"] ?? keychainDict?["sync"] ?? 0 + attributes["tomb"] = query["tomb"] ?? keychainDict?["tomb"] ?? 0 + return attributes + } + + private static func createKeysAttr(_ query: NSDictionary, _ keychainPath: URL) -> NSDictionary? { + let currentTime = Date() + let jan1st2001 = Date(timeIntervalSince1970: 978307200) + let keychainDict = NSDictionary(contentsOf: keychainPath) + + let attributes = NSMutableDictionary() + attributes["UUID"] = UUID().uuidString + attributes[kSecAttrAccessControl as String] = + SecAccessControlCreateWithFlags(kCFAllocatorDefault, + kSecAttrAccessibleWhenUnlockedThisDeviceOnly, + .or, + nil) + attributes[kSecAttrAccessGroup as String] = "*" + attributes["asen"] = query["asen"] ?? keychainDict?["asen"] ?? 0 + attributes[kSecAttrApplicationTag as String] = + query["atag"] as? Data? ?? keychainDict?["atag"] as? Data? ?? Data() + attributes[kSecAttrKeySizeInBits as String] = query["bsiz"] ?? keychainDict?["bsiz"] ?? 0 + attributes[kSecAttrCreationDate as String] = + String(describing: currentTime).replacingOccurrences(of: "UTC", with: "+0000") + attributes[kSecAttrCreator as String] = query["crtr"] ?? keychainDict?["crtr"] ?? 0 + attributes["decr"] = query["decr"] ?? keychainDict?["decr"] ?? 0 + attributes["drve"] = query["drve"] ?? keychainDict?["drve"] ?? 0 + attributes["edat"] = query["edat"] ?? keychainDict?["edat"] ?? + String(describing: jan1st2001).replacingOccurrences(of: "UTC", with: "+0000") + attributes["encr"] = query["encr"] ?? keychainDict?["encr"] ?? 0 + attributes["esiz"] = query["esiz"] ?? keychainDict?["esiz"] ?? 0 + attributes["extr"] = query["extr"] ?? keychainDict?["extr"] ?? 0 + attributes["kcls"] = query["kcls"] ?? keychainDict?["kcls"] ?? 0 + attributes[kSecAttrLabel as String] = query["labl"] ?? keychainDict?["labl"] ?? "" + attributes[kSecAttrModificationDate as String] = + String(describing: currentTime).replacingOccurrences(of: "UTC", with: "+0000") + attributes["modi"] = query["modi"] ?? keychainDict?["modi"] ?? 1 + attributes["musr"] = Data() + attributes["next"] = query["next"] ?? keychainDict?["next"] ?? 0 + attributes[kSecAttrPath as String] = keychainDict?["pdmn"] ?? "dku" + attributes["perm"] = query["perm"] ?? keychainDict?["perm"] ?? 1 + attributes["priv"] = query["priv"] ?? keychainDict?["priv"] ?? 1 + attributes["sdat"] = query["sdat"] ?? keychainDict?["sdat"] ?? String(describing: jan1st2001) + .replacingOccurrences(of: "UTC", with: "+0000") + attributes["sens"] = query["sens"] ?? keychainDict?["sens"] ?? 0 + attributes["sha1"] = query["sha1"] as? Data ?? keychainDict?["sha1"] as? Data? ?? Data() + return attributes + } + + static private func getAttributes(_ query: NSDictionary, _ keychainPath: URL) -> NSDictionary? { + // First, check if the kind of key is genp or keys + let classType = query[kSecClass as String] as? String ?? "" + if classType == "genp" { + return createGenpAttr(query, keychainPath) + } else if classType == "keys" { + return createKeysAttr(query, keychainPath) + } else { + return nil + } + } + @objc public static func debugLogger(_ logContent: String) { if PlaySettings.shared.settingsData.playChainDebugging { NSLog("PC-DEBUG: \(logContent)") @@ -69,7 +150,7 @@ public class PlayKeychain: NSObject { return errSecIO } // Place v_data in the result - if let v_data = attributes["v_data"] { + if let v_data = attributes["v_Data"] { result?.pointee = v_data as CFTypeRef } return errSecSuccess @@ -128,14 +209,20 @@ public class PlayKeychain: NSObject { -> OSStatus { // Get the path to the keychain file let keychainPath = keychainPath(query) - // Read the dictionary from the keychain file - let keychainDict = NSDictionary(contentsOf: keychainPath) // Check the `r_Attributes` key. If it is set to 1 in the query // DROP, NOT IMPLEMENTED let classType = query[kSecClass as String] as? String ?? "" if query["r_Attributes"] as? Int == 1 { - return errSecItemNotFound + let attributesDict = getAttributes(query, keychainPath) + if attributesDict == nil { + return errSecItemNotFound + } + // convert to CFDictonary + let attributes = attributesDict! as CFDictionary + result?.pointee = attributes as CFTypeRef + return errSecSuccess } + let keychainDict = NSDictionary(contentsOf: keychainPath) // If the keychain file doesn't exist, return errSecItemNotFound if keychainDict == nil { debugLogger("Keychain file not found at \(keychainPath)") diff --git a/PlayTools/PlayLoader.m b/PlayTools/PlayLoader.m index 92887d97..acaebefd 100644 --- a/PlayTools/PlayLoader.m +++ b/PlayTools/PlayLoader.m @@ -163,8 +163,6 @@ static OSStatus pt_SecItemDelete(CFDictionaryRef query) { DYLD_INTERPOSE(pt_SecItemUpdate, SecItemUpdate) DYLD_INTERPOSE(pt_SecItemDelete, SecItemDelete) - - @implementation PlayLoader static void __attribute__((constructor)) initialize(void) { From 02d23556d4b312e5fd480b2c50cdba2703c86eda Mon Sep 17 00:00:00 2001 From: Jason Ak Date: Mon, 13 Mar 2023 05:30:48 +0100 Subject: [PATCH 29/92] Bugfix: Controller binding not working if connected after app launch Fix a small regression: When connecting a controller after app launch we have to toggle the Keymap editor to get it saved before the binding work. it's due to ControlMode.visibile being init at true so we don't call `setup` when the notification fires. --- PlayTools/Controls/PlayInput.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 46128ecc..046e4a5f 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -180,11 +180,10 @@ class PlayInput { let main = OperationQueue.main centre.addObserver(forName: NSNotification.Name.GCControllerDidConnect, object: nil, queue: main) { _ in - if !mode.visible { - self.setup() - } if EditorController.shared.editorMode { self.toggleEditor(show: true) + } else { + self.setup() } } parseKeymap() From 66c5a42d398bff9402358626a4ad06899ff89af9 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 14 Mar 2023 09:54:30 +0700 Subject: [PATCH 30/92] fix: correcting types --- PlayTools/MysticRunes/PlayShadow.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index 041e4de9..fdd2c6dc 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -51,12 +51,12 @@ - (NSInteger) hook_deviceType { return 1; } -- (BOOL) pm_return_false { +- (bool) pm_return_false { // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); return false; } -- (BOOL) pm_return_true { +- (bool) pm_return_true { // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); return true; } From e3f489057421d17ffd647d0c5084bfd612dc371d Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 14 Mar 2023 10:06:55 +0700 Subject: [PATCH 31/92] fix: Log when attributes is accessed --- PlayTools/MysticRunes/PlayedApple.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/PlayTools/MysticRunes/PlayedApple.swift b/PlayTools/MysticRunes/PlayedApple.swift index 4204a14c..b2502ed3 100644 --- a/PlayTools/MysticRunes/PlayedApple.swift +++ b/PlayTools/MysticRunes/PlayedApple.swift @@ -213,6 +213,7 @@ public class PlayKeychain: NSObject { // DROP, NOT IMPLEMENTED let classType = query[kSecClass as String] as? String ?? "" if query["r_Attributes"] as? Int == 1 { + debugLogger("Attributes requested") let attributesDict = getAttributes(query, keychainPath) if attributesDict == nil { return errSecItemNotFound From b378a643a4aa4fbbe97d737e79bfa4537e044b7e Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 14 Mar 2023 10:29:46 +0700 Subject: [PATCH 32/92] feat: PlayShadow debug logging --- PlayTools/MysticRunes/PlayShadow.m | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index fdd2c6dc..8ce3b5eb 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -86,16 +86,23 @@ - (NSString *) pm_return_empty { return @""; } +- (NSDictionary *) pm_return_empty_dictionary { + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return @{}; +} + @end @implementation PlayShadowLoader + (void) load { - // NSLog(@"PC-DEBUG: [PlayMask] Loaded"); + [self debugLogger:@"PlayShadow is now loading"]; if ([[PlaySettings shared] bypass]) [self loadJailbreakBypass]; + // if ([[PlaySettings shared] bypass]) [self loadEnvironmentBypass]; # disabled as it might be too powerful } + (void) loadJailbreakBypass { + [self debugLogger:@"Jailbreak bypass loading"]; // Swizzle NSProcessInfo to troll every app that tries to detect macCatalyst // [objc_getClass("NSProcessInfo") swizzleInstanceMethod:@selector(isMacCatalystApp) withMethod:@selector(pm_return_false)]; // [objc_getClass("NSProcessInfo") swizzleInstanceMethod:@selector(isiOSAppOnMac) withMethod:@selector(pm_return_true)]; @@ -228,5 +235,15 @@ + (void) loadJailbreakBypass { [objc_getClass("ZDetection") swizzleInstanceMethod:@selector(isRootedOrJailbroken) withMethod:@selector(pm_return_false)]; } ++ (void) loadEnvironmentBypass { + [self debugLogger:@"Environment bypass loading"]; + // Completely nuke everything in the environment variables + [objc_getClass("NSProcessInfo") swizzleInstanceMethod:@selector(environment) withMethod:@selector(pm_return_empty_dictionary)]; +} + ++ (void) debugLogger: (NSString *) message { + NSLog(@"PC-DEBUG: %@", message); +} + @end From 1e2ef659653441f309eaf7f86a4429790b57c01d Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 14 Mar 2023 14:32:14 +0700 Subject: [PATCH 33/92] feat: App Tracking Transparently Denied (totally not cyberbullying) --- PlayTools/MysticRunes/PlayShadow.h | 4 +-- PlayTools/MysticRunes/PlayShadow.m | 42 +++++++++++++++++++++++++++--- PlayTools/PlayLoader.m | 2 -- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/PlayTools/MysticRunes/PlayShadow.h b/PlayTools/MysticRunes/PlayShadow.h index 53965479..b39a3be6 100644 --- a/PlayTools/MysticRunes/PlayShadow.h +++ b/PlayTools/MysticRunes/PlayShadow.h @@ -9,10 +9,10 @@ NS_ASSUME_NONNULL_BEGIN -@interface NSObject (Swizzle) +@interface NSObject (ShadowSwizzle) - (void)swizzleInstanceMethod:(SEL)origSelector withMethod:(SEL)newSelector; ++ (void)swizzleClassMethod: (SEL)origSelector withMethod: (SEL)newSelector @end - NS_ASSUME_NONNULL_END diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index 8ce3b5eb..025e9f71 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -6,7 +6,6 @@ // #import -#import "NSObject+Swizzle.h" #import #import @@ -14,7 +13,7 @@ @interface PlayShadowLoader : NSObject @end -@implementation NSObject (Swizzle) +@implementation NSObject (ShadowSwizzle) - (void) swizzleInstanceMethod:(SEL)origSelector withMethod:(SEL)newSelector { @@ -47,6 +46,30 @@ - (void) swizzleInstanceMethod:(SEL)origSelector withMethod:(SEL)newSelector } } ++ (void) swizzleClassMethod:(SEL)origSelector withMethod:(SEL)newSelector { + Class cls = object_getClass((id)self); + Method originalMethod = class_getClassMethod(cls, origSelector); + Method swizzledMethod = class_getClassMethod(cls, newSelector); + + if (class_addMethod(cls, + origSelector, + method_getImplementation(swizzledMethod), + method_getTypeEncoding(swizzledMethod)) ) { + class_replaceMethod(cls, + newSelector, + method_getImplementation(originalMethod), + method_getTypeEncoding(originalMethod)); + } else { + class_replaceMethod(cls, + newSelector, + class_replaceMethod(cls, + origSelector, + method_getImplementation(swizzledMethod), + method_getTypeEncoding(swizzledMethod)), + method_getTypeEncoding(originalMethod)); + } +} + - (NSInteger) hook_deviceType { return 1; } @@ -91,6 +114,16 @@ - (NSDictionary *) pm_return_empty_dictionary { return @{}; } ++ (void) pm_return_2_with_completion_handler:(void (^)(NSInteger))completionHandler { + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + completionHandler(2); +} + ++ (NSInteger) pm_return_2 { + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return 2; +} + @end @implementation PlayShadowLoader @@ -99,6 +132,10 @@ + (void) load { [self debugLogger:@"PlayShadow is now loading"]; if ([[PlaySettings shared] bypass]) [self loadJailbreakBypass]; // if ([[PlaySettings shared] bypass]) [self loadEnvironmentBypass]; # disabled as it might be too powerful + + // Swizzle ATTrackingManager + [objc_getClass("ATTrackingManager") swizzleClassMethod:@selector(requestTrackingAuthorizationWithCompletionHandler:) withMethod:@selector(pm_return_2_with_completion_handler:)]; + [objc_getClass("ATTrackingManager") swizzleClassMethod:@selector(trackingAuthorizationStatus) withMethod:@selector(pm_return_2)]; } + (void) loadJailbreakBypass { @@ -246,4 +283,3 @@ + (void) debugLogger: (NSString *) message { } @end - diff --git a/PlayTools/PlayLoader.m b/PlayTools/PlayLoader.m index 92887d97..acaebefd 100644 --- a/PlayTools/PlayLoader.m +++ b/PlayTools/PlayLoader.m @@ -163,8 +163,6 @@ static OSStatus pt_SecItemDelete(CFDictionaryRef query) { DYLD_INTERPOSE(pt_SecItemUpdate, SecItemUpdate) DYLD_INTERPOSE(pt_SecItemDelete, SecItemDelete) - - @implementation PlayLoader static void __attribute__((constructor)) initialize(void) { From 1ac8e0ba745f3d53dddc6763b530963d2aa54de8 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 14 Mar 2023 22:21:03 +0700 Subject: [PATCH 34/92] b haxx --- PlayTools/MysticRunes/PlayShadow.m | 40 +++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index 025e9f71..dc2c470a 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -70,7 +70,9 @@ + (void) swizzleClassMethod:(SEL)origSelector withMethod:(SEL)newSelector { } } -- (NSInteger) hook_deviceType { + + +- (NSInteger) pm_hook_deviceType { return 1; } @@ -124,6 +126,26 @@ + (NSInteger) pm_return_2 { return 2; } ++ (bool) pm_clsm_return_false { + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return false; +} + ++ (bool) pm_clsm_return_true { + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return true; +} + ++ (BOOL) pm_clsm_return_yes { + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return YES; +} + ++ (BOOL) pm_clsm_return_no { + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return NO; +} + @end @implementation PlayShadowLoader @@ -211,14 +233,14 @@ + (void) loadJailbreakBypass { [objc_getClass("FCRSystemMetadata") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; // Class: v_VDMap - [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isJailbrokenDetected) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isJailBrokenDetectedByVOS) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isDFPHookedDetecedByVOS) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isCodeInjectionDetectedByVOS) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isDebuggerCheckDetectedByVOS) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isAppSignerCheckDetectedByVOS) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(v_checkAModified) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isRuntimeTamperingDetected) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isJailbrokenDetected) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isJailBrokenDetectedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isDFPHookedDetecedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isCodeInjectionDetectedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isDebuggerCheckDetectedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isAppSignerCheckDetectedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(v_checkAModified) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isRuntimeTamperingDetected) withMethod:@selector(pm_return_false)]; // Class: SDMUtils [objc_getClass("SDMUtils") swizzleInstanceMethod:@selector(isJailBroken) withMethod:@selector(pm_return_no)]; From f0e19bdf908cf5875541393a12a9064dcad0bf7c Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 14 Mar 2023 22:31:12 +0700 Subject: [PATCH 35/92] b haxx --- PlayTools/MysticRunes/PlayShadow.m | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index dc2c470a..a885579d 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -233,14 +233,14 @@ + (void) loadJailbreakBypass { [objc_getClass("FCRSystemMetadata") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; // Class: v_VDMap - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isJailbrokenDetected) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isJailBrokenDetectedByVOS) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isDFPHookedDetecedByVOS) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isCodeInjectionDetectedByVOS) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isDebuggerCheckDetectedByVOS) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isAppSignerCheckDetectedByVOS) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(v_checkAModified) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isRuntimeTamperingDetected) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isJailbrokenDetected) withMethod:@selector(pm_clsm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isJailBrokenDetectedByVOS) withMethod:@selector(pm_clsm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isDFPHookedDetecedByVOS) withMethod:@selector(pm_clsm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isCodeInjectionDetectedByVOS) withMethod:@selector(pm_clsm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isDebuggerCheckDetectedByVOS) withMethod:@selector(pm_clsm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isAppSignerCheckDetectedByVOS) withMethod:@selector(pm_clsm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(v_checkAModified) withMethod:@selector(pm_clsm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isRuntimeTamperingDetected) withMethod:@selector(pm_clsm_return_false)]; // Class: SDMUtils [objc_getClass("SDMUtils") swizzleInstanceMethod:@selector(isJailBroken) withMethod:@selector(pm_return_no)]; From b091d58e03b9e23bf4b7ba69dd16c0eed93e0c59 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 14 Mar 2023 22:33:20 +0700 Subject: [PATCH 36/92] b haxx --- PlayTools/MysticRunes/PlayShadow.m | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index a885579d..872f495b 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -146,6 +146,11 @@ + (BOOL) pm_clsm_return_no { return NO; } ++ (int) pm_clsm_do_nothing_with_callback:(void (^)(int))callback { + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return 0; +} + @end @implementation PlayShadowLoader @@ -242,6 +247,9 @@ + (void) loadJailbreakBypass { [objc_getClass("v_VDMap") swizzleClassMethod:@selector(v_checkAModified) withMethod:@selector(pm_clsm_return_false)]; [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isRuntimeTamperingDetected) withMethod:@selector(pm_clsm_return_false)]; + // v_VPrivateUtility terminateApplicationAfter: + [objc_getClass("v_VPrivateUtility") swizzleClassMethod:@selector(terminateApplicationAfter:) withMethod:@selector(pm_clsm_do_nothing_with_callback:)]; + // Class: SDMUtils [objc_getClass("SDMUtils") swizzleInstanceMethod:@selector(isJailBroken) withMethod:@selector(pm_return_no)]; From b6fe3a39f4c4d0c234525daac2021cf7706b0c29 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 14 Mar 2023 22:52:25 +0700 Subject: [PATCH 37/92] b haxx --- PlayTools/MysticRunes/PlayShadow.m | 58 +++++++++++++++--------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index 872f495b..18dc1727 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -175,9 +175,9 @@ + (void) loadJailbreakBypass { [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(platform) withMethod:@selector(pm_return_empty)]; [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(hwModel) withMethod:@selector(pm_return_empty)]; [objc_getClass("RNDeviceInfo") swizzleInstanceMethod:@selector(getDeviceType) withMethod:@selector(hook_deviceType)]; - + // Class: UIDevice - [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_no)]; + [objc_getClass("UIDevice") swizzleClassMethod:@selector(isJailbroken) withMethod:@selector(pm_clsm_return_no)]; [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(isJailBreak) withMethod:@selector(pm_return_no)]; [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(isJailBroken) withMethod:@selector(pm_return_no)]; @@ -185,18 +185,18 @@ + (void) loadJailbreakBypass { [objc_getClass("JailbreakDetectionVC") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_no)]; // Class: DTTJailbreakDetection - [objc_getClass("DTTJailbreakDetection") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_no)]; + [objc_getClass("DTTJailbreakDetection") swizzleClassMethod:@selector(isJailbroken) withMethod:@selector(pm_clsm_return_no)]; // Class: ANSMetadata [objc_getClass("ANSMetadata") swizzleInstanceMethod:@selector(computeIsJailbroken) withMethod:@selector(pm_return_no)]; [objc_getClass("ANSMetadata") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_no)]; // Class: AppsFlyerUtils - [objc_getClass("AppsFlyerUtils") swizzleInstanceMethod:@selector(isJailBreakon) withMethod:@selector(pm_return_no)]; - [objc_getClass("AppsFlyerUtils") swizzleInstanceMethod:@selector(a) withMethod:@selector(pm_return_false)]; + [objc_getClass("AppsFlyerUtils") swizzleClassMethod:@selector(isJailBreakon) withMethod:@selector(pm_clsm_return_no)]; + [objc_getClass("AppsFlyerUtils") swizzleClassMethod:@selector(a) withMethod:@selector(pm_clsm_return_false)]; // Class: jailBreak - [objc_getClass("jailBreak") swizzleInstanceMethod:@selector(isJailBreak) withMethod:@selector(pm_return_false)]; + [objc_getClass("jailBreak") swizzleClassMethod:@selector(isJailBreak) withMethod:@selector(pm_clsm_return_false)]; // Class: GBDeviceInfo [objc_getClass("GBDeviceInfo") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_no)]; @@ -205,16 +205,16 @@ + (void) loadJailbreakBypass { [objc_getClass("CMARAppRestrictionsDelegate") swizzleInstanceMethod:@selector(isDeviceNonCompliant) withMethod:@selector(pm_return_false)]; // Class: ADYSecurityChecks - [objc_getClass("ADYSecurityChecks") swizzleInstanceMethod:@selector(isDeviceJailbroken) withMethod:@selector(pm_return_false)]; + [objc_getClass("ADYSecurityChecks") swizzleClassMethod:@selector(isDeviceJailbroken) withMethod:@selector(pm_clsm_return_false)]; // Class: UBReportMetadataDevice [objc_getClass("UBReportMetadataDevice") swizzleInstanceMethod:@selector(is_rooted) withMethod:@selector(pm_return_null)]; // Class: UtilitySystem - [objc_getClass("UtilitySystem") swizzleInstanceMethod:@selector(isJailbreak) withMethod:@selector(pm_return_false)]; + [objc_getClass("UtilitySystem") swizzleClassMethod:@selector(isJailbreak) withMethod:@selector(pm_clsm_return_false)]; // Class: GemaltoConfiguration - [objc_getClass("GemaltoConfiguration") swizzleInstanceMethod:@selector(isJailbreak) withMethod:@selector(pm_return_false)]; + [objc_getClass("GemaltoConfiguration") swizzleClassMethod:@selector(isJailbreak) withMethod:@selector(pm_clsm_return_false)]; // Class: CPWRDeviceInfo [objc_getClass("CPWRDeviceInfo") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; @@ -223,7 +223,7 @@ + (void) loadJailbreakBypass { [objc_getClass("CPWRSessionInfo") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; // Class: KSSystemInfo - [objc_getClass("KSSystemInfo") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; + [objc_getClass("KSSystemInfo") swizzleClassMethod:@selector(isJailbroken) withMethod:@selector(pm_clsm_return_false)]; // Class: EMDSKPPConfiguration [objc_getClass("EMDSKPPConfiguration") swizzleInstanceMethod:@selector(jailBroken) withMethod:@selector(pm_return_false)]; @@ -238,23 +238,20 @@ + (void) loadJailbreakBypass { [objc_getClass("FCRSystemMetadata") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; // Class: v_VDMap - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isJailbrokenDetected) withMethod:@selector(pm_clsm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isJailBrokenDetectedByVOS) withMethod:@selector(pm_clsm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isDFPHookedDetecedByVOS) withMethod:@selector(pm_clsm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isCodeInjectionDetectedByVOS) withMethod:@selector(pm_clsm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isDebuggerCheckDetectedByVOS) withMethod:@selector(pm_clsm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isAppSignerCheckDetectedByVOS) withMethod:@selector(pm_clsm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(v_checkAModified) withMethod:@selector(pm_clsm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isRuntimeTamperingDetected) withMethod:@selector(pm_clsm_return_false)]; - - // v_VPrivateUtility terminateApplicationAfter: - [objc_getClass("v_VPrivateUtility") swizzleClassMethod:@selector(terminateApplicationAfter:) withMethod:@selector(pm_clsm_do_nothing_with_callback:)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isJailbrokenDetected) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isJailBrokenDetectedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isDFPHookedDetecedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isCodeInjectionDetectedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isDebuggerCheckDetectedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isAppSignerCheckDetectedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(v_checkAModified) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isRuntimeTamperingDetected) withMethod:@selector(pm_return_false)]; // Class: SDMUtils [objc_getClass("SDMUtils") swizzleInstanceMethod:@selector(isJailBroken) withMethod:@selector(pm_return_no)]; // Class: OneSignalJailbreakDetection - [objc_getClass("OneSignalJailbreakDetection") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_no)]; + [objc_getClass("OneSignalJailbreakDetection") swizzleClassMethod:@selector(isJailbroken) withMethod:@selector(pm_clsm_return_no)]; // Class: DigiPassHandler [objc_getClass("DigiPassHandler") swizzleInstanceMethod:@selector(rootedDeviceTestResult) withMethod:@selector(pm_return_no)]; @@ -283,23 +280,24 @@ + (void) loadJailbreakBypass { [objc_getClass("FBAdBotDetector") swizzleInstanceMethod:@selector(isJailBrokenDevice) withMethod:@selector(pm_return_false)]; // Class: TNGDeviceTool - [objc_getClass("TNGDeviceTool") swizzleInstanceMethod:@selector(isJailBreak) withMethod:@selector(pm_return_false)]; - [objc_getClass("TNGDeviceTool") swizzleInstanceMethod:@selector(isJailBreak_file) withMethod:@selector(pm_return_false)]; - [objc_getClass("TNGDeviceTool") swizzleInstanceMethod:@selector(isJailBreak_cydia) withMethod:@selector(pm_return_false)]; - [objc_getClass("TNGDeviceTool") swizzleInstanceMethod:@selector(isJailBreak_appList) withMethod:@selector(pm_return_false)]; - [objc_getClass("TNGDeviceTool") swizzleInstanceMethod:@selector(isJailBreak_env) withMethod:@selector(pm_return_false)]; + [objc_getClass("TNGDeviceTool") swizzleClassMethod:@selector(isJailBreak) withMethod:@selector(pm_clsm_return_false)]; + [objc_getClass("TNGDeviceTool") swizzleClassMethod:@selector(isJailBreak_file) withMethod:@selector(pm_clsm_return_false)]; + [objc_getClass("TNGDeviceTool") swizzleClassMethod:@selector(isJailBreak_cydia) withMethod:@selector(pm_clsm_return_false)]; + [objc_getClass("TNGDeviceTool") swizzleClassMethod:@selector(isJailBreak_appList) withMethod:@selector(pm_clsm_return_false)]; + [objc_getClass("TNGDeviceTool") swizzleClassMethod:@selector(isJailBreak_env) withMethod:@selector(pm_clsm_return_false)]; // Class: DTDeviceInfo - [objc_getClass("DTDeviceInfo") swizzleInstanceMethod:@selector(isJailbreak) withMethod:@selector(pm_return_false)]; + [objc_getClass("DTDeviceInfo") swizzleClassMethod:@selector(isJailbreak) withMethod:@selector(pm_clsm_return_false)]; // Class: SecVIDeviceUtil - [objc_getClass("SecVIDeviceUtil") swizzleInstanceMethod:@selector(isJailbreak) withMethod:@selector(pm_return_false)]; + [objc_getClass("SecVIDeviceUtil") swizzleClassMethod:@selector(isJailbreak) withMethod:@selector(pm_clsm_return_false)]; // Class: RVPBridgeExtension4Jailbroken [objc_getClass("RVPBridgeExtension4Jailbroken") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; // Class: ZDetection - [objc_getClass("ZDetection") swizzleInstanceMethod:@selector(isRootedOrJailbroken) withMethod:@selector(pm_return_false)]; + [objc_getClass("ZDetection") swizzleClassMethod:@selector(isRootedOrJailbroken) withMethod:@selector(pm_clsm_return_false)]; + } + (void) loadEnvironmentBypass { From 5ac7cc65baf38637dbe7169f5013b10292191a9b Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 14 Mar 2023 23:00:22 +0700 Subject: [PATCH 38/92] fix: correctly swizzle class instances --- PlayTools/MysticRunes/PlayShadow.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index 18dc1727..977daf06 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -174,7 +174,7 @@ + (void) loadJailbreakBypass { // Some device info class [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(platform) withMethod:@selector(pm_return_empty)]; [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(hwModel) withMethod:@selector(pm_return_empty)]; - [objc_getClass("RNDeviceInfo") swizzleInstanceMethod:@selector(getDeviceType) withMethod:@selector(hook_deviceType)]; + [objc_getClass("RNDeviceInfo") swizzleInstanceMethod:@selector(getDeviceType) withMethod:@selector(pm_hook_deviceType)]; // Class: UIDevice [objc_getClass("UIDevice") swizzleClassMethod:@selector(isJailbroken) withMethod:@selector(pm_clsm_return_no)]; @@ -193,7 +193,7 @@ + (void) loadJailbreakBypass { // Class: AppsFlyerUtils [objc_getClass("AppsFlyerUtils") swizzleClassMethod:@selector(isJailBreakon) withMethod:@selector(pm_clsm_return_no)]; - [objc_getClass("AppsFlyerUtils") swizzleClassMethod:@selector(a) withMethod:@selector(pm_clsm_return_false)]; + [objc_getClass("AppsFlyerUtils") swizzleClassMethod:@selector(isJailbrokenWithSkipAdvancedJailbreakValidation:) withMethod:@selector(pm_clsm_return_false)]; // Class: jailBreak [objc_getClass("jailBreak") swizzleClassMethod:@selector(isJailBreak) withMethod:@selector(pm_clsm_return_false)]; From 47b6ba5b9a21a6a6eb8df07dc287de73869b697b Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 14 Mar 2023 23:07:00 +0700 Subject: [PATCH 39/92] chore: proper comments --- PlayTools/MysticRunes/PlayShadow.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index 977daf06..d41edb6e 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -70,7 +70,7 @@ + (void) swizzleClassMethod:(SEL)origSelector withMethod:(SEL)newSelector { } } - +// Instance methods - (NSInteger) pm_hook_deviceType { return 1; @@ -116,6 +116,8 @@ - (NSDictionary *) pm_return_empty_dictionary { return @{}; } +// Class methods + + (void) pm_return_2_with_completion_handler:(void (^)(NSInteger))completionHandler { // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); completionHandler(2); From 5abe8642e59959660f9718550f1be48521e32f86 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 14 Mar 2023 23:08:00 +0700 Subject: [PATCH 40/92] fix: some swizzle targets --- PlayTools/MysticRunes/PlayShadow.m | 1 + 1 file changed, 1 insertion(+) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index d41edb6e..b6ca3074 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -195,6 +195,7 @@ + (void) loadJailbreakBypass { // Class: AppsFlyerUtils [objc_getClass("AppsFlyerUtils") swizzleClassMethod:@selector(isJailBreakon) withMethod:@selector(pm_clsm_return_no)]; + [objc_getClass("AppsFlyerUtils") swizzleClassMethod:@selector(isJailbroken) withMethod:@selector(pm_clsm_return_no)]; [objc_getClass("AppsFlyerUtils") swizzleClassMethod:@selector(isJailbrokenWithSkipAdvancedJailbreakValidation:) withMethod:@selector(pm_clsm_return_false)]; // Class: jailBreak From 36349e02313767bf0db9709e1a3c576e7a84068c Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Fri, 31 Mar 2023 13:00:33 +0700 Subject: [PATCH 41/92] feat: res scaler --- PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m | 6 ++++-- PlayTools/PlayScreen.swift | 5 +++-- PlayTools/PlaySettings.swift | 2 ++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m index 039b76ab..6ab13dc4 100644 --- a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m +++ b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m @@ -94,11 +94,13 @@ - (long long) hook_orientation { } - (double) hook_nativeScale { - return 2.0; + return [[PlaySettings shared] customScaler]; } - (double) hook_scale { - return 2.0; + // Return rounded value of [[PlaySettings shared] customScaler] + // Even though it is a double return, this will only accept .0 value or apps will crash + return round([[PlaySettings shared] customScaler]); } - (double) get_default_height { diff --git a/PlayTools/PlayScreen.swift b/PlayTools/PlayScreen.swift index 47bc4ff6..d443a57f 100644 --- a/PlayTools/PlayScreen.swift +++ b/PlayTools/PlayScreen.swift @@ -9,6 +9,7 @@ let screen = PlayScreen.shared let isInvertFixEnabled = PlaySettings.shared.inverseScreenValues && PlaySettings.shared.adaptiveDisplay let mainScreenWidth = !isInvertFixEnabled ? PlaySettings.shared.windowSizeWidth : PlaySettings.shared.windowSizeHeight let mainScreenHeight = !isInvertFixEnabled ? PlaySettings.shared.windowSizeHeight : PlaySettings.shared.windowSizeWidth +let customScaler = PlaySettings.shared.customScaler extension CGSize { func aspectRatio() -> CGFloat { @@ -93,7 +94,7 @@ public class PlayScreen: NSObject { } @objc public static func nativeBounds(_ rect: CGRect) -> CGRect { - return rect.toAspectRatio(2) + return rect.toAspectRatio(CGFloat((customScaler))) } @objc public static func width(_ size: Int) -> Int { @@ -172,7 +173,7 @@ public class PlayScreen: NSObject { } @objc public static func nativeBoundsDefault(_ rect: CGRect) -> CGRect { - return rect.toAspectRatioDefault(2) + return rect.toAspectRatioDefault(CGFloat((customScaler))) } @objc public static func sizeAspectRatioDefault(_ size: CGSize) -> CGSize { diff --git a/PlayTools/PlaySettings.swift b/PlayTools/PlaySettings.swift index d2443171..78d19bac 100644 --- a/PlayTools/PlaySettings.swift +++ b/PlayTools/PlaySettings.swift @@ -68,6 +68,7 @@ let settings = PlaySettings.shared @objc lazy var windowFixMethod = settingsData.windowFixMethod + @objc lazy var customScaler = settingsData.customScaler } struct AppSettingsData: Codable { @@ -79,6 +80,7 @@ struct AppSettingsData: Codable { var iosDeviceModel = "iPad13,8" var windowWidth = 1920 var windowHeight = 1080 + var customScaler = 2.0 var resolution = 2 var aspectRatio = 1 var notch = false From d7b8791ba1ed8b4ae5b113ac12531109caf51bd6 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Mon, 3 Apr 2023 11:40:03 +0700 Subject: [PATCH 42/92] chore: match names --- PlayTools/MysticRunes/PlayShadow.h | 2 +- PlayTools/MysticRunes/PlayShadow.m | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/PlayTools/MysticRunes/PlayShadow.h b/PlayTools/MysticRunes/PlayShadow.h index b39a3be6..014602c9 100644 --- a/PlayTools/MysticRunes/PlayShadow.h +++ b/PlayTools/MysticRunes/PlayShadow.h @@ -1,5 +1,5 @@ // -// PlayMask.h +// PlayShadow.h // PlayTools // // Created by Venti on 08/03/2023. diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index b6ca3074..edf61ccb 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -1,5 +1,5 @@ // -// PlayMask.m +// PlayShadow.m // PlayTools // // Created by Venti on 08/03/2023. @@ -300,7 +300,6 @@ + (void) loadJailbreakBypass { // Class: ZDetection [objc_getClass("ZDetection") swizzleClassMethod:@selector(isRootedOrJailbroken) withMethod:@selector(pm_clsm_return_false)]; - } + (void) loadEnvironmentBypass { From 26535339517ce9bebee4c47bdc80ac9670e0f3ed Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Thu, 6 Apr 2023 03:44:13 +0800 Subject: [PATCH 43/92] Logging system and ignore repeated Enter key press while typing --- PlayTools/Controls/ControlMode.swift | 1 + PlayTools/Controls/MenuController.swift | 15 ++++++-- PlayTools/Controls/PlayInput.swift | 19 +++++----- PlayTools/Controls/PlayMice.swift | 1 + PlayTools/Controls/Toucher.swift | 50 +++++++++++++++++++++++-- 5 files changed, 71 insertions(+), 15 deletions(-) diff --git a/PlayTools/Controls/ControlMode.swift b/PlayTools/Controls/ControlMode.swift index 3ab5a9c3..43619c8a 100644 --- a/PlayTools/Controls/ControlMode.swift +++ b/PlayTools/Controls/ControlMode.swift @@ -36,6 +36,7 @@ public class ControlMode { PlayInput.shared.setup() } } + Toucher.writeLog(logMessage: "cursor show switched to \(show)") visible = show } } diff --git a/PlayTools/Controls/MenuController.swift b/PlayTools/Controls/MenuController.swift index 3ec96d13..d5008a59 100644 --- a/PlayTools/Controls/MenuController.swift +++ b/PlayTools/Controls/MenuController.swift @@ -44,6 +44,13 @@ extension UIApplication { func downscaleElement(_ sender: AnyObject) { EditorController.shared.focusedControl?.resize(down: true) } + + // put a mark in the toucher log, so as to align with tester description + @objc + func markToucherLog(_ sender: AnyObject) { + Toucher.writeLog(logMessage:"mark") + Toast.showHint(title: "Log marked") + } } extension UIViewController { @@ -66,12 +73,14 @@ var keymapping = ["Open/Close Keymapping Editor", "Delete selected element", "Upsize selected element", "Downsize selected element", - "Rotate display area"] + "Rotate display area", + "Put a mark in toucher log"] var keymappingSelectors = [#selector(UIApplication.switchEditorMode(_:)), #selector(UIApplication.removeElement(_:)), #selector(UIApplication.upscaleElement(_:)), #selector(UIApplication.downscaleElement(_:)), - #selector(UIViewController.rotateView(_:))] + #selector(UIViewController.rotateView(_:)), + #selector(UIApplication.markToucherLog)] class MenuController { init(with builder: UIMenuBuilder) { @@ -79,7 +88,7 @@ class MenuController { } class func keymappingMenu() -> UIMenu { - let keyCommands = [ "K", UIKeyCommand.inputDelete, UIKeyCommand.inputUpArrow, UIKeyCommand.inputDownArrow, "R" ] + let keyCommands = [ "K", UIKeyCommand.inputDelete, UIKeyCommand.inputUpArrow, UIKeyCommand.inputDownArrow, "R", "L"] let arrowKeyChildrenCommands = zip(keyCommands, keymapping).map { (command, btn) in UIKeyCommand(title: btn, diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 46128ecc..3e531170 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -5,11 +5,11 @@ import UIKit class PlayInput { static let shared = PlayInput() var actions = [Action]() - static var keyboardMapped = false - private static var keyboardWillMap = true + static var keyboardMapped = true static var shouldLockCursor = true - static var touchQueue = DispatchQueue.init(label: "playcover.toucher", qos: .userInteractive) + static var touchQueue = DispatchQueue.init(label: "playcover.toucher", qos: .userInteractive, + autoreleaseFrequency: .workItem) static public var buttonHandlers: [String: [(Bool) -> Void]] = [:] func invalidate() { @@ -89,6 +89,7 @@ class PlayInput { public func toggleEditor(show: Bool) { PlayInput.keyboardMapped = !show + Toucher.writeLog(logMessage: "editor opened? \(show)") if show { self.invalidate() mode.show(show) @@ -189,10 +190,12 @@ class PlayInput { } parseKeymap() centre.addObserver(forName: UIApplication.keyboardDidHideNotification, object: nil, queue: main) { _ in - PlayInput.keyboardWillMap = true + PlayInput.keyboardMapped = true + Toucher.writeLog(logMessage: "virtual keyboard did hide") } centre.addObserver(forName: UIApplication.keyboardWillShowNotification, object: nil, queue: main) { _ in - PlayInput.keyboardWillMap = false + PlayInput.keyboardMapped = false + Toucher.writeLog(logMessage: "virtual keyboard will show") } centre.addObserver(forName: NSNotification.Name(rawValue: "NSWindowDidBecomeKeyNotification"), object: nil, queue: main) { _ in @@ -236,11 +239,9 @@ class PlayInput { } AKInterface.shared!.initialize(keyboard: {keycode, pressed, isRepeat in - if PlayInput.keyboardWillMap != PlayInput.keyboardMapped && pressed { - PlayInput.keyboardMapped = PlayInput.keyboardWillMap - } if !PlayInput.keyboardMapped { - return false + // explicitly ignore repeated Enter key + return isRepeat && keycode == 36 } if isRepeat { return true diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index 83ebfcaa..a32a1a20 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -137,6 +137,7 @@ public class PlayMice { if EditorController.shared.editorMode && actionIndex != 2 && pressed { EditorController.shared.setKey(buttonIndex[actionIndex]!) } + Toucher.writeLog(logMessage: "mouse button pressed? \(pressed)") return true } guard let curPos = self.cursorPos() else { return true } diff --git a/PlayTools/Controls/Toucher.swift b/PlayTools/Controls/Toucher.swift index 4b223adb..cc949669 100644 --- a/PlayTools/Controls/Toucher.swift +++ b/PlayTools/Controls/Toucher.swift @@ -9,9 +9,12 @@ import UIKit class Toucher { static weak var keyWindow: UIWindow? static weak var keyView: UIView? -// static var touchQueue = DispatchQueue.init(label: "playcover.toucher", qos: .userInteractive) -// static var nextId: Int = 0 -// static var idMap = [Int?](repeating: nil, count: 64) + // For debug only + static var logEnabled = true + static var logFilePath = + NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/toucher.log" + static private var logCount = 0 + static var logFile: FileHandle? /** on invocations with phase "began", an int id is allocated, which can be used later to refer to this touch point. on invocations with phase "ended", id is set to nil representing the touch point is no longer valid. @@ -28,8 +31,49 @@ class Toucher { return } tid = PTFakeMetaTouch.fakeTouchId(tid!, at: point, with: phase, in: keyWindow, on: keyView) + writeLog(logMessage: + "\(phase.rawValue.description) \(tid!.description) \(point.debugDescription)") if tid! < 0 { tid = nil } } + + static func setupLogfile() { + if FileManager.default.createFile(atPath: logFilePath, contents: nil, attributes: nil) { + logFile = FileHandle(forWritingAtPath: logFilePath) + Toast.showOver(msg: logFilePath) + } else { + Toast.showHint(title: "logFile creation failed") + return + } + NotificationCenter.default.addObserver( + forName: NSNotification.Name(rawValue: "NSApplicationWillTerminateNotification"), + object: nil, + queue: OperationQueue.main + ) { _ in + try? logFile?.close() + } + } + + static func writeLog(logMessage: String) { + if !logEnabled { + return + } + guard let file = logFile else { + setupLogfile() + return + } + let message = "\(DispatchTime.now().rawValue) \(logMessage)\n" + guard let data = message.data(using: .utf8) else { + Toast.showHint(title: "log message is utf8 uncodable") + return + } + logCount += 1 + // roll over + if logCount > 60000 { + file.seek(toFileOffset: 0) + logCount = 0 + } + file.write(data) + } } From 8eb9ead0f494a95b0c6c7ea2059673b14c8af113 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Sat, 8 Apr 2023 12:15:37 +0700 Subject: [PATCH 44/92] fix: C strings in Model ID/OEM ID --- PlayTools/PlayLoader.m | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/PlayTools/PlayLoader.m b/PlayTools/PlayLoader.m index 92887d97..db7eec18 100644 --- a/PlayTools/PlayLoader.m +++ b/PlayTools/PlayLoader.m @@ -12,8 +12,9 @@ #import "NSObject+Swizzle.h" // Get device model from playcover .plist -#define DEVICE_MODEL ([[[PlaySettings shared] deviceModel] UTF8String]) -#define OEM_ID ([[[PlaySettings shared] oemID] UTF8String]) +// With a null terminator +#define DEVICE_MODEL [[[PlaySettings shared] deviceModel] cStringUsingEncoding:NSUTF8StringEncoding] +#define OEM_ID [[[PlaySettings shared] oemID] cStringUsingEncoding:NSUTF8StringEncoding] #define PLATFORM_IOS 2 // Define dyld_get_active_platform function for interpose @@ -60,22 +61,31 @@ static int pt_sysctl(int *name, u_int types, void *buf, size_t *size, void *arg0 static int pt_sysctlbyname(const char *name, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { if ((strcmp(name, "hw.machine") == 0) || (strcmp(name, "hw.product") == 0) || (strcmp(name, "hw.model") == 0)) { - if (oldp != NULL) { + if (oldp == NULL) { + int ret = sysctlbyname(name, oldp, oldlenp, newp, newlen); + *oldlenp = strlen(DEVICE_MODEL) + 1; + return ret; + } + else if (oldp != NULL) { int ret = sysctlbyname(name, oldp, oldlenp, newp, newlen); const char *machine = DEVICE_MODEL; strncpy((char *)oldp, machine, strlen(machine)); - *oldlenp = strlen(machine); + *oldlenp = strlen(machine) + 1; return ret; } else { int ret = sysctlbyname(name, oldp, oldlenp, newp, newlen); return ret; } } else if ((strcmp(name, "hw.target") == 0)) { - if (oldp != NULL) { + if (oldp == NULL) { + int ret = sysctlbyname(name, oldp, oldlenp, newp, newlen); + *oldlenp = strlen(OEM_ID) + 1; + return ret; + } else if (oldp != NULL) { int ret = sysctlbyname(name, oldp, oldlenp, newp, newlen); const char *machine = OEM_ID; strncpy((char *)oldp, machine, strlen(machine)); - *oldlenp = strlen(machine); + *oldlenp = strlen(machine) + 1; return ret; } else { int ret = sysctlbyname(name, oldp, oldlenp, newp, newlen); From 8fce507f875726cfda0342be7cd6396a52471c0b Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Fri, 14 Apr 2023 20:32:29 +0700 Subject: [PATCH 45/92] Revert "Merge pull request #99 from ohaiibuzzle/feat/PlayChain-attr" This reverts commit 7f02ae52c44ff03a850bea9dd6574d34b91a0eaf, reversing changes made to 1dcc1414d49108db388b414c089d008e5cbb6253. --- PlayTools/MysticRunes/PlayedApple.swift | 96 ++----------------------- 1 file changed, 4 insertions(+), 92 deletions(-) diff --git a/PlayTools/MysticRunes/PlayedApple.swift b/PlayTools/MysticRunes/PlayedApple.swift index 710f69b3..7eae64d3 100644 --- a/PlayTools/MysticRunes/PlayedApple.swift +++ b/PlayTools/MysticRunes/PlayedApple.swift @@ -45,87 +45,6 @@ public class PlayKeychain: NSObject { .appendingPathComponent("\(serviceName)-\(accountName)-\(classType).plist") } - private static func createGenpAttr(_ query: NSDictionary, _ keychainPath: URL) -> NSDictionary? { - let currentTime = Date() - let keychainDict = NSDictionary(contentsOf: keychainPath) - - let attributes = NSMutableDictionary() - attributes[kSecAttrAccessControl as String] = - SecAccessControlCreateWithFlags(kCFAllocatorDefault, - kSecAttrAccessibleWhenUnlockedThisDeviceOnly, - .or, - nil) - attributes[kSecAttrAccount as String] = query["acct"] ?? keychainDict?["acct"] ?? "" - attributes[kSecAttrAccessGroup as String] = "*" - attributes[kSecAttrCreationDate as String] = String(describing: currentTime) - .replacingOccurrences(of: "UTC", with: "+0000") - attributes[kSecAttrModificationDate as String] = String(describing: currentTime) - .replacingOccurrences(of: "UTC", with: "+0000") - attributes["musr"] = Data() - attributes[kSecAttrPath as String] = keychainDict?["pdmn"] ?? "ak" - attributes["sha1"] = Data() - attributes[kSecAttrService as String] = query["svce"] ?? keychainDict?["svce"] ?? "" - attributes[kSecAttrSynchronizable as String] = query["sync"] ?? keychainDict?["sync"] ?? 0 - attributes["tomb"] = query["tomb"] ?? keychainDict?["tomb"] ?? 0 - return attributes - } - - private static func createKeysAttr(_ query: NSDictionary, _ keychainPath: URL) -> NSDictionary? { - let currentTime = Date() - let jan1st2001 = Date(timeIntervalSince1970: 978307200) - let keychainDict = NSDictionary(contentsOf: keychainPath) - - let attributes = NSMutableDictionary() - attributes["UUID"] = UUID().uuidString - attributes[kSecAttrAccessControl as String] = - SecAccessControlCreateWithFlags(kCFAllocatorDefault, - kSecAttrAccessibleWhenUnlockedThisDeviceOnly, - .or, - nil) - attributes[kSecAttrAccessGroup as String] = "*" - attributes["asen"] = query["asen"] ?? keychainDict?["asen"] ?? 0 - attributes[kSecAttrApplicationTag as String] = - query["atag"] as? Data? ?? keychainDict?["atag"] as? Data? ?? Data() - attributes[kSecAttrKeySizeInBits as String] = query["bsiz"] ?? keychainDict?["bsiz"] ?? 0 - attributes[kSecAttrCreationDate as String] = - String(describing: currentTime).replacingOccurrences(of: "UTC", with: "+0000") - attributes[kSecAttrCreator as String] = query["crtr"] ?? keychainDict?["crtr"] ?? 0 - attributes["decr"] = query["decr"] ?? keychainDict?["decr"] ?? 0 - attributes["drve"] = query["drve"] ?? keychainDict?["drve"] ?? 0 - attributes["edat"] = query["edat"] ?? keychainDict?["edat"] ?? - String(describing: jan1st2001).replacingOccurrences(of: "UTC", with: "+0000") - attributes["encr"] = query["encr"] ?? keychainDict?["encr"] ?? 0 - attributes["esiz"] = query["esiz"] ?? keychainDict?["esiz"] ?? 0 - attributes["extr"] = query["extr"] ?? keychainDict?["extr"] ?? 0 - attributes["kcls"] = query["kcls"] ?? keychainDict?["kcls"] ?? 0 - attributes[kSecAttrLabel as String] = query["labl"] ?? keychainDict?["labl"] ?? "" - attributes[kSecAttrModificationDate as String] = - String(describing: currentTime).replacingOccurrences(of: "UTC", with: "+0000") - attributes["modi"] = query["modi"] ?? keychainDict?["modi"] ?? 1 - attributes["musr"] = Data() - attributes["next"] = query["next"] ?? keychainDict?["next"] ?? 0 - attributes[kSecAttrPath as String] = keychainDict?["pdmn"] ?? "dku" - attributes["perm"] = query["perm"] ?? keychainDict?["perm"] ?? 1 - attributes["priv"] = query["priv"] ?? keychainDict?["priv"] ?? 1 - attributes["sdat"] = query["sdat"] ?? keychainDict?["sdat"] ?? String(describing: jan1st2001) - .replacingOccurrences(of: "UTC", with: "+0000") - attributes["sens"] = query["sens"] ?? keychainDict?["sens"] ?? 0 - attributes["sha1"] = query["sha1"] as? Data ?? keychainDict?["sha1"] as? Data? ?? Data() - return attributes - } - - static private func getAttributes(_ query: NSDictionary, _ keychainPath: URL) -> NSDictionary? { - // First, check if the kind of key is genp or keys - let classType = query[kSecClass as String] as? String ?? "" - if classType == "genp" { - return createGenpAttr(query, keychainPath) - } else if classType == "keys" { - return createKeysAttr(query, keychainPath) - } else { - return nil - } - } - @objc public static func debugLogger(_ logContent: String) { if PlaySettings.shared.settingsData.playChainDebugging { NSLog("PC-DEBUG: \(logContent)") @@ -150,7 +69,7 @@ public class PlayKeychain: NSObject { return errSecIO } // Place v_data in the result - if let v_data = attributes["v_Data"] { + if let v_data = attributes["v_data"] { result?.pointee = v_data as CFTypeRef } return errSecSuccess @@ -209,21 +128,14 @@ public class PlayKeychain: NSObject { -> OSStatus { // Get the path to the keychain file let keychainPath = keychainPath(query) + // Read the dictionary from the keychain file + let keychainDict = NSDictionary(contentsOf: keychainPath) // Check the `r_Attributes` key. If it is set to 1 in the query // DROP, NOT IMPLEMENTED let classType = query[kSecClass as String] as? String ?? "" if query["r_Attributes"] as? Int == 1 { - debugLogger("Attributes requested") - let attributesDict = getAttributes(query, keychainPath) - if attributesDict == nil { - return errSecItemNotFound - } - // convert to CFDictonary - let attributes = attributesDict! as CFDictionary - result?.pointee = attributes as CFTypeRef - return errSecSuccess + return errSecItemNotFound } - let keychainDict = NSDictionary(contentsOf: keychainPath) // If the keychain file doesn't exist, return errSecItemNotFound if keychainDict == nil { debugLogger("Keychain file not found at \(keychainPath)") From 84cc14ebbbb63b700baa14e3e7fdc31d626fbc66 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Fri, 14 Apr 2023 20:33:38 +0700 Subject: [PATCH 46/92] fix: small issue in add --- PlayTools/MysticRunes/PlayedApple.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PlayTools/MysticRunes/PlayedApple.swift b/PlayTools/MysticRunes/PlayedApple.swift index 7eae64d3..34acf2b5 100644 --- a/PlayTools/MysticRunes/PlayedApple.swift +++ b/PlayTools/MysticRunes/PlayedApple.swift @@ -68,8 +68,8 @@ public class PlayKeychain: NSObject { debugLogger("Failed to write keychain file") return errSecIO } - // Place v_data in the result - if let v_data = attributes["v_data"] { + // Place v_Data in the result + if let v_data = attributes["v_Data"] { result?.pointee = v_data as CFTypeRef } return errSecSuccess From d68693f20a165a49db48385733964140f98f8a9b Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Wed, 19 Apr 2023 00:03:43 +0700 Subject: [PATCH 47/92] fix: change working directory to / like iOS --- PlayTools/PlayCover.swift | 5 +++++ PlayTools/PlaySettings.swift | 3 +++ 2 files changed, 8 insertions(+) diff --git a/PlayTools/PlayCover.swift b/PlayTools/PlayCover.swift index 4f15a11a..b8c5fe35 100644 --- a/PlayTools/PlayCover.swift +++ b/PlayTools/PlayCover.swift @@ -16,6 +16,11 @@ public class PlayCover: NSObject { AKInterface.initialize() PlayInput.shared.initialize() DiscordIPC.shared.initialize() + + if PlaySettings.shared.rootWorkDir { + // Change the working directory to / just like iOS + FileManager.default.changeCurrentDirectoryPath("/") + } } @objc static public func initMenu(menu: NSObject) { diff --git a/PlayTools/PlaySettings.swift b/PlayTools/PlaySettings.swift index cdbb79ff..5b64c3bb 100644 --- a/PlayTools/PlaySettings.swift +++ b/PlayTools/PlaySettings.swift @@ -71,6 +71,8 @@ let settings = PlaySettings.shared @objc lazy var windowFixMethod = settingsData.windowFixMethod @objc lazy var customScaler = settingsData.customScaler + + @objc lazy var rootWorkDir = settingsData.rootWorkDir } struct AppSettingsData: Codable { @@ -93,4 +95,5 @@ struct AppSettingsData: Codable { var playChainDebugging = false var inverseScreenValues = false var windowFixMethod = 0 + var rootWorkDir = true } From 878c0d5fef872f9f1f85a69ef690fbe7b25d51c0 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Sat, 22 Apr 2023 01:44:46 +0700 Subject: [PATCH 48/92] fix: buffer allocation --- PlayTools/PlayLoader.m | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/PlayTools/PlayLoader.m b/PlayTools/PlayLoader.m index 742dfed5..b80ba2ec 100644 --- a/PlayTools/PlayLoader.m +++ b/PlayTools/PlayLoader.m @@ -63,7 +63,11 @@ static int pt_sysctlbyname(const char *name, void *oldp, size_t *oldlenp, void * if ((strcmp(name, "hw.machine") == 0) || (strcmp(name, "hw.product") == 0) || (strcmp(name, "hw.model") == 0)) { if (oldp == NULL) { int ret = sysctlbyname(name, oldp, oldlenp, newp, newlen); - *oldlenp = strlen(DEVICE_MODEL) + 1; + // We don't want to accidentally decrease it because the real sysctl call will ENOMEM + // as model are much longer on Macs (eg. MacBookAir10,1) + if (*oldlenp < strlen(DEVICE_MODEL) + 1) { + *oldlenp = strlen(DEVICE_MODEL) + 1; + } return ret; } else if (oldp != NULL) { @@ -79,7 +83,9 @@ static int pt_sysctlbyname(const char *name, void *oldp, size_t *oldlenp, void * } else if ((strcmp(name, "hw.target") == 0)) { if (oldp == NULL) { int ret = sysctlbyname(name, oldp, oldlenp, newp, newlen); - *oldlenp = strlen(OEM_ID) + 1; + if (*oldlenp < strlen(OEM_ID) + 1) { + *oldlenp = strlen(OEM_ID) + 1; + } return ret; } else if (oldp != NULL) { int ret = sysctlbyname(name, oldp, oldlenp, newp, newlen); From 045a9b9d264ec33d163a036e9e3f3070bb76bd8a Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Fri, 28 Apr 2023 11:31:31 +0800 Subject: [PATCH 49/92] support fixed and free joystick --- AKPlugin.swift | 27 ++- PlayTools/Controls/ControlMode.swift | 13 +- PlayTools/Controls/MenuController.swift | 48 ++++- PlayTools/Controls/PlayAction.swift | 106 ++++++--- PlayTools/Controls/PlayController.swift | 121 +++++++++++ PlayTools/Controls/PlayInput.swift | 273 +++++++----------------- PlayTools/Controls/PlayMice.swift | 237 ++++++++------------ PlayTools/Controls/Toucher.swift | 2 +- PlayTools/Utils/Toast.swift | 56 +++++ Plugin.swift | 7 +- 10 files changed, 488 insertions(+), 402 deletions(-) create mode 100644 PlayTools/Controls/PlayController.swift diff --git a/AKPlugin.swift b/AKPlugin.swift index 3e12d5cf..ee9e72b1 100644 --- a/AKPlugin.swift +++ b/AKPlugin.swift @@ -8,7 +8,6 @@ import AppKit import CoreGraphics import Foundation -import GameController class AKPlugin: NSObject, Plugin { required override init() { @@ -63,8 +62,8 @@ class AKPlugin: NSObject, Plugin { } private var modifierFlag: UInt = 0 - func initialize(keyboard: @escaping(UInt16, Bool, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool, - swapMode: @escaping() -> Bool) { + func setupKeyboard(keyboard: @escaping(UInt16, Bool, Bool) -> Bool, + swapMode: @escaping() -> Bool) { func checkCmd(modifier: NSEvent.ModifierFlags) -> Bool { if modifier.contains(.command) { self.cmdPressed = true @@ -113,7 +112,10 @@ class AKPlugin: NSObject, Plugin { } return event }) - let mask: NSEvent.EventTypeMask = [.leftMouseDragged, .otherMouseDragged, .rightMouseDragged, .mouseMoved] + } + + func setupMouseMoved(mouseMoved: @escaping(CGFloat, CGFloat) -> Bool) { + let mask: NSEvent.EventTypeMask = [.leftMouseDragged, .otherMouseDragged, .rightMouseDragged] NSEvent.addLocalMonitorForEvents(matching: mask, handler: { event in let consumed = mouseMoved(event.deltaX, event.deltaY) if consumed { @@ -121,21 +123,28 @@ class AKPlugin: NSObject, Plugin { } return event }) + // transpass mouse moved event when no button pressed, for traffic light button to light up + NSEvent.addLocalMonitorForEvents(matching: .mouseMoved, handler: { event in + _ = mouseMoved(event.deltaX, event.deltaY) + return event + }) } - func setupMouseButton(_ _up: Int, _ _down: Int, _ dontIgnore: @escaping(Int, Bool) -> Bool) { - NSEvent.addLocalMonitorForEvents(matching: NSEvent.EventTypeMask(rawValue: UInt64(_up)), handler: { event in + func setupMouseButton(left: Bool, right: Bool, _ dontIgnore: @escaping(Bool) -> Bool) { + let downType: NSEvent.EventTypeMask = left ? .leftMouseDown : right ? .rightMouseDown : .otherMouseDown + let upType: NSEvent.EventTypeMask = left ? .leftMouseUp : right ? .rightMouseUp : .otherMouseUp + NSEvent.addLocalMonitorForEvents(matching: downType, handler: { event in // For traffic light buttons when fullscreen if event.window != NSApplication.shared.windows.first! { return event } - if dontIgnore(_up, true) { + if dontIgnore(true) { return event } return nil }) - NSEvent.addLocalMonitorForEvents(matching: NSEvent.EventTypeMask(rawValue: UInt64(_down)), handler: { event in - if dontIgnore(_up, false) { + NSEvent.addLocalMonitorForEvents(matching: upType, handler: { event in + if dontIgnore(false) { return event } return nil diff --git a/PlayTools/Controls/ControlMode.swift b/PlayTools/Controls/ControlMode.swift index 69ff6292..a30ae3fc 100644 --- a/PlayTools/Controls/ControlMode.swift +++ b/PlayTools/Controls/ControlMode.swift @@ -11,6 +11,16 @@ public class ControlMode { static public let mode = ControlMode() public var visible: Bool = true + public var keyboardMapped = true + + public static func trySwap() -> Bool { + if PlayInput.shouldLockCursor { + mode.show(!mode.visible) + return true + } + mode.show(true) + return false + } func show(_ show: Bool) { if !editor.editorMode { @@ -32,8 +42,7 @@ public class ControlMode { if screen.fullscreen { screen.switchDock(false) } - - PlayInput.shared.setup() +// PlayInput.shared.setup() } } Toucher.writeLog(logMessage: "cursor show switched to \(show)") diff --git a/PlayTools/Controls/MenuController.swift b/PlayTools/Controls/MenuController.swift index 2aaadce0..1da0786c 100644 --- a/PlayTools/Controls/MenuController.swift +++ b/PlayTools/Controls/MenuController.swift @@ -48,7 +48,7 @@ extension UIApplication { // put a mark in the toucher log, so as to align with tester description @objc func markToucherLog(_ sender: AnyObject) { - Toucher.writeLog(logMessage:"mark") + Toucher.writeLog(logMessage: "mark") Toast.showHint(title: "Log marked") } } @@ -79,25 +79,55 @@ var keymapping = [ NSLocalizedString("menu.keymapping.downsizeElement", tableName: "Playtools", value: "Downsize selected element", comment: ""), NSLocalizedString("menu.keymapping.rotateDisplay", tableName: "Playtools", - value: "Rotate display area", comment: ""), - NSLocalizedString("menu.keymapping.markLog", tableName: "Playtools", - value: "Put a mark in toucher log", comment: "") + value: "Rotate display area", comment: "") ] var keymappingSelectors = [#selector(UIApplication.switchEditorMode(_:)), #selector(UIApplication.removeElement(_:)), #selector(UIApplication.upscaleElement(_:)), #selector(UIApplication.downscaleElement(_:)), - #selector(UIViewController.rotateView(_:)), - #selector(UIApplication.markToucherLog)] + #selector(UIViewController.rotateView(_:)) + ] class MenuController { init(with builder: UIMenuBuilder) { + if Toucher.logEnabled { + builder.insertSibling(MenuController.debuggingMenu(), afterMenu: .view) + } builder.insertSibling(MenuController.keymappingMenu(), afterMenu: .view) } - class func keymappingMenu() -> UIMenu { - let keyCommands = [ "K", UIKeyCommand.inputDelete, UIKeyCommand.inputUpArrow, UIKeyCommand.inputDownArrow, "R", "L"] + static func debuggingMenu() -> UIMenu { + let menuTitle = [ + "Put a mark in toucher log" + ] + let keyCommands = ["L"] + let selectors = [ + #selector(UIApplication.markToucherLog) + ] + let arrowKeyChildrenCommands = zip(keyCommands, menuTitle).map { (command, btn) in + UIKeyCommand(title: btn, + image: nil, + action: selectors[menuTitle.firstIndex(of: btn)!], + input: command, + modifierFlags: .command, + propertyList: [CommandsList.KeymappingToolbox: btn] + ) + } + return UIMenu(title: "Debug", + image: nil, + identifier: .debuggingMenu, + options: [], + children: [ + UIMenu(title: "", + image: nil, + identifier: .debuggingOptionsMenu, + options: .displayInline, + children: arrowKeyChildrenCommands)]) + } + class func keymappingMenu() -> UIMenu { + let keyCommands = [ "K", UIKeyCommand.inputDelete, + UIKeyCommand.inputUpArrow, UIKeyCommand.inputDownArrow, "R", "L"] let arrowKeyChildrenCommands = zip(keyCommands, keymapping).map { (command, btn) in UIKeyCommand(title: btn, image: nil, @@ -126,4 +156,6 @@ class MenuController { extension UIMenu.Identifier { static var keymappingMenu: UIMenu.Identifier { UIMenu.Identifier("io.playcover.PlayTools.menus.editor") } static var keymappingOptionsMenu: UIMenu.Identifier { UIMenu.Identifier("io.playcover.PlayTools.menus.keymapping") } + static var debuggingMenu: UIMenu.Identifier { UIMenu.Identifier("io.playcover.PlayTools.menus.debug") } + static var debuggingOptionsMenu: UIMenu.Identifier { UIMenu.Identifier("io.playcover.PlayTools.menus.debugging") } } diff --git a/PlayTools/Controls/PlayAction.swift b/PlayTools/Controls/PlayAction.swift index 855f82c5..2955bb5a 100644 --- a/PlayTools/Controls/PlayAction.swift +++ b/PlayTools/Controls/PlayAction.swift @@ -60,15 +60,17 @@ class DraggableButtonAction: ButtonAction { if pressed { Toucher.touchcam(point: point, phase: UITouch.Phase.began, tid: &id) self.releasePoint = point - PlayMice.shared.draggableHandler[keyName] = self.onMouseMoved + PlayInput.draggableHandler[keyName] = self.onMouseMoved + AKInterface.shared!.hideCursor() } else { - PlayMice.shared.draggableHandler.removeValue(forKey: keyName) + PlayInput.draggableHandler.removeValue(forKey: keyName) Toucher.touchcam(point: releasePoint, phase: UITouch.Phase.ended, tid: &id) + AKInterface.shared!.unhideCursor() } } override func invalidate() { - PlayMice.shared.draggableHandler.removeValue(forKey: keyName) + PlayInput.draggableHandler.removeValue(forKey: keyName) super.invalidate() } @@ -95,9 +97,9 @@ class ContinuousJoystickAction: Action { position = center self.sensitivity = data.transform.size.absoluteSize / 4 if key == PlayMice.elementName { - PlayMice.shared.joystickHandler[key] = self.mouseUpdate + PlayInput.joystickHandler[key] = self.mouseUpdate } else { - PlayMice.shared.joystickHandler[key] = self.thumbstickUpdate + PlayInput.joystickHandler[key] = self.thumbstickUpdate } } @@ -129,22 +131,22 @@ class ContinuousJoystickAction: Action { } func invalidate() { - PlayMice.shared.joystickHandler.removeValue(forKey: key) + PlayInput.joystickHandler.removeValue(forKey: key) } } class JoystickAction: Action { let keys: [Int] let center: CGPoint + var touch: CGPoint let shift: CGFloat var id: Int? - var moving = false - private var keyPressed = [Bool](repeating: false, count: 4) init(keys: [Int], center: CGPoint, shift: CGFloat) { self.keys = keys self.center = center - self.shift = shift / 2 + self.touch = center + self.shift = shift / 4 for index in 0.. (Bool) -> Void { - return { pressed in - self.keyPressed[index] = pressed - self.update() + // if the size of joystick is large, set control type to free, otherwise fixed. + // this is a temporary method. ideally should give the user an option. + if shift < 200 { + return { pressed in + self.updateTouch(index: index, pressed: pressed) + self.handleFixed() + } + } else { + return { pressed in + self.updateTouch(index: index, pressed: pressed) + self.handleFree() + } } } - func update() { - var touch = center - if keyPressed[0] { - touch.y -= shift / 2 - } else if keyPressed[1] { - touch.y += shift / 2 - } - if keyPressed[2] { - touch.x -= shift / 2 - } else if keyPressed[3] { - touch.x += shift / 2 + func updateTouch(index: Int, pressed: Bool) { + let isPlus = index & 1 != 0 + let realShift = isPlus ? shift : -shift + if index > 1 { + if pressed { + touch.x = center.x + realShift + } else { + touch.x = center.x + } + } else { + if pressed { + touch.y = center.y + realShift + } else { + touch.y = center.y + } } - if moving { - if touch.equalTo(center) { - moving = false + } + + func atCenter() -> Bool { + return (center.x - touch.x).magnitude + (center.y - touch.y).magnitude < 8 + } + + func handleCommon(_ begin: () -> Void) { + let moving = id != nil + if atCenter() { + if moving { Toucher.touchcam(point: touch, phase: UITouch.Phase.ended, tid: &id) - } else { - Toucher.touchcam(point: touch, phase: UITouch.Phase.moved, tid: &id) } } else { - if !touch.equalTo(center) { - moving = true - Toucher.touchcam(point: touch, phase: UITouch.Phase.began, tid: &id) - } // end if - } // end else + if moving { + Toucher.touchcam(point: touch, phase: UITouch.Phase.moved, tid: &id) + } else { + begin() + } + } + } + + func handleFree() { + handleCommon { + Toucher.touchcam(point: self.center, phase: UITouch.Phase.began, tid: &id) + PlayInput.touchQueue.asyncAfter(deadline: .now() + 0.04, qos: .userInitiated) { + if self.id == nil { + return + } + Toucher.touchcam(point: self.touch, phase: UITouch.Phase.moved, tid: &self.id) + } // end closure + } + } + + func handleFixed() { + handleCommon { + Toucher.touchcam(point: self.touch, phase: UITouch.Phase.began, tid: &id) + } } } diff --git a/PlayTools/Controls/PlayController.swift b/PlayTools/Controls/PlayController.swift new file mode 100644 index 00000000..64c641ec --- /dev/null +++ b/PlayTools/Controls/PlayController.swift @@ -0,0 +1,121 @@ +// +// PlayController.swift +// PlayTools +// +// Created by 许沂聪 on 2023/4/21. +// + +import Foundation +import GameController + +class PlayController { + private static var directionPadXValue: Float = 0, + directionPadYValue: Float = 0, + thumbstickCursorControl: [String: (((CGFloat, CGFloat) -> Void)?, CGFloat, CGFloat) -> Void] + = ["Left Thumbstick": ThumbstickCursorControl().update, "Right Thumbstick": ThumbstickCursorControl().update] + + public static func initialize() { + GCController.current?.extendedGamepad?.valueChangedHandler = handleEvent + } + + static func handleEditorEvent(_ profile: GCExtendedGamepad, _ element: GCControllerElement) { + // This is the index of controller buttons, which is String, not Int + var alias: String = element.aliases.first! + if alias == "Direction Pad" { + guard let dpadElement = element as? GCControllerDirectionPad else { + Toast.showOver(msg: "cannot map direction pad: element type not recognizable") + return + } + if dpadElement.xAxis.value > 0 { + alias = dpadElement.right.aliases.first! + } else if dpadElement.xAxis.value < 0 { + alias = dpadElement.left.aliases.first! + } + if dpadElement.yAxis.value > 0 { + alias = dpadElement.down.aliases.first! + } else if dpadElement.yAxis.value < 0 { + alias = dpadElement.up.aliases.first! + } + } + EditorController.shared.setKey(alias) + } + + static func handleEvent(_ profile: GCExtendedGamepad, _ element: GCControllerElement) { + let name: String = element.aliases.first! + if let buttonElement = element as? GCControllerButtonInput { + guard let handlers = PlayInput.buttonHandlers[name] else { return } +// Toast.showOver(msg: name + ": \(buttonElement.isPressed)") + for handler in handlers { + handler(buttonElement.isPressed) + } + } else if let dpadElement = element as? GCControllerDirectionPad { + PlayController.handleDirectionPad(profile, dpadElement) + } else { + Toast.showOver(msg: "unrecognised controller element input happens") + } + } + public static func handleDirectionPad(_ profile: GCExtendedGamepad, _ dpad: GCControllerDirectionPad) { + let name = dpad.aliases.first! + let xAxis = dpad.xAxis, yAxis = dpad.yAxis + if name == "Direction Pad" { + if (xAxis.value > 0) != (directionPadXValue > 0) { + PlayController.handleEvent(profile, dpad.right) + } + if (xAxis.value < 0) != (directionPadXValue < 0) { + PlayController.handleEvent(profile, dpad.left) + } + if (yAxis.value > 0) != (directionPadYValue > 0) { + PlayController.handleEvent(profile, dpad.up) + } + if (yAxis.value < 0) != (directionPadYValue < 0) { + PlayController.handleEvent(profile, dpad.down) + } + directionPadXValue = xAxis.value + directionPadYValue = yAxis.value + return + } + let deltaX = xAxis.value, deltaY = yAxis.value + let cgDx = CGFloat(deltaX) + let cgDy = CGFloat(deltaY) + thumbstickCursorControl[name]!( + PlayInput.draggableHandler[name] ?? PlayInput.cameraMoveHandler[name], cgDx * 6, cgDy * 6) + PlayInput.joystickHandler[name]?(cgDx, cgDy) + } +} + +class ThumbstickCursorControl { + private var thumbstickVelocity: CGVector = CGVector.zero, + thumbstickPolling: Bool = false, + eventHandler: ((CGFloat, CGFloat) -> Void)! + + static private func isVectorSignificant(_ vector: CGVector) -> Bool { + return vector.dx.magnitude + vector.dy.magnitude > 0.2 + } + + public func update(handler: ((CGFloat, CGFloat) -> Void)?, velocityX: CGFloat, velocityY: CGFloat) { + guard let hdlr = handler else { + if thumbstickPolling { + self.thumbstickVelocity.dx = 0 + self.thumbstickVelocity.dy = 0 + } + return + } + self.eventHandler = hdlr + self.thumbstickVelocity.dx = velocityX + self.thumbstickVelocity.dy = velocityY + if !thumbstickPolling { + PlayInput.touchQueue.async(execute: self.thumbstickPoll) + self.thumbstickPolling = true + } + } + + private func thumbstickPoll() { + if !ThumbstickCursorControl.isVectorSignificant(self.thumbstickVelocity) { + self.thumbstickPolling = false + return + } + self.eventHandler(self.thumbstickVelocity.dx, self.thumbstickVelocity.dy) + PlayInput.touchQueue.asyncAfter( + deadline: DispatchTime.now() + 0.017, execute: self.thumbstickPoll) + } +} diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 5770a7d1..79f4c789 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -5,12 +5,15 @@ import UIKit class PlayInput { static let shared = PlayInput() var actions = [Action]() - static var keyboardMapped = true static var shouldLockCursor = true static var touchQueue = DispatchQueue.init(label: "playcover.toucher", qos: .userInteractive, autoreleaseFrequency: .workItem) - static public var buttonHandlers: [String: [(Bool) -> Void]] = [:] + static public var buttonHandlers: [String: [(Bool) -> Void]] = [:], + draggableHandler: [String: (CGFloat, CGFloat) -> Void] = [:], + cameraMoveHandler: [String: (CGFloat, CGFloat) -> Void] = [:], + cameraScaleHandler: [String: (CGFloat, CGFloat) -> Void] = [:], + joystickHandler: [String: (CGFloat, CGFloat) -> Void] = [:] func invalidate() { for action in self.actions { @@ -19,7 +22,7 @@ class PlayInput { } static public func registerButton(key: String, handler: @escaping (Bool) -> Void) { - if ["LMB", "RMB", "MMB"].contains(key) { + if "LMB" == key { PlayInput.shouldLockCursor = true } if PlayInput.buttonHandlers[key] == nil { @@ -28,36 +31,6 @@ class PlayInput { PlayInput.buttonHandlers[key]!.append(handler) } - func keyboardHandler(_ keyCode: UInt16, _ pressed: Bool) -> Bool { - let name = KeyCodeNames.virtualCodes[keyCode] ?? "Btn" - guard let handlers = PlayInput.buttonHandlers[name] else { - return false - } - var mapped = false - for handler in handlers { - PlayInput.touchQueue.async(qos: .userInteractive, execute: { - handler(pressed) - }) - mapped = true - } - return mapped - } - - func controllerButtonHandler(_ profile: GCExtendedGamepad, _ element: GCControllerElement) { - let name: String = element.aliases.first! - if let buttonElement = element as? GCControllerButtonInput { - guard let handlers = PlayInput.buttonHandlers[name] else { return } -// Toast.showOver(msg: name + ": \(buttonElement.isPressed)") - for handler in handlers { - handler(buttonElement.isPressed) - } - } else if let dpadElement = element as? GCControllerDirectionPad { - PlayMice.shared.handleControllerDirectionPad(profile, dpadElement) - } else { - Toast.showOver(msg: "unrecognised controller element input happens") - } - } - func parseKeymap() { actions = [] PlayInput.shouldLockCursor = false @@ -88,221 +61,119 @@ class PlayInput { } public func toggleEditor(show: Bool) { - PlayInput.keyboardMapped = !show + mode.keyboardMapped = !show Toucher.writeLog(logMessage: "editor opened? \(show)") if show { self.invalidate() mode.show(show) if let keyboard = GCKeyboard.coalesced!.keyboardInput { - keyboard.keyChangedHandler = { _, _, keyCode, _ in - if !PlayInput.cmdPressed() - && !PlayInput.FORBIDDEN.contains(keyCode) - && self.isSafeToBind(keyboard) - && KeyCodeNames.keyCodes[keyCode.rawValue] != nil { - EditorController.shared.setKey(keyCode.rawValue) - } + keyboard.keyChangedHandler = { _, _, keyCode, pressed in + PlayKeyboard.handleEditorEvent(keyCode: keyCode, pressed: pressed) } } if let controller = GCController.current?.extendedGamepad { - controller.valueChangedHandler = { _, element in - // This is the index of controller buttons, which is String, not Int - var alias: String = element.aliases.first! - if alias == "Direction Pad" { - guard let dpadElement = element as? GCControllerDirectionPad else { - Toast.showOver(msg: "cannot map direction pad: element type not recognizable") - return - } - if dpadElement.xAxis.value > 0 { - alias = dpadElement.right.aliases.first! - } else if dpadElement.xAxis.value < 0 { - alias = dpadElement.left.aliases.first! - } - if dpadElement.yAxis.value > 0 { - alias = dpadElement.down.aliases.first! - } else if dpadElement.yAxis.value < 0 { - alias = dpadElement.up.aliases.first! - } - } - EditorController.shared.setKey(alias) - } + controller.valueChangedHandler = PlayController.handleEditorEvent } } else { - setup() + GCKeyboard.coalesced?.keyboardInput?.keyChangedHandler = nil + PlayController.initialize() parseKeymap() - _ = self.swapMode() + _ = ControlMode.trySwap() } } - func setup() { - GCKeyboard.coalesced?.keyboardInput?.keyChangedHandler = nil - GCController.current?.extendedGamepad?.valueChangedHandler = controllerButtonHandler - } - static public func cmdPressed() -> Bool { return AKInterface.shared!.cmdPressed } - private func isSafeToBind(_ input: GCKeyboardInput) -> Bool { - var result = true - for forbidden in PlayInput.FORBIDDEN where input.button(forKeyCode: forbidden)?.isPressed ?? false { - result = false - break - } - return result - } - - private static let FORBIDDEN: [GCKeyCode] = [ - .leftGUI, - .rightGUI, - .leftAlt, - .rightAlt, - .printScreen - ] - - private func swapMode() -> Bool { - if PlayInput.shouldLockCursor { - mode.show(!mode.visible) - return true + func initialize() { + if !PlaySettings.shared.keymapping { + return } - mode.show(true) - return false - } - var root: UIViewController? { - return screen.window?.rootViewController - } + let centre = NotificationCenter.default + let main = OperationQueue.main - func setupHotkeys() { - if let keyboard = GCKeyboard.coalesced?.keyboardInput { - keyboard.button(forKeyCode: .leftGUI)?.pressedChangedHandler = { _, _, pressed in - PlayInput.lCmdPressed = pressed + centre.addObserver(forName: NSNotification.Name.GCControllerDidConnect, object: nil, queue: main) { _ in + if EditorController.shared.editorMode { + self.toggleEditor(show: true) + } else { + PlayController.initialize() } - keyboard.button(forKeyCode: .rightGUI)?.pressedChangedHandler = { _, _, pressed in - PlayInput.rCmdPressed = pressed + } + parseKeymap() + centre.addObserver(forName: NSNotification.Name(rawValue: "NSWindowDidBecomeKeyNotification"), object: nil, + queue: main) { _ in + if !mode.visible { + AKInterface.shared!.warpCursor() + } + } + DispatchQueue.main.asyncAfter(deadline: .now() + 5, qos: .utility) { + if !mode.visible || self.actions.count <= 0 || !PlayInput.shouldLockCursor { + return } + Toast.initialize() } + PlayKeyboard.initialize() + PlayMice.shared.initialize() + } +} - func syncUserDefaults() -> Float { - let persistenceKeyname = "playtoolsKeymappingDisabledAt" - let lastUse = UserDefaults.standard.float(forKey: persistenceKeyname) - var thisUse = lastUse - if lastUse < 1 { - thisUse = 2 - } else { - thisUse = Float(Date.timeIntervalSinceReferenceDate) +class PlayKeyboard { + public static func handleEditorEvent(keyCode: GCKeyCode, pressed: Bool) { + if !PlayInput.cmdPressed() + && !PlayKeyboard.FORBIDDEN.contains(keyCode) + && KeyCodeNames.keyCodes[keyCode.rawValue] != nil { + EditorController.shared.setKey(keyCode.rawValue) } - var token2: NSObjectProtocol? - let center = NotificationCenter.default - token2 = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillDisable, - object: nil, queue: OperationQueue.main) { _ in - center.removeObserver(token2!) - UserDefaults.standard.set(thisUse, forKey: persistenceKeyname) - } - return lastUse } - func initializeToasts() { - if !settings.mouseMapping || !mode.visible { - return - } - self.parseKeymap() - if self.actions.count <= 0 { - return - } - self.invalidate() - let lastUse = syncUserDefaults() - if lastUse > Float(Date.now.addingTimeInterval(-86400*14).timeIntervalSinceReferenceDate) { - return + private static let FORBIDDEN: [GCKeyCode] = [ + .leftGUI, + .rightGUI, + .leftAlt, + .rightAlt, + .printScreen + ] + + static func handleEvent(_ keyCode: UInt16, _ pressed: Bool) -> Bool { + let name = KeyCodeNames.virtualCodes[keyCode] ?? "Btn" + guard let handlers = PlayInput.buttonHandlers[name] else { + return false } - Toast.showHint(title: NSLocalizedString("hint.enableKeymapping.title", - tableName: "Playtools", - value: "Keymapping Disabled", comment: ""), - text: [NSLocalizedString("hint.enableKeymapping.content.before", - tableName: "Playtools", - value: "Press", comment: ""), - " option ⌥ ", - NSLocalizedString("hint.enableKeymapping.content.after", - tableName: "Playtools", - value: "to enable keymapping", comment: "")], - timeout: 10, - notification: NSNotification.Name.playtoolsKeymappingWillEnable) - let center = NotificationCenter.default - var token: NSObjectProtocol? - token = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillEnable, - object: nil, queue: OperationQueue.main) { _ in - center.removeObserver(token!) - Toast.showHint(title: NSLocalizedString("hint.disableKeymapping.title", - tableName: "Playtools", - value: "Keymapping Enabled", comment: ""), - text: [NSLocalizedString("hint.disableKeymapping.content.before", - tableName: "Playtools", - value: "Press", comment: ""), - " option ⌥ ", - NSLocalizedString("hint.disableKeymapping.content.after", - tableName: "Playtools", - value: "to disable keymapping", comment: "")], - timeout: 10, - notification: NSNotification.Name.playtoolsKeymappingWillDisable) + var mapped = false + for handler in handlers { + PlayInput.touchQueue.async(qos: .userInteractive, execute: { + handler(pressed) + }) + mapped = true } + return mapped } - func initialize() { - if !PlaySettings.shared.keymapping { - return - } - + public static func initialize() { let centre = NotificationCenter.default let main = OperationQueue.main - - centre.addObserver(forName: NSNotification.Name.GCControllerDidConnect, object: nil, queue: main) { _ in - if EditorController.shared.editorMode { - self.toggleEditor(show: true) - } else { - self.setup() - } - } - parseKeymap() centre.addObserver(forName: UIApplication.keyboardDidHideNotification, object: nil, queue: main) { _ in - PlayInput.keyboardMapped = true + mode.keyboardMapped = true Toucher.writeLog(logMessage: "virtual keyboard did hide") } centre.addObserver(forName: UIApplication.keyboardWillShowNotification, object: nil, queue: main) { _ in - PlayInput.keyboardMapped = false + mode.keyboardMapped = false Toucher.writeLog(logMessage: "virtual keyboard will show") } - centre.addObserver(forName: NSNotification.Name(rawValue: "NSWindowDidBecomeKeyNotification"), object: nil, - queue: main) { _ in - if !mode.visible { - AKInterface.shared!.warpCursor() - } - } - setupHotkeys() - DispatchQueue.main.asyncAfter(deadline: .now() + 5, qos: .utility, execute: initializeToasts) - - AKInterface.shared!.initialize(keyboard: {keycode, pressed, isRepeat in - if !PlayInput.keyboardMapped { + AKInterface.shared!.setupKeyboard(keyboard: {keycode, pressed, isRepeat in + if !mode.keyboardMapped { // explicitly ignore repeated Enter key return isRepeat && keycode == 36 } if isRepeat { return true } - let mapped = self.keyboardHandler(keycode, pressed) + let mapped = PlayKeyboard.handleEvent(keycode, pressed) return mapped - }, mouseMoved: {deltaX, deltaY in - if !PlayInput.keyboardMapped { - return false - } - PlayInput.touchQueue.async(qos: .userInteractive, execute: { - if mode.visible { - PlayMice.shared.handleFakeMouseMoved(deltaX: deltaX, deltaY: deltaY) - } else { - PlayMice.shared.handleMouseMoved(deltaX: deltaX, deltaY: deltaY) - } - }) - return true - }, swapMode: self.swapMode) - PlayMice.shared.initialize() + }, + swapMode: ControlMode.trySwap) } } diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index a32a1a20..477293c7 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -4,7 +4,6 @@ // import Foundation -import GameController public class PlayMice { @@ -12,25 +11,34 @@ public class PlayMice { public static let elementName = "Mouse" public func initialize() { - setupMouseButton(_up: 2, _down: 4) - setupMouseButton(_up: 8, _down: 16) - setupMouseButton(_up: 33554432, _down: 67108864) - setupScrollWheelHandler() + setupLeftButton() + setupMouseButton(right: true) + setupMouseButton(right: false) + AKInterface.shared!.setupMouseMoved(mouseMoved: {deltaX, deltaY in + // this closure's return value only takes effect when any mouse button pressed + if !mode.keyboardMapped { + return false + } + PlayInput.touchQueue.async(qos: .userInteractive, execute: { + self.handleMouseMoved(deltaX: deltaX, deltaY: deltaY) + }) + return true + }) + AKInterface.shared!.setupScrollWheel({deltaX, deltaY in + if let cameraScale = PlayInput.cameraScaleHandler[PlayMice.elementName] { + cameraScale(deltaX, deltaY) + let eventConsumed = !mode.visible + return eventConsumed + } + return false + }) } var fakedMouseTouchPointId: Int? var fakedMousePressed: Bool {fakedMouseTouchPointId != nil} - private var directionPadXValue: Float = 0, - directionPadYValue: Float = 0, - thumbstickCursorControl: [String: (((CGFloat, CGFloat) -> Void)?, CGFloat, CGFloat) -> Void] - = ["Left Thumbstick": ThumbstickCursorControl().update, "Right Thumbstick": ThumbstickCursorControl().update] - public var draggableHandler: [String: (CGFloat, CGFloat) -> Void] = [:], - cameraMoveHandler: [String: (CGFloat, CGFloat) -> Void] = [:], - cameraScaleHandler: [String: (CGFloat, CGFloat) -> Void] = [:], - joystickHandler: [String: (CGFloat, CGFloat) -> Void] = [:] public func mouseMovementMapped() -> Bool { - for handler in [draggableHandler, cameraMoveHandler, joystickHandler] + for handler in [PlayInput.cameraMoveHandler, PlayInput.joystickHandler] where handler[PlayMice.elementName] != nil { return true } @@ -66,136 +74,90 @@ public class PlayMice { return point } - public func setupScrollWheelHandler() { - AKInterface.shared!.setupScrollWheel({deltaX, deltaY in - if let cameraScale = self.cameraScaleHandler[PlayMice.elementName] { - cameraScale(deltaX, deltaY) - let eventConsumed = !mode.visible - return eventConsumed - } - return false - }) - } - - public func handleControllerDirectionPad(_ profile: GCExtendedGamepad, _ dpad: GCControllerDirectionPad) { - let name = dpad.aliases.first! - let xAxis = dpad.xAxis, yAxis = dpad.yAxis - if name == "Direction Pad" { - if (xAxis.value > 0) != (directionPadXValue > 0) { - PlayInput.shared.controllerButtonHandler(profile, dpad.right) - } - if (xAxis.value < 0) != (directionPadXValue < 0) { - PlayInput.shared.controllerButtonHandler(profile, dpad.left) - } - if (yAxis.value > 0) != (directionPadYValue > 0) { - PlayInput.shared.controllerButtonHandler(profile, dpad.up) - } - if (yAxis.value < 0) != (directionPadYValue < 0) { - PlayInput.shared.controllerButtonHandler(profile, dpad.down) - } - directionPadXValue = xAxis.value - directionPadYValue = yAxis.value - return - } - let deltaX = xAxis.value, deltaY = yAxis.value - let cgDx = CGFloat(deltaX) - let cgDy = CGFloat(deltaY) - thumbstickCursorControl[name]!(draggableHandler[name] ?? cameraMoveHandler[name], cgDx * 6, cgDy * 6) - joystickHandler[name]?(cgDx, cgDy) - } - - public func handleFakeMouseMoved(deltaX: CGFloat, deltaY: CGFloat) { - if self.fakedMousePressed { - if let pos = self.cursorPos() { - Toucher.touchcam(point: pos, phase: UITouch.Phase.moved, tid: &fakedMouseTouchPointId) - } - } - } - public func handleMouseMoved(deltaX: CGFloat, deltaY: CGFloat) { let sensy = CGFloat(PlaySettings.shared.sensitivity * 0.6) let cgDx = deltaX * sensy, cgDy = -deltaY * sensy let name = PlayMice.elementName - if let draggableUpdate = self.draggableHandler[name] { + if let draggableUpdate = PlayInput.draggableHandler[name] { draggableUpdate(cgDx, cgDy) - return + } else if mode.visible { + if self.fakedMousePressed { + if let pos = self.cursorPos() { + Toucher.touchcam(point: pos, phase: UITouch.Phase.moved, tid: &fakedMouseTouchPointId) + } + } + } else { + PlayInput.cameraMoveHandler[name]?(cgDx, cgDy) + PlayInput.joystickHandler[name]?(cgDx, cgDy) } - self.cameraMoveHandler[name]?(cgDx, cgDy) - self.joystickHandler[name]?(cgDx, cgDy) } - // TODO: get rid of this shit - let buttonIndex: [Int: Int] = [2: -1, 8: -2, 33554432: -3] - - private func setupMouseButton(_up: Int, _down: Int) { - AKInterface.shared!.setupMouseButton(_up, _down, dontIgnore) - } - - private func dontIgnore(_ actionIndex: Int, _ pressed: Bool) -> Bool { - if !PlayInput.keyboardMapped { - if EditorController.shared.editorMode && actionIndex != 2 && pressed { - EditorController.shared.setKey(buttonIndex[actionIndex]!) + private func setupMouseButton(right: Bool) { + let keyCode = right ? -2 : -3 + guard let keyName = KeyCodeNames.keyCodes[keyCode] else { + Toast.showHint(title: "Failed initializing \(right ? "right" : "other") mouse button input") + return + } + AKInterface.shared!.setupMouseButton(left: false, right: right) {pressed in + if mode.keyboardMapped { // if mapping + if let handlers = PlayInput.buttonHandlers[keyName] { + PlayInput.touchQueue.async(qos: .userInteractive, execute: { + for handler in handlers { + handler(pressed) + } + }) + // if mapped to any button, consumed and dispatch + return false + } + // if not mapped, transpass to app + return true + } else if EditorController.shared.editorMode { // if editor is open, consumed and set button + if pressed { + // asynced to return quickly. this branch contains UI operation so main queue. + // main queue is fine. should not be slower than keyboard + DispatchQueue.main.async(qos: .userInteractive, execute: { + EditorController.shared.setKey(keyCode) + Toucher.writeLog(logMessage: "mouse button editor set") + }) + } + return false + } else { // if typing, transpass event to app + Toucher.writeLog(logMessage: "mouse button pressed? \(pressed)") + return true } - Toucher.writeLog(logMessage: "mouse button pressed? \(pressed)") - return true } - guard let curPos = self.cursorPos() else { return true } - PlayInput.touchQueue.async(qos: .userInteractive, execute: { - if self.fakedMousePressed { - Toucher.touchcam(point: curPos, phase: UITouch.Phase.ended, tid: &self.fakedMouseTouchPointId) - return + } + // using high priority event handlers to prevent lag and stutter in demanding games + // but no free lunch. high priority handlers cannot execute for too long + // exceeding the time limit causes even more lag + private func setupLeftButton() { + AKInterface.shared!.setupMouseButton(left: true, right: false) {pressed in + if !mode.keyboardMapped { + Toucher.writeLog(logMessage: "left button pressed? \(pressed)") + return true } - if mode.visible && pressed { - Toucher.touchcam(point: curPos, - phase: UITouch.Phase.began, - tid: &self.fakedMouseTouchPointId) - } else { - if let handlers = PlayInput.buttonHandlers[KeyCodeNames.keyCodes[self.buttonIndex[actionIndex]!]!] { + guard let curPos = self.cursorPos() else { return true } + PlayInput.touchQueue.async(qos: .userInteractive, execute: { + if self.fakedMousePressed { + Toucher.touchcam(point: curPos, phase: UITouch.Phase.ended, tid: &self.fakedMouseTouchPointId) + return + } + if mode.visible && pressed { + Toucher.touchcam(point: curPos, + phase: UITouch.Phase.began, + tid: &self.fakedMouseTouchPointId) + return + } + if let handlers = PlayInput.buttonHandlers["LMB"] { for handler in handlers { handler(pressed) } + return } - } - }) - return false - } -} - -class ThumbstickCursorControl { - private var thumbstickVelocity: CGVector = CGVector.zero, - thumbstickPolling: Bool = false, - eventHandler: ((CGFloat, CGFloat) -> Void)! - - static private func isVectorSignificant(_ vector: CGVector) -> Bool { - return vector.dx.magnitude + vector.dy.magnitude > 0.2 - } - - public func update(handler: ((CGFloat, CGFloat) -> Void)?, velocityX: CGFloat, velocityY: CGFloat) { - guard let hdlr = handler else { - if thumbstickPolling { - self.thumbstickVelocity.dx = 0 - self.thumbstickVelocity.dy = 0 - } - return - } - self.eventHandler = hdlr - self.thumbstickVelocity.dx = velocityX - self.thumbstickVelocity.dy = velocityY - if !thumbstickPolling { - DispatchQueue.main.async(execute: self.thumbstickPoll) - self.thumbstickPolling = true - } - } - - private func thumbstickPoll() { - if !ThumbstickCursorControl.isVectorSignificant(self.thumbstickVelocity) { - self.thumbstickPolling = false - return + }) + return false } - self.eventHandler(self.thumbstickVelocity.dx, self.thumbstickVelocity.dy) - DispatchQueue.main.asyncAfter( - deadline: DispatchTime.now() + 0.017, execute: self.thumbstickPoll) } } @@ -213,8 +175,8 @@ class CameraAction: Action { swipeMove = SwipeAction() swipeScale1 = SwipeAction() swipeScale2 = SwipeAction() - PlayMice.shared.cameraMoveHandler[key] = self.moveUpdated - PlayMice.shared.cameraScaleHandler[PlayMice.elementName] = {deltaX, deltaY in + PlayInput.cameraMoveHandler[key] = self.moveUpdated + PlayInput.cameraScaleHandler[PlayMice.elementName] = {deltaX, deltaY in PlayInput.touchQueue.async(qos: .userInteractive, execute: { if mode.visible { CameraAction.dragUpdated(deltaX, deltaY) @@ -250,21 +212,12 @@ class CameraAction: Action { } func invalidate() { - PlayMice.shared.cameraMoveHandler.removeValue(forKey: key) - PlayMice.shared.cameraScaleHandler[PlayMice.elementName] = nil + PlayInput.cameraMoveHandler.removeValue(forKey: key) + PlayInput.cameraScaleHandler[PlayMice.elementName] = nil swipeMove.invalidate() swipeScale1.invalidate() swipeScale2.invalidate() } - - func debug() { - var count = 0 - for swipe in [swipeScale1, swipeScale2, swipeMove, CameraAction.swipeDrag] { - count += 1 - guard let id = swipe.getTouchId() else {continue} - Toast.showHint(title: "type:\(count), id:\(id)") - } - } } class SwipeAction: Action { @@ -333,8 +286,4 @@ class SwipeAction: Action { timer.cancel() self.doLiftOff() } - - public func getTouchId() -> Int? { - return id - } } diff --git a/PlayTools/Controls/Toucher.swift b/PlayTools/Controls/Toucher.swift index cc949669..a4dce82d 100644 --- a/PlayTools/Controls/Toucher.swift +++ b/PlayTools/Controls/Toucher.swift @@ -10,7 +10,7 @@ class Toucher { static weak var keyWindow: UIWindow? static weak var keyView: UIView? // For debug only - static var logEnabled = true + static var logEnabled = false static var logFilePath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/toucher.log" static private var logCount = 0 diff --git a/PlayTools/Utils/Toast.swift b/PlayTools/Utils/Toast.swift index 2832a3a5..7133d5a5 100644 --- a/PlayTools/Utils/Toast.swift +++ b/PlayTools/Utils/Toast.swift @@ -112,6 +112,62 @@ class Toast { } } + static func syncUserDefaults() -> Float { + let persistenceKeyname = "playtoolsKeymappingDisabledAt" + let lastUse = UserDefaults.standard.float(forKey: persistenceKeyname) + var thisUse = lastUse + if lastUse < 1 { + thisUse = 2 + } else { + thisUse = Float(Date.timeIntervalSinceReferenceDate) + } + var token2: NSObjectProtocol? + let center = NotificationCenter.default + token2 = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillDisable, + object: nil, queue: OperationQueue.main) { _ in + center.removeObserver(token2!) + UserDefaults.standard.set(thisUse, forKey: persistenceKeyname) + } + return lastUse + } + + public static func initialize() { + let lastUse = syncUserDefaults() + if lastUse > Float(Date.now.addingTimeInterval(-86400*14).timeIntervalSinceReferenceDate) { + return + } + Toast.showHint(title: NSLocalizedString("hint.enableKeymapping.title", + tableName: "Playtools", + value: "Keymapping Disabled", comment: ""), + text: [NSLocalizedString("hint.enableKeymapping.content.before", + tableName: "Playtools", + value: "Press", comment: ""), + " option ⌥ ", + NSLocalizedString("hint.enableKeymapping.content.after", + tableName: "Playtools", + value: "to enable keymapping", comment: "")], + timeout: 10, + notification: NSNotification.Name.playtoolsKeymappingWillEnable) + let center = NotificationCenter.default + var token: NSObjectProtocol? + token = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillEnable, + object: nil, queue: OperationQueue.main) { _ in + center.removeObserver(token!) + Toast.showHint(title: NSLocalizedString("hint.disableKeymapping.title", + tableName: "Playtools", + value: "Keymapping Enabled", comment: ""), + text: [NSLocalizedString("hint.disableKeymapping.content.before", + tableName: "Playtools", + value: "Press", comment: ""), + " option ⌥ ", + NSLocalizedString("hint.disableKeymapping.content.after", + tableName: "Playtools", + value: "to disable keymapping", comment: "")], + timeout: 10, + notification: NSNotification.Name.playtoolsKeymappingWillDisable) + } + } + // swiftlint:disable function_body_length private static func show(message: String, parent: UIView) { diff --git a/Plugin.swift b/Plugin.swift index 291052b9..56bbe84d 100644 --- a/Plugin.swift +++ b/Plugin.swift @@ -23,9 +23,10 @@ public protocol Plugin: NSObjectProtocol { func warpCursor() func unhideCursor() func terminateApplication() - func initialize(keyboard: @escaping(UInt16, Bool, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool, - swapMode: @escaping() -> Bool) - func setupMouseButton(_ _up: Int, _ _down: Int, _ dontIgnore: @escaping(Int, Bool) -> Bool) + func setupKeyboard(keyboard: @escaping(UInt16, Bool, Bool) -> Bool, + swapMode: @escaping() -> Bool) + func setupMouseMoved(mouseMoved: @escaping(CGFloat, CGFloat) -> Bool) + func setupMouseButton(left: Bool, right: Bool, _ dontIgnore: @escaping(Bool) -> Bool) func setupScrollWheel(_ onMoved: @escaping(CGFloat, CGFloat) -> Bool) func urlForApplicationWithBundleIdentifier(_ value: String) -> URL? func setMenuBarVisible(_ value: Bool) From 9ba2949f1b7502d5e6b70fcf3da121ea9541808e Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Sat, 29 Apr 2023 00:09:43 +0800 Subject: [PATCH 50/92] create file PlayController --- PlayTools.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/PlayTools.xcodeproj/project.pbxproj b/PlayTools.xcodeproj/project.pbxproj index e9c1825c..7bdd955b 100644 --- a/PlayTools.xcodeproj/project.pbxproj +++ b/PlayTools.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 6E84A14528D0F94E00BF7495 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA818CBA287ABFD5000BEE9D /* UIKit.framework */; }; 6E84A15028D0F97500BF7495 /* AKInterface.bundle in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6E84A14C28D0F96D00BF7495 /* AKInterface.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 951D8275299D097C00D35B20 /* Playtools.strings in Resources */ = {isa = PBXBuildFile; fileRef = 951D8277299D097C00D35B20 /* Playtools.strings */; }; + 95A553E729F2BBB200E34C26 /* PlayController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95A553E629F2BBB200E34C26 /* PlayController.swift */; }; AA71970B287A44D200623C15 /* PlayLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = AA719702287A44D200623C15 /* PlayLoader.m */; }; AA71970D287A44D200623C15 /* PlaySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719704287A44D200623C15 /* PlaySettings.swift */; }; AA71970E287A44D200623C15 /* PlayLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = AA719705287A44D200623C15 /* PlayLoader.h */; }; @@ -78,6 +79,7 @@ 6E84A14C28D0F96D00BF7495 /* AKInterface.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AKInterface.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; 951D8276299D097C00D35B20 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Playtools.strings; sourceTree = ""; }; 951D8278299D098000D35B20 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Playtools.strings"; sourceTree = ""; }; + 95A553E629F2BBB200E34C26 /* PlayController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayController.swift; sourceTree = ""; }; AA7196D8287A447700623C15 /* PlayTools.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PlayTools.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AA719702287A44D200623C15 /* PlayLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlayLoader.m; sourceTree = ""; }; AA719704287A44D200623C15 /* PlaySettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaySettings.swift; sourceTree = ""; }; @@ -202,6 +204,7 @@ AA719755287A480C00623C15 /* PlayInput.swift */, AA719756287A480C00623C15 /* ControlMode.swift */, AA719757287A480C00623C15 /* MenuController.swift */, + 95A553E629F2BBB200E34C26 /* PlayController.swift */, ); path = Controls; sourceTree = ""; @@ -449,6 +452,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 95A553E729F2BBB200E34C26 /* PlayController.swift in Sources */, AA71975A287A480D00623C15 /* Toucher.swift in Sources */, AA7197A1287A481500623C15 /* CircleMenuLoader.swift in Sources */, 6E76639B28D0FAE700DE4AF9 /* Plugin.swift in Sources */, From 6f6ef3bd315aead37d38d44a7764b6019a1a94dc Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Sat, 29 Apr 2023 03:46:08 +0800 Subject: [PATCH 51/92] fix text mismatch --- PlayTools/Utils/Toast.swift | 20 ++++++++++---------- PlayTools/en.lproj/Playtools.strings | Bin 3000 -> 3348 bytes PlayTools/zh-Hans.lproj/Playtools.strings | Bin 2924 -> 2622 bytes 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/PlayTools/Utils/Toast.swift b/PlayTools/Utils/Toast.swift index 7133d5a5..1e69ee04 100644 --- a/PlayTools/Utils/Toast.swift +++ b/PlayTools/Utils/Toast.swift @@ -136,16 +136,16 @@ class Toast { if lastUse > Float(Date.now.addingTimeInterval(-86400*14).timeIntervalSinceReferenceDate) { return } - Toast.showHint(title: NSLocalizedString("hint.enableKeymapping.title", + Toast.showHint(title: NSLocalizedString("hint.mouseMapping.title", tableName: "Playtools", - value: "Keymapping Disabled", comment: ""), - text: [NSLocalizedString("hint.enableKeymapping.content.before", + value: "Mouse mapping disabled", comment: ""), + text: [NSLocalizedString("hint.mouseMapping.content.before", tableName: "Playtools", value: "Press", comment: ""), " option ⌥ ", - NSLocalizedString("hint.enableKeymapping.content.after", + NSLocalizedString("hint.mouseMapping.content.after", tableName: "Playtools", - value: "to enable keymapping", comment: "")], + value: "to enable mouse mapping", comment: "")], timeout: 10, notification: NSNotification.Name.playtoolsKeymappingWillEnable) let center = NotificationCenter.default @@ -153,16 +153,16 @@ class Toast { token = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillEnable, object: nil, queue: OperationQueue.main) { _ in center.removeObserver(token!) - Toast.showHint(title: NSLocalizedString("hint.disableKeymapping.title", + Toast.showHint(title: NSLocalizedString("hint.showCursor.title", tableName: "Playtools", - value: "Keymapping Enabled", comment: ""), - text: [NSLocalizedString("hint.disableKeymapping.content.before", + value: "Cursor locked", comment: ""), + text: [NSLocalizedString("hint.showCursor.content.before", tableName: "Playtools", value: "Press", comment: ""), " option ⌥ ", - NSLocalizedString("hint.disableKeymapping.content.after", + NSLocalizedString("hint.showCursor.content.after", tableName: "Playtools", - value: "to disable keymapping", comment: "")], + value: "to unlock cursor", comment: "")], timeout: 10, notification: NSNotification.Name.playtoolsKeymappingWillDisable) } diff --git a/PlayTools/en.lproj/Playtools.strings b/PlayTools/en.lproj/Playtools.strings index 1e82629eb58fd99c06b0626d339ec84318dafe43..0311d2a53e281d84fa4d99be96f93d92df923b5b 100644 GIT binary patch literal 3348 zcmd6p!EPEc5QgWRr?5EY01r^*RC21)R*8D+Wg$RNU_;s6MD*d?e!q=tFWLYXAVRx% z?eX}ZnSW;d_s_tt&DmFb;XUTPYQ9&tX6DuA_Q>2LGb7vZ*7}UulF+FW7$J~rZ`WGX=eHCEtAa z-xaoB3TKUHYafk-Dvz;vRKT6u$3?t3J<`O7JAd}dvuAcB-vYZXv>M{*G zgd}wU)e)g{{w;>BSg-t7$g4IZf~H_+WYP%f()Y2TDe@~YQ=LV*EOy0imvEfEh8>Pc z7t_9gbvKgE@>rf1`2!Jr_5Mqo$uT<1B~H_6Lw9ee{#`!p#ue-Jsl2rxQy*3a_KAD9 zo`>;{lJE{Q8Ru_5ZN3H#Y2PKhD@%0iD}8SM-fK-;?&DgdTd=y2Vl$+MRMegY-|FD; z&U&B8IA5!hRH}=YVwWo2jGy?@t_>0YH*C_4X25-YfpoZM=b0Nb_4=wu)LojirjLRx_#pP`6BfgzhAm7x+yCjn*i7*ZHw81g0?vgym_GZX-2@_;fR@dAcY Xh7uq<7c5pfS&%al>aECu#r$3 diff --git a/PlayTools/zh-Hans.lproj/Playtools.strings b/PlayTools/zh-Hans.lproj/Playtools.strings index 393a22385186c07ad2d48215bd8ae8d7df08caa1..11b93063807edc81d941ed4b67adca9ee03171c7 100644 GIT binary patch delta 571 zcmaDOwohb241Y00217nWIfFApDMJxM@#KRnq7(bX&SZdj@-Q#}uCb zI}3gXT9+nGcMepjop>$@o5sm4%o1$9fr{Cj$v_ptXhs9ADVi+LBF^_IHZ*yAGt6Y5 zO72{sTq#2_Ln?#s#6n}R<;Y5S{_BTml=eiIq$;Gb#9(NUpWMbQrtx9|TM$Tke$d|r zhUv)KeUtt+$d`Jg#xUeDfL(@c-{jwn%6uTVfb?pm_e5s|PIh2f5CrmWRaI@7uYX?s zQ$OjTUvV42wu210mb4;>GkM*Vr+#13EK|z@L24j|bNhWxxL7wUx}^*hfFPDZ5a$G4 zKalFleXKM1IFt3Jf@~72pV*%f1{BJhyp2r+F8nF>u75=I#B&jo6}aRkN3gHsQ3y(z z=8(iW0TS#WafQiiI5c=P>-l5Q#p^g#luoDS^xLIbRf3|5oXJwzMYEfD8(%Pc!&yx$wJ&JB6g|jkuZCd7_1q%fDiyY)W>oF literal 2924 zcmd5;%Wi`(5ZpsA%~x1CRXM;1R6R90Rcfm~Zn+R3G)hWPfK=&!JDyllDbUVjgPyG{Btl<=o|)`Way&tAidqaC4}Bx? zWgtU&)BOedBGhsTFlPm;JG?ur9bnZ=+a$1|NBXKfdKjJS)&?snxxqU`o7kGGDOoz! z8_G2>41lChdzu019DCT{Tkq;ow{t|OAE*nQNuf`w#^^dTB@>*OIn}5G^yG%|=R$NE zi@bfD_%Byw`_cYCUN=K7!#-ej@!~S{_G*4H9d#5@SFTUAB2XKsKTRE}8GH*yFEF}< zf&_SHQiA22m7-NC3zd!BSGvsc%`l$p*5Y7EFGh2?3bikiDaxh?t{Ow-GOS0X40M*$ zbLDG|5%Pn+Qq+k?9Td0hAeW9u(skahtfLN8abs-SXR3|-8FAerj#M9WOV}8d*cR>F z3DqrGf_pYHbFUOpTY&(}&xU5&vgJ6lc8bfYN8@}&gcV+?z>Y))s_4@Cd<2><{9 From 12b84a09664be95d3056ad21dfd422bd5e1be452 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Sat, 6 May 2023 13:55:53 +0800 Subject: [PATCH 52/92] fix joystick opposite direction --- PlayTools/Controls/PlayAction.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/PlayTools/Controls/PlayAction.swift b/PlayTools/Controls/PlayAction.swift index 2955bb5a..cc49f5d7 100644 --- a/PlayTools/Controls/PlayAction.swift +++ b/PlayTools/Controls/PlayAction.swift @@ -141,7 +141,7 @@ class JoystickAction: Action { var touch: CGPoint let shift: CGFloat var id: Int? - + private var keyPressed = [Bool](repeating: false, count: 4) init(keys: [Int], center: CGPoint, shift: CGFloat) { self.keys = keys self.center = center @@ -189,17 +189,22 @@ class JoystickAction: Action { } func updateTouch(index: Int, pressed: Bool) { + self.keyPressed[index] = pressed let isPlus = index & 1 != 0 let realShift = isPlus ? shift : -shift if index > 1 { if pressed { touch.x = center.x + realShift + } else if self.keyPressed[index ^ 1] { + touch.x = center.x - realShift } else { touch.x = center.x } } else { if pressed { touch.y = center.y + realShift + } else if self.keyPressed[index ^ 1] { + touch.y = center.y - realShift } else { touch.y = center.y } From 22d0a4418311c9091cdbe79aef120fe1e2264256 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Sun, 7 May 2023 05:21:53 +0800 Subject: [PATCH 53/92] invalidate when disable keyboard mapping --- PlayTools/Controls/ControlMode.swift | 23 +++++++++++++++-------- PlayTools/Controls/PlayInput.swift | 10 +++++----- PlayTools/Controls/PlayMice.swift | 9 ++++++++- PlayTools/Keymap/EditorController.swift | 2 +- PlayTools/Utils/Toast.swift | 8 ++++---- 5 files changed, 33 insertions(+), 19 deletions(-) diff --git a/PlayTools/Controls/ControlMode.swift b/PlayTools/Controls/ControlMode.swift index a30ae3fc..e4b4f5b8 100644 --- a/PlayTools/Controls/ControlMode.swift +++ b/PlayTools/Controls/ControlMode.swift @@ -22,27 +22,34 @@ public class ControlMode { return false } + func setMapping(_ mapped: Bool) { + keyboardMapped = mapped + if mapped { + PlayInput.shared.parseKeymap() + } else { + PlayInput.shared.invalidate() + } + } + func show(_ show: Bool) { if !editor.editorMode { if show { if !visible { - NotificationCenter.default.post(name: NSNotification.Name.playtoolsKeymappingWillDisable, + NotificationCenter.default.post(name: NSNotification.Name.playtoolsCursorWillShow, object: nil, userInfo: [:]) if screen.fullscreen { screen.switchDock(true) } AKInterface.shared!.unhideCursor() -// PlayInput.shared.invalidate() } } else { if visible { - NotificationCenter.default.post(name: NSNotification.Name.playtoolsKeymappingWillEnable, + NotificationCenter.default.post(name: NSNotification.Name.playtoolsCursorWillHide, object: nil, userInfo: [:]) AKInterface.shared!.hideCursor() if screen.fullscreen { screen.switchDock(false) } -// PlayInput.shared.setup() } } Toucher.writeLog(logMessage: "cursor show switched to \(show)") @@ -52,9 +59,9 @@ public class ControlMode { } extension NSNotification.Name { - public static let playtoolsKeymappingWillEnable: NSNotification.Name - = NSNotification.Name("playtools.keymappingWillEnable") + public static let playtoolsCursorWillHide: NSNotification.Name + = NSNotification.Name("playtools.cursorWillHide") - public static let playtoolsKeymappingWillDisable: NSNotification.Name - = NSNotification.Name("playtools.keymappingWillDisable") + public static let playtoolsCursorWillShow: NSNotification.Name + = NSNotification.Name("playtools.cursorWillShow") } diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 79f4c789..e8a1ad4c 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -32,9 +32,9 @@ class PlayInput { } func parseKeymap() { - actions = [] - PlayInput.shouldLockCursor = false + actions = [PlayMice.shared] PlayInput.buttonHandlers.removeAll(keepingCapacity: true) + PlayInput.shouldLockCursor = false for button in keymap.keymapData.buttonModels { actions.append(ButtonAction(data: button)) } @@ -61,7 +61,7 @@ class PlayInput { } public func toggleEditor(show: Bool) { - mode.keyboardMapped = !show + mode.setMapping(!show) Toucher.writeLog(logMessage: "editor opened? \(show)") if show { self.invalidate() @@ -156,11 +156,11 @@ class PlayKeyboard { let centre = NotificationCenter.default let main = OperationQueue.main centre.addObserver(forName: UIApplication.keyboardDidHideNotification, object: nil, queue: main) { _ in - mode.keyboardMapped = true + mode.setMapping(true) Toucher.writeLog(logMessage: "virtual keyboard did hide") } centre.addObserver(forName: UIApplication.keyboardWillShowNotification, object: nil, queue: main) { _ in - mode.keyboardMapped = false + mode.setMapping(false) Toucher.writeLog(logMessage: "virtual keyboard will show") } AKInterface.shared!.setupKeyboard(keyboard: {keycode, pressed, isRepeat in diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index 477293c7..487090db 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -5,7 +5,7 @@ import Foundation -public class PlayMice { +public class PlayMice: Action { public static let shared = PlayMice() public static let elementName = "Mouse" @@ -139,6 +139,7 @@ public class PlayMice { } guard let curPos = self.cursorPos() else { return true } PlayInput.touchQueue.async(qos: .userInteractive, execute: { + // considering cases where cursor becomes hidden while holding left button if self.fakedMousePressed { Toucher.touchcam(point: curPos, phase: UITouch.Phase.ended, tid: &self.fakedMouseTouchPointId) return @@ -149,6 +150,7 @@ public class PlayMice { tid: &self.fakedMouseTouchPointId) return } + // considering cases where cursor becomes visible while holding left button if let handlers = PlayInput.buttonHandlers["LMB"] { for handler in handlers { handler(pressed) @@ -159,6 +161,11 @@ public class PlayMice { return false } } + // For all other actions, this is a destructor. should release held resources. + func invalidate() { + Toucher.touchcam(point: self.cursorPos() ?? CGPoint(x: 10, y: 10), + phase: UITouch.Phase.ended, tid: &self.fakedMouseTouchPointId) + } } class CameraAction: Action { diff --git a/PlayTools/Keymap/EditorController.swift b/PlayTools/Keymap/EditorController.swift index 43af3eaf..3b44ee66 100644 --- a/PlayTools/Keymap/EditorController.swift +++ b/PlayTools/Keymap/EditorController.swift @@ -74,7 +74,7 @@ class EditorController { tableName: "Playtools", value: "Click a button to edit its position or key bind\n" + "Click an empty area to open input menu", comment: "")], - notification: NSNotification.Name.playtoolsKeymappingWillEnable) + notification: NSNotification.Name.playtoolsCursorWillHide) } // Toast.showOver(msg: "\(UIApplication.shared.windows.count)") lock.unlock() diff --git a/PlayTools/Utils/Toast.swift b/PlayTools/Utils/Toast.swift index 1e69ee04..52741855 100644 --- a/PlayTools/Utils/Toast.swift +++ b/PlayTools/Utils/Toast.swift @@ -123,7 +123,7 @@ class Toast { } var token2: NSObjectProtocol? let center = NotificationCenter.default - token2 = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillDisable, + token2 = center.addObserver(forName: NSNotification.Name.playtoolsCursorWillShow, object: nil, queue: OperationQueue.main) { _ in center.removeObserver(token2!) UserDefaults.standard.set(thisUse, forKey: persistenceKeyname) @@ -147,10 +147,10 @@ class Toast { tableName: "Playtools", value: "to enable mouse mapping", comment: "")], timeout: 10, - notification: NSNotification.Name.playtoolsKeymappingWillEnable) + notification: NSNotification.Name.playtoolsCursorWillHide) let center = NotificationCenter.default var token: NSObjectProtocol? - token = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillEnable, + token = center.addObserver(forName: NSNotification.Name.playtoolsCursorWillHide, object: nil, queue: OperationQueue.main) { _ in center.removeObserver(token!) Toast.showHint(title: NSLocalizedString("hint.showCursor.title", @@ -164,7 +164,7 @@ class Toast { tableName: "Playtools", value: "to unlock cursor", comment: "")], timeout: 10, - notification: NSNotification.Name.playtoolsKeymappingWillDisable) + notification: NSNotification.Name.playtoolsCursorWillShow) } } From 45b05d2633ea12c994d604947b7d486481bade39 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Sun, 7 May 2023 11:32:58 +0700 Subject: [PATCH 54/92] fix: make the Toast non-editable --- PlayTools/Utils/Toast.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/PlayTools/Utils/Toast.swift b/PlayTools/Utils/Toast.swift index 1e69ee04..e8910884 100644 --- a/PlayTools/Utils/Toast.swift +++ b/PlayTools/Utils/Toast.swift @@ -83,6 +83,10 @@ class Toast { messageLabel.center.x = parent.center.x messageLabel.center.y = -messageLabel.frame.size.height / 2 + // Disable editing + messageLabel.isEditable = false + messageLabel.isSelectable = false + hintView.append(messageLabel) parent.addSubview(messageLabel) From 6abed8e7ba06795cc3ee4edb0d2d817efb3ef2aa Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Sun, 7 May 2023 14:40:12 +0800 Subject: [PATCH 55/92] show cursor when typing --- PlayTools/Controls/ControlMode.swift | 5 +++-- PlayTools/Controls/PlayInput.swift | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/PlayTools/Controls/ControlMode.swift b/PlayTools/Controls/ControlMode.swift index e4b4f5b8..f690406c 100644 --- a/PlayTools/Controls/ControlMode.swift +++ b/PlayTools/Controls/ControlMode.swift @@ -23,16 +23,17 @@ public class ControlMode { } func setMapping(_ mapped: Bool) { - keyboardMapped = mapped if mapped { PlayInput.shared.parseKeymap() } else { + show(true) PlayInput.shared.invalidate() } + keyboardMapped = mapped } func show(_ show: Bool) { - if !editor.editorMode { + if keyboardMapped { if show { if !visible { NotificationCenter.default.post(name: NSNotification.Name.playtoolsCursorWillShow, diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index e8a1ad4c..d9866252 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -65,7 +65,6 @@ class PlayInput { Toucher.writeLog(logMessage: "editor opened? \(show)") if show { self.invalidate() - mode.show(show) if let keyboard = GCKeyboard.coalesced!.keyboardInput { keyboard.keyChangedHandler = { _, _, keyCode, pressed in PlayKeyboard.handleEditorEvent(keyCode: keyCode, pressed: pressed) From df458a74dea7fe2e5ea4830d2908612a39010ccd Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Sun, 7 May 2023 10:49:48 +0700 Subject: [PATCH 56/92] fix: option to disable auto keymap disabling for buggy games --- PlayTools/Controls/PlayInput.swift | 18 +++++++++++------- PlayTools/Controls/Toucher.swift | 2 +- PlayTools/PlaySettings.swift | 3 +++ PlayTools/Utils/Toast.swift | 4 ++++ 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 79f4c789..6e64f6dc 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -155,13 +155,17 @@ class PlayKeyboard { public static func initialize() { let centre = NotificationCenter.default let main = OperationQueue.main - centre.addObserver(forName: UIApplication.keyboardDidHideNotification, object: nil, queue: main) { _ in - mode.keyboardMapped = true - Toucher.writeLog(logMessage: "virtual keyboard did hide") - } - centre.addObserver(forName: UIApplication.keyboardWillShowNotification, object: nil, queue: main) { _ in - mode.keyboardMapped = false - Toucher.writeLog(logMessage: "virtual keyboard will show") + + // Initialize observers to automatically hide keymappings + if PlaySettings.shared.noKMOnInput { + centre.addObserver(forName: UIApplication.keyboardDidHideNotification, object: nil, queue: main) { _ in + mode.keyboardMapped = true + Toucher.writeLog(logMessage: "virtual keyboard did hide") + } + centre.addObserver(forName: UIApplication.keyboardWillShowNotification, object: nil, queue: main) { _ in + mode.keyboardMapped = false + Toucher.writeLog(logMessage: "virtual keyboard will show") + } } AKInterface.shared!.setupKeyboard(keyboard: {keycode, pressed, isRepeat in if !mode.keyboardMapped { diff --git a/PlayTools/Controls/Toucher.swift b/PlayTools/Controls/Toucher.swift index a4dce82d..cc949669 100644 --- a/PlayTools/Controls/Toucher.swift +++ b/PlayTools/Controls/Toucher.swift @@ -10,7 +10,7 @@ class Toucher { static weak var keyWindow: UIWindow? static weak var keyView: UIView? // For debug only - static var logEnabled = false + static var logEnabled = true static var logFilePath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/toucher.log" static private var logCount = 0 diff --git a/PlayTools/PlaySettings.swift b/PlayTools/PlaySettings.swift index 0fab777e..2f10eb37 100644 --- a/PlayTools/PlaySettings.swift +++ b/PlayTools/PlaySettings.swift @@ -71,6 +71,8 @@ let settings = PlaySettings.shared @objc lazy var customScaler = settingsData.customScaler @objc lazy var rootWorkDir = settingsData.rootWorkDir + + @objc lazy var noKMOnInput = settingsData.noKMOnInput } struct AppSettingsData: Codable { @@ -93,4 +95,5 @@ struct AppSettingsData: Codable { var inverseScreenValues = false var windowFixMethod = 0 var rootWorkDir = true + var noKMOnInput = false } diff --git a/PlayTools/Utils/Toast.swift b/PlayTools/Utils/Toast.swift index 1e69ee04..e8910884 100644 --- a/PlayTools/Utils/Toast.swift +++ b/PlayTools/Utils/Toast.swift @@ -83,6 +83,10 @@ class Toast { messageLabel.center.x = parent.center.x messageLabel.center.y = -messageLabel.frame.size.height / 2 + // Disable editing + messageLabel.isEditable = false + messageLabel.isSelectable = false + hintView.append(messageLabel) parent.addSubview(messageLabel) From 582171dada2d705c112ff58e5a2120a2ea5afbe2 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Sun, 7 May 2023 16:52:25 +0700 Subject: [PATCH 57/92] fix: disable keyboard mapping on editor mode change (thanks Xyct!) --- PlayTools/Controls/ControlMode.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/PlayTools/Controls/ControlMode.swift b/PlayTools/Controls/ControlMode.swift index a30ae3fc..f3af4d5e 100644 --- a/PlayTools/Controls/ControlMode.swift +++ b/PlayTools/Controls/ControlMode.swift @@ -47,6 +47,9 @@ public class ControlMode { } Toucher.writeLog(logMessage: "cursor show switched to \(show)") visible = show + if PlaySettings.shared.noKMOnInput { + keyboardMapped = false + } } } } From 1b039283342b568989c2f8e0ee02ba6a29bbcb60 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Sun, 7 May 2023 16:54:33 +0700 Subject: [PATCH 58/92] fix: disable logging on Toucher --- PlayTools/Controls/Toucher.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlayTools/Controls/Toucher.swift b/PlayTools/Controls/Toucher.swift index cc949669..a4dce82d 100644 --- a/PlayTools/Controls/Toucher.swift +++ b/PlayTools/Controls/Toucher.swift @@ -10,7 +10,7 @@ class Toucher { static weak var keyWindow: UIWindow? static weak var keyView: UIView? // For debug only - static var logEnabled = true + static var logEnabled = false static var logFilePath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/toucher.log" static private var logCount = 0 From e04a277054ad3d38a7bb8339843bdf020a84331f Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 16 May 2023 01:17:15 +0700 Subject: [PATCH 59/92] fix: update after syncing with upstream --- PlayTools/Controls/PlayInput.swift | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index d9866252..d98372a3 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -154,13 +154,15 @@ class PlayKeyboard { public static func initialize() { let centre = NotificationCenter.default let main = OperationQueue.main - centre.addObserver(forName: UIApplication.keyboardDidHideNotification, object: nil, queue: main) { _ in - mode.setMapping(true) - Toucher.writeLog(logMessage: "virtual keyboard did hide") - } - centre.addObserver(forName: UIApplication.keyboardWillShowNotification, object: nil, queue: main) { _ in - mode.setMapping(false) - Toucher.writeLog(logMessage: "virtual keyboard will show") + if PlaySettings.shared.noKMOnInput { + centre.addObserver(forName: UIApplication.keyboardDidHideNotification, object: nil, queue: main) { _ in + mode.setMapping(true) + Toucher.writeLog(logMessage: "virtual keyboard did hide") + } + centre.addObserver(forName: UIApplication.keyboardWillShowNotification, object: nil, queue: main) { _ in + mode.setMapping(false) + Toucher.writeLog(logMessage: "virtual keyboard will show") + } } AKInterface.shared!.setupKeyboard(keyboard: {keycode, pressed, isRepeat in if !mode.keyboardMapped { From 82af019ff275578c6c02070ce65b08d5314e07e0 Mon Sep 17 00:00:00 2001 From: OHaiiBuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 16 May 2023 15:09:51 +0700 Subject: [PATCH 60/92] fix: flipped enable condition for the editor Co-authored-by: Xyct <87l46110@gmail.com> --- PlayTools/Controls/ControlMode.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlayTools/Controls/ControlMode.swift b/PlayTools/Controls/ControlMode.swift index a79338cf..ba3437d4 100644 --- a/PlayTools/Controls/ControlMode.swift +++ b/PlayTools/Controls/ControlMode.swift @@ -55,7 +55,7 @@ public class ControlMode { } Toucher.writeLog(logMessage: "cursor show switched to \(show)") visible = show - if PlaySettings.shared.noKMOnInput { + if !PlaySettings.shared.noKMOnInput { keyboardMapped = false } } From 6ad9218c1373f6a4c2f79e59ee88bf1b05162f47 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Tue, 16 May 2023 22:42:25 +0800 Subject: [PATCH 61/92] fix draggable button associate mouse cursor (#114) --- AKPlugin.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/AKPlugin.swift b/AKPlugin.swift index ee9e72b1..13f27c1b 100644 --- a/AKPlugin.swift +++ b/AKPlugin.swift @@ -38,9 +38,10 @@ class AKPlugin: NSObject, Plugin { } var cmdPressed: Bool = false - + var cursorHideLevel = 0 func hideCursor() { NSCursor.hide() + cursorHideLevel += 1 CGAssociateMouseAndMouseCursorPosition(0) warpCursor() } @@ -54,7 +55,10 @@ class AKPlugin: NSObject, Plugin { func unhideCursor() { NSCursor.unhide() - CGAssociateMouseAndMouseCursorPosition(1) + cursorHideLevel -= 1 + if cursorHideLevel <= 0 { + CGAssociateMouseAndMouseCursorPosition(1) + } } func terminateApplication() { From 20c8392c3d050f3168a3e049b92dd039a276685f Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Thu, 18 May 2023 02:54:00 +0800 Subject: [PATCH 62/92] make noKMOnInput do the thing --- PlayTools/Controls/ControlMode.swift | 17 ++++++++++++++--- PlayTools/Controls/PlayInput.swift | 18 ++++++++++++++++-- PlayTools/Controls/PlayMice.swift | 4 ++++ 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/PlayTools/Controls/ControlMode.swift b/PlayTools/Controls/ControlMode.swift index ba3437d4..f99ed3c1 100644 --- a/PlayTools/Controls/ControlMode.swift +++ b/PlayTools/Controls/ControlMode.swift @@ -14,6 +14,7 @@ public class ControlMode { public var keyboardMapped = true public static func trySwap() -> Bool { + // this function is called from `AKPlugin` when `option` is pressed. if PlayInput.shouldLockCursor { mode.show(!mode.visible) return true @@ -24,16 +25,24 @@ public class ControlMode { func setMapping(_ mapped: Bool) { if mapped { + // `parseKeymap` and `invalidate` roughly do the opposite thing PlayInput.shared.parseKeymap() } else { - show(true) + // to avoid infinite recursion + if !visible { + show(true) + } PlayInput.shared.invalidate() } keyboardMapped = mapped } func show(_ show: Bool) { - if keyboardMapped { + // special cases where function of `option` should be temparorily disabled. + // if the new auto keymapping feature is enabled, it could cause problems to switch + // cursor show state while typing. + if (!PlaySettings.shared.noKMOnInput && !editor.editorMode) + || (PlaySettings.shared.noKMOnInput && keyboardMapped) { if show { if !visible { NotificationCenter.default.post(name: NSNotification.Name.playtoolsCursorWillShow, @@ -56,7 +65,9 @@ public class ControlMode { Toucher.writeLog(logMessage: "cursor show switched to \(show)") visible = show if !PlaySettings.shared.noKMOnInput { - keyboardMapped = false + // we want to set keymapping as the reverse of curosr show status, not always false. + // as well as do some logic along with it + setMapping(!show) } } } diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index d98372a3..754f2b51 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -16,12 +16,14 @@ class PlayInput { joystickHandler: [String: (CGFloat, CGFloat) -> Void] = [:] func invalidate() { + // this is called whenever keymapping disabled, to release all mapping resource for action in self.actions { action.invalidate() } } static public func registerButton(key: String, handler: @escaping (Bool) -> Void) { + // this function is called when setting up `button` type of mapping if "LMB" == key { PlayInput.shouldLockCursor = true } @@ -34,7 +36,13 @@ class PlayInput { func parseKeymap() { actions = [PlayMice.shared] PlayInput.buttonHandlers.removeAll(keepingCapacity: true) - PlayInput.shouldLockCursor = false + // `shouldLockCursor` is used to disable `option` toggle when there is no mouse mapping + // but in the case this new feature disabled, `option` should always function. + // this variable is initilized here to be checked for mouse mapping later. + // intialize it to the reverse of the new feature's enable state makes + // it always true if the new feature is disabled, as it won't be set false in + // any case anywhere else in this case. + PlayInput.shouldLockCursor = !PlaySettings.shared.noKMOnInput for button in keymap.keymapData.buttonModels { actions.append(ButtonAction(data: button)) } @@ -65,6 +73,8 @@ class PlayInput { Toucher.writeLog(logMessage: "editor opened? \(show)") if show { self.invalidate() + // there is no special reason to use GC API for editor, instead of NSEvents. + // just voider did this and I'm not changing it yet. if let keyboard = GCKeyboard.coalesced!.keyboardInput { keyboard.keyChangedHandler = { _, _, keyCode, pressed in PlayKeyboard.handleEditorEvent(keyCode: keyCode, pressed: pressed) @@ -163,6 +173,9 @@ class PlayKeyboard { mode.setMapping(false) Toucher.writeLog(logMessage: "virtual keyboard will show") } + } else { + // we want to initialize keymapping to false + mode.setMapping(false) } AKInterface.shared!.setupKeyboard(keyboard: {keycode, pressed, isRepeat in if !mode.keyboardMapped { @@ -174,7 +187,8 @@ class PlayKeyboard { } let mapped = PlayKeyboard.handleEvent(keycode, pressed) return mapped - }, + }, // passing the function to be called when `option` pressed. + // return `true` meaning this key press is consumed, `false` dispatching it to the App swapMode: ControlMode.trySwap) } } diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index 487090db..d56c9a19 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -38,6 +38,7 @@ public class PlayMice: Action { var fakedMousePressed: Bool {fakedMouseTouchPointId != nil} public func mouseMovementMapped() -> Bool { + // this is called from `parseKeymap` to set `shouldLockCursor`'s value for handler in [PlayInput.cameraMoveHandler, PlayInput.joystickHandler] where handler[PlayMice.elementName] != nil { return true @@ -220,6 +221,9 @@ class CameraAction: Action { func invalidate() { PlayInput.cameraMoveHandler.removeValue(forKey: key) + // when noKMOnInput is false, swipe/pan gesture handler would be invalidated when keymapping disabled. + // as it's just a temporary toggle, not fixing it. + // but should remove that toggle as long as new feature considered stable. PlayInput.cameraScaleHandler[PlayMice.elementName] = nil swipeMove.invalidate() swipeScale1.invalidate() From b50f238b243260ab86ffbb8784e8e88926f5e187 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Wed, 24 May 2023 22:16:50 +0700 Subject: [PATCH 63/92] feat: (attempt no. 2) allow PlayChain to handle attrs --- PlayTools/MysticRunes/PlayedApple.swift | 66 +++++++++++++++++++++---- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/PlayTools/MysticRunes/PlayedApple.swift b/PlayTools/MysticRunes/PlayedApple.swift index 34acf2b5..71aaf291 100644 --- a/PlayTools/MysticRunes/PlayedApple.swift +++ b/PlayTools/MysticRunes/PlayedApple.swift @@ -37,6 +37,9 @@ public class PlayKeychain: NSObject { private static func keychainPath(_ attributes: NSDictionary) -> URL { let keychainFolder = getKeychainDirectory() + if attributes["r_Ref"] as? Int == 1 { + attributes.setValue("keys", forKey: "class") + } // Generate a key path based on the key attributes let accountName = attributes[kSecAttrAccount as String] as? String ?? "" let serviceName = attributes[kSecAttrService as String] as? String ?? "" @@ -56,10 +59,10 @@ public class PlayKeychain: NSObject { @objc static public func add(_ attributes: NSDictionary, result: UnsafeMutablePointer?) -> OSStatus { let keychainPath = keychainPath(attributes) // Check if the keychain file already exists - if FileManager.default.fileExists(atPath: keychainPath.path) { - debugLogger("Keychain file already exists") - return errSecDuplicateItem - } + // if FileManager.default.fileExists(atPath: keychainPath.path) { + // debugLogger("Keychain file already exists") + // return errSecDuplicateItem + // } // Write the dictionary to the keychain file do { try attributes.write(to: keychainPath) @@ -131,16 +134,61 @@ public class PlayKeychain: NSObject { // Read the dictionary from the keychain file let keychainDict = NSDictionary(contentsOf: keychainPath) // Check the `r_Attributes` key. If it is set to 1 in the query - // DROP, NOT IMPLEMENTED let classType = query[kSecClass as String] as? String ?? "" - if query["r_Attributes"] as? Int == 1 { - return errSecItemNotFound - } + // If the keychain file doesn't exist, return errSecItemNotFound if keychainDict == nil { debugLogger("Keychain file not found at \(keychainPath)") return errSecItemNotFound } + + if query["r_Attributes"] as? Int == 1 { + // if the keychainDict is nil, we need to return errSecItemNotFound + if keychainDict == nil { + debugLogger("Keychain file not found at \(keychainPath)") + return errSecItemNotFound + } + + // Create a dummy dictionary and return it + let dummyDict = NSMutableDictionary() + dummyDict.setValue(classType, forKey: "class") + dummyDict.setValue(query[kSecAttrAccount as String], forKey: "acct") + dummyDict.setValue(query[kSecAttrService as String], forKey: "svce") + dummyDict.setValue(query[kSecAttrGeneric as String], forKey: "gena") + result?.pointee = dummyDict + return errSecSuccess + } + + // Check for r_Ref + if query["r_Ref"] as? Int == 1 { + // Return the data on v_PersistentRef or v_Data if they exist + var key: CFTypeRef? + if let vData = keychainDict!["v_Data"] { + NSLog("found v_Data") + debugLogger("Read keychain file from \(keychainPath)") + key = vData as CFTypeRef + } + if let vPersistentRef = keychainDict!["v_PersistentRef"] { + NSLog("found persistent ref") + debugLogger("Read keychain file from \(keychainPath)") + key = vPersistentRef as CFTypeRef + } + + if key == nil { + debugLogger("Keychain file not found at \(keychainPath)") + return errSecItemNotFound + } + + let dummyKeyAttrs = [ + kSecAttrKeyType: keychainDict?["type"] ?? kSecAttrKeyTypeRSA, + kSecAttrKeyClass: keychainDict!["kcls"] ?? kSecAttrKeyClassPublic + ] as CFDictionary + + let secKey = SecKeyCreateWithData(key as! CFData, dummyKeyAttrs, nil) // swiftlint:disable:this force_cast + result?.pointee = secKey + return errSecSuccess + } + // Return v_Data if it exists if let vData = keychainDict!["v_Data"] { debugLogger("Read keychain file from \(keychainPath)") @@ -164,4 +212,4 @@ public class PlayKeychain: NSObject { return errSecItemNotFound } -} \ No newline at end of file +} From 067ac14f9d8b877dfa2ab53ac2c4b26cf205cd03 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Mon, 29 May 2023 12:27:47 +0700 Subject: [PATCH 64/92] b haxx --- PlayTools/MysticRunes/PlayedApple.swift | 49 +++++++++++++++++++++---- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/PlayTools/MysticRunes/PlayedApple.swift b/PlayTools/MysticRunes/PlayedApple.swift index 71aaf291..80f9eae7 100644 --- a/PlayTools/MysticRunes/PlayedApple.swift +++ b/PlayTools/MysticRunes/PlayedApple.swift @@ -35,7 +35,7 @@ public class PlayKeychain: NSObject { return keychainFolder } - private static func keychainPath(_ attributes: NSDictionary) -> URL { + private static func getKeychainPath(_ attributes: NSDictionary) -> URL { let keychainFolder = getKeychainDirectory() if attributes["r_Ref"] as? Int == 1 { attributes.setValue("keys", forKey: "class") @@ -48,6 +48,27 @@ public class PlayKeychain: NSObject { .appendingPathComponent("\(serviceName)-\(accountName)-\(classType).plist") } + private static func findSimilarKeys(_ attributes: NSDictionary) -> URL? { + // Things we can fuzz: accountName + + let keychainFolder = getKeychainDirectory() + let serviceName = attributes[kSecAttrService as String] as? String ?? "" + let classType = attributes[kSecClass as String] as? String ?? "" + + let everyKeys = try? FileManager.default.contentsOfDirectory(at: keychainFolder!, + includingPropertiesForKeys: nil, + options: .skipsHiddenFiles) + let searchRegex = try? NSRegularExpression(pattern: "\(serviceName)-.*-\(classType).plist", + options: .caseInsensitive) + + for key in everyKeys! where searchRegex!.matches(in: key.path, + options: [], + range: NSRange(location: 0, length: key.path.count)).count > 0 { + return keychainFolder!.appendingPathComponent(key.lastPathComponent) + } + return nil + } + @objc public static func debugLogger(_ logContent: String) { if PlaySettings.shared.settingsData.playChainDebugging { NSLog("PC-DEBUG: \(logContent)") @@ -57,7 +78,7 @@ public class PlayKeychain: NSObject { // Store the entire dictionary as a plist // SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result) @objc static public func add(_ attributes: NSDictionary, result: UnsafeMutablePointer?) -> OSStatus { - let keychainPath = keychainPath(attributes) + let keychainPath = getKeychainPath(attributes) // Check if the keychain file already exists // if FileManager.default.fileExists(atPath: keychainPath.path) { // debugLogger("Keychain file already exists") @@ -81,7 +102,7 @@ public class PlayKeychain: NSObject { // SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate) @objc static public func update(_ query: NSDictionary, attributesToUpdate: NSDictionary) -> OSStatus { // Get the path to the keychain file - let keychainPath = keychainPath(query) + let keychainPath = getKeychainPath(query) // Read the dictionary from the keychain file let keychainDict = NSDictionary(contentsOf: keychainPath) debugLogger("Read keychain file from \(keychainPath)") @@ -114,7 +135,7 @@ public class PlayKeychain: NSObject { // SecItemDelete(CFDictionaryRef query) @objc static public func delete(_ query: NSDictionary) -> OSStatus { // Get the path to the keychain file - let keychainPath = keychainPath(query) + let keychainPath = getKeychainPath(query) // Delete the keychain file do { try FileManager.default.removeItem(at: keychainPath) @@ -130,7 +151,18 @@ public class PlayKeychain: NSObject { @objc static public func copyMatching(_ query: NSDictionary, result: UnsafeMutablePointer?) -> OSStatus { // Get the path to the keychain file - let keychainPath = keychainPath(query) + var keychainPath = getKeychainPath(query) + // If the keychain file doesn't exist, attempt to find a similar key + if !FileManager.default.fileExists(atPath: keychainPath.path) { + if let similarKey = findSimilarKeys(query) { + NSLog("Found similar key at \(similarKey)") + keychainPath = similarKey + } else { + debugLogger("Keychain file not found at \(keychainPath)") + return errSecItemNotFound + } + } + // Read the dictionary from the keychain file let keychainDict = NSDictionary(contentsOf: keychainPath) // Check the `r_Attributes` key. If it is set to 1 in the query @@ -143,6 +175,7 @@ public class PlayKeychain: NSObject { } if query["r_Attributes"] as? Int == 1 { + return errSecItemNotFound // if the keychainDict is nil, we need to return errSecItemNotFound if keychainDict == nil { debugLogger("Keychain file not found at \(keychainPath)") @@ -152,9 +185,9 @@ public class PlayKeychain: NSObject { // Create a dummy dictionary and return it let dummyDict = NSMutableDictionary() dummyDict.setValue(classType, forKey: "class") - dummyDict.setValue(query[kSecAttrAccount as String], forKey: "acct") - dummyDict.setValue(query[kSecAttrService as String], forKey: "svce") - dummyDict.setValue(query[kSecAttrGeneric as String], forKey: "gena") + dummyDict.setValue(keychainDict![kSecAttrAccount as String], forKey: "acct") + dummyDict.setValue(keychainDict![kSecAttrService as String], forKey: "svce") + dummyDict.setValue(keychainDict![kSecAttrGeneric as String], forKey: "gena") result?.pointee = dummyDict return errSecSuccess } From 3487f5cb07c1322a5e7a85fbb503cb3f255b975d Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 30 May 2023 15:50:30 +0700 Subject: [PATCH 65/92] b haxx --- .../Controls/PTFakeTouch/NSObject+Swizzle.m | 9 +++++++++ PlayTools/MysticRunes/PlayShadow.m | 3 +++ PlayTools/MysticRunes/PlayedApple.swift | 16 ++++++++-------- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m index ca66f114..4894d488 100644 --- a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m +++ b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m @@ -116,6 +116,12 @@ - (void) hook_setCurrentSubscription:(VSSubscription *)currentSubscription { // do nothing } +// Hook for UIUserInterfaceIdiom + +- (long long) hook_userInterfaceIdiom { + return UIUserInterfaceIdiomPad; +} + bool menuWasCreated = false; - (id) initWithRootMenuHook:(id)rootMenu { self = [self initWithRootMenuHook:rootMenu]; @@ -215,6 +221,9 @@ + (void)load { [objc_getClass("_UIMenuBuilder") swizzleInstanceMethod:sel_getUid("initWithRootMenu:") withMethod:@selector(initWithRootMenuHook:)]; [objc_getClass("IOSViewController") swizzleInstanceMethod:@selector(prefersPointerLocked) withMethod:@selector(hook_prefersPointerLocked)]; + // Set idiom to iPad + [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(userInterfaceIdiom) withMethod:@selector(hook_userInterfaceIdiom)]; + [objc_getClass("UITraitCollection") swizzleInstanceMethod:@selector(userInterfaceIdiom) withMethod:@selector(hook_userInterfaceIdiom)]; [objc_getClass("VSSubscriptionRegistrationCenter") swizzleInstanceMethod:@selector(setCurrentSubscription:) withMethod:@selector(hook_setCurrentSubscription:)]; } diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index edf61ccb..4aa68f82 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -165,6 +165,9 @@ + (void) load { // Swizzle ATTrackingManager [objc_getClass("ATTrackingManager") swizzleClassMethod:@selector(requestTrackingAuthorizationWithCompletionHandler:) withMethod:@selector(pm_return_2_with_completion_handler:)]; [objc_getClass("ATTrackingManager") swizzleClassMethod:@selector(trackingAuthorizationStatus) withMethod:@selector(pm_return_2)]; + + // canResizeToFitContent + [objc_getClass("UIWindow") swizzleInstanceMethod:@selector(canResizeToFitContent) withMethod:@selector(pm_return_true)]; } + (void) loadJailbreakBypass { diff --git a/PlayTools/MysticRunes/PlayedApple.swift b/PlayTools/MysticRunes/PlayedApple.swift index 80f9eae7..0e8bbf9a 100644 --- a/PlayTools/MysticRunes/PlayedApple.swift +++ b/PlayTools/MysticRunes/PlayedApple.swift @@ -154,13 +154,14 @@ public class PlayKeychain: NSObject { var keychainPath = getKeychainPath(query) // If the keychain file doesn't exist, attempt to find a similar key if !FileManager.default.fileExists(atPath: keychainPath.path) { - if let similarKey = findSimilarKeys(query) { - NSLog("Found similar key at \(similarKey)") - keychainPath = similarKey - } else { - debugLogger("Keychain file not found at \(keychainPath)") - return errSecItemNotFound - } + return errSecItemNotFound + // if let similarKey = findSimilarKeys(query) { + // NSLog("Found similar key at \(similarKey)") + // keychainPath = similarKey + // } else { + // debugLogger("Keychain file not found at \(keychainPath)") + // return errSecItemNotFound + // } } // Read the dictionary from the keychain file @@ -175,7 +176,6 @@ public class PlayKeychain: NSObject { } if query["r_Attributes"] as? Int == 1 { - return errSecItemNotFound // if the keychainDict is nil, we need to return errSecItemNotFound if keychainDict == nil { debugLogger("Keychain file not found at \(keychainPath)") From 091d380f2b74780be4f5f4854be1e57e1a2e27e5 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Thu, 20 Jul 2023 14:29:19 +0700 Subject: [PATCH 66/92] b haxx --- PlayTools/PlayCover.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlayTools/PlayCover.swift b/PlayTools/PlayCover.swift index b8c5fe35..4c136966 100644 --- a/PlayTools/PlayCover.swift +++ b/PlayTools/PlayCover.swift @@ -12,7 +12,7 @@ public class PlayCover: NSObject { var menuController: MenuController? @objc static public func launch() { - quitWhenClose() + // quitWhenClose() AKInterface.initialize() PlayInput.shared.initialize() DiscordIPC.shared.initialize() From c4f5ef0f08fe963d7763e01657c3ae7b74a06537 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 25 Jul 2023 22:08:30 +0700 Subject: [PATCH 67/92] fix: no it is not really an iPad interface wise --- PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m | 10 +++++----- PlayTools/MysticRunes/PlayShadow.m | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m index 4894d488..df626f3d 100644 --- a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m +++ b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m @@ -118,9 +118,9 @@ - (void) hook_setCurrentSubscription:(VSSubscription *)currentSubscription { // Hook for UIUserInterfaceIdiom -- (long long) hook_userInterfaceIdiom { - return UIUserInterfaceIdiomPad; -} +// - (long long) hook_userInterfaceIdiom { +// return UIUserInterfaceIdiomPad; +// } bool menuWasCreated = false; - (id) initWithRootMenuHook:(id)rootMenu { @@ -222,8 +222,8 @@ + (void)load { [objc_getClass("_UIMenuBuilder") swizzleInstanceMethod:sel_getUid("initWithRootMenu:") withMethod:@selector(initWithRootMenuHook:)]; [objc_getClass("IOSViewController") swizzleInstanceMethod:@selector(prefersPointerLocked) withMethod:@selector(hook_prefersPointerLocked)]; // Set idiom to iPad - [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(userInterfaceIdiom) withMethod:@selector(hook_userInterfaceIdiom)]; - [objc_getClass("UITraitCollection") swizzleInstanceMethod:@selector(userInterfaceIdiom) withMethod:@selector(hook_userInterfaceIdiom)]; + // [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(userInterfaceIdiom) withMethod:@selector(hook_userInterfaceIdiom)]; + // [objc_getClass("UITraitCollection") swizzleInstanceMethod:@selector(userInterfaceIdiom) withMethod:@selector(hook_userInterfaceIdiom)]; [objc_getClass("VSSubscriptionRegistrationCenter") swizzleInstanceMethod:@selector(setCurrentSubscription:) withMethod:@selector(hook_setCurrentSubscription:)]; } diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index 4aa68f82..7ecc9572 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -167,7 +167,7 @@ + (void) load { [objc_getClass("ATTrackingManager") swizzleClassMethod:@selector(trackingAuthorizationStatus) withMethod:@selector(pm_return_2)]; // canResizeToFitContent - [objc_getClass("UIWindow") swizzleInstanceMethod:@selector(canResizeToFitContent) withMethod:@selector(pm_return_true)]; + // [objc_getClass("UIWindow") swizzleInstanceMethod:@selector(canResizeToFitContent) withMethod:@selector(pm_return_true)]; } + (void) loadJailbreakBypass { From 1b4457f99e49403d1a950b712c87c52693a1cfe2 Mon Sep 17 00:00:00 2001 From: OHaiiBuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Mon, 4 Sep 2023 15:33:22 +0700 Subject: [PATCH 68/92] fix: quitWhenClose() --- PlayTools/PlayCover.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlayTools/PlayCover.swift b/PlayTools/PlayCover.swift index 4c136966..b8c5fe35 100644 --- a/PlayTools/PlayCover.swift +++ b/PlayTools/PlayCover.swift @@ -12,7 +12,7 @@ public class PlayCover: NSObject { var menuController: MenuController? @objc static public func launch() { - // quitWhenClose() + quitWhenClose() AKInterface.initialize() PlayInput.shared.initialize() DiscordIPC.shared.initialize() From 83fd0203054e302cf756c67c16901c13dde371a3 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Tue, 19 Sep 2023 06:41:22 +0800 Subject: [PATCH 69/92] Architecture Refactor --- AKPlugin.swift | 16 +- PlayTools.xcodeproj/project.pbxproj | 164 +++++++++- PlayTools/Controls/ActionDispatcher.swift | 164 ++++++++++ .../{ => Backend/Action}/PlayAction.swift | 193 ++++++++++- .../Controls/{ => Backend}/Toucher.swift | 0 PlayTools/Controls/ControlMode.swift | 82 ----- PlayTools/Controls/Frontend/ControlMode.swift | 133 ++++++++ .../Controller/ControllerEventAdapter.swift | 15 + .../EditorControllerEventAdapter.swift | 36 +++ .../TouchscreenControllerEventAdapter.swift | 99 ++++++ .../TransparentControllerEventAdapter.swift | 27 ++ .../Frontend/EventAdapter/EventAdapter.swift | 12 + .../Frontend/EventAdapter/EventAdapters.swift | 65 ++++ .../EditorKeyboardEventAdapter.swift | 38 +++ .../TouchscreenKeyboardEventAdapter.swift | 24 ++ .../TransparentKeyboardEventAdapter.swift | 18 ++ .../Keyboard/KeyboardEventAdapter.swift | 14 + .../CameraControlMouseEventAdapter.swift | 40 +++ .../Instances/EditorMouseEventAdapter.swift | 56 ++++ .../TouchscreenMouseEventAdapter.swift | 85 +++++ .../TransparentMouseEventAdapter.swift | 34 ++ .../Mouse/MouseEventAdapter.swift | 19 ++ .../Controls/Frontend/ModeAutomaton.swift | 60 ++++ PlayTools/Controls/MenuController.swift | 2 +- PlayTools/Controls/PlayController.swift | 121 ------- PlayTools/Controls/PlayInput.swift | 174 +--------- PlayTools/Controls/PlayMice.swift | 300 ------------------ PlayTools/Keymap/EditorController.swift | 2 - PlayTools/Keymap/KeyCodeNames.swift | 89 ++++++ Plugin.swift | 4 +- 30 files changed, 1381 insertions(+), 705 deletions(-) create mode 100644 PlayTools/Controls/ActionDispatcher.swift rename PlayTools/Controls/{ => Backend/Action}/PlayAction.swift (51%) rename PlayTools/Controls/{ => Backend}/Toucher.swift (100%) delete mode 100644 PlayTools/Controls/ControlMode.swift create mode 100644 PlayTools/Controls/Frontend/ControlMode.swift create mode 100644 PlayTools/Controls/Frontend/EventAdapter/Controller/ControllerEventAdapter.swift create mode 100644 PlayTools/Controls/Frontend/EventAdapter/Controller/Instances/EditorControllerEventAdapter.swift create mode 100644 PlayTools/Controls/Frontend/EventAdapter/Controller/Instances/TouchscreenControllerEventAdapter.swift create mode 100644 PlayTools/Controls/Frontend/EventAdapter/Controller/Instances/TransparentControllerEventAdapter.swift create mode 100644 PlayTools/Controls/Frontend/EventAdapter/EventAdapter.swift create mode 100644 PlayTools/Controls/Frontend/EventAdapter/EventAdapters.swift create mode 100644 PlayTools/Controls/Frontend/EventAdapter/Keyboard/Instances/EditorKeyboardEventAdapter.swift create mode 100644 PlayTools/Controls/Frontend/EventAdapter/Keyboard/Instances/TouchscreenKeyboardEventAdapter.swift create mode 100644 PlayTools/Controls/Frontend/EventAdapter/Keyboard/Instances/TransparentKeyboardEventAdapter.swift create mode 100644 PlayTools/Controls/Frontend/EventAdapter/Keyboard/KeyboardEventAdapter.swift create mode 100644 PlayTools/Controls/Frontend/EventAdapter/Mouse/Instances/CameraControlMouseEventAdapter.swift create mode 100644 PlayTools/Controls/Frontend/EventAdapter/Mouse/Instances/EditorMouseEventAdapter.swift create mode 100644 PlayTools/Controls/Frontend/EventAdapter/Mouse/Instances/TouchscreenMouseEventAdapter.swift create mode 100644 PlayTools/Controls/Frontend/EventAdapter/Mouse/Instances/TransparentMouseEventAdapter.swift create mode 100644 PlayTools/Controls/Frontend/EventAdapter/Mouse/MouseEventAdapter.swift create mode 100644 PlayTools/Controls/Frontend/ModeAutomaton.swift delete mode 100644 PlayTools/Controls/PlayController.swift delete mode 100644 PlayTools/Controls/PlayMice.swift diff --git a/AKPlugin.swift b/AKPlugin.swift index ee9e72b1..934a2eff 100644 --- a/AKPlugin.swift +++ b/AKPlugin.swift @@ -114,7 +114,7 @@ class AKPlugin: NSObject, Plugin { }) } - func setupMouseMoved(mouseMoved: @escaping(CGFloat, CGFloat) -> Bool) { + func setupMouseMoved(_ mouseMoved: @escaping(CGFloat, CGFloat) -> Bool) { let mask: NSEvent.EventTypeMask = [.leftMouseDragged, .otherMouseDragged, .rightMouseDragged] NSEvent.addLocalMonitorForEvents(matching: mask, handler: { event in let consumed = mouseMoved(event.deltaX, event.deltaY) @@ -130,7 +130,7 @@ class AKPlugin: NSObject, Plugin { }) } - func setupMouseButton(left: Bool, right: Bool, _ dontIgnore: @escaping(Bool) -> Bool) { + func setupMouseButton(left: Bool, right: Bool, _ consumed: @escaping(Int, Bool) -> Bool) { let downType: NSEvent.EventTypeMask = left ? .leftMouseDown : right ? .rightMouseDown : .otherMouseDown let upType: NSEvent.EventTypeMask = left ? .leftMouseUp : right ? .rightMouseUp : .otherMouseUp NSEvent.addLocalMonitorForEvents(matching: downType, handler: { event in @@ -138,16 +138,16 @@ class AKPlugin: NSObject, Plugin { if event.window != NSApplication.shared.windows.first! { return event } - if dontIgnore(true) { - return event + if consumed(event.buttonNumber, true) { + return nil } - return nil + return event }) NSEvent.addLocalMonitorForEvents(matching: upType, handler: { event in - if dontIgnore(false) { - return event + if consumed(event.buttonNumber, false) { + return nil } - return nil + return event }) } diff --git a/PlayTools.xcodeproj/project.pbxproj b/PlayTools.xcodeproj/project.pbxproj index 7bdd955b..1279f4c8 100644 --- a/PlayTools.xcodeproj/project.pbxproj +++ b/PlayTools.xcodeproj/project.pbxproj @@ -15,12 +15,27 @@ 6E84A14528D0F94E00BF7495 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA818CBA287ABFD5000BEE9D /* UIKit.framework */; }; 6E84A15028D0F97500BF7495 /* AKInterface.bundle in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6E84A14C28D0F96D00BF7495 /* AKInterface.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 951D8275299D097C00D35B20 /* Playtools.strings in Resources */ = {isa = PBXBuildFile; fileRef = 951D8277299D097C00D35B20 /* Playtools.strings */; }; - 95A553E729F2BBB200E34C26 /* PlayController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95A553E629F2BBB200E34C26 /* PlayController.swift */; }; + 9562D1512AB484C7002C329D /* EventAdapters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9562D1502AB484C7002C329D /* EventAdapters.swift */; }; + 9562D1532AB484FD002C329D /* EventAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9562D1522AB484FD002C329D /* EventAdapter.swift */; }; + 9562D1582AB4FB9B002C329D /* TouchscreenKeyboardEventAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9562D1572AB4FB9B002C329D /* TouchscreenKeyboardEventAdapter.swift */; }; + 9562D15A2AB4FBBB002C329D /* TransparentKeyboardEventAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9562D1592AB4FBBB002C329D /* TransparentKeyboardEventAdapter.swift */; }; + 9562D15C2AB4FC79002C329D /* TransparentMouseEventAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9562D15B2AB4FC79002C329D /* TransparentMouseEventAdapter.swift */; }; + 9562D15E2AB4FCB8002C329D /* TouchscreenControllerEventAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9562D15D2AB4FCB8002C329D /* TouchscreenControllerEventAdapter.swift */; }; + 9562D1602AB4FD46002C329D /* TransparentControllerEventAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9562D15F2AB4FD46002C329D /* TransparentControllerEventAdapter.swift */; }; + 9562D1622AB4FE73002C329D /* CameraControlMouseEventAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9562D1612AB4FE73002C329D /* CameraControlMouseEventAdapter.swift */; }; + 9562D1642AB4FEB4002C329D /* TouchscreenMouseEventAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9562D1632AB4FEB4002C329D /* TouchscreenMouseEventAdapter.swift */; }; + 9562D1662AB5049B002C329D /* KeyboardEventAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9562D1652AB5049B002C329D /* KeyboardEventAdapter.swift */; }; + 9562D16B2AB505AD002C329D /* MouseEventAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9562D16A2AB505AD002C329D /* MouseEventAdapter.swift */; }; + 9562D16D2AB505D4002C329D /* ControllerEventAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9562D16C2AB505D4002C329D /* ControllerEventAdapter.swift */; }; + 9562D16F2AB50D34002C329D /* ActionDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9562D16E2AB50D34002C329D /* ActionDispatcher.swift */; }; + 9562D1732AB52416002C329D /* EditorKeyboardEventAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9562D1722AB52416002C329D /* EditorKeyboardEventAdapter.swift */; }; + 9562D1752AB52479002C329D /* EditorMouseEventAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9562D1742AB52479002C329D /* EditorMouseEventAdapter.swift */; }; + 9562D1772AB52550002C329D /* EditorControllerEventAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9562D1762AB52550002C329D /* EditorControllerEventAdapter.swift */; }; + 9562D1792AB64458002C329D /* ModeAutomaton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9562D1782AB64458002C329D /* ModeAutomaton.swift */; }; AA71970B287A44D200623C15 /* PlayLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = AA719702287A44D200623C15 /* PlayLoader.m */; }; AA71970D287A44D200623C15 /* PlaySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719704287A44D200623C15 /* PlaySettings.swift */; }; AA71970E287A44D200623C15 /* PlayLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = AA719705287A44D200623C15 /* PlayLoader.h */; }; AA71970F287A44D200623C15 /* PlayCover.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719706287A44D200623C15 /* PlayCover.swift */; }; - AA719758287A480D00623C15 /* PlayMice.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719722287A480C00623C15 /* PlayMice.swift */; }; AA719759287A480D00623C15 /* PlayAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719723287A480C00623C15 /* PlayAction.swift */; }; AA71975A287A480D00623C15 /* Toucher.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719724287A480C00623C15 /* Toucher.swift */; }; AA719789287A480D00623C15 /* PlayInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719755287A480C00623C15 /* PlayInput.swift */; }; @@ -79,7 +94,23 @@ 6E84A14C28D0F96D00BF7495 /* AKInterface.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AKInterface.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; 951D8276299D097C00D35B20 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Playtools.strings; sourceTree = ""; }; 951D8278299D098000D35B20 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Playtools.strings"; sourceTree = ""; }; - 95A553E629F2BBB200E34C26 /* PlayController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayController.swift; sourceTree = ""; }; + 9562D1502AB484C7002C329D /* EventAdapters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventAdapters.swift; sourceTree = ""; }; + 9562D1522AB484FD002C329D /* EventAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventAdapter.swift; sourceTree = ""; }; + 9562D1572AB4FB9B002C329D /* TouchscreenKeyboardEventAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchscreenKeyboardEventAdapter.swift; sourceTree = ""; }; + 9562D1592AB4FBBB002C329D /* TransparentKeyboardEventAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransparentKeyboardEventAdapter.swift; sourceTree = ""; }; + 9562D15B2AB4FC79002C329D /* TransparentMouseEventAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransparentMouseEventAdapter.swift; sourceTree = ""; }; + 9562D15D2AB4FCB8002C329D /* TouchscreenControllerEventAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchscreenControllerEventAdapter.swift; sourceTree = ""; }; + 9562D15F2AB4FD46002C329D /* TransparentControllerEventAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransparentControllerEventAdapter.swift; sourceTree = ""; }; + 9562D1612AB4FE73002C329D /* CameraControlMouseEventAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraControlMouseEventAdapter.swift; sourceTree = ""; }; + 9562D1632AB4FEB4002C329D /* TouchscreenMouseEventAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchscreenMouseEventAdapter.swift; sourceTree = ""; }; + 9562D1652AB5049B002C329D /* KeyboardEventAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardEventAdapter.swift; sourceTree = ""; }; + 9562D16A2AB505AD002C329D /* MouseEventAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MouseEventAdapter.swift; sourceTree = ""; }; + 9562D16C2AB505D4002C329D /* ControllerEventAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControllerEventAdapter.swift; sourceTree = ""; }; + 9562D16E2AB50D34002C329D /* ActionDispatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionDispatcher.swift; sourceTree = ""; }; + 9562D1722AB52416002C329D /* EditorKeyboardEventAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorKeyboardEventAdapter.swift; sourceTree = ""; }; + 9562D1742AB52479002C329D /* EditorMouseEventAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorMouseEventAdapter.swift; sourceTree = ""; }; + 9562D1762AB52550002C329D /* EditorControllerEventAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorControllerEventAdapter.swift; sourceTree = ""; }; + 9562D1782AB64458002C329D /* ModeAutomaton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModeAutomaton.swift; sourceTree = ""; }; AA7196D8287A447700623C15 /* PlayTools.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PlayTools.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AA719702287A44D200623C15 /* PlayLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlayLoader.m; sourceTree = ""; }; AA719704287A44D200623C15 /* PlaySettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaySettings.swift; sourceTree = ""; }; @@ -87,7 +118,6 @@ AA719706287A44D200623C15 /* PlayCover.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayCover.swift; sourceTree = ""; }; AA719708287A44D200623C15 /* PlayTools.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlayTools.h; sourceTree = ""; }; AA71970A287A44D200623C15 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - AA719722287A480C00623C15 /* PlayMice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayMice.swift; sourceTree = ""; }; AA719723287A480C00623C15 /* PlayAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayAction.swift; sourceTree = ""; }; AA719724287A480C00623C15 /* Toucher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Toucher.swift; sourceTree = ""; }; AA719755287A480C00623C15 /* PlayInput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayInput.swift; sourceTree = ""; }; @@ -154,6 +184,103 @@ name = AKInterface; sourceTree = ""; }; + 9562D14F2AB4849C002C329D /* EventAdapter */ = { + isa = PBXGroup; + children = ( + 9562D1562AB48523002C329D /* Controller */, + 9562D1552AB4851C002C329D /* Mouse */, + 9562D1542AB48504002C329D /* Keyboard */, + 9562D1502AB484C7002C329D /* EventAdapters.swift */, + 9562D1522AB484FD002C329D /* EventAdapter.swift */, + ); + path = EventAdapter; + sourceTree = ""; + }; + 9562D1542AB48504002C329D /* Keyboard */ = { + isa = PBXGroup; + children = ( + 9562D1672AB5050D002C329D /* Instances */, + 9562D1652AB5049B002C329D /* KeyboardEventAdapter.swift */, + ); + path = Keyboard; + sourceTree = ""; + }; + 9562D1552AB4851C002C329D /* Mouse */ = { + isa = PBXGroup; + children = ( + 9562D1682AB50564002C329D /* Instances */, + 9562D16A2AB505AD002C329D /* MouseEventAdapter.swift */, + ); + path = Mouse; + sourceTree = ""; + }; + 9562D1562AB48523002C329D /* Controller */ = { + isa = PBXGroup; + children = ( + 9562D1692AB50588002C329D /* Instances */, + 9562D16C2AB505D4002C329D /* ControllerEventAdapter.swift */, + ); + path = Controller; + sourceTree = ""; + }; + 9562D1672AB5050D002C329D /* Instances */ = { + isa = PBXGroup; + children = ( + 9562D1572AB4FB9B002C329D /* TouchscreenKeyboardEventAdapter.swift */, + 9562D1592AB4FBBB002C329D /* TransparentKeyboardEventAdapter.swift */, + 9562D1722AB52416002C329D /* EditorKeyboardEventAdapter.swift */, + ); + path = Instances; + sourceTree = ""; + }; + 9562D1682AB50564002C329D /* Instances */ = { + isa = PBXGroup; + children = ( + 9562D15B2AB4FC79002C329D /* TransparentMouseEventAdapter.swift */, + 9562D1612AB4FE73002C329D /* CameraControlMouseEventAdapter.swift */, + 9562D1632AB4FEB4002C329D /* TouchscreenMouseEventAdapter.swift */, + 9562D1742AB52479002C329D /* EditorMouseEventAdapter.swift */, + ); + path = Instances; + sourceTree = ""; + }; + 9562D1692AB50588002C329D /* Instances */ = { + isa = PBXGroup; + children = ( + 9562D15D2AB4FCB8002C329D /* TouchscreenControllerEventAdapter.swift */, + 9562D15F2AB4FD46002C329D /* TransparentControllerEventAdapter.swift */, + 9562D1762AB52550002C329D /* EditorControllerEventAdapter.swift */, + ); + path = Instances; + sourceTree = ""; + }; + 9562D1702AB51B41002C329D /* Frontend */ = { + isa = PBXGroup; + children = ( + 9562D14F2AB4849C002C329D /* EventAdapter */, + 9562D1782AB64458002C329D /* ModeAutomaton.swift */, + AA719756287A480C00623C15 /* ControlMode.swift */, + ); + path = Frontend; + sourceTree = ""; + }; + 9562D1712AB51B57002C329D /* Backend */ = { + isa = PBXGroup; + children = ( + 9562D17A2AB6E5ED002C329D /* Action */, + AA719724287A480C00623C15 /* Toucher.swift */, + ); + path = Backend; + sourceTree = ""; + }; + 9562D17A2AB6E5ED002C329D /* Action */ = { + isa = PBXGroup; + children = ( + AA719723287A480C00623C15 /* PlayAction.swift */, + ); + path = Action; + sourceTree = ""; + }; AA7196CE287A447700623C15 = { isa = PBXGroup; children = ( @@ -198,13 +325,11 @@ isa = PBXGroup; children = ( AA719813287A813A00623C15 /* PTFakeTouch */, - AA719722287A480C00623C15 /* PlayMice.swift */, - AA719723287A480C00623C15 /* PlayAction.swift */, - AA719724287A480C00623C15 /* Toucher.swift */, - AA719755287A480C00623C15 /* PlayInput.swift */, - AA719756287A480C00623C15 /* ControlMode.swift */, + 9562D1712AB51B57002C329D /* Backend */, + 9562D1702AB51B41002C329D /* Frontend */, AA719757287A480C00623C15 /* MenuController.swift */, - 95A553E629F2BBB200E34C26 /* PlayController.swift */, + AA719755287A480C00623C15 /* PlayInput.swift */, + 9562D16E2AB50D34002C329D /* ActionDispatcher.swift */, ); path = Controls; sourceTree = ""; @@ -452,29 +577,43 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 95A553E729F2BBB200E34C26 /* PlayController.swift in Sources */, + 9562D16D2AB505D4002C329D /* ControllerEventAdapter.swift in Sources */, AA71975A287A480D00623C15 /* Toucher.swift in Sources */, AA7197A1287A481500623C15 /* CircleMenuLoader.swift in Sources */, 6E76639B28D0FAE700DE4AF9 /* Plugin.swift in Sources */, B1271729288284BE0025112B /* DiscordActivity.swift in Sources */, AA7197AB287A481500623C15 /* ControlModel.swift in Sources */, AA7197AE287A481500623C15 /* DragElementsView.swift in Sources */, + 9562D1622AB4FE73002C329D /* CameraControlMouseEventAdapter.swift in Sources */, + 9562D1642AB4FEB4002C329D /* TouchscreenMouseEventAdapter.swift in Sources */, + 9562D1602AB4FD46002C329D /* TransparentControllerEventAdapter.swift in Sources */, + 9562D1792AB64458002C329D /* ModeAutomaton.swift in Sources */, AA71970D287A44D200623C15 /* PlaySettings.swift in Sources */, AA719759287A480D00623C15 /* PlayAction.swift in Sources */, 6E7663A528D0FEBE00DE4AF9 /* AKPluginLoader.swift in Sources */, + 9562D16F2AB50D34002C329D /* ActionDispatcher.swift in Sources */, 2847AE48298EFC0F00B0F983 /* PlayScreen.swift in Sources */, + 9562D1532AB484FD002C329D /* EventAdapter.swift in Sources */, AA7197A2287A481500623C15 /* CircleMenu.swift in Sources */, + 9562D15A2AB4FBBB002C329D /* TransparentKeyboardEventAdapter.swift in Sources */, AA71978B287A480D00623C15 /* MenuController.swift in Sources */, + 9562D1732AB52416002C329D /* EditorKeyboardEventAdapter.swift in Sources */, + 9562D1752AB52479002C329D /* EditorMouseEventAdapter.swift in Sources */, AA7197AA287A481500623C15 /* Toast.swift in Sources */, AA719789287A480D00623C15 /* PlayInput.swift in Sources */, AA71970B287A44D200623C15 /* PlayLoader.m in Sources */, + 9562D1512AB484C7002C329D /* EventAdapters.swift in Sources */, AA7197AF287A481500623C15 /* KeyCodeNames.swift in Sources */, AA71978A287A480D00623C15 /* ControlMode.swift in Sources */, - AA719758287A480D00623C15 /* PlayMice.swift in Sources */, AA7197AD287A481500623C15 /* EditorController.swift in Sources */, + 9562D16B2AB505AD002C329D /* MouseEventAdapter.swift in Sources */, + 9562D15C2AB4FC79002C329D /* TransparentMouseEventAdapter.swift in Sources */, + 9562D1772AB52550002C329D /* EditorControllerEventAdapter.swift in Sources */, AA7197A3287A481500623C15 /* CircleMenuButton.swift in Sources */, + 9562D1582AB4FB9B002C329D /* TouchscreenKeyboardEventAdapter.swift in Sources */, AA7197A9287A481500623C15 /* PlayInfo.swift in Sources */, AA71986A287A81A000623C15 /* PTFakeMetaTouch.m in Sources */, + 9562D15E2AB4FCB8002C329D /* TouchscreenControllerEventAdapter.swift in Sources */, ABCECEE629750BA600746595 /* PlayedApple.swift in Sources */, AA71986C287A81A000623C15 /* NSObject+Swizzle.m in Sources */, AA71970F287A44D200623C15 /* PlayCover.swift in Sources */, @@ -483,6 +622,7 @@ AA71985F287A81A000623C15 /* IOHIDEvent+KIF.m in Sources */, B1E8CF8A28BBE2AB004340D3 /* Keymapping.swift in Sources */, AB7DA47529B85BFB0034ACB2 /* PlayShadow.m in Sources */, + 9562D1662AB5049B002C329D /* KeyboardEventAdapter.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/PlayTools/Controls/ActionDispatcher.swift b/PlayTools/Controls/ActionDispatcher.swift new file mode 100644 index 00000000..73b81c91 --- /dev/null +++ b/PlayTools/Controls/ActionDispatcher.swift @@ -0,0 +1,164 @@ +// +// ActionDispatcher.swift +// PlayTools +// +// Created by 许沂聪 on 2023/9/16. +// + +import Foundation + +// If the same key is mapped to multiple different tasks, distinguish by priority +public class ActionDispatchPriority { + static public let DRAGGABLE = 0 + static public let DEFAULT = 1 + static public let CAMERA = 2 +} + +// This class reads keymap and thereby dispatch events + +public class ActionDispatcher { + static private let keymapVersion = "2.0." + static private var actions = [Action]() + static private let PRIORITY_COUNT = 3 + static private var buttonHandlers: [String: [(Bool) -> Void]] = [:], + directionPadHandlers: [[String: (CGFloat, CGFloat) -> Void]] = Array( + repeating: [:], count: PRIORITY_COUNT) + + static private func clear() { + invalidateActions() + actions = [] + buttonHandlers.removeAll(keepingCapacity: true) + for priority in 0.. ActionDispatchPriority.DRAGGABLE || + ( + getDispatchPriority(key: KeyCodeNames.mouseMove) ?? ActionDispatchPriority.DRAGGABLE + ) + > ActionDispatchPriority.DRAGGABLE + } + + static public func register(key: String, handler: @escaping (Bool) -> Void) { + // this function is called when setting up `button` type of mapping + if buttonHandlers[key] == nil { + buttonHandlers[key] = [] + } + buttonHandlers[key]!.append(handler) + } + + static public func register(key: String, + handler: @escaping (CGFloat, CGFloat) -> Void, + priority: Int = ActionDispatchPriority.DEFAULT) { + directionPadHandlers[priority][key] = handler + } + + static public func unregister(key: String) { + // Only draggable can be unregistered + directionPadHandlers[ActionDispatchPriority.DRAGGABLE].removeValue(forKey: key) + } + + // Frontend interfaces + + static public var cursorHideNecessary = true + + static public func invalidateActions() { + for action in actions { + // This is just a rescue feature, in case any key stuck pressed for any reason + // Might be called on control mode state transition + action.invalidate() + } + } + + static public func getDispatchPriority(key: String) -> Int? { + for priority in 0.. Bool { + guard let handlers = buttonHandlers[key] else { + return false + } + var mapped = false + for handler in handlers { + PlayInput.touchQueue.async(qos: .userInteractive, execute: { + handler(pressed) + }) + mapped = true + } + // return value matters. A false value makes a beep sound + return mapped + } + + static public func dispatch(key: String, valueX: CGFloat, valueY: CGFloat) -> Bool { + // WARNING: if you want to change this, beware of concurrency contention + PlayInput.touchQueue.async(qos: .userInteractive, execute: { + for priority in 0.. Void) { + let when = DispatchTime.now() + delay + PlayInput.touchQueue.asyncAfter(deadline: when, execute: closure) + } + // Count swipe duration + var counter = 0 + // if should wait before beginning next touch + var cooldown = false + var lastCounter = 0 + + func checkEnded() { + if self.counter == self.lastCounter { + if self.counter < 4 { + counter += 1 + } else { + timer.suspend() + self.doLiftOff() + } + } + self.lastCounter = self.counter + } + + public func move(from: () -> CGPoint?, deltaX: CGFloat, deltaY: CGFloat) { + if id == nil { + if cooldown { + return + } + guard let start = from() else {return} + location = start + counter = 0 + Toucher.touchcam(point: location, phase: UITouch.Phase.began, tid: &id) + timer.resume() + } + // count touch duration + counter += 1 + self.location.x += deltaX + self.location.y -= deltaY + Toucher.touchcam(point: self.location, phase: UITouch.Phase.moved, tid: &id) + } + + public func doLiftOff() { + if id == nil { + return + } + Toucher.touchcam(point: self.location, phase: UITouch.Phase.ended, tid: &id) + delay(0.02) { + self.cooldown = false + } + cooldown = true + } + + func invalidate() { + timer.cancel() + self.doLiftOff() + } +} + +class FakeMouseAction: Action { + var id: Int? + var pos: CGPoint! + public init() { + ActionDispatcher.register(key: KeyCodeNames.fakeMouse, handler: buttonPressHandler) + ActionDispatcher.register(key: KeyCodeNames.fakeMouse, handler: buttonLiftHandler) + } + + func buttonPressHandler(xValue: CGFloat, yValue: CGFloat) { + pos = CGPoint(x: xValue, y: yValue) +// DispatchQueue.main.async { +// Toast.showHint(title: "Fake mouse pressed", text: ["\(self.pos)"]) +// } + Toucher.touchcam(point: pos, phase: UITouch.Phase.began, tid: &id) + ActionDispatcher.register(key: KeyCodeNames.fakeMouse, + handler: movementHandler, + priority: ActionDispatchPriority.DRAGGABLE) + } + + func buttonLiftHandler(pressed: Bool) { + if pressed { + Toast.showHint(title: "Error", text: ["Fake mouse lift handler received a press event"]) + return + } +// DispatchQueue.main.async { +// Toast.showHint(title: " lift Fake mouse", text: ["\(self.pos)"]) +// } + ActionDispatcher.unregister(key: KeyCodeNames.fakeMouse) + Toucher.touchcam(point: pos, phase: UITouch.Phase.ended, tid: &id) + } + + func movementHandler(xValue: CGFloat, yValue: CGFloat) { + pos.x = xValue + pos.y = yValue + Toucher.touchcam(point: pos, phase: UITouch.Phase.moved, tid: &id) + } + + func invalidate() { + Toucher.touchcam(point: pos ?? CGPoint(x: 10, y: 10), + phase: UITouch.Phase.ended, tid: &self.id) + } + +} diff --git a/PlayTools/Controls/Toucher.swift b/PlayTools/Controls/Backend/Toucher.swift similarity index 100% rename from PlayTools/Controls/Toucher.swift rename to PlayTools/Controls/Backend/Toucher.swift diff --git a/PlayTools/Controls/ControlMode.swift b/PlayTools/Controls/ControlMode.swift deleted file mode 100644 index f99ed3c1..00000000 --- a/PlayTools/Controls/ControlMode.swift +++ /dev/null @@ -1,82 +0,0 @@ -// -// ControlMode.swift -// PlayTools -// - -import Foundation - -let mode = ControlMode.mode - -public class ControlMode { - - static public let mode = ControlMode() - public var visible: Bool = true - public var keyboardMapped = true - - public static func trySwap() -> Bool { - // this function is called from `AKPlugin` when `option` is pressed. - if PlayInput.shouldLockCursor { - mode.show(!mode.visible) - return true - } - mode.show(true) - return false - } - - func setMapping(_ mapped: Bool) { - if mapped { - // `parseKeymap` and `invalidate` roughly do the opposite thing - PlayInput.shared.parseKeymap() - } else { - // to avoid infinite recursion - if !visible { - show(true) - } - PlayInput.shared.invalidate() - } - keyboardMapped = mapped - } - - func show(_ show: Bool) { - // special cases where function of `option` should be temparorily disabled. - // if the new auto keymapping feature is enabled, it could cause problems to switch - // cursor show state while typing. - if (!PlaySettings.shared.noKMOnInput && !editor.editorMode) - || (PlaySettings.shared.noKMOnInput && keyboardMapped) { - if show { - if !visible { - NotificationCenter.default.post(name: NSNotification.Name.playtoolsCursorWillShow, - object: nil, userInfo: [:]) - if screen.fullscreen { - screen.switchDock(true) - } - AKInterface.shared!.unhideCursor() - } - } else { - if visible { - NotificationCenter.default.post(name: NSNotification.Name.playtoolsCursorWillHide, - object: nil, userInfo: [:]) - AKInterface.shared!.hideCursor() - if screen.fullscreen { - screen.switchDock(false) - } - } - } - Toucher.writeLog(logMessage: "cursor show switched to \(show)") - visible = show - if !PlaySettings.shared.noKMOnInput { - // we want to set keymapping as the reverse of curosr show status, not always false. - // as well as do some logic along with it - setMapping(!show) - } - } - } -} - -extension NSNotification.Name { - public static let playtoolsCursorWillHide: NSNotification.Name - = NSNotification.Name("playtools.cursorWillHide") - - public static let playtoolsCursorWillShow: NSNotification.Name - = NSNotification.Name("playtools.cursorWillShow") -} diff --git a/PlayTools/Controls/Frontend/ControlMode.swift b/PlayTools/Controls/Frontend/ControlMode.swift new file mode 100644 index 00000000..993b8a12 --- /dev/null +++ b/PlayTools/Controls/Frontend/ControlMode.swift @@ -0,0 +1,133 @@ +// +// ControlMode.swift +// PlayTools +// + +import Foundation +import GameController + +let mode = ControlMode.mode + +// This class handles different control logic under different control mode + +public class ControlMode: Equatable { + static public let mode = ControlMode() + + static public let TEXT_INPUT = "textInput" + static public let CAMERA_ROTATE = "cameraRotate" + static public let ARBITRARY_CLICK = "arbitraryClick" + static public let OFF = "off" + static public let EDITOR = "editor" + + private var controlMode = ControlMode.OFF + + private var keyboardAdapter: KeyboardEventAdapter! + private var mouseAdapter: MouseEventAdapter! + private var controllerAdapter: ControllerEventAdapter! + + public func cursorHidden() -> Bool { + return mouseAdapter?.cursorHidden() ?? false + } + + public func initialize() { + let centre = NotificationCenter.default + let main = OperationQueue.main + if PlaySettings.shared.noKMOnInput { + centre.addObserver(forName: UIApplication.keyboardDidHideNotification, object: nil, queue: main) { _ in + ModeAutomaton.onKeyboardHide() + Toucher.writeLog(logMessage: "virtual keyboard did hide") + } + centre.addObserver(forName: UIApplication.keyboardWillShowNotification, object: nil, queue: main) { _ in + ModeAutomaton.onKeyboardShow() + Toucher.writeLog(logMessage: "virtual keyboard will show") + } + set(ControlMode.ARBITRARY_CLICK) + } else { + set(ControlMode.OFF) + } + + centre.addObserver(forName: NSNotification.Name.GCControllerDidConnect, object: nil, queue: main) { _ in + GCController.current?.extendedGamepad?.valueChangedHandler = {profile, element in + self.controllerAdapter.handleValueChanged(profile, element) + } + } + + AKInterface.shared!.setupKeyboard(keyboard: { keycode, pressed, isRepeat in + self.keyboardAdapter.handleKey(keycode: keycode, pressed: pressed, isRepeat: isRepeat)}, + swapMode: ModeAutomaton.onOption) + + AKInterface.shared!.setupScrollWheel({deltaX, deltaY in + self.mouseAdapter.handleScrollWheel(deltaX: deltaX, deltaY: deltaY) + }) + + AKInterface.shared!.setupMouseMoved({deltaX, deltaY in + self.mouseAdapter.handleMove(deltaX: deltaX, deltaY: deltaY) + }) + + AKInterface.shared!.setupMouseButton(left: true, right: false, {_, pressed in + self.mouseAdapter.handleLeftButton(pressed: pressed) + }) + + AKInterface.shared!.setupMouseButton(left: false, right: false, {id, pressed in + self.mouseAdapter.handleOtherButton(id: id, pressed: pressed) + }) + + AKInterface.shared!.setupMouseButton(left: false, right: true, {id, pressed in + self.mouseAdapter.handleOtherButton(id: id, pressed: pressed) + }) + + ActionDispatcher.build() + } + + public func set(_ mode: String) { + let wasHidden = mouseAdapter?.cursorHidden() ?? false + let first = mouseAdapter == nil + keyboardAdapter = EventAdapters.keyboard(controlMode: mode) + mouseAdapter = EventAdapters.mouse(controlMode: mode) + controllerAdapter = EventAdapters.controller(controlMode: mode) + controlMode = mode + if !first { +// Toast.showHint(title: "should hide cursor? \(mouseAdapter.cursorHidden())", +// text: ["current state: " + mode]) + } + if mouseAdapter.cursorHidden() != wasHidden { + if wasHidden { + NotificationCenter.default.post(name: NSNotification.Name.playtoolsCursorWillShow, + object: nil, userInfo: [:]) + if screen.fullscreen { + screen.switchDock(true) + } + AKInterface.shared!.unhideCursor() + } else { + NotificationCenter.default.post(name: NSNotification.Name.playtoolsCursorWillHide, + object: nil, userInfo: [:]) + AKInterface.shared!.hideCursor() + if screen.fullscreen { + screen.switchDock(false) + } + } + Toucher.writeLog(logMessage: "cursor show switched to \(!wasHidden)") + } + } + + public static func == (lhs: String, rhs: ControlMode) -> Bool { + lhs == rhs.controlMode + } + + public static func == (lhs: ControlMode, rhs: String) -> Bool { + rhs == lhs + } + + public static func == (lhs: ControlMode, rhs: ControlMode) -> Bool { + rhs.controlMode == lhs.controlMode + } + +} + +extension NSNotification.Name { + public static let playtoolsCursorWillHide: NSNotification.Name + = NSNotification.Name("playtools.cursorWillHide") + + public static let playtoolsCursorWillShow: NSNotification.Name + = NSNotification.Name("playtools.cursorWillShow") +} diff --git a/PlayTools/Controls/Frontend/EventAdapter/Controller/ControllerEventAdapter.swift b/PlayTools/Controls/Frontend/EventAdapter/Controller/ControllerEventAdapter.swift new file mode 100644 index 00000000..039d457d --- /dev/null +++ b/PlayTools/Controls/Frontend/EventAdapter/Controller/ControllerEventAdapter.swift @@ -0,0 +1,15 @@ +// +// ControllerEventAdapter.swift +// PlayTools +// +// Created by 许沂聪 on 2023/9/16. +// + +import Foundation +import GameController + +// All controller events under any mode + +public protocol ControllerEventAdapter: EventAdapter { + func handleValueChanged(_ profile: GCExtendedGamepad, _ element: GCControllerElement) +} diff --git a/PlayTools/Controls/Frontend/EventAdapter/Controller/Instances/EditorControllerEventAdapter.swift b/PlayTools/Controls/Frontend/EventAdapter/Controller/Instances/EditorControllerEventAdapter.swift new file mode 100644 index 00000000..655c2367 --- /dev/null +++ b/PlayTools/Controls/Frontend/EventAdapter/Controller/Instances/EditorControllerEventAdapter.swift @@ -0,0 +1,36 @@ +// +// EditorControllerEventAdapter.swift +// PlayTools +// +// Created by 许沂聪 on 2023/9/16. +// + +import Foundation +import GameController + +// Controller events handler when in editor mode + +public class EditorControllerEventAdapter: ControllerEventAdapter { + public func handleValueChanged(_ profile: GCExtendedGamepad, _ element: GCControllerElement) { + // This is the index of controller buttons, which is String, not Int + var alias: String = element.aliases.first! + if alias == "Direction Pad" { + guard let dpadElement = element as? GCControllerDirectionPad else { + Toast.showOver(msg: "cannot map direction pad: element type not recognizable") + return + } + if dpadElement.xAxis.value > 0 { + alias = dpadElement.right.aliases.first! + } else if dpadElement.xAxis.value < 0 { + alias = dpadElement.left.aliases.first! + } + if dpadElement.yAxis.value > 0 { + alias = dpadElement.down.aliases.first! + } else if dpadElement.yAxis.value < 0 { + alias = dpadElement.up.aliases.first! + } + } + EditorController.shared.setKey(alias) + } + +} diff --git a/PlayTools/Controls/Frontend/EventAdapter/Controller/Instances/TouchscreenControllerEventAdapter.swift b/PlayTools/Controls/Frontend/EventAdapter/Controller/Instances/TouchscreenControllerEventAdapter.swift new file mode 100644 index 00000000..23c5722e --- /dev/null +++ b/PlayTools/Controls/Frontend/EventAdapter/Controller/Instances/TouchscreenControllerEventAdapter.swift @@ -0,0 +1,99 @@ +// +// TouchscreenControllerEventAdapter.swift +// PlayTools +// +// Created by 许沂聪 on 2023/9/16. +// + +import Foundation +import GameController + +// Controller events handler when keymap is on + +public class TouchscreenControllerEventAdapter: ControllerEventAdapter { + + private var directionPadXValue: Float = 0, + directionPadYValue: Float = 0 + static private var thumbstickCursorControl: [String: (CGFloat, CGFloat) -> Void] = [:] + + public func handleValueChanged(_ profile: GCExtendedGamepad, _ element: GCControllerElement) { + let name: String = element.aliases.first! + if let buttonElement = element as? GCControllerButtonInput { + _ = ActionDispatcher.dispatch(key: name, pressed: buttonElement.isPressed) + } else if let dpadElement = element as? GCControllerDirectionPad { + handleDirectionPad(profile, dpadElement) + } else { + Toast.showOver(msg: "unrecognised controller element input happens") + } + } + + private func handleDirectionPad(_ profile: GCExtendedGamepad, _ dpad: GCControllerDirectionPad) { + let name = dpad.aliases.first! + let xAxis = dpad.xAxis, yAxis = dpad.yAxis + if name == "Direction Pad" { + if (xAxis.value > 0) != (directionPadXValue > 0) { + handleValueChanged(profile, dpad.right) + } + if (xAxis.value < 0) != (directionPadXValue < 0) { + handleValueChanged(profile, dpad.left) + } + if (yAxis.value > 0) != (directionPadYValue > 0) { + handleValueChanged(profile, dpad.up) + } + if (yAxis.value < 0) != (directionPadYValue < 0) { + handleValueChanged(profile, dpad.down) + } + directionPadXValue = xAxis.value + directionPadYValue = yAxis.value + return + } + let deltaX = xAxis.value, deltaY = yAxis.value + let cgDx = CGFloat(deltaX) + let cgDy = CGFloat(deltaY) + let dispatchType = ActionDispatcher.getDispatchPriority(key: name) + if dispatchType == nil { + return + } else if dispatchType == ActionDispatchPriority.DEFAULT { + _ = ActionDispatcher.dispatch(key: name, valueX: cgDx, valueY: cgDy) + } else { + if TouchscreenControllerEventAdapter.thumbstickCursorControl[name] == nil { + TouchscreenControllerEventAdapter.thumbstickCursorControl[name] = ThumbstickCursorControl(name).update + } + TouchscreenControllerEventAdapter.thumbstickCursorControl[name]!(cgDx * 6, cgDy * 6) + } + } + +} + +class ThumbstickCursorControl { + private var thumbstickVelocity: CGVector = CGVector.zero, + thumbstickPolling: Bool = false, + key: String + + init(_ key: String) { + self.key = key + } + + static private func isVectorSignificant(_ vector: CGVector) -> Bool { + return vector.dx.magnitude + vector.dy.magnitude > 0.2 + } + + public func update(velocityX: CGFloat, velocityY: CGFloat) { + self.thumbstickVelocity.dx = velocityX + self.thumbstickVelocity.dy = velocityY + if !thumbstickPolling { + PlayInput.touchQueue.async(execute: self.thumbstickPoll) + self.thumbstickPolling = true + } + } + + private func thumbstickPoll() { + if !ThumbstickCursorControl.isVectorSignificant(self.thumbstickVelocity) { + self.thumbstickPolling = false + return + } + _ = ActionDispatcher.dispatch(key: key, valueX: thumbstickVelocity.dx, valueY: thumbstickVelocity.dy) + PlayInput.touchQueue.asyncAfter( + deadline: DispatchTime.now() + 0.017, execute: self.thumbstickPoll) + } +} diff --git a/PlayTools/Controls/Frontend/EventAdapter/Controller/Instances/TransparentControllerEventAdapter.swift b/PlayTools/Controls/Frontend/EventAdapter/Controller/Instances/TransparentControllerEventAdapter.swift new file mode 100644 index 00000000..519ad53f --- /dev/null +++ b/PlayTools/Controls/Frontend/EventAdapter/Controller/Instances/TransparentControllerEventAdapter.swift @@ -0,0 +1,27 @@ +// +// TransparentControllerEventAdapter.swift +// PlayTools +// +// Created by 许沂聪 on 2023/9/16. +// + +import Foundation +import GameController + +// Controller events handler when keymap is off + +public class TransparentControllerEventAdapter: ControllerEventAdapter { + public func handleValueChanged(_ profile: GCExtendedGamepad, _ element: GCControllerElement) { + /* + Controller event is currently handled by GameController APIs. + This API runs concurrently with the NSEvent things. + In other words, whatever I do in its handler, the NSEvent is not affected. + The drawbacks of this API is, it executes through Main Dispatch Queue + and may be delayed under high CPU pressure + + However, we didn't find a suitable alternative for it. + But high CPU 3D games usually don't need to map controllers + so it's OK for now + */ + } +} diff --git a/PlayTools/Controls/Frontend/EventAdapter/EventAdapter.swift b/PlayTools/Controls/Frontend/EventAdapter/EventAdapter.swift new file mode 100644 index 00000000..4dea098b --- /dev/null +++ b/PlayTools/Controls/Frontend/EventAdapter/EventAdapter.swift @@ -0,0 +1,12 @@ +// +// EventAdapter.swift +// PlayTools +// +// Created by 许沂聪 on 2023/9/15. +// + +import Foundation + +public protocol EventAdapter { + // Just a stub, may add things later +} diff --git a/PlayTools/Controls/Frontend/EventAdapter/EventAdapters.swift b/PlayTools/Controls/Frontend/EventAdapter/EventAdapters.swift new file mode 100644 index 00000000..99ea1aea --- /dev/null +++ b/PlayTools/Controls/Frontend/EventAdapter/EventAdapters.swift @@ -0,0 +1,65 @@ +// +// EventAdapters.swift +// PlayTools +// +// Created by 许沂聪 on 2023/9/15. +// + +import Foundation + +// This is a builder class for event adapters + +public class EventAdapters { + + static func keyboard(controlMode: String) -> KeyboardEventAdapter { + if controlMode == ControlMode.OFF || controlMode == ControlMode.TEXT_INPUT { + return TransparentKeyboardEventAdapter() + + } else if controlMode == ControlMode.CAMERA_ROTATE || controlMode == ControlMode.ARBITRARY_CLICK { + return TouchscreenKeyboardEventAdapter() + } else if controlMode == ControlMode.EDITOR { + return EditorKeyboardEventAdapter() + } else { + Toast.showHint(title: "Control mode switch error", + text: ["Cannot find keyboard event adapter for control mode " + controlMode]) + return TransparentKeyboardEventAdapter() + } + } + + static func mouse(controlMode: String) -> MouseEventAdapter { + if controlMode == ControlMode.OFF || controlMode == ControlMode.TEXT_INPUT { + return TransparentMouseEventAdapter() + + } else if controlMode == ControlMode.CAMERA_ROTATE { + return CameraControlMouseEventAdapter() + + } else if controlMode == ControlMode.ARBITRARY_CLICK { + return TouchscreenMouseEventAdapter() + + } else if controlMode == ControlMode.EDITOR { + return EditorMouseEventAdapter() + } else { + Toast.showHint(title: "Control mode switch error", + text: ["Cannot find mouse event adapter for control mode " + controlMode]) + return TouchscreenMouseEventAdapter() + } + } + + static func controller(controlMode: String) -> ControllerEventAdapter { + if controlMode == ControlMode.OFF { + return TransparentControllerEventAdapter() + + } else if controlMode == ControlMode.CAMERA_ROTATE + || controlMode == ControlMode.ARBITRARY_CLICK + || controlMode == ControlMode.TEXT_INPUT { + return TouchscreenControllerEventAdapter() + + } else if controlMode == ControlMode.EDITOR { + return EditorControllerEventAdapter() + } else { + Toast.showHint(title: "Control mode switch error", + text: ["Cannot find controller event adapter for control mode " + controlMode]) + return TouchscreenControllerEventAdapter() + } + } +} diff --git a/PlayTools/Controls/Frontend/EventAdapter/Keyboard/Instances/EditorKeyboardEventAdapter.swift b/PlayTools/Controls/Frontend/EventAdapter/Keyboard/Instances/EditorKeyboardEventAdapter.swift new file mode 100644 index 00000000..60681287 --- /dev/null +++ b/PlayTools/Controls/Frontend/EventAdapter/Keyboard/Instances/EditorKeyboardEventAdapter.swift @@ -0,0 +1,38 @@ +// +// EditorKeyboardEventAdapter.swift +// PlayTools +// +// Created by 许沂聪 on 2023/9/16. +// + +import Foundation +import GameController + +// Keyboard events handler when in editor mode + +public class EditorKeyboardEventAdapter: KeyboardEventAdapter { + private static let FORBIDDEN: [GCKeyCode] = [ + .leftGUI, + .rightGUI, + .leftAlt, + .rightAlt, + .printScreen + ] + + public func handleKey(keycode: UInt16, pressed: Bool, isRepeat: Bool) -> Bool { + if AKInterface.shared!.cmdPressed { + return false + } + guard let rawValue = KeyCodeNames.mapNSEventVirtualCodeToGCKeyCodeRawValue[keycode] else { + return false + } + let gcKeyCode = GCKeyCode(rawValue: rawValue) + if EditorKeyboardEventAdapter.FORBIDDEN.contains(gcKeyCode) { +// Toast.showHint(title: "Invalid Key", text: ["This key is intentionally forbidden. Keyname: \(name)"]) + return false + } + EditorController.shared.setKey(rawValue) + return true + } + +} diff --git a/PlayTools/Controls/Frontend/EventAdapter/Keyboard/Instances/TouchscreenKeyboardEventAdapter.swift b/PlayTools/Controls/Frontend/EventAdapter/Keyboard/Instances/TouchscreenKeyboardEventAdapter.swift new file mode 100644 index 00000000..18d3251d --- /dev/null +++ b/PlayTools/Controls/Frontend/EventAdapter/Keyboard/Instances/TouchscreenKeyboardEventAdapter.swift @@ -0,0 +1,24 @@ +// +// TouchscreenKeyboardEventAdapter.swift +// PlayTools +// +// Created by 许沂聪 on 2023/9/16. +// + +import Foundation + +// Keyboard events handler when keyboard mapping is on + +public class TouchscreenKeyboardEventAdapter: KeyboardEventAdapter { + public func handleKey(keycode: UInt16, pressed: Bool, isRepeat: Bool) -> Bool { + + if isRepeat { + // eat, eat, eat! + return true + } + + let name = KeyCodeNames.virtualCodes[keycode] ?? "Btn" + return ActionDispatcher.dispatch(key: name, pressed: pressed) + } + +} diff --git a/PlayTools/Controls/Frontend/EventAdapter/Keyboard/Instances/TransparentKeyboardEventAdapter.swift b/PlayTools/Controls/Frontend/EventAdapter/Keyboard/Instances/TransparentKeyboardEventAdapter.swift new file mode 100644 index 00000000..03c181b2 --- /dev/null +++ b/PlayTools/Controls/Frontend/EventAdapter/Keyboard/Instances/TransparentKeyboardEventAdapter.swift @@ -0,0 +1,18 @@ +// +// TextInputKeyboardEventAdapter.swift +// PlayTools +// +// Created by 许沂聪 on 2023/9/16. +// + +import Foundation + +// Keyboard events handler when keyboard mapping is off + +public class TransparentKeyboardEventAdapter: KeyboardEventAdapter { + public func handleKey(keycode: UInt16, pressed: Bool, isRepeat: Bool) -> Bool { + // explicitly eat repeated Enter key + isRepeat && keycode == 36 + } + +} diff --git a/PlayTools/Controls/Frontend/EventAdapter/Keyboard/KeyboardEventAdapter.swift b/PlayTools/Controls/Frontend/EventAdapter/Keyboard/KeyboardEventAdapter.swift new file mode 100644 index 00000000..ca742a8c --- /dev/null +++ b/PlayTools/Controls/Frontend/EventAdapter/Keyboard/KeyboardEventAdapter.swift @@ -0,0 +1,14 @@ +// +// KeyboardEventAdapter.swift +// PlayTools +// +// Created by 许沂聪 on 2023/9/16. +// + +import Foundation + +// All keyboard events under any mode + +public protocol KeyboardEventAdapter: EventAdapter { + func handleKey(keycode: UInt16, pressed: Bool, isRepeat: Bool) -> Bool +} diff --git a/PlayTools/Controls/Frontend/EventAdapter/Mouse/Instances/CameraControlMouseEventAdapter.swift b/PlayTools/Controls/Frontend/EventAdapter/Mouse/Instances/CameraControlMouseEventAdapter.swift new file mode 100644 index 00000000..38de66f6 --- /dev/null +++ b/PlayTools/Controls/Frontend/EventAdapter/Mouse/Instances/CameraControlMouseEventAdapter.swift @@ -0,0 +1,40 @@ +// +// CameraControlMouseEventAdapter.swift +// PlayTools +// +// Created by 许沂聪 on 2023/9/16. +// + +import Foundation + +// Mouse events handler when cursor is locked and keyboard mapping is on + +public class CameraControlMouseEventAdapter: MouseEventAdapter { + public func handleScrollWheel(deltaX: CGFloat, deltaY: CGFloat) -> Bool { + _ = ActionDispatcher.dispatch(key: KeyCodeNames.scrollWheelScale, valueX: deltaX, valueY: deltaY) + // I dont know why but this is the logic before the refactor. + // Might be a mistake but keeping it for now + return true + } + + public func handleMove(deltaX: CGFloat, deltaY: CGFloat) -> Bool { + let sensy = CGFloat(PlaySettings.shared.sensitivity * 0.6) + let cgDx = deltaX * sensy, + cgDy = -deltaY * sensy + return ActionDispatcher.dispatch(key: KeyCodeNames.mouseMove, valueX: cgDx, valueY: cgDy) + } + + public func handleLeftButton(pressed: Bool) -> Bool { + ActionDispatcher.dispatch(key: KeyCodeNames.leftMouseButton, pressed: pressed) + } + + public func handleOtherButton(id: Int, pressed: Bool) -> Bool { + ActionDispatcher.dispatch(key: EditorMouseEventAdapter.getMouseButtonName(id), + pressed: pressed) + } + + public func cursorHidden() -> Bool { + true + } + +} diff --git a/PlayTools/Controls/Frontend/EventAdapter/Mouse/Instances/EditorMouseEventAdapter.swift b/PlayTools/Controls/Frontend/EventAdapter/Mouse/Instances/EditorMouseEventAdapter.swift new file mode 100644 index 00000000..77295cf9 --- /dev/null +++ b/PlayTools/Controls/Frontend/EventAdapter/Mouse/Instances/EditorMouseEventAdapter.swift @@ -0,0 +1,56 @@ +// +// EditorMouseEventAdapter.swift +// PlayTools +// +// Created by 许沂聪 on 2023/9/16. +// + +import Foundation + +// Mouse events handler when in editor mode + +public class EditorMouseEventAdapter: MouseEventAdapter { + private static let buttonName: [String] = [ +// "InvalidMouseButton", + KeyCodeNames.leftMouseButton, + KeyCodeNames.rightMouseButton, + KeyCodeNames.middleMouseButton + ] + + public static func getMouseButtonName(_ id: Int) -> String { + return id < EditorMouseEventAdapter.buttonName.count ? + EditorMouseEventAdapter.buttonName[id] : + "MBtn\(id)" + } + + public func handleOtherButton(id: Int, pressed: Bool) -> Bool { + if pressed { + let name = EditorMouseEventAdapter.getMouseButtonName(id) + // asynced to return quickly. Editor contains UI operation so main queue. + // main queue is fine. should not be slower than keyboard + DispatchQueue.main.async(qos: .userInteractive, execute: { + EditorController.shared.setKey(name) + Toucher.writeLog(logMessage: "mouse button editor set") + }) + } + return true + } + + public func handleScrollWheel(deltaX: CGFloat, deltaY: CGFloat) -> Bool { + false + } + + public func handleMove(deltaX: CGFloat, deltaY: CGFloat) -> Bool { + false + } + + public func handleLeftButton(pressed: Bool) -> Bool { + // Event flows to EditorController via UIKit + false + } + + public func cursorHidden() -> Bool { + false + } + +} diff --git a/PlayTools/Controls/Frontend/EventAdapter/Mouse/Instances/TouchscreenMouseEventAdapter.swift b/PlayTools/Controls/Frontend/EventAdapter/Mouse/Instances/TouchscreenMouseEventAdapter.swift new file mode 100644 index 00000000..3e84a191 --- /dev/null +++ b/PlayTools/Controls/Frontend/EventAdapter/Mouse/Instances/TouchscreenMouseEventAdapter.swift @@ -0,0 +1,85 @@ +// +// TouchscreenMouseEventAdapter.swift +// PlayTools +// +// Created by 许沂聪 on 2023/9/16. +// + +import Foundation + +// Mouse events handler when cursor is free and keyboard mapping is on + +public class TouchscreenMouseEventAdapter: MouseEventAdapter { + + static public func cursorPos() -> CGPoint? { + // IMPROVE: this is expensive (maybe?) + var point = AKInterface.shared!.mousePoint + let rect = AKInterface.shared!.windowFrame + if rect.width < 1 || rect.height < 1 { + return nil + } + let viewRect: CGRect = screen.screenRect + let widthRate = viewRect.width / rect.width + var rate = viewRect.height / rect.height + if widthRate > rate { + // Keep aspect ratio + rate = widthRate + } + if screen.fullscreen { + // Vertically in center + point.y -= (rect.height - viewRect.height / rate)/2 + } + point.y *= rate + point.y = viewRect.height - point.y + // For traffic light buttons when not fullscreen + if point.y < 0 { + return nil + } + // Horizontally in center + point.x -= (rect.width - viewRect.width / rate)/2 + point.x *= rate + return point + } + + public func handleScrollWheel(deltaX: CGFloat, deltaY: CGFloat) -> Bool { + _ = ActionDispatcher.dispatch(key: KeyCodeNames.scrollWheelDrag, valueX: deltaX, valueY: deltaY) + // I dont know why but this is the logic before the refactor. + // Might be a mistake but keeping it for now + return false + } + + public func handleMove(deltaX: CGFloat, deltaY: CGFloat) -> Bool { + // fake mouse handler: + // default direction pad: press handler + // draggable direction pad: move handler + // default button: lift handler + // kinda hacky but.. IT WORKS! + if ActionDispatcher.getDispatchPriority(key: KeyCodeNames.fakeMouse) + == ActionDispatchPriority.DRAGGABLE { + guard let pos = TouchscreenMouseEventAdapter.cursorPos() else { return false } + return ActionDispatcher.dispatch(key: KeyCodeNames.fakeMouse, valueX: pos.x, valueY: pos.y) + } + return false + } + + public func handleLeftButton(pressed: Bool) -> Bool { + // It is necessary to calculate pos before pushing to dispatch queue + // Otherwise, we don't know whether to return false or true + guard let pos = TouchscreenMouseEventAdapter.cursorPos() else { return false } + if pressed { + return ActionDispatcher.dispatch(key: KeyCodeNames.fakeMouse, valueX: pos.x, valueY: pos.y) + } else { + return ActionDispatcher.dispatch(key: KeyCodeNames.fakeMouse, pressed: pressed) + } + } + + public func handleOtherButton(id: Int, pressed: Bool) -> Bool { + ActionDispatcher.dispatch(key: EditorMouseEventAdapter.getMouseButtonName(id), + pressed: pressed) + } + + public func cursorHidden() -> Bool { + false + } + +} diff --git a/PlayTools/Controls/Frontend/EventAdapter/Mouse/Instances/TransparentMouseEventAdapter.swift b/PlayTools/Controls/Frontend/EventAdapter/Mouse/Instances/TransparentMouseEventAdapter.swift new file mode 100644 index 00000000..590907f3 --- /dev/null +++ b/PlayTools/Controls/Frontend/EventAdapter/Mouse/Instances/TransparentMouseEventAdapter.swift @@ -0,0 +1,34 @@ +// +// TransparentMouseEventAdapter.swift +// PlayTools +// +// Created by 许沂聪 on 2023/9/16. +// + +import Foundation + +// Mouse events handler when keymapping is disabled + +public class TransparentMouseEventAdapter: MouseEventAdapter { + public func handleScrollWheel(deltaX: CGFloat, deltaY: CGFloat) -> Bool { + // When editing text, scroll should effect on text input box + false + } + + public func handleMove(deltaX: CGFloat, deltaY: CGFloat) -> Bool { + false + } + + public func handleLeftButton(pressed: Bool) -> Bool { + false + } + + public func handleOtherButton(id: Int, pressed: Bool) -> Bool { + false + } + + public func cursorHidden() -> Bool { + false + } + +} diff --git a/PlayTools/Controls/Frontend/EventAdapter/Mouse/MouseEventAdapter.swift b/PlayTools/Controls/Frontend/EventAdapter/Mouse/MouseEventAdapter.swift new file mode 100644 index 00000000..bb2f5279 --- /dev/null +++ b/PlayTools/Controls/Frontend/EventAdapter/Mouse/MouseEventAdapter.swift @@ -0,0 +1,19 @@ +// +// MouseEventAdapter.swift +// PlayTools +// +// Created by 许沂聪 on 2023/9/16. +// + +import Foundation + +// All mouse events under any mode + +public protocol MouseEventAdapter: EventAdapter { + func cursorHidden() -> Bool + + func handleScrollWheel(deltaX: CGFloat, deltaY: CGFloat) -> Bool + func handleMove(deltaX: CGFloat, deltaY: CGFloat) -> Bool + func handleLeftButton(pressed: Bool) -> Bool + func handleOtherButton(id: Int, pressed: Bool) -> Bool +} diff --git a/PlayTools/Controls/Frontend/ModeAutomaton.swift b/PlayTools/Controls/Frontend/ModeAutomaton.swift new file mode 100644 index 00000000..b19b69dd --- /dev/null +++ b/PlayTools/Controls/Frontend/ModeAutomaton.swift @@ -0,0 +1,60 @@ +// +// ModeAutomaton.swift +// PlayTools +// +// Created by 许沂聪 on 2023/9/17. +// + +import Foundation + +// This class manages control mode transitions + +public class ModeAutomaton { + static public func onOption() -> Bool { + if mode == ControlMode.EDITOR || mode == ControlMode.TEXT_INPUT { + return false + } + if mode == ControlMode.OFF { + mode.set(ControlMode.CAMERA_ROTATE) + + } else if mode == ControlMode.ARBITRARY_CLICK && ActionDispatcher.cursorHideNecessary { + mode.set(ControlMode.CAMERA_ROTATE) + + } else if mode == ControlMode.CAMERA_ROTATE { + if PlaySettings.shared.noKMOnInput { + mode.set(ControlMode.ARBITRARY_CLICK) + } else { + mode.set(ControlMode.OFF) + } + } + // Some people want option key act as touchpad-touchscreen mapper + return false + } + + static public func onCmdK() { + EditorController.shared.switchMode() + + if mode == ControlMode.EDITOR && !EditorController.shared.editorMode { + mode.set(ControlMode.CAMERA_ROTATE) + ActionDispatcher.build() + Toucher.writeLog(logMessage: "editor closed") + } else if EditorController.shared.editorMode { + mode.set(ControlMode.EDITOR) + Toucher.writeLog(logMessage: "editor opened") + } + } + + static public func onKeyboardShow() { + if mode == ControlMode.EDITOR { + return + } + mode.set(ControlMode.TEXT_INPUT) + } + + static public func onKeyboardHide() { + if mode == ControlMode.EDITOR { + return + } + mode.set(ControlMode.ARBITRARY_CLICK) + } +} diff --git a/PlayTools/Controls/MenuController.swift b/PlayTools/Controls/MenuController.swift index 1da0786c..80a5608d 100644 --- a/PlayTools/Controls/MenuController.swift +++ b/PlayTools/Controls/MenuController.swift @@ -27,7 +27,7 @@ class RotateViewController: UIViewController { extension UIApplication { @objc func switchEditorMode(_ sender: AnyObject) { - EditorController.shared.switchMode() + ModeAutomaton.onCmdK() } @objc diff --git a/PlayTools/Controls/PlayController.swift b/PlayTools/Controls/PlayController.swift deleted file mode 100644 index 64c641ec..00000000 --- a/PlayTools/Controls/PlayController.swift +++ /dev/null @@ -1,121 +0,0 @@ -// -// PlayController.swift -// PlayTools -// -// Created by 许沂聪 on 2023/4/21. -// - -import Foundation -import GameController - -class PlayController { - private static var directionPadXValue: Float = 0, - directionPadYValue: Float = 0, - thumbstickCursorControl: [String: (((CGFloat, CGFloat) -> Void)?, CGFloat, CGFloat) -> Void] - = ["Left Thumbstick": ThumbstickCursorControl().update, "Right Thumbstick": ThumbstickCursorControl().update] - - public static func initialize() { - GCController.current?.extendedGamepad?.valueChangedHandler = handleEvent - } - - static func handleEditorEvent(_ profile: GCExtendedGamepad, _ element: GCControllerElement) { - // This is the index of controller buttons, which is String, not Int - var alias: String = element.aliases.first! - if alias == "Direction Pad" { - guard let dpadElement = element as? GCControllerDirectionPad else { - Toast.showOver(msg: "cannot map direction pad: element type not recognizable") - return - } - if dpadElement.xAxis.value > 0 { - alias = dpadElement.right.aliases.first! - } else if dpadElement.xAxis.value < 0 { - alias = dpadElement.left.aliases.first! - } - if dpadElement.yAxis.value > 0 { - alias = dpadElement.down.aliases.first! - } else if dpadElement.yAxis.value < 0 { - alias = dpadElement.up.aliases.first! - } - } - EditorController.shared.setKey(alias) - } - - static func handleEvent(_ profile: GCExtendedGamepad, _ element: GCControllerElement) { - let name: String = element.aliases.first! - if let buttonElement = element as? GCControllerButtonInput { - guard let handlers = PlayInput.buttonHandlers[name] else { return } -// Toast.showOver(msg: name + ": \(buttonElement.isPressed)") - for handler in handlers { - handler(buttonElement.isPressed) - } - } else if let dpadElement = element as? GCControllerDirectionPad { - PlayController.handleDirectionPad(profile, dpadElement) - } else { - Toast.showOver(msg: "unrecognised controller element input happens") - } - } - public static func handleDirectionPad(_ profile: GCExtendedGamepad, _ dpad: GCControllerDirectionPad) { - let name = dpad.aliases.first! - let xAxis = dpad.xAxis, yAxis = dpad.yAxis - if name == "Direction Pad" { - if (xAxis.value > 0) != (directionPadXValue > 0) { - PlayController.handleEvent(profile, dpad.right) - } - if (xAxis.value < 0) != (directionPadXValue < 0) { - PlayController.handleEvent(profile, dpad.left) - } - if (yAxis.value > 0) != (directionPadYValue > 0) { - PlayController.handleEvent(profile, dpad.up) - } - if (yAxis.value < 0) != (directionPadYValue < 0) { - PlayController.handleEvent(profile, dpad.down) - } - directionPadXValue = xAxis.value - directionPadYValue = yAxis.value - return - } - let deltaX = xAxis.value, deltaY = yAxis.value - let cgDx = CGFloat(deltaX) - let cgDy = CGFloat(deltaY) - thumbstickCursorControl[name]!( - PlayInput.draggableHandler[name] ?? PlayInput.cameraMoveHandler[name], cgDx * 6, cgDy * 6) - PlayInput.joystickHandler[name]?(cgDx, cgDy) - } -} - -class ThumbstickCursorControl { - private var thumbstickVelocity: CGVector = CGVector.zero, - thumbstickPolling: Bool = false, - eventHandler: ((CGFloat, CGFloat) -> Void)! - - static private func isVectorSignificant(_ vector: CGVector) -> Bool { - return vector.dx.magnitude + vector.dy.magnitude > 0.2 - } - - public func update(handler: ((CGFloat, CGFloat) -> Void)?, velocityX: CGFloat, velocityY: CGFloat) { - guard let hdlr = handler else { - if thumbstickPolling { - self.thumbstickVelocity.dx = 0 - self.thumbstickVelocity.dy = 0 - } - return - } - self.eventHandler = hdlr - self.thumbstickVelocity.dx = velocityX - self.thumbstickVelocity.dy = velocityY - if !thumbstickPolling { - PlayInput.touchQueue.async(execute: self.thumbstickPoll) - self.thumbstickPolling = true - } - } - - private func thumbstickPoll() { - if !ThumbstickCursorControl.isVectorSignificant(self.thumbstickVelocity) { - self.thumbstickPolling = false - return - } - self.eventHandler(self.thumbstickVelocity.dx, self.thumbstickVelocity.dy) - PlayInput.touchQueue.asyncAfter( - deadline: DispatchTime.now() + 0.017, execute: self.thumbstickPoll) - } -} diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 754f2b51..bcd6c339 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -1,99 +1,14 @@ import Foundation -import GameController import UIKit +// This class is a coordinator (and module entrance), coordinating other concrete classes + class PlayInput { static let shared = PlayInput() - var actions = [Action]() - static var shouldLockCursor = true - static var touchQueue = DispatchQueue.init(label: "playcover.toucher", qos: .userInteractive, + static var touchQueue = DispatchQueue.init(label: "playcover.toucher", + qos: .userInteractive, autoreleaseFrequency: .workItem) - static public var buttonHandlers: [String: [(Bool) -> Void]] = [:], - draggableHandler: [String: (CGFloat, CGFloat) -> Void] = [:], - cameraMoveHandler: [String: (CGFloat, CGFloat) -> Void] = [:], - cameraScaleHandler: [String: (CGFloat, CGFloat) -> Void] = [:], - joystickHandler: [String: (CGFloat, CGFloat) -> Void] = [:] - - func invalidate() { - // this is called whenever keymapping disabled, to release all mapping resource - for action in self.actions { - action.invalidate() - } - } - - static public func registerButton(key: String, handler: @escaping (Bool) -> Void) { - // this function is called when setting up `button` type of mapping - if "LMB" == key { - PlayInput.shouldLockCursor = true - } - if PlayInput.buttonHandlers[key] == nil { - PlayInput.buttonHandlers[key] = [] - } - PlayInput.buttonHandlers[key]!.append(handler) - } - - func parseKeymap() { - actions = [PlayMice.shared] - PlayInput.buttonHandlers.removeAll(keepingCapacity: true) - // `shouldLockCursor` is used to disable `option` toggle when there is no mouse mapping - // but in the case this new feature disabled, `option` should always function. - // this variable is initilized here to be checked for mouse mapping later. - // intialize it to the reverse of the new feature's enable state makes - // it always true if the new feature is disabled, as it won't be set false in - // any case anywhere else in this case. - PlayInput.shouldLockCursor = !PlaySettings.shared.noKMOnInput - for button in keymap.keymapData.buttonModels { - actions.append(ButtonAction(data: button)) - } - - for draggableButton in keymap.keymapData.draggableButtonModels { - actions.append(DraggableButtonAction(data: draggableButton)) - } - - for mouse in keymap.keymapData.mouseAreaModel { - actions.append(CameraAction(data: mouse)) - } - - for joystick in keymap.keymapData.joystickModel { - // Left Thumbstick, Right Thumbstick, Mouse - if joystick.keyName.contains(Character("u")) { - actions.append(ContinuousJoystickAction(data: joystick)) - } else { // Keyboard - actions.append(JoystickAction(data: joystick)) - } - } - if !PlayInput.shouldLockCursor { - PlayInput.shouldLockCursor = PlayMice.shared.mouseMovementMapped() - } - } - - public func toggleEditor(show: Bool) { - mode.setMapping(!show) - Toucher.writeLog(logMessage: "editor opened? \(show)") - if show { - self.invalidate() - // there is no special reason to use GC API for editor, instead of NSEvents. - // just voider did this and I'm not changing it yet. - if let keyboard = GCKeyboard.coalesced!.keyboardInput { - keyboard.keyChangedHandler = { _, _, keyCode, pressed in - PlayKeyboard.handleEditorEvent(keyCode: keyCode, pressed: pressed) - } - } - if let controller = GCController.current?.extendedGamepad { - controller.valueChangedHandler = PlayController.handleEditorEvent - } - } else { - GCKeyboard.coalesced?.keyboardInput?.keyChangedHandler = nil - PlayController.initialize() - parseKeymap() - _ = ControlMode.trySwap() - } - } - - static public func cmdPressed() -> Bool { - return AKInterface.shared!.cmdPressed - } func initialize() { if !PlaySettings.shared.keymapping { @@ -103,92 +18,19 @@ class PlayInput { let centre = NotificationCenter.default let main = OperationQueue.main - centre.addObserver(forName: NSNotification.Name.GCControllerDidConnect, object: nil, queue: main) { _ in - if EditorController.shared.editorMode { - self.toggleEditor(show: true) - } else { - PlayController.initialize() - } - } - parseKeymap() centre.addObserver(forName: NSNotification.Name(rawValue: "NSWindowDidBecomeKeyNotification"), object: nil, queue: main) { _ in - if !mode.visible { + if mode.cursorHidden() { AKInterface.shared!.warpCursor() } } + DispatchQueue.main.asyncAfter(deadline: .now() + 5, qos: .utility) { - if !mode.visible || self.actions.count <= 0 || !PlayInput.shouldLockCursor { + if mode.cursorHidden() || !ActionDispatcher.cursorHideNecessary { return } Toast.initialize() } - PlayKeyboard.initialize() - PlayMice.shared.initialize() - - } -} - -class PlayKeyboard { - public static func handleEditorEvent(keyCode: GCKeyCode, pressed: Bool) { - if !PlayInput.cmdPressed() - && !PlayKeyboard.FORBIDDEN.contains(keyCode) - && KeyCodeNames.keyCodes[keyCode.rawValue] != nil { - EditorController.shared.setKey(keyCode.rawValue) - } - } - - private static let FORBIDDEN: [GCKeyCode] = [ - .leftGUI, - .rightGUI, - .leftAlt, - .rightAlt, - .printScreen - ] - - static func handleEvent(_ keyCode: UInt16, _ pressed: Bool) -> Bool { - let name = KeyCodeNames.virtualCodes[keyCode] ?? "Btn" - guard let handlers = PlayInput.buttonHandlers[name] else { - return false - } - var mapped = false - for handler in handlers { - PlayInput.touchQueue.async(qos: .userInteractive, execute: { - handler(pressed) - }) - mapped = true - } - return mapped - } - - public static func initialize() { - let centre = NotificationCenter.default - let main = OperationQueue.main - if PlaySettings.shared.noKMOnInput { - centre.addObserver(forName: UIApplication.keyboardDidHideNotification, object: nil, queue: main) { _ in - mode.setMapping(true) - Toucher.writeLog(logMessage: "virtual keyboard did hide") - } - centre.addObserver(forName: UIApplication.keyboardWillShowNotification, object: nil, queue: main) { _ in - mode.setMapping(false) - Toucher.writeLog(logMessage: "virtual keyboard will show") - } - } else { - // we want to initialize keymapping to false - mode.setMapping(false) - } - AKInterface.shared!.setupKeyboard(keyboard: {keycode, pressed, isRepeat in - if !mode.keyboardMapped { - // explicitly ignore repeated Enter key - return isRepeat && keycode == 36 - } - if isRepeat { - return true - } - let mapped = PlayKeyboard.handleEvent(keycode, pressed) - return mapped - }, // passing the function to be called when `option` pressed. - // return `true` meaning this key press is consumed, `false` dispatching it to the App - swapMode: ControlMode.trySwap) + mode.initialize() } } diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift deleted file mode 100644 index d56c9a19..00000000 --- a/PlayTools/Controls/PlayMice.swift +++ /dev/null @@ -1,300 +0,0 @@ -// -// PlayMice.swift -// PlayTools -// - -import Foundation - -public class PlayMice: Action { - - public static let shared = PlayMice() - public static let elementName = "Mouse" - - public func initialize() { - setupLeftButton() - setupMouseButton(right: true) - setupMouseButton(right: false) - AKInterface.shared!.setupMouseMoved(mouseMoved: {deltaX, deltaY in - // this closure's return value only takes effect when any mouse button pressed - if !mode.keyboardMapped { - return false - } - PlayInput.touchQueue.async(qos: .userInteractive, execute: { - self.handleMouseMoved(deltaX: deltaX, deltaY: deltaY) - }) - return true - }) - AKInterface.shared!.setupScrollWheel({deltaX, deltaY in - if let cameraScale = PlayInput.cameraScaleHandler[PlayMice.elementName] { - cameraScale(deltaX, deltaY) - let eventConsumed = !mode.visible - return eventConsumed - } - return false - }) - } - - var fakedMouseTouchPointId: Int? - var fakedMousePressed: Bool {fakedMouseTouchPointId != nil} - - public func mouseMovementMapped() -> Bool { - // this is called from `parseKeymap` to set `shouldLockCursor`'s value - for handler in [PlayInput.cameraMoveHandler, PlayInput.joystickHandler] - where handler[PlayMice.elementName] != nil { - return true - } - return false - } - - public func cursorPos() -> CGPoint? { - var point = AKInterface.shared!.mousePoint - let rect = AKInterface.shared!.windowFrame - if rect.width < 1 || rect.height < 1 { - return nil - } - let viewRect: CGRect = screen.screenRect - let widthRate = viewRect.width / rect.width - var rate = viewRect.height / rect.height - if widthRate > rate { - // Keep aspect ratio - rate = widthRate - } - if screen.fullscreen { - // Vertically in center - point.y -= (rect.height - viewRect.height / rate)/2 - } - point.y *= rate - point.y = viewRect.height - point.y - // For traffic light buttons when not fullscreen - if point.y < 0 { - return nil - } - // Horizontally in center - point.x -= (rect.width - viewRect.width / rate)/2 - point.x *= rate - return point - } - - public func handleMouseMoved(deltaX: CGFloat, deltaY: CGFloat) { - let sensy = CGFloat(PlaySettings.shared.sensitivity * 0.6) - let cgDx = deltaX * sensy, - cgDy = -deltaY * sensy - let name = PlayMice.elementName - if let draggableUpdate = PlayInput.draggableHandler[name] { - draggableUpdate(cgDx, cgDy) - } else if mode.visible { - if self.fakedMousePressed { - if let pos = self.cursorPos() { - Toucher.touchcam(point: pos, phase: UITouch.Phase.moved, tid: &fakedMouseTouchPointId) - } - } - } else { - PlayInput.cameraMoveHandler[name]?(cgDx, cgDy) - PlayInput.joystickHandler[name]?(cgDx, cgDy) - } - } - - private func setupMouseButton(right: Bool) { - let keyCode = right ? -2 : -3 - guard let keyName = KeyCodeNames.keyCodes[keyCode] else { - Toast.showHint(title: "Failed initializing \(right ? "right" : "other") mouse button input") - return - } - AKInterface.shared!.setupMouseButton(left: false, right: right) {pressed in - if mode.keyboardMapped { // if mapping - if let handlers = PlayInput.buttonHandlers[keyName] { - PlayInput.touchQueue.async(qos: .userInteractive, execute: { - for handler in handlers { - handler(pressed) - } - }) - // if mapped to any button, consumed and dispatch - return false - } - // if not mapped, transpass to app - return true - } else if EditorController.shared.editorMode { // if editor is open, consumed and set button - if pressed { - // asynced to return quickly. this branch contains UI operation so main queue. - // main queue is fine. should not be slower than keyboard - DispatchQueue.main.async(qos: .userInteractive, execute: { - EditorController.shared.setKey(keyCode) - Toucher.writeLog(logMessage: "mouse button editor set") - }) - } - return false - } else { // if typing, transpass event to app - Toucher.writeLog(logMessage: "mouse button pressed? \(pressed)") - return true - } - } - } - // using high priority event handlers to prevent lag and stutter in demanding games - // but no free lunch. high priority handlers cannot execute for too long - // exceeding the time limit causes even more lag - private func setupLeftButton() { - AKInterface.shared!.setupMouseButton(left: true, right: false) {pressed in - if !mode.keyboardMapped { - Toucher.writeLog(logMessage: "left button pressed? \(pressed)") - return true - } - guard let curPos = self.cursorPos() else { return true } - PlayInput.touchQueue.async(qos: .userInteractive, execute: { - // considering cases where cursor becomes hidden while holding left button - if self.fakedMousePressed { - Toucher.touchcam(point: curPos, phase: UITouch.Phase.ended, tid: &self.fakedMouseTouchPointId) - return - } - if mode.visible && pressed { - Toucher.touchcam(point: curPos, - phase: UITouch.Phase.began, - tid: &self.fakedMouseTouchPointId) - return - } - // considering cases where cursor becomes visible while holding left button - if let handlers = PlayInput.buttonHandlers["LMB"] { - for handler in handlers { - handler(pressed) - } - return - } - }) - return false - } - } - // For all other actions, this is a destructor. should release held resources. - func invalidate() { - Toucher.touchcam(point: self.cursorPos() ?? CGPoint(x: 10, y: 10), - phase: UITouch.Phase.ended, tid: &self.fakedMouseTouchPointId) - } -} - -class CameraAction: Action { - var swipeMove, swipeScale1, swipeScale2: SwipeAction - static var swipeDrag = SwipeAction() - var key: String! - var center: CGPoint - var distance1: CGFloat = 100, distance2: CGFloat = 100 - init(data: MouseArea) { - self.key = data.keyName - let centerX = data.transform.xCoord.absoluteX - let centerY = data.transform.yCoord.absoluteY - center = CGPoint(x: centerX, y: centerY) - swipeMove = SwipeAction() - swipeScale1 = SwipeAction() - swipeScale2 = SwipeAction() - PlayInput.cameraMoveHandler[key] = self.moveUpdated - PlayInput.cameraScaleHandler[PlayMice.elementName] = {deltaX, deltaY in - PlayInput.touchQueue.async(qos: .userInteractive, execute: { - if mode.visible { - CameraAction.dragUpdated(deltaX, deltaY) - } else { - self.scaleUpdated(deltaX, deltaY) - } - }) - } - } - func moveUpdated(_ deltaX: CGFloat, _ deltaY: CGFloat) { - swipeMove.move(from: {return center}, deltaX: deltaX, deltaY: deltaY) - } - - func scaleUpdated(_ deltaX: CGFloat, _ deltaY: CGFloat) { - let distance = distance1 + distance2 - let moveY = deltaY * (distance / 100.0) - distance1 += moveY - distance2 += moveY - - swipeScale1.move(from: { - self.distance1 = 100 - return CGPoint(x: center.x, y: center.y - 100) - }, deltaX: 0, deltaY: moveY) - - swipeScale2.move(from: { - self.distance2 = 100 - return CGPoint(x: center.x, y: center.y + 100) - }, deltaX: 0, deltaY: -moveY) - } - - static func dragUpdated(_ deltaX: CGFloat, _ deltaY: CGFloat) { - swipeDrag.move(from: PlayMice.shared.cursorPos, deltaX: deltaX * 4, deltaY: -deltaY * 4) - } - - func invalidate() { - PlayInput.cameraMoveHandler.removeValue(forKey: key) - // when noKMOnInput is false, swipe/pan gesture handler would be invalidated when keymapping disabled. - // as it's just a temporary toggle, not fixing it. - // but should remove that toggle as long as new feature considered stable. - PlayInput.cameraScaleHandler[PlayMice.elementName] = nil - swipeMove.invalidate() - swipeScale1.invalidate() - swipeScale2.invalidate() - } -} - -class SwipeAction: Action { - var location: CGPoint = CGPoint.zero - private var id: Int? - let timer = DispatchSource.makeTimerSource(flags: [], queue: PlayInput.touchQueue) - init() { - timer.schedule(deadline: DispatchTime.now() + 1, repeating: 0.1, leeway: DispatchTimeInterval.milliseconds(50)) - timer.setEventHandler(qos: .userInteractive, handler: self.checkEnded) - timer.activate() - timer.suspend() - } - - func delay(_ delay: Double, closure: @escaping () -> Void) { - let when = DispatchTime.now() + delay - PlayInput.touchQueue.asyncAfter(deadline: when, execute: closure) - } - // Count swipe duration - var counter = 0 - // if should wait before beginning next touch - var cooldown = false - var lastCounter = 0 - - func checkEnded() { - if self.counter == self.lastCounter { - if self.counter < 4 { - counter += 1 - } else { - timer.suspend() - self.doLiftOff() - } - } - self.lastCounter = self.counter - } - - public func move(from: () -> CGPoint?, deltaX: CGFloat, deltaY: CGFloat) { - if id == nil { - if cooldown { - return - } - guard let start = from() else {return} - location = start - counter = 0 - Toucher.touchcam(point: location, phase: UITouch.Phase.began, tid: &id) - timer.resume() - } - // count touch duration - counter += 1 - self.location.x += deltaX - self.location.y -= deltaY - Toucher.touchcam(point: self.location, phase: UITouch.Phase.moved, tid: &id) - } - - public func doLiftOff() { - if id == nil { - return - } - Toucher.touchcam(point: self.location, phase: UITouch.Phase.ended, tid: &id) - delay(0.02) { - self.cooldown = false - } - cooldown = true - } - - func invalidate() { - timer.cancel() - self.doLiftOff() - } -} diff --git a/PlayTools/Keymap/EditorController.swift b/PlayTools/Keymap/EditorController.swift index 3b44ee66..70eccdcc 100644 --- a/PlayTools/Keymap/EditorController.swift +++ b/PlayTools/Keymap/EditorController.swift @@ -58,12 +58,10 @@ class EditorController { // menu still holds this object until next responder hit test editorWindow = nil previousWindow?.makeKeyAndVisible() - PlayInput.shared.toggleEditor(show: false) focusedControl = nil Toast.showHint(title: NSLocalizedString("hint.keymapSaved", tableName: "Playtools", value: "Keymap Saved", comment: "")) } else { - PlayInput.shared.toggleEditor(show: true) previousWindow = screen.keyWindow editorWindow = initWindow() editorWindow?.makeKeyAndVisible() diff --git a/PlayTools/Keymap/KeyCodeNames.swift b/PlayTools/Keymap/KeyCodeNames.swift index 4e62696b..fc953d3c 100644 --- a/PlayTools/Keymap/KeyCodeNames.swift +++ b/PlayTools/Keymap/KeyCodeNames.swift @@ -2,6 +2,16 @@ class KeyCodeNames { public static let defaultCode = -10 + public static let leftMouseButton = "LMB" + public static let rightMouseButton = "RMB" + public static let middleMouseButton = "MMB" + public static let mouseMove = "Mouse" + + // Internal used names, not stored to keymap + public static let scrollWheelScale = "ScrollWheelScale" + public static let scrollWheelDrag = "ScrollWheelDrag" + public static let fakeMouse = "FakeMouse" + public static let keyCodes = [ -4: "cA", -5: "cX", @@ -177,6 +187,85 @@ public static let virtualCodes: [UInt16: String] = [ 54: "RCmd", 59: "LCtrl", 62: "RCtrl" +] + public static let mapNSEventVirtualCodeToGCKeyCodeRawValue: [UInt16: Int] = [ + 0: 4, + 1: 22, + 2: 7, + 3: 9, + 4: 11, + 5: 10, + 6: 29, + 7: 27, + 8: 6, + 9: 25, + 11: 5, + 12: 20, + 13: 26, + 14: 8, + 15: 21, + 16: 28, + 17: 23, + 18: 30, + 19: 31, + 20: 32, + 21: 33, + 22: 35, + 23: 34, + 24: 46, + 25: 38, + 26: 36, + 27: 45, + 28: 37, + 29: 39, + 30: 48, + 31: 18, + 32: 24, + 33: 47, + 34: 12, + 35: 19, + 36: 40, + 37: 15, + 38: 13, + 39: 52, + 40: 14, + 41: 51, + 42: 49, + 43: 54, + 44: 56, + 45: 17, + 46: 16, + 47: 55, + 48: 43, + 49: 44, + 50: 53, + 51: 42, + 53: 41, + 54: 231, + 55: 227, + 56: 225, + 57: 57, + 58: 226, + 59: 224, + 60: 229, + 61: 230, + 62: 228, + 96: 62, + 97: 63, + 98: 64, + 99: 60, + 100: 65, + 101: 66, + 103: 68, + 109: 67, + 111: 69, + 118: 61, + 120: 59, + 122: 58, + 123: 80, + 124: 79, + 125: 81, + 126: 82 ] } let mapGCKeyCodeRawValuetoNSEventVirtualCode = [ diff --git a/Plugin.swift b/Plugin.swift index 56bbe84d..f10429c1 100644 --- a/Plugin.swift +++ b/Plugin.swift @@ -25,8 +25,8 @@ public protocol Plugin: NSObjectProtocol { func terminateApplication() func setupKeyboard(keyboard: @escaping(UInt16, Bool, Bool) -> Bool, swapMode: @escaping() -> Bool) - func setupMouseMoved(mouseMoved: @escaping(CGFloat, CGFloat) -> Bool) - func setupMouseButton(left: Bool, right: Bool, _ dontIgnore: @escaping(Bool) -> Bool) + func setupMouseMoved(_ mouseMoved: @escaping(CGFloat, CGFloat) -> Bool) + func setupMouseButton(left: Bool, right: Bool, _ consumed: @escaping(Int, Bool) -> Bool) func setupScrollWheel(_ onMoved: @escaping(CGFloat, CGFloat) -> Bool) func urlForApplicationWithBundleIdentifier(_ value: String) -> URL? func setMenuBarVisible(_ value: Bool) From 5f0ce6cd26a90e98e0820dbc63015c027e8aceee Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Thu, 21 Sep 2023 01:50:30 +0800 Subject: [PATCH 70/92] fix cmd+K may change selected element to K --- .../Keyboard/Instances/EditorKeyboardEventAdapter.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlayTools/Controls/Frontend/EventAdapter/Keyboard/Instances/EditorKeyboardEventAdapter.swift b/PlayTools/Controls/Frontend/EventAdapter/Keyboard/Instances/EditorKeyboardEventAdapter.swift index 60681287..796c553b 100644 --- a/PlayTools/Controls/Frontend/EventAdapter/Keyboard/Instances/EditorKeyboardEventAdapter.swift +++ b/PlayTools/Controls/Frontend/EventAdapter/Keyboard/Instances/EditorKeyboardEventAdapter.swift @@ -20,7 +20,7 @@ public class EditorKeyboardEventAdapter: KeyboardEventAdapter { ] public func handleKey(keycode: UInt16, pressed: Bool, isRepeat: Bool) -> Bool { - if AKInterface.shared!.cmdPressed { + if AKInterface.shared!.cmdPressed || !pressed || isRepeat { return false } guard let rawValue = KeyCodeNames.mapNSEventVirtualCodeToGCKeyCodeRawValue[keycode] else { From 0a0a2f8c5d5e2759bc66cc6c810d411e466775e9 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Fri, 22 Sep 2023 13:47:22 +0700 Subject: [PATCH 71/92] fix: temporarily disable extra jb bypass for release --- PlayTools/MysticRunes/PlayShadow.m | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index 7ecc9572..8e2d5160 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -159,7 +159,10 @@ @implementation PlayShadowLoader + (void) load { [self debugLogger:@"PlayShadow is now loading"]; - if ([[PlaySettings shared] bypass]) [self loadJailbreakBypass]; + // Gate this behind an environment variable + if ([[NSProcessInfo processInfo].environment[@"USE_EXTRA_ANTIJB"] isEqualToString:@"1"]) { + [self loadJailbreakBypass]; + } // if ([[PlaySettings shared] bypass]) [self loadEnvironmentBypass]; # disabled as it might be too powerful // Swizzle ATTrackingManager From 98a7384306d78da8b63619d8234387e883945cf8 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Mon, 25 Sep 2023 08:38:01 +0800 Subject: [PATCH 72/92] fix dispatcher data race --- PlayTools/Controls/ActionDispatcher.swift | 84 ++++++++++++++++++----- 1 file changed, 66 insertions(+), 18 deletions(-) diff --git a/PlayTools/Controls/ActionDispatcher.swift b/PlayTools/Controls/ActionDispatcher.swift index 73b81c91..17a5470d 100644 --- a/PlayTools/Controls/ActionDispatcher.swift +++ b/PlayTools/Controls/ActionDispatcher.swift @@ -6,6 +6,7 @@ // import Foundation +import Atomics // If the same key is mapped to multiple different tasks, distinguish by priority public class ActionDispatchPriority { @@ -19,18 +20,26 @@ public class ActionDispatchPriority { public class ActionDispatcher { static private let keymapVersion = "2.0." static private var actions = [Action]() + static private var buttonHandlers: [String: [(Bool) -> Void]] = [:] + static private let PRIORITY_COUNT = 3 - static private var buttonHandlers: [String: [(Bool) -> Void]] = [:], - directionPadHandlers: [[String: (CGFloat, CGFloat) -> Void]] = Array( - repeating: [:], count: PRIORITY_COUNT) + // You can't put more than 8 cameras or 8 joysticks in a keymap right? + static private let MAPPING_COUNT_PER_PRIORITY = 8 + static private let directionPadHandlers: [[ManagedAtomic]] = Array( + (0..(.EMPTY)}) + }) + ) static private func clear() { invalidateActions() actions = [] buttonHandlers.removeAll(keepingCapacity: true) - for priority in 0.. Void, priority: Int = ActionDispatchPriority.DEFAULT) { - directionPadHandlers[priority][key] = handler + let atomicHandler = directionPadHandlers[priority].first(where: { handler in + handler.load(ordering: .relaxed).key == key + }) ?? + directionPadHandlers[priority].first(where: { handler in + handler.load(ordering: .relaxed).key.isEmpty + }) +// DispatchQueue.main.async { +// if screen.keyWindow == nil { +// return +// } +// Toast.showHint(title: "register", +// text: ["key: \(key), atomicHandler: \(String(describing: atomicHandler))"]) +// } + atomicHandler?.store(AtomicHandler(key, handler), ordering: .releasing) } static public func unregister(key: String) { // Only draggable can be unregistered - directionPadHandlers[ActionDispatchPriority.DRAGGABLE].removeValue(forKey: key) + let atomicHandler = directionPadHandlers[ActionDispatchPriority.DRAGGABLE].first(where: { handler in + handler.load(ordering: .relaxed).key == key + }) +// DispatchQueue.main.async { +// if screen.keyWindow == nil { +// return +// } +// Toast.showHint(title: "unregister", +// text: ["key: \(key), atomicHandler: \(String(describing: atomicHandler))"]) +// } + atomicHandler?.store(.EMPTY, ordering: .releasing) } // Frontend interfaces @@ -124,8 +156,12 @@ public class ActionDispatcher { } static public func getDispatchPriority(key: String) -> Int? { - for priority in 0.. Bool { - // WARNING: if you want to change this, beware of concurrency contention - PlayInput.touchQueue.async(qos: .userInteractive, execute: { - for priority in 0.. Void + init(_ key: String, _ handle: @escaping (CGFloat, CGFloat) -> Void) { + self.key = key + self.handle = handle } } From 5213ae40c44590441cbe97afab945ae5deb5a986 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Mon, 25 Sep 2023 08:54:27 +0800 Subject: [PATCH 73/92] fix controller thumbstick camera mapping --- PlayTools/Controls/Backend/Action/PlayAction.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PlayTools/Controls/Backend/Action/PlayAction.swift b/PlayTools/Controls/Backend/Action/PlayAction.swift index c0e328c4..92cd81f5 100644 --- a/PlayTools/Controls/Backend/Action/PlayAction.swift +++ b/PlayTools/Controls/Backend/Action/PlayAction.swift @@ -270,7 +270,8 @@ class CameraAction: Action { swipeMove = SwipeAction() swipeScale1 = SwipeAction() swipeScale2 = SwipeAction() - ActionDispatcher.register(key: key, handler: self.moveUpdated) + ActionDispatcher.register(key: key, handler: self.moveUpdated, + priority: ActionDispatchPriority.CAMERA) ActionDispatcher.register(key: KeyCodeNames.scrollWheelScale, handler: self.scaleUpdated) ActionDispatcher.register(key: KeyCodeNames.scrollWheelDrag, From 6b50b31472d82f7d307e2ee167e63453ca53c9ac Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Tue, 26 Sep 2023 04:00:02 +0800 Subject: [PATCH 74/92] embrace enum --- PlayTools/Controls/ActionDispatcher.swift | 32 +++++------ .../Controls/Backend/Action/PlayAction.swift | 6 +-- PlayTools/Controls/Frontend/ControlMode.swift | 25 ++++----- .../TouchscreenControllerEventAdapter.swift | 2 +- .../Frontend/EventAdapter/EventAdapters.swift | 54 ++++++++----------- .../TouchscreenMouseEventAdapter.swift | 3 +- .../Controls/Frontend/ModeAutomaton.swift | 30 +++++------ 7 files changed, 67 insertions(+), 85 deletions(-) diff --git a/PlayTools/Controls/ActionDispatcher.swift b/PlayTools/Controls/ActionDispatcher.swift index 17a5470d..8071172e 100644 --- a/PlayTools/Controls/ActionDispatcher.swift +++ b/PlayTools/Controls/ActionDispatcher.swift @@ -9,10 +9,10 @@ import Foundation import Atomics // If the same key is mapped to multiple different tasks, distinguish by priority -public class ActionDispatchPriority { - static public let DRAGGABLE = 0 - static public let DEFAULT = 1 - static public let CAMERA = 2 +public enum ActionDispatchPriority: Int { + case DRAGGABLE + case DEFAULT + case CAMERA } // This class reads keymap and thereby dispatch events @@ -91,14 +91,8 @@ public class ActionDispatcher { // but in the case this new feature disabled, `option` should always function. // this variable is set here to be checked for mouse mapping later. cursorHideNecessary = - ( - getDispatchPriority(key: KeyCodeNames.leftMouseButton) ?? ActionDispatchPriority.DRAGGABLE - ) - > ActionDispatchPriority.DRAGGABLE || - ( - getDispatchPriority(key: KeyCodeNames.mouseMove) ?? ActionDispatchPriority.DRAGGABLE - ) - > ActionDispatchPriority.DRAGGABLE + (getDispatchPriority(key: KeyCodeNames.leftMouseButton) ?? .DRAGGABLE) != .DRAGGABLE || + (getDispatchPriority(key: KeyCodeNames.mouseMove) ?? .DRAGGABLE) != .DRAGGABLE } static public func register(key: String, handler: @escaping (Bool) -> Void) { @@ -111,11 +105,11 @@ public class ActionDispatcher { static public func register(key: String, handler: @escaping (CGFloat, CGFloat) -> Void, - priority: Int = ActionDispatchPriority.DEFAULT) { - let atomicHandler = directionPadHandlers[priority].first(where: { handler in + priority: ActionDispatchPriority = .DEFAULT) { + let atomicHandler = directionPadHandlers[priority.rawValue].first(where: { handler in handler.load(ordering: .relaxed).key == key }) ?? - directionPadHandlers[priority].first(where: { handler in + directionPadHandlers[priority.rawValue].first(where: { handler in handler.load(ordering: .relaxed).key.isEmpty }) // DispatchQueue.main.async { @@ -130,7 +124,7 @@ public class ActionDispatcher { static public func unregister(key: String) { // Only draggable can be unregistered - let atomicHandler = directionPadHandlers[ActionDispatchPriority.DRAGGABLE].first(where: { handler in + let atomicHandler = directionPadHandlers[ActionDispatchPriority.DRAGGABLE.rawValue].first(where: { handler in handler.load(ordering: .relaxed).key == key }) // DispatchQueue.main.async { @@ -155,18 +149,18 @@ public class ActionDispatcher { } } - static public func getDispatchPriority(key: String) -> Int? { + static public func getDispatchPriority(key: String) -> ActionDispatchPriority? { if let priority = directionPadHandlers.firstIndex(where: { handlers in handlers.contains(where: { handler in handler.load(ordering: .acquiring).key == key }) }) { // Toast.showHint(title: "\(key) priority", text: ["\(priority)"]) - return priority + return ActionDispatchPriority(rawValue: priority) } if buttonHandlers[key] != nil { - return ActionDispatchPriority.DEFAULT + return .DEFAULT } return nil } diff --git a/PlayTools/Controls/Backend/Action/PlayAction.swift b/PlayTools/Controls/Backend/Action/PlayAction.swift index 92cd81f5..f72dfcb6 100644 --- a/PlayTools/Controls/Backend/Action/PlayAction.swift +++ b/PlayTools/Controls/Backend/Action/PlayAction.swift @@ -63,7 +63,7 @@ class DraggableButtonAction: ButtonAction { self.releasePoint = point ActionDispatcher.register(key: KeyCodeNames.mouseMove, handler: self.onMouseMoved, - priority: ActionDispatchPriority.DRAGGABLE) + priority: .DRAGGABLE) if !mode.cursorHidden() { AKInterface.shared!.hideCursor() } @@ -271,7 +271,7 @@ class CameraAction: Action { swipeScale1 = SwipeAction() swipeScale2 = SwipeAction() ActionDispatcher.register(key: key, handler: self.moveUpdated, - priority: ActionDispatchPriority.CAMERA) + priority: .CAMERA) ActionDispatcher.register(key: KeyCodeNames.scrollWheelScale, handler: self.scaleUpdated) ActionDispatcher.register(key: KeyCodeNames.scrollWheelDrag, @@ -393,7 +393,7 @@ class FakeMouseAction: Action { Toucher.touchcam(point: pos, phase: UITouch.Phase.began, tid: &id) ActionDispatcher.register(key: KeyCodeNames.fakeMouse, handler: movementHandler, - priority: ActionDispatchPriority.DRAGGABLE) + priority: .DRAGGABLE) } func buttonLiftHandler(pressed: Bool) { diff --git a/PlayTools/Controls/Frontend/ControlMode.swift b/PlayTools/Controls/Frontend/ControlMode.swift index 993b8a12..3e8be384 100644 --- a/PlayTools/Controls/Frontend/ControlMode.swift +++ b/PlayTools/Controls/Frontend/ControlMode.swift @@ -8,18 +8,19 @@ import GameController let mode = ControlMode.mode +public enum ControlModeLiteral: String { + case TEXT_INPUT = "textInput" + case CAMERA_ROTATE = "cameraRotate" + case ARBITRARY_CLICK = "arbitraryClick" + case OFF = "off" + case EDITOR = "editor" +} // This class handles different control logic under different control mode public class ControlMode: Equatable { static public let mode = ControlMode() - static public let TEXT_INPUT = "textInput" - static public let CAMERA_ROTATE = "cameraRotate" - static public let ARBITRARY_CLICK = "arbitraryClick" - static public let OFF = "off" - static public let EDITOR = "editor" - - private var controlMode = ControlMode.OFF + private var controlMode = ControlModeLiteral.OFF private var keyboardAdapter: KeyboardEventAdapter! private var mouseAdapter: MouseEventAdapter! @@ -41,9 +42,9 @@ public class ControlMode: Equatable { ModeAutomaton.onKeyboardShow() Toucher.writeLog(logMessage: "virtual keyboard will show") } - set(ControlMode.ARBITRARY_CLICK) + set(.ARBITRARY_CLICK) } else { - set(ControlMode.OFF) + set(.OFF) } centre.addObserver(forName: NSNotification.Name.GCControllerDidConnect, object: nil, queue: main) { _ in @@ -79,7 +80,7 @@ public class ControlMode: Equatable { ActionDispatcher.build() } - public func set(_ mode: String) { + public func set(_ mode: ControlModeLiteral) { let wasHidden = mouseAdapter?.cursorHidden() ?? false let first = mouseAdapter == nil keyboardAdapter = EventAdapters.keyboard(controlMode: mode) @@ -110,11 +111,11 @@ public class ControlMode: Equatable { } } - public static func == (lhs: String, rhs: ControlMode) -> Bool { + public static func == (lhs: ControlModeLiteral, rhs: ControlMode) -> Bool { lhs == rhs.controlMode } - public static func == (lhs: ControlMode, rhs: String) -> Bool { + public static func == (lhs: ControlMode, rhs: ControlModeLiteral) -> Bool { rhs == lhs } diff --git a/PlayTools/Controls/Frontend/EventAdapter/Controller/Instances/TouchscreenControllerEventAdapter.swift b/PlayTools/Controls/Frontend/EventAdapter/Controller/Instances/TouchscreenControllerEventAdapter.swift index 23c5722e..f7943aa0 100644 --- a/PlayTools/Controls/Frontend/EventAdapter/Controller/Instances/TouchscreenControllerEventAdapter.swift +++ b/PlayTools/Controls/Frontend/EventAdapter/Controller/Instances/TouchscreenControllerEventAdapter.swift @@ -53,7 +53,7 @@ public class TouchscreenControllerEventAdapter: ControllerEventAdapter { let dispatchType = ActionDispatcher.getDispatchPriority(key: name) if dispatchType == nil { return - } else if dispatchType == ActionDispatchPriority.DEFAULT { + } else if dispatchType == .DEFAULT { _ = ActionDispatcher.dispatch(key: name, valueX: cgDx, valueY: cgDy) } else { if TouchscreenControllerEventAdapter.thumbstickCursorControl[name] == nil { diff --git a/PlayTools/Controls/Frontend/EventAdapter/EventAdapters.swift b/PlayTools/Controls/Frontend/EventAdapter/EventAdapters.swift index 99ea1aea..95371743 100644 --- a/PlayTools/Controls/Frontend/EventAdapter/EventAdapters.swift +++ b/PlayTools/Controls/Frontend/EventAdapter/EventAdapters.swift @@ -11,55 +11,43 @@ import Foundation public class EventAdapters { - static func keyboard(controlMode: String) -> KeyboardEventAdapter { - if controlMode == ControlMode.OFF || controlMode == ControlMode.TEXT_INPUT { + static func keyboard(controlMode: ControlModeLiteral) -> KeyboardEventAdapter { + switch controlMode { + case .OFF: fallthrough + case .TEXT_INPUT: return TransparentKeyboardEventAdapter() - - } else if controlMode == ControlMode.CAMERA_ROTATE || controlMode == ControlMode.ARBITRARY_CLICK { + case .CAMERA_ROTATE: fallthrough + case .ARBITRARY_CLICK: return TouchscreenKeyboardEventAdapter() - } else if controlMode == ControlMode.EDITOR { + case .EDITOR: return EditorKeyboardEventAdapter() - } else { - Toast.showHint(title: "Control mode switch error", - text: ["Cannot find keyboard event adapter for control mode " + controlMode]) - return TransparentKeyboardEventAdapter() } } - static func mouse(controlMode: String) -> MouseEventAdapter { - if controlMode == ControlMode.OFF || controlMode == ControlMode.TEXT_INPUT { + static func mouse(controlMode: ControlModeLiteral) -> MouseEventAdapter { + switch controlMode { + case .OFF: fallthrough + case .TEXT_INPUT: return TransparentMouseEventAdapter() - - } else if controlMode == ControlMode.CAMERA_ROTATE { + case .CAMERA_ROTATE: return CameraControlMouseEventAdapter() - - } else if controlMode == ControlMode.ARBITRARY_CLICK { + case .ARBITRARY_CLICK: return TouchscreenMouseEventAdapter() - - } else if controlMode == ControlMode.EDITOR { + case .EDITOR: return EditorMouseEventAdapter() - } else { - Toast.showHint(title: "Control mode switch error", - text: ["Cannot find mouse event adapter for control mode " + controlMode]) - return TouchscreenMouseEventAdapter() } } - static func controller(controlMode: String) -> ControllerEventAdapter { - if controlMode == ControlMode.OFF { + static func controller(controlMode: ControlModeLiteral) -> ControllerEventAdapter { + switch controlMode { + case .OFF: return TransparentControllerEventAdapter() - - } else if controlMode == ControlMode.CAMERA_ROTATE - || controlMode == ControlMode.ARBITRARY_CLICK - || controlMode == ControlMode.TEXT_INPUT { + case .TEXT_INPUT: fallthrough + case .CAMERA_ROTATE: fallthrough + case .ARBITRARY_CLICK: return TouchscreenControllerEventAdapter() - - } else if controlMode == ControlMode.EDITOR { + case .EDITOR: return EditorControllerEventAdapter() - } else { - Toast.showHint(title: "Control mode switch error", - text: ["Cannot find controller event adapter for control mode " + controlMode]) - return TouchscreenControllerEventAdapter() } } } diff --git a/PlayTools/Controls/Frontend/EventAdapter/Mouse/Instances/TouchscreenMouseEventAdapter.swift b/PlayTools/Controls/Frontend/EventAdapter/Mouse/Instances/TouchscreenMouseEventAdapter.swift index 3e84a191..325c5007 100644 --- a/PlayTools/Controls/Frontend/EventAdapter/Mouse/Instances/TouchscreenMouseEventAdapter.swift +++ b/PlayTools/Controls/Frontend/EventAdapter/Mouse/Instances/TouchscreenMouseEventAdapter.swift @@ -54,8 +54,7 @@ public class TouchscreenMouseEventAdapter: MouseEventAdapter { // draggable direction pad: move handler // default button: lift handler // kinda hacky but.. IT WORKS! - if ActionDispatcher.getDispatchPriority(key: KeyCodeNames.fakeMouse) - == ActionDispatchPriority.DRAGGABLE { + if ActionDispatcher.getDispatchPriority(key: KeyCodeNames.fakeMouse) == .DRAGGABLE { guard let pos = TouchscreenMouseEventAdapter.cursorPos() else { return false } return ActionDispatcher.dispatch(key: KeyCodeNames.fakeMouse, valueX: pos.x, valueY: pos.y) } diff --git a/PlayTools/Controls/Frontend/ModeAutomaton.swift b/PlayTools/Controls/Frontend/ModeAutomaton.swift index b19b69dd..1e9b2d0a 100644 --- a/PlayTools/Controls/Frontend/ModeAutomaton.swift +++ b/PlayTools/Controls/Frontend/ModeAutomaton.swift @@ -11,20 +11,20 @@ import Foundation public class ModeAutomaton { static public func onOption() -> Bool { - if mode == ControlMode.EDITOR || mode == ControlMode.TEXT_INPUT { + if mode == .EDITOR || mode == .TEXT_INPUT { return false } - if mode == ControlMode.OFF { - mode.set(ControlMode.CAMERA_ROTATE) + if mode == .OFF { + mode.set(.CAMERA_ROTATE) - } else if mode == ControlMode.ARBITRARY_CLICK && ActionDispatcher.cursorHideNecessary { - mode.set(ControlMode.CAMERA_ROTATE) + } else if mode == .ARBITRARY_CLICK && ActionDispatcher.cursorHideNecessary { + mode.set(.CAMERA_ROTATE) - } else if mode == ControlMode.CAMERA_ROTATE { + } else if mode == .CAMERA_ROTATE { if PlaySettings.shared.noKMOnInput { - mode.set(ControlMode.ARBITRARY_CLICK) + mode.set(.ARBITRARY_CLICK) } else { - mode.set(ControlMode.OFF) + mode.set(.OFF) } } // Some people want option key act as touchpad-touchscreen mapper @@ -34,27 +34,27 @@ public class ModeAutomaton { static public func onCmdK() { EditorController.shared.switchMode() - if mode == ControlMode.EDITOR && !EditorController.shared.editorMode { - mode.set(ControlMode.CAMERA_ROTATE) + if mode == .EDITOR && !EditorController.shared.editorMode { + mode.set(.CAMERA_ROTATE) ActionDispatcher.build() Toucher.writeLog(logMessage: "editor closed") } else if EditorController.shared.editorMode { - mode.set(ControlMode.EDITOR) + mode.set(.EDITOR) Toucher.writeLog(logMessage: "editor opened") } } static public func onKeyboardShow() { - if mode == ControlMode.EDITOR { + if mode == .EDITOR { return } - mode.set(ControlMode.TEXT_INPUT) + mode.set(.TEXT_INPUT) } static public func onKeyboardHide() { - if mode == ControlMode.EDITOR { + if mode == .EDITOR { return } - mode.set(ControlMode.ARBITRARY_CLICK) + mode.set(.ARBITRARY_CLICK) } } From 98704e57f7cf57bef8f0b36205178295f6bdfc53 Mon Sep 17 00:00:00 2001 From: TheMoonThatRises <58153205+TheMoonThatRises@users.noreply.github.com> Date: Thu, 5 Oct 2023 22:26:37 -0600 Subject: [PATCH 75/92] refactor: point SwordRPC to PlayCover repository --- PlayTools.xcodeproj/project.pbxproj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/PlayTools.xcodeproj/project.pbxproj b/PlayTools.xcodeproj/project.pbxproj index 1279f4c8..0f5ed15c 100644 --- a/PlayTools.xcodeproj/project.pbxproj +++ b/PlayTools.xcodeproj/project.pbxproj @@ -65,10 +65,10 @@ AB7DA47529B85BFB0034ACB2 /* PlayShadow.m in Sources */ = {isa = PBXBuildFile; fileRef = AB7DA47429B85BFB0034ACB2 /* PlayShadow.m */; }; AB7DA47729B8A78B0034ACB2 /* PlayShadow.h in Headers */ = {isa = PBXBuildFile; fileRef = AB7DA47629B8A78B0034ACB2 /* PlayShadow.h */; }; ABCECEE629750BA600746595 /* PlayedApple.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABCECEE529750BA600746595 /* PlayedApple.swift */; }; - B127172228817AB90025112B /* SwordRPC in Frameworks */ = {isa = PBXBuildFile; productRef = B127172128817AB90025112B /* SwordRPC */; }; B127172528817C040025112B /* DiscordIPC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B127172428817C040025112B /* DiscordIPC.swift */; }; B1271729288284BE0025112B /* DiscordActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1271728288284BE0025112B /* DiscordActivity.swift */; }; B1E8CF8A28BBE2AB004340D3 /* Keymapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1E8CF8928BBE2AB004340D3 /* Keymapping.swift */; }; + B6D774FF2ACFC3D900C0D9D8 /* SwordRPC in Frameworks */ = {isa = PBXBuildFile; productRef = B6D774FE2ACFC3D900C0D9D8 /* SwordRPC */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -165,7 +165,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - B127172228817AB90025112B /* SwordRPC in Frameworks */, + B6D774FF2ACFC3D900C0D9D8 /* SwordRPC in Frameworks */, 6E84A14528D0F94E00BF7495 /* UIKit.framework in Frameworks */, AA818CB9287ABFB1000BEE9D /* IOKit.framework in Frameworks */, ); @@ -476,7 +476,7 @@ ); name = PlayTools; packageProductDependencies = ( - B127172128817AB90025112B /* SwordRPC */, + B6D774FE2ACFC3D900C0D9D8 /* SwordRPC */, ); productName = PlayTools; productReference = AA7196D8287A447700623C15 /* PlayTools.framework */; @@ -512,7 +512,7 @@ ); mainGroup = AA7196CE287A447700623C15; packageReferences = ( - B127172028817AB90025112B /* XCRemoteSwiftPackageReference "swordRPC" */, + B6D774FD2ACFC3D900C0D9D8 /* XCRemoteSwiftPackageReference "SwordRPC" */, ); productRefGroup = AA7196D9287A447700623C15 /* Products */; projectDirPath = ""; @@ -951,9 +951,9 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - B127172028817AB90025112B /* XCRemoteSwiftPackageReference "swordRPC" */ = { + B6D774FD2ACFC3D900C0D9D8 /* XCRemoteSwiftPackageReference "SwordRPC" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/khoralee/swordRPC"; + repositoryURL = "https://github.com/PlayCover/SwordRPC"; requirement = { branch = main; kind = branch; @@ -962,9 +962,9 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - B127172128817AB90025112B /* SwordRPC */ = { + B6D774FE2ACFC3D900C0D9D8 /* SwordRPC */ = { isa = XCSwiftPackageProductDependency; - package = B127172028817AB90025112B /* XCRemoteSwiftPackageReference "swordRPC" */; + package = B6D774FD2ACFC3D900C0D9D8 /* XCRemoteSwiftPackageReference "SwordRPC" */; productName = SwordRPC; }; /* End XCSwiftPackageProductDependency section */ From df8d7a3bfeec06f1995a07d176ba55735fb3c431 Mon Sep 17 00:00:00 2001 From: Ryu-ga Date: Fri, 6 Oct 2023 19:33:57 +0900 Subject: [PATCH 76/92] Patch for Sonoa14.1 betas --- .../Controls/PTFakeTouch/NSObject+Swizzle.m | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m index df626f3d..c29aa45d 100644 --- a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m +++ b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m @@ -50,6 +50,16 @@ - (void) swizzleInstanceMethod:(SEL)origSelector withMethod:(SEL)newSelector } } +- (void) swizzleExchangeMethod:(SEL)origSelector withMethod:(SEL)newSelector +{ + Class cls = [self class]; + // If current class doesn't exist selector, then get super + Method originalMethod = class_getInstanceMethod(cls, origSelector); + Method swizzledMethod = class_getInstanceMethod(cls, newSelector); + + method_exchangeImplementations(originalMethod, swizzledMethod); +} + - (BOOL) hook_prefersPointerLocked { return false; } @@ -112,10 +122,6 @@ - (double) get_default_width { } -- (void) hook_setCurrentSubscription:(VSSubscription *)currentSubscription { - // do nothing -} - // Hook for UIUserInterfaceIdiom // - (long long) hook_userInterfaceIdiom { @@ -167,7 +173,8 @@ + (void)load { // This is an experimental fix if ([[PlaySettings shared] inverseScreenValues]) { // This lines set External Scene settings and other IOS10 Runtime services by swizzling - [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frameDefault)]; + // In Sonoma 14.1 betas, frame method seems to be moved to FBSSceneSettingsCore + [objc_getClass("FBSSceneSettingsCore") swizzleExchangeMethod:@selector(frame) withMethod:@selector(hook_frameDefault)]; [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_boundsDefault)]; [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_sizeDelfault)]; @@ -178,7 +185,7 @@ + (void)load { [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(scale) withMethod:@selector(hook_scale)]; } else { // This acutally runs when adaptiveDisplay is normally triggered - [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frame)]; + [objc_getClass("FBSSceneSettingsCore") swizzleExchangeMethod:@selector(frame) withMethod:@selector(hook_frame)]; [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_bounds)]; [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_size)]; @@ -199,7 +206,7 @@ + (void)load { CGFloat newValueH = (CGFloat)[self get_default_height]; [[PlaySettings shared] setValue:@(newValueH) forKey:@"windowSizeHeight"]; if (![[PlaySettings shared] inverseScreenValues]) { - [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frameDefault)]; + [objc_getClass("FBSSceneSettingsCore") swizzleExchangeMethod:@selector(frame) withMethod:@selector(hook_frameDefault)]; [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_boundsDefault)]; [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_sizeDelfault)]; } @@ -213,7 +220,7 @@ + (void)load { } else { if ([[PlaySettings shared] adaptiveDisplay]) { - [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frame)]; + [objc_getClass("FBSSceneSettingsCore") swizzleExchangeMethod:@selector(frame) withMethod:@selector(hook_frame)]; [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_bounds)]; [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_size)]; } @@ -224,8 +231,6 @@ + (void)load { // Set idiom to iPad // [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(userInterfaceIdiom) withMethod:@selector(hook_userInterfaceIdiom)]; // [objc_getClass("UITraitCollection") swizzleInstanceMethod:@selector(userInterfaceIdiom) withMethod:@selector(hook_userInterfaceIdiom)]; - - [objc_getClass("VSSubscriptionRegistrationCenter") swizzleInstanceMethod:@selector(setCurrentSubscription:) withMethod:@selector(hook_setCurrentSubscription:)]; } @end From a8daaf0234b57daae172c8ae95ffe045a42cee5b Mon Sep 17 00:00:00 2001 From: Ryu-ga Date: Fri, 6 Oct 2023 19:59:31 +0900 Subject: [PATCH 77/92] Update --- .../Controls/PTFakeTouch/NSObject+Swizzle.m | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m index c29aa45d..d59acae3 100644 --- a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m +++ b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m @@ -174,7 +174,10 @@ + (void)load { if ([[PlaySettings shared] inverseScreenValues]) { // This lines set External Scene settings and other IOS10 Runtime services by swizzling // In Sonoma 14.1 betas, frame method seems to be moved to FBSSceneSettingsCore - [objc_getClass("FBSSceneSettingsCore") swizzleExchangeMethod:@selector(frame) withMethod:@selector(hook_frameDefault)]; + if(@available(iOS 17.0.1, *)) + [objc_getClass("FBSSceneSettingsCore") swizzleExchangeMethod:@selector(frame) withMethod:@selector(hook_frameDefault)]; + else + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frameDefault)]; [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_boundsDefault)]; [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_sizeDelfault)]; @@ -185,7 +188,10 @@ + (void)load { [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(scale) withMethod:@selector(hook_scale)]; } else { // This acutally runs when adaptiveDisplay is normally triggered - [objc_getClass("FBSSceneSettingsCore") swizzleExchangeMethod:@selector(frame) withMethod:@selector(hook_frame)]; + if(@available(iOS 17.0.1, *)) + [objc_getClass("FBSSceneSettingsCore") swizzleExchangeMethod:@selector(frame) withMethod:@selector(hook_frame)]; + else + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frame)]; [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_bounds)]; [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_size)]; @@ -206,7 +212,10 @@ + (void)load { CGFloat newValueH = (CGFloat)[self get_default_height]; [[PlaySettings shared] setValue:@(newValueH) forKey:@"windowSizeHeight"]; if (![[PlaySettings shared] inverseScreenValues]) { - [objc_getClass("FBSSceneSettingsCore") swizzleExchangeMethod:@selector(frame) withMethod:@selector(hook_frameDefault)]; + if(@available(iOS 17.0.1, *)) + [objc_getClass("FBSSceneSettingsCore") swizzleExchangeMethod:@selector(frame) withMethod:@selector(hook_frameDefault)]; + else + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frameDefault)]; [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_boundsDefault)]; [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_sizeDelfault)]; } @@ -220,7 +229,10 @@ + (void)load { } else { if ([[PlaySettings shared] adaptiveDisplay]) { - [objc_getClass("FBSSceneSettingsCore") swizzleExchangeMethod:@selector(frame) withMethod:@selector(hook_frame)]; + if(@available(iOS 17.0.1, *)) + [objc_getClass("FBSSceneSettingsCore") swizzleExchangeMethod:@selector(frame) withMethod:@selector(hook_frame)]; + else + [objc_getClass("FBSSceneSettingsCore") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frame)]; [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_bounds)]; [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_size)]; } From 05c11e9498370da84cf8f98c270479d51b4cab4f Mon Sep 17 00:00:00 2001 From: Ryu-ga <37541583+Ryu-ga@users.noreply.github.com> Date: Fri, 6 Oct 2023 20:48:06 +0900 Subject: [PATCH 78/92] Update PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m Co-authored-by: Xyct <87l46110@gmail.com> --- PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m index d59acae3..a7f5606e 100644 --- a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m +++ b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m @@ -232,7 +232,7 @@ + (void)load { if(@available(iOS 17.0.1, *)) [objc_getClass("FBSSceneSettingsCore") swizzleExchangeMethod:@selector(frame) withMethod:@selector(hook_frame)]; else - [objc_getClass("FBSSceneSettingsCore") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frame)]; + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frame)]; [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_bounds)]; [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_size)]; } From 46b465e94de13f562d5ede43d035e681b915238d Mon Sep 17 00:00:00 2001 From: Ryu-ga Date: Fri, 6 Oct 2023 21:35:49 +0900 Subject: [PATCH 79/92] Fix mismatching iOS Suport version acording to /System/Library/CoreServices/SystemVersion.plist --- PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m index a7f5606e..cf8da824 100644 --- a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m +++ b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m @@ -174,7 +174,7 @@ + (void)load { if ([[PlaySettings shared] inverseScreenValues]) { // This lines set External Scene settings and other IOS10 Runtime services by swizzling // In Sonoma 14.1 betas, frame method seems to be moved to FBSSceneSettingsCore - if(@available(iOS 17.0.1, *)) + if(@available(iOS 17.1, *)) [objc_getClass("FBSSceneSettingsCore") swizzleExchangeMethod:@selector(frame) withMethod:@selector(hook_frameDefault)]; else [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frameDefault)]; @@ -188,7 +188,7 @@ + (void)load { [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(scale) withMethod:@selector(hook_scale)]; } else { // This acutally runs when adaptiveDisplay is normally triggered - if(@available(iOS 17.0.1, *)) + if(@available(iOS 17.1, *)) [objc_getClass("FBSSceneSettingsCore") swizzleExchangeMethod:@selector(frame) withMethod:@selector(hook_frame)]; else [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frame)]; @@ -212,7 +212,7 @@ + (void)load { CGFloat newValueH = (CGFloat)[self get_default_height]; [[PlaySettings shared] setValue:@(newValueH) forKey:@"windowSizeHeight"]; if (![[PlaySettings shared] inverseScreenValues]) { - if(@available(iOS 17.0.1, *)) + if(@available(iOS 17.1, *)) [objc_getClass("FBSSceneSettingsCore") swizzleExchangeMethod:@selector(frame) withMethod:@selector(hook_frameDefault)]; else [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frameDefault)]; @@ -229,7 +229,7 @@ + (void)load { } else { if ([[PlaySettings shared] adaptiveDisplay]) { - if(@available(iOS 17.0.1, *)) + if(@available(iOS 17.1, *)) [objc_getClass("FBSSceneSettingsCore") swizzleExchangeMethod:@selector(frame) withMethod:@selector(hook_frame)]; else [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frame)]; From 1351fde8794fe73fd45265c7e734695de11140e9 Mon Sep 17 00:00:00 2001 From: Ryu-ga Date: Sat, 7 Oct 2023 14:53:41 +0900 Subject: [PATCH 80/92] Revert removal of VSS --- PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m index cf8da824..ee52b69f 100644 --- a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m +++ b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m @@ -122,6 +122,10 @@ - (double) get_default_width { } +- (void) hook_setCurrentSubscription:(VSSubscription *)currentSubscription { + // do nothing +} + // Hook for UIUserInterfaceIdiom // - (long long) hook_userInterfaceIdiom { @@ -243,6 +247,8 @@ + (void)load { // Set idiom to iPad // [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(userInterfaceIdiom) withMethod:@selector(hook_userInterfaceIdiom)]; // [objc_getClass("UITraitCollection") swizzleInstanceMethod:@selector(userInterfaceIdiom) withMethod:@selector(hook_userInterfaceIdiom)]; + + [objc_getClass("VSSubscriptionRegistrationCenter") swizzleInstanceMethod:@selector(setCurrentSubscription:) withMethod:@selector(hook_setCurrentSubscription:)]; } @end From 8ac4c5dcc5e76906fdfedc873f053702382f870a Mon Sep 17 00:00:00 2001 From: Ryu-ga Date: Wed, 11 Oct 2023 10:58:35 +0900 Subject: [PATCH 81/92] Fix traliling whitespace --- PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m index ee52b69f..591c57aa 100644 --- a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m +++ b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m @@ -247,7 +247,7 @@ + (void)load { // Set idiom to iPad // [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(userInterfaceIdiom) withMethod:@selector(hook_userInterfaceIdiom)]; // [objc_getClass("UITraitCollection") swizzleInstanceMethod:@selector(userInterfaceIdiom) withMethod:@selector(hook_userInterfaceIdiom)]; - + [objc_getClass("VSSubscriptionRegistrationCenter") swizzleInstanceMethod:@selector(setCurrentSubscription:) withMethod:@selector(hook_setCurrentSubscription:)]; } From 8b94b29ef82b5d6dc53941349c0314e92e729709 Mon Sep 17 00:00:00 2001 From: TheMoonThatRises <58153205+TheMoonThatRises@users.noreply.github.com> Date: Sun, 22 Oct 2023 14:18:30 -0600 Subject: [PATCH 82/92] fix: swipe action invalidation crash --- PlayTools/Controls/Backend/Action/PlayAction.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/PlayTools/Controls/Backend/Action/PlayAction.swift b/PlayTools/Controls/Backend/Action/PlayAction.swift index f72dfcb6..e6452c53 100644 --- a/PlayTools/Controls/Backend/Action/PlayAction.swift +++ b/PlayTools/Controls/Backend/Action/PlayAction.swift @@ -320,6 +320,10 @@ class SwipeAction: Action { timer.suspend() } + deinit { + timer.cancel() + } + func delay(_ delay: Double, closure: @escaping () -> Void) { let when = DispatchTime.now() + delay PlayInput.touchQueue.asyncAfter(deadline: when, execute: closure) @@ -372,7 +376,7 @@ class SwipeAction: Action { } func invalidate() { - timer.cancel() + timer.suspend() self.doLiftOff() } } From b2769b75715e431a01bc373094aa996ce8a90b91 Mon Sep 17 00:00:00 2001 From: TheMoonThatRises <58153205+TheMoonThatRises@users.noreply.github.com> Date: Sun, 22 Oct 2023 14:19:06 -0600 Subject: [PATCH 83/92] fix: buttons still pressed after option --- PlayTools/Controls/Frontend/ControlMode.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/PlayTools/Controls/Frontend/ControlMode.swift b/PlayTools/Controls/Frontend/ControlMode.swift index 3e8be384..b1e6c714 100644 --- a/PlayTools/Controls/Frontend/ControlMode.swift +++ b/PlayTools/Controls/Frontend/ControlMode.swift @@ -91,13 +91,18 @@ public class ControlMode: Equatable { // Toast.showHint(title: "should hide cursor? \(mouseAdapter.cursorHidden())", // text: ["current state: " + mode]) } - if mouseAdapter.cursorHidden() != wasHidden { + if mouseAdapter.cursorHidden() != wasHidden && settings.keymapping { if wasHidden { NotificationCenter.default.post(name: NSNotification.Name.playtoolsCursorWillShow, object: nil, userInfo: [:]) if screen.fullscreen { screen.switchDock(true) } + + if mode == .OFF || mode == .EDITOR { + ActionDispatcher.invalidateActions() + } + AKInterface.shared!.unhideCursor() } else { NotificationCenter.default.post(name: NSNotification.Name.playtoolsCursorWillHide, From 22a4dff67ccb199e64209e66dc6a6da5e750099b Mon Sep 17 00:00:00 2001 From: TheMoonThatRises <58153205+TheMoonThatRises@users.noreply.github.com> Date: Sun, 22 Oct 2023 14:19:36 -0600 Subject: [PATCH 84/92] fix: keymap editing available when disabled --- PlayTools/Controls/Frontend/ModeAutomaton.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/PlayTools/Controls/Frontend/ModeAutomaton.swift b/PlayTools/Controls/Frontend/ModeAutomaton.swift index 1e9b2d0a..66ebfeec 100644 --- a/PlayTools/Controls/Frontend/ModeAutomaton.swift +++ b/PlayTools/Controls/Frontend/ModeAutomaton.swift @@ -32,6 +32,10 @@ public class ModeAutomaton { } static public func onCmdK() { + guard settings.keymapping else { + return + } + EditorController.shared.switchMode() if mode == .EDITOR && !EditorController.shared.editorMode { From db383ac0e9cd85c443ea485771a6f00ce45fd016 Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 3 Nov 2023 09:30:10 +0800 Subject: [PATCH 85/92] fix: some games cannot rotate display issue --- PlayTools/Controls/MenuController.swift | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/PlayTools/Controls/MenuController.swift b/PlayTools/Controls/MenuController.swift index 80a5608d..d3929056 100644 --- a/PlayTools/Controls/MenuController.swift +++ b/PlayTools/Controls/MenuController.swift @@ -51,6 +51,19 @@ extension UIApplication { Toucher.writeLog(logMessage: "mark") Toast.showHint(title: "Log marked") } + + @objc + func rotateView(_ sender: AnyObject) { + Toast.showHint(title: "Rotate") + + for scene in connectedScenes { + guard let windowScene = scene as? UIWindowScene else { continue } + for window in windowScene.windows { + guard let rootViewController = window.rootViewController else { continue } + rootViewController.rotateView(sender) + } + } + } } extension UIViewController { @@ -85,7 +98,7 @@ var keymappingSelectors = [#selector(UIApplication.switchEditorMode(_:)), #selector(UIApplication.removeElement(_:)), #selector(UIApplication.upscaleElement(_:)), #selector(UIApplication.downscaleElement(_:)), - #selector(UIViewController.rotateView(_:)) + #selector(UIApplication.rotateView(_:)) ] class MenuController { From ffa151fb998065c4d592f222520261028d7202eb Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 3 Nov 2023 09:45:55 +0800 Subject: [PATCH 86/92] fix: toast not display during rotation issue --- PlayTools/Controls/MenuController.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/PlayTools/Controls/MenuController.swift b/PlayTools/Controls/MenuController.swift index d3929056..19db6674 100644 --- a/PlayTools/Controls/MenuController.swift +++ b/PlayTools/Controls/MenuController.swift @@ -54,8 +54,6 @@ extension UIApplication { @objc func rotateView(_ sender: AnyObject) { - Toast.showHint(title: "Rotate") - for scene in connectedScenes { guard let windowScene = scene as? UIWindowScene else { continue } for window in windowScene.windows { @@ -63,6 +61,10 @@ extension UIApplication { rootViewController.rotateView(sender) } } + + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2, execute: { + Toast.showHint(title: "Rotated") + }) } } From 56a071275246e3b230e1c6304aefe8c2c6d925ed Mon Sep 17 00:00:00 2001 From: TheMoonThatRises <58153205+TheMoonThatRises@users.noreply.github.com> Date: Sun, 5 Nov 2023 01:34:31 -0600 Subject: [PATCH 87/92] fix: camera bug --- PlayTools/Controls/Backend/Action/PlayAction.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/PlayTools/Controls/Backend/Action/PlayAction.swift b/PlayTools/Controls/Backend/Action/PlayAction.swift index e6452c53..37e57fc1 100644 --- a/PlayTools/Controls/Backend/Action/PlayAction.swift +++ b/PlayTools/Controls/Backend/Action/PlayAction.swift @@ -339,7 +339,6 @@ class SwipeAction: Action { if self.counter < 4 { counter += 1 } else { - timer.suspend() self.doLiftOff() } } @@ -369,6 +368,7 @@ class SwipeAction: Action { return } Toucher.touchcam(point: self.location, phase: UITouch.Phase.ended, tid: &id) + timer.suspend() delay(0.02) { self.cooldown = false } @@ -376,7 +376,6 @@ class SwipeAction: Action { } func invalidate() { - timer.suspend() self.doLiftOff() } } From 999e3b7f0744908538250eeb60a242c1525780b8 Mon Sep 17 00:00:00 2001 From: TheMoonThatRises <58153205+TheMoonThatRises@users.noreply.github.com> Date: Sun, 5 Nov 2023 12:09:22 -0700 Subject: [PATCH 88/92] put `doLiftOff` in same thread as `checkEnded` Co-Authored-By: Xyct <16048758+XuYicong@users.noreply.github.com> --- PlayTools/Controls/Backend/Action/PlayAction.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlayTools/Controls/Backend/Action/PlayAction.swift b/PlayTools/Controls/Backend/Action/PlayAction.swift index 37e57fc1..4f024362 100644 --- a/PlayTools/Controls/Backend/Action/PlayAction.swift +++ b/PlayTools/Controls/Backend/Action/PlayAction.swift @@ -376,7 +376,7 @@ class SwipeAction: Action { } func invalidate() { - self.doLiftOff() + PlayInput.touchQueue.async(execute: self.doLiftOff) } } From 96fad457608327565ca6e51983cdbbcc461d8192 Mon Sep 17 00:00:00 2001 From: TheMoonThatRises <58153205+TheMoonThatRises@users.noreply.github.com> Date: Sun, 5 Nov 2023 13:43:10 -0700 Subject: [PATCH 89/92] fix: smart keymapping disallow textfield input --- PlayTools/Controls/Frontend/ControlMode.swift | 12 ++++++------ PlayTools/Controls/Frontend/ModeAutomaton.swift | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/PlayTools/Controls/Frontend/ControlMode.swift b/PlayTools/Controls/Frontend/ControlMode.swift index b1e6c714..1430062f 100644 --- a/PlayTools/Controls/Frontend/ControlMode.swift +++ b/PlayTools/Controls/Frontend/ControlMode.swift @@ -34,13 +34,13 @@ public class ControlMode: Equatable { let centre = NotificationCenter.default let main = OperationQueue.main if PlaySettings.shared.noKMOnInput { - centre.addObserver(forName: UIApplication.keyboardDidHideNotification, object: nil, queue: main) { _ in - ModeAutomaton.onKeyboardHide() - Toucher.writeLog(logMessage: "virtual keyboard did hide") + centre.addObserver(forName: UITextField.textDidEndEditingNotification, object: nil, queue: main) { _ in + ModeAutomaton.onTextFieldFinish() + Toucher.writeLog(logMessage: "textfield end edit") } - centre.addObserver(forName: UIApplication.keyboardWillShowNotification, object: nil, queue: main) { _ in - ModeAutomaton.onKeyboardShow() - Toucher.writeLog(logMessage: "virtual keyboard will show") + centre.addObserver(forName: UITextField.textDidBeginEditingNotification, object: nil, queue: main) { _ in + ModeAutomaton.onTextFieldEdit() + Toucher.writeLog(logMessage: "textfield begin edit") } set(.ARBITRARY_CLICK) } else { diff --git a/PlayTools/Controls/Frontend/ModeAutomaton.swift b/PlayTools/Controls/Frontend/ModeAutomaton.swift index 66ebfeec..41804480 100644 --- a/PlayTools/Controls/Frontend/ModeAutomaton.swift +++ b/PlayTools/Controls/Frontend/ModeAutomaton.swift @@ -48,14 +48,14 @@ public class ModeAutomaton { } } - static public func onKeyboardShow() { + static public func onTextFieldEdit() { if mode == .EDITOR { return } mode.set(.TEXT_INPUT) } - static public func onKeyboardHide() { + static public func onTextFieldFinish() { if mode == .EDITOR { return } From c8b0cd6d8a8cf86b735ea032671a12aee4ddd1dd Mon Sep 17 00:00:00 2001 From: TheMoonThatRises <58153205+TheMoonThatRises@users.noreply.github.com> Date: Thu, 9 Nov 2023 21:31:43 -0700 Subject: [PATCH 90/92] add uitextview observer also fixed some naming for clarity --- PlayTools/Controls/Frontend/ControlMode.swift | 16 ++++++++++++---- PlayTools/Controls/Frontend/ModeAutomaton.swift | 4 ++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/PlayTools/Controls/Frontend/ControlMode.swift b/PlayTools/Controls/Frontend/ControlMode.swift index 1430062f..74056bf9 100644 --- a/PlayTools/Controls/Frontend/ControlMode.swift +++ b/PlayTools/Controls/Frontend/ControlMode.swift @@ -35,12 +35,20 @@ public class ControlMode: Equatable { let main = OperationQueue.main if PlaySettings.shared.noKMOnInput { centre.addObserver(forName: UITextField.textDidEndEditingNotification, object: nil, queue: main) { _ in - ModeAutomaton.onTextFieldFinish() - Toucher.writeLog(logMessage: "textfield end edit") + ModeAutomaton.onUITextInputEndEdit() + Toucher.writeLog(logMessage: "uitextinput end edit") } centre.addObserver(forName: UITextField.textDidBeginEditingNotification, object: nil, queue: main) { _ in - ModeAutomaton.onTextFieldEdit() - Toucher.writeLog(logMessage: "textfield begin edit") + ModeAutomaton.onUITextInputBeginEdit() + Toucher.writeLog(logMessage: "uitextinput begin edit") + } + centre.addObserver(forName: UITextView.textDidEndEditingNotification, object: nil, queue: main) { _ in + ModeAutomaton.onUITextInputEndEdit() + Toucher.writeLog(logMessage: "uitextinput end edit") + } + centre.addObserver(forName: UITextView.textDidBeginEditingNotification, object: nil, queue: main) { _ in + ModeAutomaton.onUITextInputBeginEdit() + Toucher.writeLog(logMessage: "uitextinput begin edit") } set(.ARBITRARY_CLICK) } else { diff --git a/PlayTools/Controls/Frontend/ModeAutomaton.swift b/PlayTools/Controls/Frontend/ModeAutomaton.swift index 41804480..2aca0fec 100644 --- a/PlayTools/Controls/Frontend/ModeAutomaton.swift +++ b/PlayTools/Controls/Frontend/ModeAutomaton.swift @@ -48,14 +48,14 @@ public class ModeAutomaton { } } - static public func onTextFieldEdit() { + static public func onUITextInputBeginEdit() { if mode == .EDITOR { return } mode.set(.TEXT_INPUT) } - static public func onTextFieldFinish() { + static public func onUITextInputEndEdit() { if mode == .EDITOR { return } From c7adf5d77f47daa55d99898b749cbc397277151b Mon Sep 17 00:00:00 2001 From: ytai <653297351@qq.com> Date: Tue, 5 Dec 2023 05:35:40 +0800 Subject: [PATCH 91/92] Add toggle for scroll wheel (#132) * comment scroll wheel * Update * Update * Update * Delete package resolved * Update * Revert change in NSObject --- PlayTools/Controls/Frontend/ControlMode.swift | 8 +++++--- PlayTools/PlaySettings.swift | 7 +++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/PlayTools/Controls/Frontend/ControlMode.swift b/PlayTools/Controls/Frontend/ControlMode.swift index 74056bf9..cf6eb9a2 100644 --- a/PlayTools/Controls/Frontend/ControlMode.swift +++ b/PlayTools/Controls/Frontend/ControlMode.swift @@ -65,9 +65,11 @@ public class ControlMode: Equatable { self.keyboardAdapter.handleKey(keycode: keycode, pressed: pressed, isRepeat: isRepeat)}, swapMode: ModeAutomaton.onOption) - AKInterface.shared!.setupScrollWheel({deltaX, deltaY in - self.mouseAdapter.handleScrollWheel(deltaX: deltaX, deltaY: deltaY) - }) + if PlaySettings.shared.enableScrollWheel { + AKInterface.shared!.setupScrollWheel({deltaX, deltaY in + self.mouseAdapter.handleScrollWheel(deltaX: deltaX, deltaY: deltaY) + }) + } AKInterface.shared!.setupMouseMoved({deltaX, deltaY in self.mouseAdapter.handleMove(deltaX: deltaX, deltaY: deltaY) diff --git a/PlayTools/PlaySettings.swift b/PlayTools/PlaySettings.swift index 2f10eb37..8ba67509 100644 --- a/PlayTools/PlaySettings.swift +++ b/PlayTools/PlaySettings.swift @@ -69,10 +69,12 @@ let settings = PlaySettings.shared @objc lazy var windowFixMethod = settingsData.windowFixMethod @objc lazy var customScaler = settingsData.customScaler - + @objc lazy var rootWorkDir = settingsData.rootWorkDir - + @objc lazy var noKMOnInput = settingsData.noKMOnInput + + @objc lazy var enableScrollWheel = settingsData.enableScrollWheel } struct AppSettingsData: Codable { @@ -96,4 +98,5 @@ struct AppSettingsData: Codable { var windowFixMethod = 0 var rootWorkDir = true var noKMOnInput = false + var enableScrollWheel = true } From 3e13e68b263c26b73fc7d405c07a544d158e0392 Mon Sep 17 00:00:00 2001 From: TheMoonThatRises <58153205+TheMoonThatRises@users.noreply.github.com> Date: Mon, 11 Dec 2023 08:21:08 -0700 Subject: [PATCH 92/92] remove playcontroller --- PlayTools/Controls/PlayController.swift | 121 ------------------------ 1 file changed, 121 deletions(-) delete mode 100644 PlayTools/Controls/PlayController.swift diff --git a/PlayTools/Controls/PlayController.swift b/PlayTools/Controls/PlayController.swift deleted file mode 100644 index 64c641ec..00000000 --- a/PlayTools/Controls/PlayController.swift +++ /dev/null @@ -1,121 +0,0 @@ -// -// PlayController.swift -// PlayTools -// -// Created by 许沂聪 on 2023/4/21. -// - -import Foundation -import GameController - -class PlayController { - private static var directionPadXValue: Float = 0, - directionPadYValue: Float = 0, - thumbstickCursorControl: [String: (((CGFloat, CGFloat) -> Void)?, CGFloat, CGFloat) -> Void] - = ["Left Thumbstick": ThumbstickCursorControl().update, "Right Thumbstick": ThumbstickCursorControl().update] - - public static func initialize() { - GCController.current?.extendedGamepad?.valueChangedHandler = handleEvent - } - - static func handleEditorEvent(_ profile: GCExtendedGamepad, _ element: GCControllerElement) { - // This is the index of controller buttons, which is String, not Int - var alias: String = element.aliases.first! - if alias == "Direction Pad" { - guard let dpadElement = element as? GCControllerDirectionPad else { - Toast.showOver(msg: "cannot map direction pad: element type not recognizable") - return - } - if dpadElement.xAxis.value > 0 { - alias = dpadElement.right.aliases.first! - } else if dpadElement.xAxis.value < 0 { - alias = dpadElement.left.aliases.first! - } - if dpadElement.yAxis.value > 0 { - alias = dpadElement.down.aliases.first! - } else if dpadElement.yAxis.value < 0 { - alias = dpadElement.up.aliases.first! - } - } - EditorController.shared.setKey(alias) - } - - static func handleEvent(_ profile: GCExtendedGamepad, _ element: GCControllerElement) { - let name: String = element.aliases.first! - if let buttonElement = element as? GCControllerButtonInput { - guard let handlers = PlayInput.buttonHandlers[name] else { return } -// Toast.showOver(msg: name + ": \(buttonElement.isPressed)") - for handler in handlers { - handler(buttonElement.isPressed) - } - } else if let dpadElement = element as? GCControllerDirectionPad { - PlayController.handleDirectionPad(profile, dpadElement) - } else { - Toast.showOver(msg: "unrecognised controller element input happens") - } - } - public static func handleDirectionPad(_ profile: GCExtendedGamepad, _ dpad: GCControllerDirectionPad) { - let name = dpad.aliases.first! - let xAxis = dpad.xAxis, yAxis = dpad.yAxis - if name == "Direction Pad" { - if (xAxis.value > 0) != (directionPadXValue > 0) { - PlayController.handleEvent(profile, dpad.right) - } - if (xAxis.value < 0) != (directionPadXValue < 0) { - PlayController.handleEvent(profile, dpad.left) - } - if (yAxis.value > 0) != (directionPadYValue > 0) { - PlayController.handleEvent(profile, dpad.up) - } - if (yAxis.value < 0) != (directionPadYValue < 0) { - PlayController.handleEvent(profile, dpad.down) - } - directionPadXValue = xAxis.value - directionPadYValue = yAxis.value - return - } - let deltaX = xAxis.value, deltaY = yAxis.value - let cgDx = CGFloat(deltaX) - let cgDy = CGFloat(deltaY) - thumbstickCursorControl[name]!( - PlayInput.draggableHandler[name] ?? PlayInput.cameraMoveHandler[name], cgDx * 6, cgDy * 6) - PlayInput.joystickHandler[name]?(cgDx, cgDy) - } -} - -class ThumbstickCursorControl { - private var thumbstickVelocity: CGVector = CGVector.zero, - thumbstickPolling: Bool = false, - eventHandler: ((CGFloat, CGFloat) -> Void)! - - static private func isVectorSignificant(_ vector: CGVector) -> Bool { - return vector.dx.magnitude + vector.dy.magnitude > 0.2 - } - - public func update(handler: ((CGFloat, CGFloat) -> Void)?, velocityX: CGFloat, velocityY: CGFloat) { - guard let hdlr = handler else { - if thumbstickPolling { - self.thumbstickVelocity.dx = 0 - self.thumbstickVelocity.dy = 0 - } - return - } - self.eventHandler = hdlr - self.thumbstickVelocity.dx = velocityX - self.thumbstickVelocity.dy = velocityY - if !thumbstickPolling { - PlayInput.touchQueue.async(execute: self.thumbstickPoll) - self.thumbstickPolling = true - } - } - - private func thumbstickPoll() { - if !ThumbstickCursorControl.isVectorSignificant(self.thumbstickVelocity) { - self.thumbstickPolling = false - return - } - self.eventHandler(self.thumbstickVelocity.dx, self.thumbstickVelocity.dy) - PlayInput.touchQueue.asyncAfter( - deadline: DispatchTime.now() + 0.017, execute: self.thumbstickPoll) - } -}