diff --git a/AKInterface-Bridging-Header.h b/AKInterface-Bridging-Header.h new file mode 100644 index 00000000..1b2cb5d6 --- /dev/null +++ b/AKInterface-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/AKPlugin.swift b/AKPlugin.swift new file mode 100644 index 00000000..2f1ce05b --- /dev/null +++ b/AKPlugin.swift @@ -0,0 +1,83 @@ +// +// MacPlugin.swift +// AKInterface +// +// Created by Isaac Marovitz on 13/09/2022. +// + +import AppKit +import Foundation + +class AKPlugin: NSObject, Plugin { + required override init() { + } + + var screenCount: Int { + NSScreen.screens.count + } + + var mousePoint: CGPoint { + NSApplication.shared.windows.first!.mouseLocationOutsideOfEventStream as CGPoint + } + + var windowFrame: CGRect { + NSApplication.shared.windows.first!.frame as CGRect + } + + var isMainScreenEqualToFirst: Bool { + return NSScreen.main == NSScreen.screens.first + } + + var mainScreenFrame: CGRect { + return NSScreen.main!.frame as CGRect + } + + var isFullscreen: Bool { + NSApplication.shared.windows.first!.styleMask.contains(.fullScreen) + } + + func hideCursor() { + NSCursor.hide() + } + + func unhideCursor() { + NSCursor.unhide() + } + + func terminateApplication() { + NSApplication.shared.terminate(self) + } + + func eliminateRedundantKeyPressEvents(_ dontIgnore: @escaping() -> Bool) { + NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: { event in + if dontIgnore() { + return event + } + return nil + }) + } + + func setupMouseButton(_ _up: Int, _ _down: Int, _ dontIgnore: @escaping(Int, Bool, 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) { + return event + } + return nil + }) + NSEvent.addLocalMonitorForEvents(matching: NSEvent.EventTypeMask(rawValue: UInt64(_down)), handler: { event in + if dontIgnore(_up, false, true) { + return event + } + return nil + }) + } + + func urlForApplicationWithBundleIdentifier(_ value: String) -> URL? { + NSWorkspace.shared.urlForApplication(withBundleIdentifier: value) + } + + func setMenuBarVisible(_ visible: Bool) { + NSMenu.setMenuBarVisible(visible) + } +} diff --git a/PlayTools.xcodeproj/project.pbxproj b/PlayTools.xcodeproj/project.pbxproj index f5a5f5dc..d3b169e3 100644 --- a/PlayTools.xcodeproj/project.pbxproj +++ b/PlayTools.xcodeproj/project.pbxproj @@ -7,6 +7,12 @@ objects = { /* Begin PBXBuildFile section */ + 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 */; }; + 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, ); }; }; 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 */; }; @@ -22,10 +28,6 @@ AA7197A1287A481500623C15 /* CircleMenuLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA71978D287A481500623C15 /* CircleMenuLoader.swift */; }; AA7197A2287A481500623C15 /* CircleMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA71978E287A481500623C15 /* CircleMenu.swift */; }; AA7197A3287A481500623C15 /* CircleMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA71978F287A481500623C15 /* CircleMenuButton.swift */; }; - AA7197A5287A481500623C15 /* Dynamic.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719793287A481500623C15 /* Dynamic.swift */; }; - AA7197A6287A481500623C15 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719794287A481500623C15 /* Logger.swift */; }; - AA7197A7287A481500623C15 /* Invocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719795287A481500623C15 /* Invocation.swift */; }; - AA7197A8287A481500623C15 /* TypeMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719796287A481500623C15 /* TypeMapping.swift */; }; AA7197A9287A481500623C15 /* PlayInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719797287A481500623C15 /* PlayInfo.swift */; }; AA7197AA287A481500623C15 /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719798287A481500623C15 /* Toast.swift */; }; AA7197AB287A481500623C15 /* ControlModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA71979A287A481500623C15 /* ControlModel.swift */; }; @@ -79,10 +81,32 @@ AA719871287A81A000623C15 /* FixCategoryBug.h in Headers */ = {isa = PBXBuildFile; fileRef = AA719843287A81A000623C15 /* FixCategoryBug.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 */; }; - AA818CBB287ABFD5000BEE9D /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA818CBA287ABFD5000BEE9D /* UIKit.framework */; }; + 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 */; }; /* End PBXBuildFile section */ +/* Begin PBXCopyFilesBuildPhase section */ + 6E84A14728D0F94E00BF7495 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 6E84A15028D0F97500BF7495 /* AKInterface.bundle in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ + 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 = ""; }; + 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; }; AA3430B0287AB7F2004E208A /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 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 = ""; }; @@ -102,10 +126,6 @@ AA71978D287A481500623C15 /* CircleMenuLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircleMenuLoader.swift; sourceTree = ""; }; AA71978E287A481500623C15 /* CircleMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircleMenu.swift; sourceTree = ""; }; AA71978F287A481500623C15 /* CircleMenuButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircleMenuButton.swift; sourceTree = ""; }; - AA719793287A481500623C15 /* Dynamic.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dynamic.swift; sourceTree = ""; }; - AA719794287A481500623C15 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; - AA719795287A481500623C15 /* Invocation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Invocation.swift; sourceTree = ""; }; - AA719796287A481500623C15 /* TypeMapping.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypeMapping.swift; sourceTree = ""; }; AA719797287A481500623C15 /* PlayInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayInfo.swift; sourceTree = ""; }; AA719798287A481500623C15 /* Toast.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Toast.swift; sourceTree = ""; }; AA71979A287A481500623C15 /* ControlModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControlModel.swift; sourceTree = ""; }; @@ -160,25 +180,47 @@ 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; }; + 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 6E84A14928D0F96D00BF7495 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; AA7196D5287A447700623C15 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B127172228817AB90025112B /* SwordRPC in Frameworks */, + 6E84A14528D0F94E00BF7495 /* UIKit.framework in Frameworks */, AA818CB9287ABFB1000BEE9D /* IOKit.framework in Frameworks */, - AA818CBB287ABFD5000BEE9D /* UIKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 6E76639928D0FA6F00DE4AF9 /* AKInterface */ = { + isa = PBXGroup; + children = ( + 6E76639A28D0FAE700DE4AF9 /* Plugin.swift */, + 6E7663A028D0FB5300DE4AF9 /* AKPlugin.swift */, + 6E76639628D0FA6200DE4AF9 /* AKInterface-Bridging-Header.h */, + ); + name = AKInterface; + sourceTree = ""; + }; AA7196CE287A447700623C15 = { isa = PBXGroup; children = ( AA7196DA287A447700623C15 /* PlayTools */, + 6E76639928D0FA6F00DE4AF9 /* AKInterface */, AA7196D9287A447700623C15 /* Products */, AA818CB6287ABFA7000BEE9D /* Recovered References */, AA818CB7287ABFB1000BEE9D /* Frameworks */, @@ -189,6 +231,7 @@ isa = PBXGroup; children = ( AA7196D8287A447700623C15 /* PlayTools.framework */, + 6E84A14C28D0F96D00BF7495 /* AKInterface.bundle */, ); name = Products; sourceTree = ""; @@ -196,18 +239,19 @@ AA7196DA287A447700623C15 /* PlayTools */ = { isa = PBXGroup; children = ( + B127172328817AC70025112B /* DiscordActivity */, AA719721287A480C00623C15 /* Controls */, AA719799287A481500623C15 /* Keymap */, AA71978C287A481500623C15 /* Menu */, AA719791287A481500623C15 /* Utils */, AA71970A287A44D200623C15 /* Info.plist */, AA719706287A44D200623C15 /* PlayCover.swift */, - AA719705287A44D200623C15 /* PlayLoader.h */, - AA719702287A44D200623C15 /* PlayLoader.m */, AA719703287A44D200623C15 /* PlayScreen.swift */, AA719704287A44D200623C15 /* PlaySettings.swift */, - AA719708287A44D200623C15 /* PlayTools.h */, AA719709287A44D200623C15 /* PlayUI.swift */, + AA719705287A44D200623C15 /* PlayLoader.h */, + AA719702287A44D200623C15 /* PlayLoader.m */, + AA719708287A44D200623C15 /* PlayTools.h */, ); path = PlayTools; sourceTree = ""; @@ -239,24 +283,13 @@ AA719791287A481500623C15 /* Utils */ = { isa = PBXGroup; children = ( - AA719792287A481500623C15 /* Dynamic */, AA719797287A481500623C15 /* PlayInfo.swift */, AA719798287A481500623C15 /* Toast.swift */, + 6E7663A428D0FEBE00DE4AF9 /* AKPluginLoader.swift */, ); path = Utils; sourceTree = ""; }; - AA719792287A481500623C15 /* Dynamic */ = { - isa = PBXGroup; - children = ( - AA719793287A481500623C15 /* Dynamic.swift */, - AA719794287A481500623C15 /* Logger.swift */, - AA719795287A481500623C15 /* Invocation.swift */, - AA719796287A481500623C15 /* TypeMapping.swift */, - ); - path = Dynamic; - sourceTree = ""; - }; AA719799287A481500623C15 /* Keymap */ = { isa = PBXGroup; children = ( @@ -264,6 +297,7 @@ AA71979C287A481500623C15 /* EditorController.swift */, AA71979D287A481500623C15 /* DragElementsView.swift */, AA71979E287A481500623C15 /* KeyCodeNames.swift */, + B1E8CF8928BBE2AB004340D3 /* Keymapping.swift */, ); path = Keymap; sourceTree = ""; @@ -346,6 +380,15 @@ name = Frameworks; sourceTree = ""; }; + B127172328817AC70025112B /* DiscordActivity */ = { + isa = PBXGroup; + children = ( + B127172428817C040025112B /* DiscordIPC.swift */, + B1271728288284BE0025112B /* DiscordActivity.swift */, + ); + path = DiscordActivity; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -386,6 +429,23 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 6E84A14B28D0F96D00BF7495 /* AKInterface */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6E84A14D28D0F96D00BF7495 /* Build configuration list for PBXNativeTarget "AKInterface" */; + buildPhases = ( + 6E84A14828D0F96D00BF7495 /* Sources */, + 6E84A14928D0F96D00BF7495 /* Frameworks */, + 6E84A14A28D0F96D00BF7495 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = AKInterface; + productName = AKInterface; + productReference = 6E84A14C28D0F96D00BF7495 /* AKInterface.bundle */; + productType = "com.apple.product-type.bundle"; + }; AA7196D7287A447700623C15 /* PlayTools */ = { isa = PBXNativeTarget; buildConfigurationList = AA7196DF287A447700623C15 /* Build configuration list for PBXNativeTarget "PlayTools" */; @@ -395,12 +455,16 @@ AA7196D5287A447700623C15 /* Frameworks */, AA7196D6287A447700623C15 /* Resources */, 6ED6ACAB28AAB30F0040D234 /* SwiftLint */, + 6E84A14728D0F94E00BF7495 /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( ); name = PlayTools; + packageProductDependencies = ( + B127172128817AB90025112B /* SwordRPC */, + ); productName = PlayTools; productReference = AA7196D8287A447700623C15 /* PlayTools.framework */; productType = "com.apple.product-type.framework"; @@ -414,6 +478,10 @@ BuildIndependentTargetsInParallel = NO; LastUpgradeCheck = 1340; TargetAttributes = { + 6E84A14B28D0F96D00BF7495 = { + CreatedOnToolsVersion = 14.0; + LastSwiftMigration = 1400; + }; AA7196D7287A447700623C15 = { CreatedOnToolsVersion = 13.4.1; LastSwiftMigration = 1340; @@ -429,16 +497,27 @@ Base, ); mainGroup = AA7196CE287A447700623C15; + packageReferences = ( + B127172028817AB90025112B /* XCRemoteSwiftPackageReference "swordRPC" */, + ); productRefGroup = AA7196D9287A447700623C15 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( AA7196D7287A447700623C15 /* PlayTools */, + 6E84A14B28D0F96D00BF7495 /* AKInterface */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 6E84A14A28D0F96D00BF7495 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; AA7196D6287A447700623C15 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -470,6 +549,15 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 6E84A14828D0F96D00BF7495 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6E7663A128D0FB5300DE4AF9 /* AKPlugin.swift in Sources */, + 6E76639C28D0FAE700DE4AF9 /* Plugin.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; AA7196D4287A447700623C15 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -480,17 +568,18 @@ AA71975A287A480D00623C15 /* Toucher.swift in Sources */, AA71984A287A81A000623C15 /* NSString+KIFAdditions.m in Sources */, AA7197A1287A481500623C15 /* CircleMenuLoader.swift in Sources */, + 6E76639B28D0FAE700DE4AF9 /* Plugin.swift in Sources */, + B1271729288284BE0025112B /* DiscordActivity.swift in Sources */, AA71984E287A81A000623C15 /* UIScrollView-KIFAdditions.m in Sources */, AA719849287A81A000623C15 /* NSException-KIFAdditions.m in Sources */, AA7197AB287A481500623C15 /* ControlModel.swift in Sources */, - AA7197A8287A481500623C15 /* TypeMapping.swift in Sources */, AA7197AE287A481500623C15 /* DragElementsView.swift in Sources */, - AA7197A7287A481500623C15 /* Invocation.swift in Sources */, AA71970D287A44D200623C15 /* PlaySettings.swift in Sources */, AA71984F287A81A000623C15 /* UIView-Debugging.m in Sources */, AA719759287A480D00623C15 /* PlayAction.swift in Sources */, AA719847287A81A000623C15 /* UIAccessibilityElement-KIFAdditions.m in Sources */, AA71984D287A81A000623C15 /* UIWindow-KIFAdditions.m in Sources */, + 6E7663A528D0FEBE00DE4AF9 /* AKPluginLoader.swift in Sources */, AA719867287A81A000623C15 /* NSPredicate+KIFAdditions.m in Sources */, AA719845287A81A000623C15 /* NSBundle+Swizzle.m in Sources */, AA7197A2287A481500623C15 /* CircleMenu.swift in Sources */, @@ -500,10 +589,8 @@ AA719789287A480D00623C15 /* PlayInput.swift in Sources */, AA719865287A81A000623C15 /* UIEvent+KIFAdditions.m in Sources */, AA71970B287A44D200623C15 /* PlayLoader.m in Sources */, - AA7197A5287A481500623C15 /* Dynamic.swift in Sources */, AA719712287A44D200623C15 /* PlayUI.swift in Sources */, AA719860287A81A000623C15 /* UIView-KIFAdditions.m in Sources */, - AA7197A6287A481500623C15 /* Logger.swift in Sources */, AA7197AF287A481500623C15 /* KeyCodeNames.swift in Sources */, AA71978A287A480D00623C15 /* ControlMode.swift in Sources */, AA719758287A480D00623C15 /* PlayMice.swift in Sources */, @@ -514,9 +601,11 @@ AA71970C287A44D200623C15 /* PlayScreen.swift in Sources */, AA71986C287A81A000623C15 /* NSObject+Swizzle.m in Sources */, AA71970F287A44D200623C15 /* PlayCover.swift in Sources */, + B127172528817C040025112B /* DiscordIPC.swift in Sources */, AA719850287A81A000623C15 /* UITouch-KIFAdditions.m in Sources */, AA71985F287A81A000623C15 /* IOHIDEvent+KIF.m in Sources */, AA719861287A81A000623C15 /* NSFileManager-KIFAdditions.m in Sources */, + B1E8CF8A28BBE2AB004340D3 /* Keymapping.swift in Sources */, AA71985A287A81A000623C15 /* UIApplication-KIFAdditions.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -524,6 +613,70 @@ /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ + 6E84A14E28D0F96D00BF7495 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INFOPLIST_KEY_NSPrincipalClass = "$(PRODUCT_BUNDLE_NAME).AKPlugin"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 12.3; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = io.playcover.AKInterface; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "AKInterface-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + WRAPPER_EXTENSION = bundle; + }; + name = Debug; + }; + 6E84A14F28D0F96D00BF7495 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INFOPLIST_KEY_NSPrincipalClass = "$(PRODUCT_BUNDLE_NAME).AKPlugin"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 12.3; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = io.playcover.AKInterface; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "AKInterface-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + WRAPPER_EXTENSION = bundle; + }; + name = Release; + }; AA7196DD287A447700623C15 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -678,14 +831,15 @@ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SCAN_ALL_SOURCE_FILES_FOR_INCLUDES = YES; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = "$(SWIFT_MODULE_NAME)/$(SWIFT_MODULE_NAME).h"; SWIFT_OBJC_INTERFACE_HEADER_NAME = "$(SWIFT_MODULE_NAME)-Swift.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = 2; }; name = Debug; }; @@ -722,19 +876,29 @@ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SCAN_ALL_SOURCE_FILES_FOR_INCLUDES = YES; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = "$(SWIFT_MODULE_NAME)/$(SWIFT_MODULE_NAME).h"; SWIFT_OBJC_INTERFACE_HEADER_NAME = "$(SWIFT_MODULE_NAME)-Swift.h"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = 2; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 6E84A14D28D0F96D00BF7495 /* Build configuration list for PBXNativeTarget "AKInterface" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6E84A14E28D0F96D00BF7495 /* Debug */, + 6E84A14F28D0F96D00BF7495 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; AA7196D2287A447700623C15 /* Build configuration list for PBXProject "PlayTools" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -754,6 +918,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + B127172028817AB90025112B /* XCRemoteSwiftPackageReference "swordRPC" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/khoralee/swordRPC"; + requirement = { + branch = main; + kind = branch; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + B127172128817AB90025112B /* SwordRPC */ = { + isa = XCSwiftPackageProductDependency; + package = B127172028817AB90025112B /* XCRemoteSwiftPackageReference "swordRPC" */; + productName = SwordRPC; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = AA7196CF287A447700623C15 /* Project object */; } diff --git a/PlayTools.xcodeproj/xcshareddata/xcschemes/PlayTools.xcscheme b/PlayTools.xcodeproj/xcshareddata/xcschemes/PlayTools.xcscheme index 87f1d7e8..fca9673d 100644 --- a/PlayTools.xcodeproj/xcshareddata/xcschemes/PlayTools.xcscheme +++ b/PlayTools.xcodeproj/xcshareddata/xcschemes/PlayTools.xcscheme @@ -6,6 +6,20 @@ parallelizeBuildables = "YES" buildImplicitDependencies = "YES"> + + + + Bool { - return [1, 2].contains(Dynamic(event).type.asInt) - } + static public let mode = ControlMode() + public var visible: Bool = PlaySettings.shared.mouseMapping func show(_ show: Bool) { if !editor.editorMode { @@ -23,16 +19,16 @@ let mode = ControlMode.mode if screen.fullscreen { screen.switchDock(true) } - if PlaySettings.shared.gamingMode { - Dynamic.NSCursor.unhide() + if PlaySettings.shared.mouseMapping { + AKInterface.shared!.unhideCursor() disableCursor(1) } PlayInput.shared.invalidate() } } else { if visible { - if PlaySettings.shared.gamingMode { - Dynamic.NSCursor.hide() + if PlaySettings.shared.mouseMapping { + AKInterface.shared!.hideCursor() disableCursor(0) } if screen.fullscreen { diff --git a/PlayTools/Controls/MenuController.swift b/PlayTools/Controls/MenuController.swift index bf688e2a..10d34a23 100644 --- a/PlayTools/Controls/MenuController.swift +++ b/PlayTools/Controls/MenuController.swift @@ -7,7 +7,24 @@ import UIKit -extension UIWindow { +class RotateViewController: UIViewController { + static let orientationList: [UIInterfaceOrientation] = [ + .landscapeLeft, .portrait, .landscapeRight, .portraitUpsideDown] + static var orientationTraverser = 0 + + static func rotate() { + orientationTraverser += 1 + } + + override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { + RotateViewController.orientationList[ + RotateViewController.orientationTraverser % RotateViewController.orientationList.count] + } + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { .all } + override var modalPresentationStyle: UIModalPresentationStyle { get {.fullScreen} set {} } +} + +extension UIApplication { @objc func switchEditorMode(_ sender: AnyObject) { EditorController.shared.switchMode() @@ -29,6 +46,18 @@ extension UIWindow { } } +extension UIViewController { + @objc + func rotateView(_ sender: AnyObject) { + RotateViewController.rotate() + let viewController = RotateViewController(nibName: nil, bundle: nil) + self.present(viewController, animated: true) + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1, execute: { + self.dismiss(animated: true) + }) + } +} + struct CommandsList { static let KeymappingToolbox = "keymapping" } @@ -36,11 +65,13 @@ struct CommandsList { var keymapping = ["Open/Close Keymapping Editor", "Delete selected element", "Upsize selected element", - "Downsize selected element"] -var keymappingSelectors = [#selector(UIWindow.switchEditorMode(_:)), - #selector(UIWindow.removeElement(_:)), - #selector(UIWindow.upscaleElement(_:)), - #selector(UIWindow.downscaleElement(_:))] + "Downsize selected element", + "Rotate display area"] +var keymappingSelectors = [#selector(UIApplication.switchEditorMode(_:)), + #selector(UIApplication.removeElement(_:)), + #selector(UIApplication.upscaleElement(_:)), + #selector(UIApplication.downscaleElement(_:)), + #selector(UIViewController.rotateView(_:))] class MenuController { init(with builder: UIMenuBuilder) { @@ -51,7 +82,7 @@ class MenuController { @available(iOS 15.0, *) class func keymappingMenu() -> UIMenu { - let keyCommands = [ "K", UIKeyCommand.inputDelete, UIKeyCommand.inputUpArrow, UIKeyCommand.inputDownArrow ] + let keyCommands = [ "K", UIKeyCommand.inputDelete, UIKeyCommand.inputUpArrow, UIKeyCommand.inputDownArrow, "R" ] let arrowKeyChildrenCommands = zip(keyCommands, keymapping).map { (command, btn) in UIKeyCommand(title: btn, diff --git a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m index c8bddcc7..d8cb36e0 100644 --- a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m +++ b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m @@ -89,10 +89,6 @@ +(void)load [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_size)]; } - if ([[PlaySettings shared] refreshRate] == 120){ - [objc_getClass("UnityAppController") swizzleInstanceMethod:@selector(callbackFramerateChange:) withMethod:@selector(hook_callbackFramerateChange:)]; - } - [objc_getClass("NSMenuItem") swizzleClassMethod:@selector(enabled) withMethod:@selector(hook_enabled)]; [objc_getClass("IOSViewController") swizzleInstanceMethod:@selector(prefersPointerLocked) withMethod:@selector(hook_prefersPointerLocked)]; @@ -111,11 +107,6 @@ -(BOOL) hook_prefersPointerLocked { return false; } -- (void) hook_callbackFramerateChange:(int)targetFPS { - printf("FPS %d", targetFPS); - [self hook_callbackFramerateChange:120]; -} - - (MTLPixelFormat) hook_stencilAttachmentPixelFormat { return MTLPixelFormatDepth32Float; } diff --git a/PlayTools/Controls/PTFakeTouch/PTFakeMetaTouch.h b/PlayTools/Controls/PTFakeTouch/PTFakeMetaTouch.h index 46f047b8..3cf3f7be 100644 --- a/PlayTools/Controls/PTFakeTouch/PTFakeMetaTouch.h +++ b/PlayTools/Controls/PTFakeTouch/PTFakeMetaTouch.h @@ -31,10 +31,10 @@ double ColourDistance(RGB e1, RGB e2); * @param phase 操作的类别 type of the operation * @param window key window in which touch event is to happen * - * @return realid returns the allocated ID for this touch point, or -1 if no such point + * @return deleted whether the system had deleted a previous touch */ -+ (NSInteger)fakeTouchId:(NSInteger)pointId AtPoint:(CGPoint)point withTouchPhase:(UITouchPhase)phase inWindow:(UIWindow*)window; ++ (NSInteger)fakeTouchId:(NSInteger)pointId AtPoint:(CGPoint)point withTouchPhase:(UITouchPhase)phase inWindow:(UIWindow*)window onView:(UIView*)view; /** * Get a not used pointId 获取一个没有使用过的触屏序列号 obtain a never used touch screen sequence number * diff --git a/PlayTools/Controls/PTFakeTouch/PTFakeMetaTouch.m b/PlayTools/Controls/PTFakeTouch/PTFakeMetaTouch.m index c71ee87a..ac04c75c 100644 --- a/PlayTools/Controls/PTFakeTouch/PTFakeMetaTouch.m +++ b/PlayTools/Controls/PTFakeTouch/PTFakeMetaTouch.m @@ -10,10 +10,16 @@ #import "UITouch-KIFAdditions.h" #import "UIApplication-KIFAdditions.h" #import "UIEvent+KIFAdditions.h" +#import "CoreFoundation/CFRunLoop.h" #include +#include static NSMutableArray *touchAry; static NSMutableArray *livingTouchAry; +static CFRunLoopSourceRef source; + +static UITouch* toRemove = NULL, *toStationarify = NULL; +NSArray *safeTouchAry; void disableCursor(boolean_t disable){ void *handle; @@ -59,6 +65,29 @@ void moveCursorTo(CGPoint point){ } } +void eventSendCallback(void* info) { + UIEvent *event = [[UIApplication sharedApplication] _touchesEvent]; + // to retain objects from being released + [event _clearTouches]; + NSArray *myAry = safeTouchAry; + for (UITouch *aTouch in myAry) { + switch (aTouch.phase) { + case UITouchPhaseEnded: + case UITouchPhaseCancelled: + toRemove = aTouch; + break; + case UITouchPhaseBegan: +// case UITouchPhaseMoved: + toStationarify = aTouch; + break; + default: + break; + } + [event _addTouch:aTouch forDelayedDelivery:NO]; + } + [[UIApplication sharedApplication] sendEvent:event]; +} + @implementation PTFakeMetaTouch + (void)load{ @@ -71,6 +100,15 @@ + (void)load{ [touch setPhaseAndUpdateTimestamp:UITouchPhaseEnded]; [touchAry addObject:touch]; } + CFRunLoopSourceContext context; + memset(&context, 0, sizeof(CFRunLoopSourceContext)); + context.perform = eventSendCallback; + // content of context is copied + source = CFRunLoopSourceCreate(NULL, -2, &context); + CFRunLoopRef loop = CFRunLoopGetMain(); + CFRunLoopAddSource(loop, source, kCFRunLoopCommonModes); +// CFRunLoopMode mode = (CFRunLoopMode)UITrackingRunLoopMode; +// CFRunLoopAddSource(loop, source, GSEventReceiveRunLoopMode); } + (UITouch* ) touch: (NSInteger) pointId { @@ -80,43 +118,54 @@ + (UITouch* ) touch: (NSInteger) pointId { return nil; } -+ (NSInteger)fakeTouchId:(NSInteger)pointId AtPoint:(CGPoint)point withTouchPhase:(UITouchPhase)phase inWindow:(UIWindow*)window{ ++ (NSInteger)fakeTouchId:(NSInteger)pointId AtPoint:(CGPoint)point withTouchPhase:(UITouchPhase)phase inWindow:(UIWindow*)window onView:(UIView*)view{ + bool deleted = false; + UITouch* touch = NULL; + bool needsCopy = false; + if(toRemove != NULL) { + touch = toRemove; + toRemove = NULL; + [livingTouchAry removeObjectIdenticalTo:touch]; + deleted = true; + needsCopy = true; + } + if(toStationarify != NULL) { + // in case this is changed during the operations + touch = toStationarify; + toStationarify = NULL; + if(touch.phase == UITouchPhaseBegan) { + [touch setPhaseAndUpdateTimestamp:UITouchPhaseStationary]; + } + } pointId -= 1; // ideally should be phase began when this hit // but if by any means other phases come... well lets be forgiving - NSUInteger livingIndex = [livingTouchAry indexOfObjectIdenticalTo:touchAry[pointId]]; - if(livingIndex == NSNotFound) { - if(phase == UITouchPhaseEnded) return -1; - UITouch *touch = [[UITouch alloc] initAtPoint:point inWindow:window]; - livingIndex = livingTouchAry.count; + touch = touchAry[pointId]; + bool old = [livingTouchAry containsObject:touch]; + bool new = !old; + if(new) { + if(phase == UITouchPhaseEnded) return deleted; + touch = [[UITouch alloc] initAtPoint:point inWindow:window onView:view]; [livingTouchAry addObject:touch]; [touchAry setObject:touch atIndexedSubscript:pointId ]; - } - UITouch *touch = [livingTouchAry objectAtIndex:livingIndex]; - - [touch setLocationInWindow:point]; - if(touch.phase!=UITouchPhaseBegan){ - [touch setPhaseAndUpdateTimestamp:phase]; - } - -// UIEvent *event = [self eventWithTouches:livingTouchAry]; - UIEvent *event = [[UIApplication sharedApplication] _touchesEvent]; - dispatch_sync(dispatch_get_main_queue(), ^{ - [event _clearTouches]; - for (UITouch *aTouch in livingTouchAry) { - [event _addTouch:aTouch forDelayedDelivery:NO]; + needsCopy = true; + } else { + if(touch.phase == UITouchPhaseBegan && phase == UITouchPhaseMoved) { + return deleted; } - [[UIApplication sharedApplication] sendEvent:event]; - }); - if ((touch.phase==UITouchPhaseBegan)||touch.phase==UITouchPhaseMoved) { - [touch setPhaseAndUpdateTimestamp:UITouchPhaseStationary]; + [touch setLocationInWindow:point]; } - - if (phase == UITouchPhaseEnded) { - [livingTouchAry removeObjectAtIndex:livingIndex]; -// [touchAry removeObjectAtIndex:pointId]; + [touch setPhaseAndUpdateTimestamp:phase]; +// CFRunLoopSourceContext context; +// CFRunLoopSourceGetContext(source, &context); + if(needsCopy) { + CFTypeRef delayRelease = CFBridgingRetain(safeTouchAry); + safeTouchAry = [[NSArray alloc] initWithArray:livingTouchAry copyItems:NO]; + CFBridgingRelease(delayRelease); } - return livingIndex; + CFRunLoopSourceSignal(source); +// UIEvent *event = [self eventWithTouches:livingTouchAry]; + return deleted; } diff --git a/PlayTools/Controls/PTFakeTouch/addition/UITouch-KIFAdditions.h b/PlayTools/Controls/PTFakeTouch/addition/UITouch-KIFAdditions.h index d2d63d6f..be81e6dd 100644 --- a/PlayTools/Controls/PTFakeTouch/addition/UITouch-KIFAdditions.h +++ b/PlayTools/Controls/PTFakeTouch/addition/UITouch-KIFAdditions.h @@ -36,6 +36,7 @@ KW_FIX_CATEGORY_BUG_H(UITouch_KIFAdditions) - (id)initInView:(UIView *)view; - (id)initAtPoint:(CGPoint)point inView:(UIView *)view; - (id)initAtPoint:(CGPoint)point inWindow:(UIWindow *)window; +- (id)initAtPoint:(CGPoint)point inWindow:(UIWindow *)window onView:(UIView*)view; - (id)initTouch; - (void)resetTouch; diff --git a/PlayTools/Controls/PTFakeTouch/addition/UITouch-KIFAdditions.m b/PlayTools/Controls/PTFakeTouch/addition/UITouch-KIFAdditions.m index 399239d3..bdcff737 100644 --- a/PlayTools/Controls/PTFakeTouch/addition/UITouch-KIFAdditions.m +++ b/PlayTools/Controls/PTFakeTouch/addition/UITouch-KIFAdditions.m @@ -34,6 +34,12 @@ - (id)initInView:(UIView *)view; } - (id)initAtPoint:(CGPoint)point inWindow:(UIWindow *)window; +{ + UIView *view = [window hitTest:point withEvent:nil]; + return [self initAtPoint:point inWindow:window onView:view]; +} + +- (id)initAtPoint:(CGPoint)point inWindow:(UIWindow *)window onView:(UIView *)view; { self = [super init]; if (self == nil) { @@ -46,7 +52,7 @@ - (id)initAtPoint:(CGPoint)point inWindow:(UIWindow *)window; //[self setTapCount:1]; [self _setLocationInWindow:point resetPrevious:YES]; - UIView *hitTestView = [window hitTest:point withEvent:nil]; + UIView *hitTestView = view; [self setView:hitTestView]; [self setPhase:UITouchPhaseBegan]; diff --git a/PlayTools/Controls/PlayAction.swift b/PlayTools/Controls/PlayAction.swift index cb5f2f38..65d2ecda 100644 --- a/PlayTools/Controls/PlayAction.swift +++ b/PlayTools/Controls/PlayAction.swift @@ -19,29 +19,43 @@ extension GCKeyboard { class ButtonAction: Action { func invalidate() { Toucher.touchcam(point: point, phase: UITouch.Phase.ended, tid: id) + if let keyboard = GCKeyboard.coalesced?.keyboardInput { + keyboard.button(forKeyCode: key)?.pressedChangedHandler = nil + } } let key: GCKeyCode - let keyid: Int let point: CGPoint var id: Int - init(id: Int, keyid: Int, key: GCKeyCode, point: CGPoint) { - self.keyid = keyid + init(id: Int, key: GCKeyCode, point: CGPoint) { self.key = key self.point = point self.id = id if let keyboard = GCKeyboard.coalesced?.keyboardInput { - if !PlayMice.shared.setMiceButtons(keyid, action: self) { - keyboard.button(forKeyCode: key)?.pressedChangedHandler = { _, _, pressed in + if !PlayMice.shared.setMiceButtons(key.rawValue, action: self) { + let handler = keyboard.button(forKeyCode: key)?.pressedChangedHandler + keyboard.button(forKeyCode: key)?.pressedChangedHandler = { button, value, pressed in if !mode.visible && !PlayInput.cmdPressed() { self.update(pressed: pressed) } + if let previous = handler { + previous(button, value, pressed) + } } } } } + convenience init(id: Int, data: Button) { + self.init( + id: id, + key: GCKeyCode(rawValue: data.keyCode), + point: CGPoint( + x: data.transform.xCoord.absoluteX, + y: data.transform.yCoord.absoluteY)) + } + func update(pressed: Bool) { if pressed { Toucher.touchcam(point: point, phase: UITouch.Phase.began, tid: id) @@ -56,10 +70,12 @@ class DraggableButtonAction: ButtonAction { var releasePoint: CGPoint - override init(id: Int, keyid: Int, key: GCKeyCode, point: CGPoint) { + override init(id: Int, key: GCKeyCode, point: CGPoint) { self.releasePoint = point - super.init(id: id, keyid: keyid, key: key, point: point) - PlayMice.shared.setupMouseMovedHandler() + super.init(id: id, key: key, point: point) + if settings.mouseMapping { + PlayMice.shared.setupMouseMovedHandler() + } } override func update(pressed: Bool) { @@ -80,8 +96,8 @@ class DraggableButtonAction: ButtonAction { } func onMouseMoved(deltaX: CGFloat, deltaY: CGFloat) { - self.releasePoint.x += deltaX * CGFloat(PlaySettings.shared.sensivity) - self.releasePoint.y -= deltaY * CGFloat(PlaySettings.shared.sensivity) + self.releasePoint.x += deltaX * CGFloat(PlaySettings.shared.sensitivity) + self.releasePoint.y -= deltaY * CGFloat(PlaySettings.shared.sensitivity) Toucher.touchcam(point: self.releasePoint, phase: UITouch.Phase.moved, tid: id) } } @@ -100,25 +116,40 @@ class JoystickAction: Action { self.id = id if let keyboard = GCKeyboard.coalesced?.keyboardInput { for key in keys { - keyboard.button(forKeyCode: key)?.pressedChangedHandler = { _, _, _ in + 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) + } } } -// keyboard.button(forKeyCode: keys[1])?.pressedChangedHandler = { _, _, _ in -// self.update() -// } -// keyboard.button(forKeyCode: keys[2])?.pressedChangedHandler = { _, _, _ in -// self.update() -// } -// keyboard.button(forKeyCode: keys[3])?.pressedChangedHandler = { _, _, _ in -// self.update() -// } } } + convenience init(id: Int, data: Joystick) { + self.init( + id: id, + keys: [ + GCKeyCode(rawValue: CFIndex(data.upKeyCode)), + GCKeyCode(rawValue: CFIndex(data.downKeyCode)), + GCKeyCode(rawValue: CFIndex(data.leftKeyCode)), + GCKeyCode(rawValue: CFIndex(data.rightKeyCode)) + ], + center: CGPoint( + x: data.transform.xCoord.absoluteX, + y: data.transform.yCoord.absoluteY), + shift: data.transform.size.absoluteSize) + } + 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() { diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 16c8f0bb..a3bb0d64 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -2,7 +2,7 @@ import Foundation import GameController import UIKit -final class PlayInput: NSObject { +class PlayInput { static let shared = PlayInput() var actions = [Action]() var timeoutForBind = true @@ -19,34 +19,30 @@ final class PlayInput: NSObject { func setup() { actions = [] + // ID 1 is left for mouse area var counter = 2 - for key in settings.layout { - if key.count == 4 { - actions.append(ButtonAction(id: counter, - keyid: Int(key[0]), - key: GCKeyCode.init(rawValue: CFIndex(key[0])), - point: CGPoint(x: key[1].absoluteX, - y: key[2].absoluteY))) - } else if key.count == 5 { - actions.append(DraggableButtonAction(id: counter, - keyid: Int(key[0]), - key: GCKeyCode.init(rawValue: CFIndex(key[0])), - point: CGPoint(x: key[1].absoluteX, - y: key[2].absoluteY))) - } else if key.count == 8 { - actions.append(JoystickAction(id: counter, - keys: [GCKeyCode.init(rawValue: CFIndex(key[0])), - GCKeyCode.init(rawValue: CFIndex(key[1])), - GCKeyCode.init(rawValue: CFIndex(key[2])), - GCKeyCode.init(rawValue: CFIndex(key[3]))], - center: CGPoint(x: key[4].absoluteX, - y: key[5].absoluteY), - shift: key[6].absoluteSize)) - } else if key.count == 2 && PlaySettings.shared.gamingMode { - PlayMice.shared.setup(key) + for button in keymap.keymapData.buttonModels { + actions.append(ButtonAction(id: counter, data: button)) + counter += 1 + } + + for draggableButton in keymap.keymapData.draggableButtonModels { + actions.append(DraggableButtonAction(id: counter, data: draggableButton)) + counter += 1 + } + + if settings.mouseMapping { + for mouse in keymap.keymapData.mouseAreaModel { + PlayMice.shared.setup(mouse) + counter += 1 } + } + + for joystick in keymap.keymapData.joystickModel { + actions.append(JoystickAction(id: counter, data: joystick)) counter += 1 } + if let keyboard = GCKeyboard.coalesced?.keyboardInput { keyboard.keyChangedHandler = { _, _, keyCode, _ in if editor.editorMode @@ -56,10 +52,10 @@ final class PlayInput: NSObject { EditorController.shared.setKeyCode(keyCode.rawValue) } } - keyboard.button(forKeyCode: GCKeyCode(rawValue: 227))?.pressedChangedHandler = { _, _, pressed in + keyboard.button(forKeyCode: .leftGUI)?.pressedChangedHandler = { _, _, pressed in PlayInput.lCmdPressed = pressed } - keyboard.button(forKeyCode: GCKeyCode(rawValue: 231))?.pressedChangedHandler = { _, _, pressed in + keyboard.button(forKeyCode: .rightGUI)?.pressedChangedHandler = { _, _, pressed in PlayInput.rCmdPressed = pressed } keyboard.button(forKeyCode: .leftAlt)?.pressedChangedHandler = { _, _, pressed in @@ -72,8 +68,6 @@ final class PlayInput: NSObject { } static public func cmdPressed() -> Bool { - // return keyboard.button(forKeyCode: GCKeyCode(rawValue: 227))!.isPressed - // || keyboard.button(forKeyCode: GCKeyCode(rawValue: 231))!.isPressed return lCmdPressed || rCmdPressed } @@ -89,15 +83,15 @@ final class PlayInput: NSObject { } private static let FORBIDDEN: [GCKeyCode] = [ - GCKeyCode.init(rawValue: 227), // LCmd - GCKeyCode.init(rawValue: 231), // RCmd + .leftGUI, + .rightGUI, .leftAlt, .rightAlt, .printScreen ] private func swapMode(_ pressed: Bool) { - if !PlaySettings.shared.gamingMode { + if !settings.mouseMapping { return } if pressed { @@ -113,7 +107,7 @@ final class PlayInput: NSObject { } func initialize() { - if PlaySettings.shared.keymapping == false { + if !PlaySettings.shared.keymapping { return } @@ -129,19 +123,13 @@ final class PlayInput: NSObject { } setup() - // fix beep sound - eliminateRedundantKeyPressEvents() + + // Fix beep sound + AKInterface.shared! + .eliminateRedundantKeyPressEvents({ self.dontIgnore() }) } - private func eliminateRedundantKeyPressEvents() { - // dont know how to dynamically get it here - let NSEventMaskKeyDown: UInt64 = 1024 - Dynamic.NSEvent.addLocalMonitorForEventsMatchingMask( NSEventMaskKeyDown, handler: { event in - if (mode.visible && !EditorController.shared.editorMode) || PlayInput.cmdPressed() { - return event - } -// Toast.showOver(msg: "mask: \(NSEventMaskKeyDown)") - return nil - } as ResponseBlock) + func dontIgnore() -> Bool { + (mode.visible && !EditorController.shared.editorMode) || PlayInput.cmdPressed() } } diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index b8df5587..fc5d5c71 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -4,74 +4,90 @@ // import Foundation - import GameController -import CoreGraphics - -typealias ResponseBlock = @convention(block) (_ event: Any) -> Any? - -typealias ResponseBlockBool = @convention(block) (_ event: Any) -> Bool - -@objc final public class PlayMice: NSObject { - - @objc public static let shared = PlayMice() - private var camera: CameraControl? +public class PlayMice { + public static let shared = PlayMice() private static var isInit = false - private var acceptMouseEvents = !PlaySettings.shared.gamingMode + private var camera: CameraControl? + private var acceptMouseEvents = !PlaySettings.shared.mouseMapping - public override init() { - super.init() + public init() { if !PlayMice.isInit { setupMouseButton(_up: 2, _down: 4) setupMouseButton(_up: 8, _down: 16) setupMouseButton(_up: 33554432, _down: 67108864) PlayMice.isInit = true + if acceptMouseEvents { + setupMouseMovedHandler() + } } } + var fakedMousePressed = false + public var cursorPos: CGPoint { var point = CGPoint(x: 0, y: 0) if #available(macOS 11, *) { - point = Dynamic(screen.nsWindow).mouseLocationOutsideOfEventStream.asCGPoint! + point = AKInterface.shared!.mousePoint + } + let rect = AKInterface.shared!.windowFrame + 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 let rect = (Dynamic(screen.nsWindow).frame.asCGRect) { - point.x = (point.x / rect.width) * screen.screenRect.width - point.y = screen.screenRect.height - ((point.y / rect.height) * screen.screenRect.height) + // 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 + return point } - public func setup(_ key: [CGFloat]) { - camera = CameraControl(centerX: key[0].absoluteX, centerY: key[1].absoluteY) + func setup(_ data: MouseArea) { + camera = CameraControl( + centerX: data.transform.xCoord.absoluteX, + centerY: data.transform.yCoord.absoluteY) setupMouseMovedHandler() } public func setupMouseMovedHandler() { for mouse in GCMouse.mice() { mouse.mouseInput?.mouseMovedHandler = { _, deltaX, deltaY in - Toucher.touchQueue.async { - if !mode.visible { - if let draggableButton = DraggableButtonAction.activeButton { - draggableButton.onMouseMoved(deltaX: CGFloat(deltaX), deltaY: CGFloat(deltaY)) - } else { - self.camera?.updated(CGFloat(deltaX), CGFloat(deltaY)) - } + if !mode.visible { + if let draggableButton = DraggableButtonAction.activeButton { + draggableButton.onMouseMoved(deltaX: CGFloat(deltaX), deltaY: CGFloat(deltaY)) + } else { + self.camera?.updated(CGFloat(deltaX), CGFloat(deltaY)) + } + if self.acceptMouseEvents && self.fakedMousePressed { + Toucher.touchcam(point: self.cursorPos, phase: UITouch.Phase.moved, tid: 1) } } +// Toast.showOver(msg: "\(self.cursorPos)") } } } public func stop() { - for mouse in GCMouse.mice() { - mouse.mouseInput?.mouseMovedHandler = nil - } +// for mouse in GCMouse.mice() { +// mouse.mouseInput?.mouseMovedHandler = nil +// } camera?.stop() camera = nil - mouseActions = [:] + mouseActions.keys.forEach { key in + mouseActions[key] = [] + } } func setMiceButtons(_ keyId: Int, action: ButtonAction) -> Bool { @@ -82,38 +98,64 @@ typealias ResponseBlockBool = @convention(block) (_ event: Any) -> Bool return false } - var mouseActions: [Int: ButtonAction] = [:] + var mouseActions: [Int: [ButtonAction]] = [2: [], 8: [], 33554432: []] private func setupMouseButton(_up: Int, _down: Int) { - Dynamic.NSEvent.addLocalMonitorForEventsMatchingMask(_up, handler: { event in - if !mode.visible || self.acceptMouseEvents { - self.mouseActions[_up]?.update(pressed: true) - if self.acceptMouseEvents { - return event + AKInterface.shared!.setupMouseButton(_up, _down, dontIgnore(_:_:_:)) + } + + private func dontIgnore(_ actionIndex: Int, _ state: Bool, _ isEventWindow: Bool) -> Bool { + if EditorController.shared.editorMode { + if state { + if actionIndex == 8 { + EditorController.shared.setKeyCode(-2) + } else if actionIndex == 33554432 { + EditorController.shared.setKeyCode(-3) } - return nil + return true + } else { + return true } - return event - } as ResponseBlock) - Dynamic.NSEvent.addLocalMonitorForEventsMatchingMask(_down, handler: { event in - if !mode.visible || self.acceptMouseEvents { - self.mouseActions[_up]?.update(pressed: false) - if self.acceptMouseEvents { - return event + } + if self.acceptMouseEvents { + if state { + if !self.fakedMousePressed + // For traffic light buttons when not fullscreen + && self.cursorPos.y > 0 + // For traffic light buttons when fullscreen + && isEventWindow { + Toucher.touchcam(point: self.cursorPos, + phase: UITouch.Phase.began, + tid: 1) + self.fakedMousePressed = true + return false } - return nil + return true + } else { + if self.fakedMousePressed { + self.fakedMousePressed = false + Toucher.touchcam(point: self.cursorPos, phase: UITouch.Phase.ended, tid: 1) + return false + } + return true } - return event - } as ResponseBlock) + } + if !mode.visible { + self.mouseActions[actionIndex]!.forEach({ buttonAction in + buttonAction.update(pressed: state) + }) + return false + } + return true } private func setMiceButton(_ keyId: Int, action: ButtonAction) { switch keyId { - case -1: mouseActions[2] = action - case -2: mouseActions[8] = action - case -3: mouseActions[33554432] = action + case -1: mouseActions[2]!.append(action) + case -2: mouseActions[8]!.append(action) + case -3: mouseActions[33554432]!.append(action) default: - mouseActions[2] = action + mouseActions[2]!.append(action) } } } @@ -136,68 +178,50 @@ final class CameraControl { Toucher.touchQueue.asyncAfter(deadline: when, execute: closure) } - // if max speed of this touch is high - var movingFast = false - // seq number for each move event. Used in closure to determine if this move is the last - var sequence = 0 // like sequence but resets when touch begins. Used to calc touch duration var counter = 0 // if should wait before beginning next touch var cooldown = false - // if the touch point had been prevented from lifting off because of moving slow - var idled = false + // in how many tests has this been identified as stationary + var stationaryCount = 0 + let stationaryThreshold = 2 + + @objc 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: checkEnded) + return + } + self.doLiftOff() + } @objc func updated(_ deltaX: CGFloat, _ deltaY: CGFloat) { if mode.visible || cooldown { return } - sequence += 1 // count touch duration counter += 1 if !isMoving { isMoving = true - movingFast = false - idled = false location = center counter = 0 + stationaryCount = 0 Toucher.touchcam(point: self.center, phase: UITouch.Phase.began, tid: 1) + + delay(0.01, closure: checkEnded) } - // if not moving fast, regard the user fine-tuning the camera(e.g. aiming) - // so hold the touch for longer to avoid cold startup - if deltaX.magnitude + deltaY.magnitude > 4 { - // if we had mistaken this as player aiming - if self.idled { - // since not aiming, re-touch to re-gain control - self.doLiftOff() - return - } - movingFast = true + if self.counter == 120 { + self.doLiftOff() + return } - self.location.x += deltaX * CGFloat(PlaySettings.shared.sensivity) - self.location.y -= deltaY * CGFloat(PlaySettings.shared.sensivity) + self.location.x += deltaX * CGFloat(PlaySettings.shared.sensitivity) + self.location.y -= deltaY * CGFloat(PlaySettings.shared.sensitivity) Toucher.touchcam(point: self.location, phase: UITouch.Phase.moved, tid: 1) - let previous = sequence - - delay(0.016) { - // if no other touch events in the past 0.016 sec - if previous != self.sequence { - return - } - // and slow touch lasts for sufficient time - if self.movingFast || self.counter > 64 { - self.doLiftOff() - } else { - self.idled = true - // idle for at most 4 seconds - self.delay(4) { - if previous != self.sequence { - return - } - self.doLiftOff() - } - } - } - + if stationaryCount > self.stationaryThreshold { + self.counter = 0 + } + stationaryCount = 0 } public func doLiftOff() { @@ -205,18 +229,20 @@ final class CameraControl { return } Toucher.touchcam(point: self.location, phase: UITouch.Phase.ended, tid: 1) +// DispatchQueue.main.async { +// Toast.showOver(msg: "mouse released") +// } self.isMoving = false // ending and beginning too frequently leads to the beginning event not recognized // so let the beginning event wait some time - // 0.016 here is safe as long as the 0.016 above works - delay(0.016) { + // pause for one frame or two + delay(0.02) { self.cooldown = false } cooldown = true } func stop() { - sequence = 0 self.doLiftOff() } } diff --git a/PlayTools/Controls/Toucher.swift b/PlayTools/Controls/Toucher.swift index 1e14095d..edcbf553 100644 --- a/PlayTools/Controls/Toucher.swift +++ b/PlayTools/Controls/Toucher.swift @@ -8,15 +8,19 @@ import UIKit class Toucher { - static var keyWindow: UIWindow? + static weak var keyWindow: UIWindow? + static weak var keyView: UIView? static var touchQueue = DispatchQueue.init(label: "playcover.toucher", qos: .userInteractive) static func touchcam(point: CGPoint, phase: UITouch.Phase, tid: Int) { touchQueue.async { - if keyWindow == nil { + if keyWindow == nil || keyView == nil { keyWindow = screen.keyWindow + DispatchQueue.main.sync { + keyView = keyWindow?.hitTest(point, with: nil) + } } - PTFakeMetaTouch.fakeTouchId(tid, at: point, with: phase, in: keyWindow) + PTFakeMetaTouch.fakeTouchId(tid, at: point, with: phase, in: keyWindow, on: keyView) } } } diff --git a/PlayTools/DiscordActivity/DiscordActivity.swift b/PlayTools/DiscordActivity/DiscordActivity.swift new file mode 100644 index 00000000..4a26d88a --- /dev/null +++ b/PlayTools/DiscordActivity/DiscordActivity.swift @@ -0,0 +1,16 @@ +// +// DiscordActivity.swift +// PlayTools +// +// Created by 이승윤 on 2022/07/16. +// + +import Foundation + +class DiscordActivity: Codable { + var enable = true + var applicationID = "" + var details = "" + var state = "" + var image = "" +} diff --git a/PlayTools/DiscordActivity/DiscordIPC.swift b/PlayTools/DiscordActivity/DiscordIPC.swift new file mode 100644 index 00000000..a50b43c8 --- /dev/null +++ b/PlayTools/DiscordActivity/DiscordIPC.swift @@ -0,0 +1,80 @@ +// +// DiscordIPC.swift +// PlayTools +// +// Created by 이승윤 on 2022/07/15. +// + +import Foundation +import SwordRPC + +class DiscordIPC { + public static let shared = DiscordIPC() + + func initialize() { + if PlaySettings.shared.discordActivity.enable { + let ipc: SwordRPC + let custom = PlaySettings.shared.discordActivity + if custom.applicationID.isEmpty { + ipc = SwordRPC(appId: "996108521680678993") + } else { + ipc = SwordRPC(appId: custom.applicationID) + } + let activity = createActivity(from: custom) + ipc.connect() + ipc.setPresence(activity) + } + } + + func createActivity(from custom: DiscordActivity) -> RichPresence { + var activity = RichPresence() + + if custom.details.isEmpty { + let name = Bundle.main.infoDictionary?["CFBundleName"] as? String ?? "" + let displayName = Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String ?? name + activity.details = "Playing \(displayName)" + } else { + if custom.details.count == 1 { custom.details += " " } + activity.details = custom.details + } + + let poweredStr = "Powered by PlayCover" + if custom.state.isEmpty { + activity.state = poweredStr + } else { + if custom.state.count == 1 { custom.state += " " } + activity.state = custom.state + activity.assets.smallText = poweredStr + activity.assets.largeText = poweredStr + } + + let logo = "https://github.com/PlayCover/playcover-website/raw/master/src/assets/images/play-cover.png" + if custom.image.isEmpty { + let bundleID = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String ?? "" + if let appImage = loadImage(bundleID: bundleID) { + activity.assets.largeImage = appImage + activity.assets.largeText = nil + activity.assets.smallImage = logo + } else { + activity.assets.largeImage = logo + } + } else { + activity.assets.largeImage = custom.image + activity.assets.largeText = nil + activity.assets.smallImage = logo + } + + activity.timestamps.start = Date() + + activity.buttons[0].label = "Download PlayCover" + activity.buttons[0].url = "https://github.com/PlayCover/PlayCover/releases" + activity.buttons.removeLast() + + return activity + } + + // TODO : load App icon image from appstore + func loadImage(bundleID: String) -> String? { + return nil + } +} diff --git a/PlayTools/Keymap/ControlModel.swift b/PlayTools/Keymap/ControlModel.swift index 066291db..4a21569c 100644 --- a/PlayTools/Keymap/ControlModel.swift +++ b/PlayTools/Keymap/ControlModel.swift @@ -1,6 +1,6 @@ import GameController -@objc class ControlData: NSObject { +class ControlData { var keyCodes: [Int] var size: CGFloat var xCoord: CGFloat @@ -15,7 +15,7 @@ import GameController self.parent = parent } - init(keyCodes: [Int], size: CGFloat, xCoord: CGFloat, yCoord: CGFloat, sensivity: CGFloat) { + init(keyCodes: [Int], size: CGFloat, xCoord: CGFloat, yCoord: CGFloat, sensitivity: CGFloat) { self.keyCodes = keyCodes self.size = size self.xCoord = xCoord @@ -52,38 +52,10 @@ class Element: UIButton { } class ControlModel { - static func createControlFromData(data: [CGFloat]) -> ControlModel? { - if data.count == 4 { - return ButtonModel(data: ControlData(keyCodes: [Int(data[0])], - size: data[3], - xCoord: data[1], - yCoord: data[2], - parent: nil)) - } else if data.count == 5 { - return DraggableButtonModel(data: ControlData(keyCodes: [Int(data[0])], - size: data[3], - xCoord: data[1], - yCoord: data[2])) - } else if data.count == 8 { - return JoystickModel(data: ControlData(keyCodes: [Int(data[0]), - Int(data[1]), Int(data[2]), - Int(data[3])], - size: data[6], - xCoord: data[4], - yCoord: data[5])) - } else if data.count == 2 { - return MouseAreaModel(data: ControlData(size: 25, xCoord: data[0], yCoord: data[1])) - } - return nil - } var data: ControlData var button: Element - func save() -> [CGFloat] { - return [] - } - func update() {} func focus(_ focus: Bool) {} @@ -133,8 +105,10 @@ class ButtonModel: ControlModel { update() } - override func save() -> [CGFloat] { - return [CGFloat(data.keyCodes[0]), data.xCoord, data.yCoord, data.size] + func save() -> Button { + Button( + keyCode: data.keyCodes[0], + transform: KeyModelTransform(size: data.size, xCoord: data.xCoord, yCoord: data.yCoord)) } override func update() { @@ -173,27 +147,6 @@ class ButtonModel: ControlModel { } } -class RMBModel: ButtonModel { - override func setKeyCodes(keys: [Int]) { - data.keyCodes = [-2] - button.setTitle("RMB", for: UIControl.State.normal) - } -} - -class LMBModel: ButtonModel { - override func setKeyCodes(keys: [Int]) { - data.keyCodes = [-1] - button.setTitle("LMB", for: UIControl.State.normal) - } -} - -class MMBModel: ButtonModel { - override func setKeyCodes(keys: [Int]) { - data.keyCodes = [-3] - button.setTitle("MMB", for: UIControl.State.normal) - } -} - class JoystickButtonModel: ControlModel { override init(data: ControlData) { super.init(data: data) @@ -240,9 +193,9 @@ class JoystickButtonModel: ControlModel { class DraggableButtonModel: MouseAreaModel { var childButton: JoystickButtonModel? - override func save() -> [CGFloat] { - return [CGFloat(childButton!.data.keyCodes[0]), - self.data.xCoord, self.data.yCoord, self.data.size, self.data.size] + func save() -> Button { + return Button(keyCode: childButton!.data.keyCodes[0], + transform: KeyModelTransform(size: data.size, xCoord: data.xCoord, yCoord: data.yCoord)) } override func setKeyCodes(keys: [Int]) { @@ -270,13 +223,13 @@ class DraggableButtonModel: MouseAreaModel { class JoystickModel: ControlModel { var joystickButtons = [JoystickButtonModel]() - override func save() -> [CGFloat] { - var data = [CGFloat]() - for joystickButton in joystickButtons { - data.append(CGFloat(joystickButton.data.keyCodes[0])) - } - data.append(contentsOf: [self.data.xCoord, self.data.yCoord, self.data.size, self.data.size]) - return data + func save() -> Joystick { + Joystick( + upKeyCode: joystickButtons[0].data.keyCodes[0], + rightKeyCode: joystickButtons[3].data.keyCodes[0], + downKeyCode: joystickButtons[1].data.keyCodes[0], + leftKeyCode: joystickButtons[2].data.keyCodes[0], + transform: KeyModelTransform(size: data.size, xCoord: data.xCoord, yCoord: data.yCoord)) } override init(data: ControlData) { @@ -349,8 +302,8 @@ class JoystickModel: ControlModel { } class MouseAreaModel: ControlModel { - override func save() -> [CGFloat] { - return [data.xCoord, data.yCoord] + func save() -> MouseArea { + MouseArea(transform: KeyModelTransform(size: data.size, xCoord: data.xCoord, yCoord: data.yCoord)) } override func focus(_ focus: Bool) { diff --git a/PlayTools/Keymap/EditorController.swift b/PlayTools/Keymap/EditorController.swift index 7a1280de..19fd736a 100644 --- a/PlayTools/Keymap/EditorController.swift +++ b/PlayTools/Keymap/EditorController.swift @@ -9,7 +9,7 @@ class EditorViewController: UIViewController { } } -final class EditorController: NSObject { +class EditorController { static let shared = EditorController() @@ -17,15 +17,14 @@ final class EditorController: NSObject { var focusedControl: ControlModel? - lazy var editorWindow: UIWindow = initWindow() - var previousWindow: UIWindow? + var editorWindow: UIWindow? + weak var previousWindow: UIWindow? var controls: [ControlModel] = [] - lazy var viewController = EditorViewController(nibName: nil, bundle: nil) - lazy var view: EditorView! = viewController.view as? EditorView + var view: EditorView! {editorWindow?.rootViewController?.view as? EditorView} private func initWindow() -> UIWindow { let window = UIWindow(windowScene: screen.windowScene!) - window.rootViewController = viewController + window.rootViewController = EditorViewController(nibName: nil, bundle: nil) return window } @@ -50,12 +49,14 @@ final class EditorController: NSObject { public func switchMode() { lock.lock() - if editorMode { KeymapHolder.shared.hide() saveButtons() - editorMode = false -// editorWindow.windowScene = nil + editorWindow?.isHidden = true + editorWindow?.windowScene = nil + editorWindow?.rootViewController = nil + // menu still holds this object until next responder hit test + editorWindow = nil previousWindow?.makeKeyAndVisible() mode.show(false) focusedControl = nil @@ -63,19 +64,16 @@ final class EditorController: NSObject { } else { mode.show(true) previousWindow = screen.keyWindow -// editorWindow.windowScene = screen.windowScene - editorMode = true - editorWindow.makeKeyAndVisible() + editorWindow = initWindow() + editorWindow?.makeKeyAndVisible() showButtons() Toast.showOver(msg: "Click to start keymmaping edit") } +// Toast.showOver(msg: "\(UIApplication.shared.windows.count)") lock.unlock() } - var editorMode: Bool { - get { !editorWindow.isHidden } - set { editorWindow.isHidden = !newValue} - } + var editorMode: Bool { !(editorWindow?.isHidden ?? true)} public func setKeyCode(_ key: Int) { if editorMode { @@ -89,24 +87,65 @@ final class EditorController: NSObject { } func showButtons() { - for btn in settings.layout { - if let ctrl = ControlModel.createControlFromData(data: btn) { - addControlToView(control: ctrl) - } + for button in keymap.keymapData.buttonModels { + let ctrl = ButtonModel(data: ControlData( + keyCodes: [button.keyCode], + size: button.transform.size, + xCoord: button.transform.xCoord, + yCoord: button.transform.yCoord, + parent: nil)) + addControlToView(control: ctrl) + } + for button in keymap.keymapData.draggableButtonModels { + let ctrl = DraggableButtonModel(data: ControlData( + keyCodes: [button.keyCode], + size: button.transform.size, + xCoord: button.transform.xCoord, + yCoord: button.transform.yCoord, + parent: nil)) + addControlToView(control: ctrl) + } + for mouse in keymap.keymapData.mouseAreaModel { + let ctrl = + MouseAreaModel(data: ControlData( + size: mouse.transform.size, + xCoord: mouse.transform.xCoord, + yCoord: mouse.transform.yCoord)) + addControlToView(control: ctrl) + } + for joystick in keymap.keymapData.joystickModel { + let ctrl = JoystickModel(data: ControlData( + keyCodes: [joystick.upKeyCode, joystick.downKeyCode, joystick.leftKeyCode, joystick.rightKeyCode], + size: joystick.transform.size, + xCoord: joystick.transform.xCoord, + yCoord: joystick.transform.yCoord)) + addControlToView(control: ctrl) } } func saveButtons() { - var updatedLayout = [[CGFloat]]() - for model in controls { - updatedLayout.append(model.save()) + var keymapData = KeymappingData(bundleIdentifier: keymap.bundleIdentifier) + controls.forEach { + switch $0 { + case let model as JoystickModel: + keymapData.joystickModel.append(model.save()) + // subclasses must be checked first + case let model as DraggableButtonModel: + keymapData.draggableButtonModels.append(model.save()) + case let model as MouseAreaModel: + keymapData.mouseAreaModel.append(model.save()) + case let model as ButtonModel: + keymapData.buttonModels.append(model.save()) + default: + break + } } - settings.layout = updatedLayout + keymap.keymapData = keymapData controls = [] view.subviews.forEach { $0.removeFromSuperview() } } - @objc public func addJoystick(_ center: CGPoint) { + public func addJoystick(_ center: CGPoint) { if editorMode { addControlToView(control: JoystickModel(data: ControlData(keyCodes: [GCKeyCode.keyW.rawValue, GCKeyCode.keyS.rawValue, @@ -118,7 +157,7 @@ final class EditorController: NSObject { } } - @objc public func addButton(_ toPoint: CGPoint) { + public func addButton(_ toPoint: CGPoint) { if editorMode { addControlToView(control: ButtonModel(data: ControlData(keyCodes: [-1], size: 5, @@ -128,9 +167,9 @@ final class EditorController: NSObject { } } - @objc public func addRMB(_ toPoint: CGPoint) { + public func addRMB(_ toPoint: CGPoint) { if editorMode { - addControlToView(control: RMBModel(data: ControlData(keyCodes: [-2], + addControlToView(control: ButtonModel(data: ControlData(keyCodes: [-2], size: 5, xCoord: toPoint.x.relativeX, yCoord: toPoint.y.relativeY, @@ -138,9 +177,9 @@ final class EditorController: NSObject { } } - @objc public func addLMB(_ toPoint: CGPoint) { + public func addLMB(_ toPoint: CGPoint) { if editorMode { - addControlToView(control: LMBModel(data: ControlData(keyCodes: [-1], + addControlToView(control: ButtonModel(data: ControlData(keyCodes: [-1], size: 5, xCoord: toPoint.x.relativeX, yCoord: toPoint.y.relativeY, @@ -148,9 +187,9 @@ final class EditorController: NSObject { } } - @objc public func addMMB(_ toPoint: CGPoint) { + public func addMMB(_ toPoint: CGPoint) { if editorMode { - addControlToView(control: MMBModel(data: ControlData(keyCodes: [-3], + addControlToView(control: ButtonModel(data: ControlData(keyCodes: [-3], size: 5, xCoord: toPoint.x.relativeX, yCoord: toPoint.y.relativeY, @@ -158,7 +197,7 @@ final class EditorController: NSObject { } } - @objc public func addMouseArea(_ center: CGPoint) { + public func addMouseArea(_ center: CGPoint) { if editorMode { addControlToView(control: MouseAreaModel(data: ControlData(size: 25, xCoord: center.x.relativeX, @@ -166,7 +205,7 @@ final class EditorController: NSObject { } } - @objc public func addDraggableButton(_ center: CGPoint, _ keyCode: Int) { + public func addDraggableButton(_ center: CGPoint, _ keyCode: Int) { if editorMode { addControlToView(control: DraggableButtonModel(data: ControlData(keyCodes: [keyCode], size: 15, @@ -215,6 +254,7 @@ class EditorView: UIView { for cntrl in editor.controls { cntrl.focus(false) } + editor.focusedControl = nil KeymapHolder.shared.add(sender.location(in: self)) } diff --git a/PlayTools/Keymap/Keymapping.swift b/PlayTools/Keymap/Keymapping.swift new file mode 100644 index 00000000..24d8af68 --- /dev/null +++ b/PlayTools/Keymap/Keymapping.swift @@ -0,0 +1,97 @@ +// +// Keymapping.swift +// PlayTools +// +// Created by 이승윤 on 2022/08/29. +// + +import Foundation + +let keymap = Keymapping.shared + +class Keymapping { + static let shared = Keymapping() + + let bundleIdentifier = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String ?? "" + var keymapUrl: URL + var keymapData: KeymappingData { + didSet { + encode() + } + } + + init() { + keymapUrl = URL(fileURLWithPath: "/Users/\(NSUserName())/Library/Containers/io.playcover.PlayCover") + .appendingPathComponent("Keymapping") + if !FileManager.default.fileExists(atPath: keymapUrl.path) { + do { + try FileManager.default.createDirectory( + atPath: keymapUrl.path, + withIntermediateDirectories: true, + attributes: [:]) + } catch { + print("[PlayTools] Failed to create Keymapping directory.\n%@") + } + } + keymapUrl.appendPathComponent("\(bundleIdentifier).plist") + keymapData = KeymappingData(bundleIdentifier: bundleIdentifier) + if !decode() { + encode() + } + } + + func encode() { + let encoder = PropertyListEncoder() + encoder.outputFormat = .xml + do { + let data = try encoder.encode(keymapData) + try data.write(to: keymapUrl) + } catch { + print("[PlayTools] Keymapping encode failed.\n%@") + } + } + + func decode() -> Bool { + do { + let data = try Data(contentsOf: keymapUrl) + keymapData = try PropertyListDecoder().decode(KeymappingData.self, from: data) + return true + } catch { + keymapData = KeymappingData(bundleIdentifier: bundleIdentifier) + print("[PlayTools] Keymapping decode failed.\n%@") + return false + } + } +} + +struct KeyModelTransform: Codable { + var size: CGFloat + var xCoord: CGFloat + var yCoord: CGFloat +} + +struct Button: Codable { + var keyCode: Int + var transform: KeyModelTransform +} + +struct Joystick: Codable { + var upKeyCode: Int + var rightKeyCode: Int + var downKeyCode: Int + var leftKeyCode: Int + var transform: KeyModelTransform +} + +struct MouseArea: Codable { + var transform: KeyModelTransform +} + +struct KeymappingData: Codable { + var buttonModels: [Button] = [] + var draggableButtonModels: [Button] = [] + var joystickModel: [Joystick] = [] + var mouseAreaModel: [MouseArea] = [] + var bundleIdentifier: String + var version = "2.0.0" +} diff --git a/PlayTools/PlayCover.swift b/PlayTools/PlayCover.swift index cdd4bfe3..15092916 100644 --- a/PlayTools/PlayCover.swift +++ b/PlayTools/PlayCover.swift @@ -5,25 +5,21 @@ import Foundation import UIKit -import Security -import MetalKit -import WebKit final public class PlayCover: NSObject { @objc static let shared = PlayCover() var menuController: MenuController? - var firstTime = true private override init() {} @objc static public func launch() { quitWhenClose() - PlaySettings.shared.setupLayout() + AKInterface.initialize() PlayInput.shared.initialize() - PlaySettings.shared.clearLegacy() + DiscordIPC.shared.initialize() } @objc static public func quitWhenClose() { @@ -33,7 +29,7 @@ final public class PlayCover: NSObject { queue: OperationQueue.main ) { noti in if PlayScreen.shared.nsWindow?.isEqual(noti.object) ?? false { - Dynamic.NSApplication.sharedApplication.terminate(self) + AKInterface.shared!.terminateApplication() } } } diff --git a/PlayTools/PlayLoader.m b/PlayTools/PlayLoader.m index ef2a15ba..f2f0ddde 100644 --- a/PlayTools/PlayLoader.m +++ b/PlayTools/PlayLoader.m @@ -23,13 +23,13 @@ #define CS_OPS_ENTITLEMENTS_BLOB 7 /* get entitlements blob */ #define CS_OPS_IDENTITY 11 /* get codesign identity */ -int dyld_get_active_platform(); +int dyld_get_active_platform(void); -int my_dyld_get_active_platform() { return 2; } +int my_dyld_get_active_platform(void) { return 2; } -extern void *dyld_get_base_platform(void *platform); +extern uint64_t dyld_get_base_platform(void *platform); -void *my_dyld_get_base_platform(void *platform) { return 2; } +uint64_t my_dyld_get_base_platform(void *platform) { return 2; } // #define DEVICE_MODEL ("iPad13,8") //#define DEVICE_MODEL ("iPad8,6") @@ -39,8 +39,8 @@ // #define OEM_ID ("J320xAP") // get device model from playcover .plist -#define DEVICE_MODEL ([[[PlaySettings shared] GET_IPAD_MODEL] UTF8String]) -#define OEM_ID ([[[PlaySettings shared] GET_OEM_ID] UTF8String]) +#define DEVICE_MODEL ([[[PlaySettings shared] deviceModel] UTF8String]) +#define OEM_ID ([[[PlaySettings shared] oemID] UTF8String]) static int my_uname(struct utsname *uts) { int result = 0; @@ -88,9 +88,9 @@ static int my_sysctlbyname(const char *name, void *oldp, size_t *oldlenp, void * (strcmp(name, "hw.model") == 0)) { if (oldp != NULL) { int ret = sysctlbyname(name, oldp, oldlenp, newp, newlen); - const char *mechine = DEVICE_MODEL; - strncpy((char *)oldp, mechine, strlen(mechine)); - *oldlenp = strlen(mechine); + const char *machine = DEVICE_MODEL; + strncpy((char *)oldp, machine, strlen(machine)); + *oldlenp = strlen(machine); return ret; } else { int ret = sysctlbyname(name, oldp, oldlenp, newp, newlen); @@ -99,9 +99,9 @@ static int my_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); - const char *mechine = OEM_ID; - strncpy((char *)oldp, mechine, strlen(mechine)); - *oldlenp = strlen(mechine); + const char *machine = OEM_ID; + strncpy((char *)oldp, machine, strlen(machine)); + *oldlenp = strlen(machine); return ret; } else { int ret = sysctlbyname(name, oldp, oldlenp, newp, newlen); @@ -149,7 +149,7 @@ static int my_sysctlbyname(const char *name, void *oldp, size_t *oldlenp, void * static bool isGenshin = false; -extern int csops(pid_t pid, unsigned int ops, void *useraddr, size_t usersize); +extern int csops(pid_t pid, unsigned int ops, user_addr_t useraddr, size_t usersize); int my_csops(pid_t pid, uint32_t ops, user_addr_t useraddr, user_size_t usersize) { if (isGenshin) { diff --git a/PlayTools/PlayScreen.swift b/PlayTools/PlayScreen.swift index 7a5e67df..4f124384 100644 --- a/PlayTools/PlayScreen.swift +++ b/PlayTools/PlayScreen.swift @@ -2,10 +2,9 @@ // ScreenController.swift // PlayTools // + import Foundation import UIKit -import SwiftUI -import AVFoundation let screen = PlayScreen.shared let mainScreenWidth = PlaySettings.shared.windowSizeWidth @@ -26,7 +25,6 @@ extension CGSize { } extension CGRect { - func aspectRatio() -> CGFloat { if mainScreenWidth > mainScreenHeight { return mainScreenWidth / mainScreenHeight @@ -45,27 +43,25 @@ extension CGRect { } extension UIScreen { - static var aspectRatio: CGFloat { - let count = Dynamic.NSScreen.screens.count.asInt ?? 0 + let count = AKInterface.shared!.screenCount if PlaySettings.shared.notch { if count == 1 { return mainScreenWidth / mainScreenHeight // 1.6 or 1.77777778 } else { - if Dynamic.NSScreen.mainScreen.asObject == Dynamic.NSScreen.screens.first { + if AKInterface.shared!.isMainScreenEqualToFirst { return mainScreenWidth / mainScreenHeight } } } - if let frame = Dynamic(Dynamic.NSScreen.mainScreen.asObject).frame.asCGRect { - return frame.aspectRatio() - } - return mainScreenWidth / mainScreenHeight + + let frame = AKInterface.shared!.mainScreenFrame + return frame.aspectRatio() } } -public final class PlayScreen: NSObject { +public class PlayScreen: NSObject { @objc public static let shared = PlayScreen() @objc public static func frame(_ rect: CGRect) -> CGRect { @@ -88,7 +84,7 @@ public final class PlayScreen: NSObject { return size.toAspectRatio() } var fullscreen: Bool { - return Dynamic(nsWindow).styleMask.contains(16384).asBool ?? false + return AKInterface.shared!.isFullscreen } @objc public var screenRect: CGRect { @@ -131,12 +127,8 @@ public final class PlayScreen: NSObject { window?.nsWindow } - var nsScreen: NSObject? { - Dynamic(nsWindow).nsScreen.asObject - } - func switchDock(_ visible: Bool) { - Dynamic.NSMenu.setMenuBarVisible(visible) + AKInterface.shared!.setMenuBarVisible(visible) } } @@ -162,40 +154,7 @@ extension CGFloat { } } -extension UIView { - - class func allSubviews(from parenView: UIView) -> [T] { - return parenView.subviews.flatMap { subView -> [T] in - var result = allSubviews(from: subView) as [T] - if let view = subView as? T { result.append(view) } - return result - } - } -} - -extension UIScreen { - - @objc open var maximumFramesPerSecond: Int { - return PlaySettings.shared.refreshRate - } - - @available(iOS 15.0, *) - @objc open var preferredFrameRateRange: CAFrameRateRange { - return CAFrameRateRange(minimum: 60, - maximum: Float(PlaySettings.shared.refreshRate), - __preferred: Float(PlaySettings.shared.refreshRate)) - } - -} - -extension CADisplayLink { - @objc open var preferredFramesPerSecond: Int { - return PlaySettings.shared.refreshRate - } -} - extension UIWindow { - var nsWindow: NSObject? { guard let nsWindows = NSClassFromString("NSApplication")? .value(forKeyPath: "sharedApplication.windows") as? [AnyObject] else { return nil } @@ -208,22 +167,3 @@ extension UIWindow { return nil } } - -extension NSObject { - func call(_ method: String, object: CGSize) -> Bool { - if self.responds(to: Selector(method)) { - self.perform(Selector(method), with: object) - return true - } else { - return false - } - } - func call(_ method: String) -> Bool { - if self.responds(to: Selector(method)) { - self.perform(Selector(method)) - return true - } else { - return false - } - } -} diff --git a/PlayTools/PlaySettings.swift b/PlayTools/PlaySettings.swift index 620328f2..609b8465 100644 --- a/PlayTools/PlaySettings.swift +++ b/PlayTools/PlaySettings.swift @@ -3,186 +3,75 @@ import UIKit let settings = PlaySettings.shared -extension Dictionary { - - func store(_ toURL: URL) throws { - let data = try PropertyListSerialization.data(fromPropertyList: self, format: .xml, options: 0) - try data.write(to: toURL, options: .atomic) - } - - static func read( _ from: URL) throws -> Dictionary? { - var format = PropertyListSerialization.PropertyListFormat.xml - if let data = FileManager.default.contents(atPath: from.path) { - return try PropertyListSerialization - .propertyList(from: data, - options: .mutableContainersAndLeaves, - format: &format) as? Dictionary - } - return nil - } - -} - @objc public final class PlaySettings: NSObject { - - private static let fileExtension = "plist" - @objc public static let shared = PlaySettings() - private static let enableWindowAutoSize = "pc.enableWindowAutoSize" - - private static let gamingmodeKey = "pc.gamingMode" - - lazy var gamingMode: Bool = { - if let key = settings[PlaySettings.gamingmodeKey] as? Bool { - return key - } - return PlaySettings.isGame - }() - - private static let notchKey = "pc.hasNotch" - - lazy var notch: Bool = { - if let key = settings[PlaySettings.notchKey] as? Bool { - return key - } - return false - }() - - private static let layoutKey = "pc.layout" - - lazy var layout: [[CGFloat]] = [] { - didSet { - do { - settings[PlaySettings.layoutKey] = layout - allPrefs[Bundle.main.bundleIdentifier!] = settings - try allPrefs.store(PlaySettings.settingsUrl) - } catch { - print("failed to save settings: \(error)") - } - } - } - - public func setupLayout() { - layout = settings[PlaySettings.layoutKey] as? [[CGFloat]] ?? [] - } - - private static let adaptiveDisplayKey = "pc.adaptiveDisplay" - @objc public var adaptiveDisplay: Bool { - if let key = settings[PlaySettings.adaptiveDisplayKey] as? Bool { - return key - } - return PlaySettings.isGame - } + let bundleIdentifier = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String ?? "" + let settingsUrl: URL + var settingsData: AppSettingsData - private static let keymappingKey = "pc.keymapping" - @objc public var keymapping: Bool { - if let key = settings[PlaySettings.keymappingKey] as? Bool { - return key + override init() { + settingsUrl = URL(fileURLWithPath: "/Users/\(NSUserName())/Library/Containers/io.playcover.PlayCover") + .appendingPathComponent("App Settings") + .appendingPathComponent("\(bundleIdentifier).plist") + do { + let data = try Data(contentsOf: settingsUrl) + settingsData = try PropertyListDecoder().decode(AppSettingsData.self, from: data) + } catch { + settingsData = AppSettingsData() + print("[PlayTools] PlaySettings decode failed.\n%@") } - return PlaySettings.isGame } - private static let refreshRateKey = "pc.refreshRate" - @objc lazy public var refreshRate: Int = { - if let key = settings[PlaySettings.refreshRateKey] as? Int { - return key - } - return 60 - }() + lazy var discordActivity = settingsData.discordActivity - private static let sensivityKey = "pc.sensivity" + lazy var keymapping = settingsData.keymapping - @objc lazy public var sensivity: Float = { - if let key = settings[PlaySettings.sensivityKey] as? Float { - return key / 100 - } - return 0.5 - }() + lazy var mouseMapping = settingsData.mouseMapping - private static let gameWindowSizeHeight = "pc.gameWindowSizeHeight" - @objc lazy public var windowSizeHeight: CGFloat = { - if let key = settings[PlaySettings.gameWindowSizeHeight] as? CGFloat { - return key - } - return 1080.0 - }() + lazy var notch = settingsData.notch - private static let gameWindowSizeWidth = "pc.gameWindowSizeWidth" - @objc lazy public var windowSizeWidth: CGFloat = { - if let key = settings[PlaySettings.gameWindowSizeWidth] as? CGFloat { - return key - } - return 1920.0 - }() + lazy var sensitivity = settingsData.sensitivity / 100 - private static let ipadModelKey = "pc.ipadModel" - @objc lazy public var GET_IPAD_MODEL: NSString = { - if let key = settings[PlaySettings.ipadModelKey] as? NSString { - return key - } - return "iPad8,6" - }() + lazy var windowSizeHeight = CGFloat(settingsData.windowHeight) - @objc lazy public var GET_OEM_ID: NSString = { - if let key = settings[PlaySettings.ipadModelKey] as? NSString { - switch key { - case "iPad6,7": - return "J98aAP" - case "iPad8,6": - return "J320xAP" - case "iPad13,8": - return "J522AP" - default: - return "J320xAP" - } - } - return "J320xAP" - }() + lazy var windowSizeWidth = CGFloat(settingsData.windowWidth) - static var isGame: Bool { - if let info = Bundle.main.infoDictionary?.description { - for keyword in PlaySettings.keywords { - if info.contains(keyword) && !info.contains("xbox") { - return true - } - } - } - return false - } + @objc lazy var adaptiveDisplay = settingsData.resolution == 0 ? false : true - lazy var settings: [String: Any] = { - if let prefs = allPrefs[Bundle.main.bundleIdentifier!] as? [String: Any] { - return prefs - } - return [PlaySettings.adaptiveDisplayKey: PlaySettings.isGame, PlaySettings.keymappingKey: PlaySettings.isGame] - }() + @objc lazy var deviceModel = settingsData.iosDeviceModel as NSString - lazy var allPrefs: [String: Any] = { - do { - if let all = try [String: Any].read(PlaySettings.settingsUrl) { - return all - } - } catch { - print("failed to load settings: \(error)") + @objc lazy var oemID: NSString = { + switch settingsData.iosDeviceModel { + case "iPad6,7": + return "J98aAP" + case "iPad8,6": + return "J320xAP" + case "iPad13,8": + return "J522AP" + case "iPhone15,3": + return "A2896" + case "iPhone14,3": + return "A2645" + default: + return "J320xAP" } - return [:] }() +} - public func clearLegacy() { - UserDefaults.standard.removeObject(forKey: "layout") - UserDefaults.standard.removeObject(forKey: "pclayout") - UserDefaults.standard.removeObject(forKey: "playcover.macro") - UserDefaults.standard.removeObject(forKey: PlaySettings.sensivityKey) - UserDefaults.standard.removeObject(forKey: PlaySettings.refreshRateKey) - UserDefaults.standard.removeObject(forKey: PlaySettings.keymappingKey) - UserDefaults.standard.removeObject(forKey: PlaySettings.adaptiveDisplayKey) - UserDefaults.standard.removeObject(forKey: PlaySettings.gameWindowSizeWidth) - UserDefaults.standard.removeObject(forKey: PlaySettings.gameWindowSizeHeight) - UserDefaults.standard.removeObject(forKey: PlaySettings.enableWindowAutoSize) - } - - public static let settingsUrl = URL(fileURLWithPath: "/Users/\(NSUserName())/Library/Preferences/playcover.plist") - - private static var keywords = ["game", "unity", "metal", "netflix", "opengl", "minecraft", "mihoyo", "disney"] +struct AppSettingsData: Codable { + var keymapping = true + var mouseMapping = true + var sensitivity: Float = 50 + + var disableTimeout = false + var iosDeviceModel = "iPad13,8" + var windowWidth = 1920 + var windowHeight = 1080 + var resolution = 2 + var aspectRatio = 1 + var notch = false + var bypass = false + var discordActivity = DiscordActivity() + var version = "2.0.0" } diff --git a/PlayTools/PlayUI.swift b/PlayTools/PlayUI.swift index 52e60a00..702d1c88 100644 --- a/PlayTools/PlayUI.swift +++ b/PlayTools/PlayUI.swift @@ -6,14 +6,9 @@ import Foundation import UIKit -let shared = PlayUI.shared - -final class PlayUI { - +class PlayUI { static let shared = PlayUI() - private init() {} - func showAlert(_ title: String, _ content: String) { let alertController = UIAlertController(title: title, message: content, preferredStyle: .alert) PlayInput.shared.root?.present(alertController, animated: true, completion: nil) @@ -24,7 +19,7 @@ final class PlayUI { message: "Please, install it from playcover.io site to use this app.", preferredStyle: .alert) alertController.addAction(UIAlertAction(title: "OK", style: .default) { _ in - Dynamic.NSApplication.sharedApplication.terminate(self) + AKInterface.shared!.terminateApplication() }) PlayInput.shared.root?.present(alertController, animated: true, completion: nil) } diff --git a/PlayTools/Utils/AKPluginLoader.swift b/PlayTools/Utils/AKPluginLoader.swift new file mode 100644 index 00000000..97700e46 --- /dev/null +++ b/PlayTools/Utils/AKPluginLoader.swift @@ -0,0 +1,32 @@ +// +// AKPluginLoader.swift +// PlayTools +// +// Created by Isaac Marovitz on 13/09/2022. +// + +import Foundation + +class AKInterface { + public static var shared: Plugin? + + public static func initialize() { + shared = loadPlugin() + } + + private static func loadPlugin() -> Plugin? { + // 1. Form the plugin's bundle URL + guard let bundleURL = Bundle.main.builtInPlugInsURL? + .appendingPathComponent("AKInterface") + .appendingPathExtension("bundle") else { return nil } + + // 2. Create a bundle instance with the plugin URL + guard let bundle = Bundle(url: bundleURL) else { return nil } + + // 3. Load the bundle and our plugin class + guard let pluginClass = bundle.principalClass as? Plugin.Type else { return nil } + + // 4. Create an instance of the plugin class + return pluginClass.init() + } +} diff --git a/PlayTools/Utils/Dynamic/Dynamic.swift b/PlayTools/Utils/Dynamic/Dynamic.swift deleted file mode 100644 index b14fa385..00000000 --- a/PlayTools/Utils/Dynamic/Dynamic.swift +++ /dev/null @@ -1,317 +0,0 @@ -// -// Dynamic -// Created by Mhd Hejazi on 4/15/20. -// Copyright © 2020 Samabox. All rights reserved. -// - -import Foundation - -public typealias ObjC = Dynamic - -@dynamicCallable -@dynamicMemberLookup -public class Dynamic: CustomDebugStringConvertible, Loggable { - public static var loggingEnabled: Bool = false { - didSet { - Invocation.loggingEnabled = loggingEnabled - } - } - var loggingEnabled: Bool { Self.loggingEnabled } - - public static let `nil` = Dynamic(nil) - - private let object: AnyObject? - private let memberName: String? - private var invocation: Invocation? - private var error: Error? - - public var isError: Bool { error != nil || object is Error } - public var debugDescription: String { object?.debugDescription ?? "" } - - public init(_ object: Any?, memberName: String? = nil) { - self.object = object as AnyObject? - self.memberName = memberName - - log(.end).log(.start) - log("# Dynamic") - log("Object:", object ?? "").log("Member:", memberName ?? "") - } - - public init(className: String) { - self.object = NSClassFromString(className) - self.memberName = nil - - log(.end).log(.start) - log("# Dynamic") - log("Class:", className) - } - - public static subscript(dynamicMember className: String) -> Dynamic { - Dynamic(className: className) - } - - public subscript(dynamicMember member: String) -> Dynamic { - get { - getProperty(member) - } - set { - self[dynamicMember: member] = newValue.resolve() - } - } - - public subscript(dynamicMember member: String) -> T? { - get { - self[dynamicMember: member].unwrap() - } - set { - setProperty(member, value: newValue) - } - } - - @discardableResult - public func dynamicallyCall(withKeywordArguments pairs: KeyValuePairs) -> Dynamic { - // Constructors - if object is AnyClass, memberName == nil { - if pairs.isEmpty { - return self.`init`.dynamicallyCall(withKeywordArguments: pairs) - } else { - return self.`initWith`.dynamicallyCall(withKeywordArguments: pairs) - } - } - - guard let name = memberName else { return self } - - let selector = name + pairs.reduce("") { result, pair in - if result.isEmpty { - return (pair.key.first?.uppercased() ?? "") + pair.key.dropFirst() + ":" - } else { - return result + (pair.key + ":") - } - } - callMethod(selector, with: pairs.map { $0.value }) - return self - } - - @discardableResult - public func dynamicallyCall(withKeywordArguments pairs: KeyValuePairs) -> T? { - let result: Dynamic = dynamicallyCall(withKeywordArguments: pairs) - return result.unwrap() - } - - private func getProperty(_ name: String) -> Dynamic { - log("Get:", "\(object?.debugDescription ?? "").\(name)") - - let resolved = resolve() - if resolved is Error { - return self - } - - if resolved == nil { - return Self.nil - } - - log(.end) - - return Dynamic(resolved, memberName: name) - } - - private func setProperty(_ name: String, value: T?) { - log("Set:", "\(object?.debugDescription ?? "").\(name)") - - let resolved = resolve() - log(.end) - - var setterName: String - if name.count > 2, name.hasPrefix("is"), name[name.index(name.startIndex, offsetBy: 2)].isUppercase { - setterName = "set" + name.dropFirst(2) - } else { - setterName = "set" + (name.first?.uppercased() ?? "") + name.dropFirst() - } - - Dynamic(resolved, memberName: setterName)(value) - } - - private func callMethod(_ selector: String, with arguments: [Any?] = []) { - guard var target = object as? NSObject, !isError else { return } - log("Call: [\(type(of: target)) \(selector)]") - - var invocation: Invocation - - // Call `alloc()` before `init()` - if target is AnyClass, selector.hasPrefix("init") { - guard let allocated = allocate(type: target) else { return } - target = allocated - } - - do { - invocation = try Invocation(target: target, selector: NSSelectorFromString(selector)) - } catch { - print("WARNING: Trying to access an unrecognized member: \(type(of: target)).\(selector)") - self.error = error - return - } - - self.invocation = invocation - - for index in 0.. NSObject? { - do { - let invocation = try Invocation(target: type, selector: NSSelectorFromString("alloc")) - invocation.invoke() - return invocation.returnedObject as? NSObject - } catch { - self.error = error - return nil - } - } - - private func resolve() -> AnyObject? { - // This is a class. Return it. - if object is AnyClass && memberName == nil { - return object - } - - guard let object = object else { - return nil - } - - // This is a method we have called before. Return the result. - if let result = invocation?.returnedObject { - return result - } - - // This is an error caused by a previous call. Just pass it. - if object is Error { - return object - } - if error != nil { - return error as AnyObject? - } - - // This is a wrapped object. Return it. - guard let name = memberName else { - return object - } - - // This is a wrapped object with a member name. Return the member. - if invocation?.isInvoked != true { - callMethod(name) - } - - return invocation?.returnedObject ?? error as AnyObject? - } - - @available(*, unavailable, message: "Call init() directly from the class name.") - public func alloc() {} -} - -extension Dynamic { - public var asAnyObject: AnyObject? { - let result = resolve() - log(.end) - return result - } - - public var asValue: NSValue? { - if let object = resolve() { - log(.end) - return NSValue(nonretainedObject: object) - } - - log(.end) - - guard let invocation = invocation, - let returnType = invocation.returnType, - invocation.returnsAny else { return nil } - - let buffer = UnsafeMutablePointer.allocate(capacity: invocation.returnLength) - defer { buffer.deallocate() } - buffer.initialize(repeating: 0, count: invocation.returnLength) - - invocation.getReturnValue(result: &buffer.pointee) - - let value = NSValue(bytes: buffer, objCType: UnsafePointer(returnType)) - return value - } - - public var asObject: NSObject? { asAnyObject as? NSObject } - public var asArray: NSArray? { asAnyObject as? NSArray } - public var asDictionary: NSDictionary? { asAnyObject as? NSDictionary } - public var asString: String? { asAnyObject?.description } - public var asInt8: Int8? { unwrap() } - public var asUInt8: UInt8? { unwrap() } - public var asInt16: Int16? { unwrap() } - public var asUInt16: UInt16? { unwrap() } - public var asInt32: Int32? { unwrap() } - public var asUInt32: UInt32? { unwrap() } - public var asInt64: Int64? { unwrap() } - public var asUInt64: UInt64? { unwrap() } - public var asFloat: Float? { unwrap() } - public var asDouble: Double? { unwrap() } - public var asBool: Bool? { unwrap() } - public var asInt: Int? { unwrap() } - public var asUInt: UInt? { unwrap() } - public var asSelector: Selector? { unwrap() } - - public func asInferred() -> T? { unwrap() } - - private func unwrap() -> T? { - guard let value = asValue else { return nil } - guard let invocation = invocation else { - if let result = object as? T { - return result - } - return nil - } - - let encoding = invocation.returnTypeString - if encoding == "^v" || encoding == "@" { - return value.nonretainedObjectValue as? T - } - - var storedSize = 0 - var storedAlignment = 0 - NSGetSizeAndAlignment(invocation.returnType!, &storedSize, &storedAlignment) - guard MemoryLayout.size == storedSize && MemoryLayout.alignment == storedAlignment else { - return nil - } - - let buffer = UnsafeMutablePointer.allocate(capacity: 1) - defer { buffer.deallocate() } - value.getValue(buffer) - - return buffer.pointee - } -} - -#if canImport(UIKit) -import UIKit - -extension Dynamic { - public var asCGPoint: CGPoint? { unwrap() } - public var asCGVector: CGVector? { unwrap() } - public var asCGFloat: CGFloat? { unwrap() } - public var asCGSize: CGSize? { unwrap() } - public var asCGRect: CGRect? { unwrap() } - public var asCGAffineTransform: CGAffineTransform? { unwrap() } - public var asUIEdgeInsets: UIEdgeInsets? { unwrap() } - public var asUIOffset: UIOffset? { unwrap() } - - #if !os(watchOS) - public var asCATransform3D: CATransform3D? { unwrap() } - #endif -} -#endif diff --git a/PlayTools/Utils/Dynamic/Invocation.swift b/PlayTools/Utils/Dynamic/Invocation.swift deleted file mode 100644 index a08a2f44..00000000 --- a/PlayTools/Utils/Dynamic/Invocation.swift +++ /dev/null @@ -1,238 +0,0 @@ -// -// Dynamic -// Created by Mhd Hejazi on 4/15/20. -// Copyright © 2020 Samabox. All rights reserved. -// - -import Foundation - -class Invocation: Loggable { - public static var loggingEnabled: Bool = false - var loggingEnabled: Bool { Self.loggingEnabled } - - private let target: NSObject - private let selector: Selector - - var invocation: NSObject? - - var numberOfArguments: Int = 0 - var returnLength: Int = 0 - var returnType: UnsafePointer? - var returnTypeString: String? { - guard let returnType = returnType else { return nil } - return String(cString: returnType) - } - var returnsObject: Bool { - /// `@` is the type encoding for an object - returnTypeString == "@" - } - var returnsAny: Bool { - /// `v` is the type encoding for Void - returnTypeString != "v" - } - lazy var returnedObject: AnyObject? = { - returnedObjectValue() - }() - private(set) var isInvoked: Bool = false - - init(target: NSObject, selector: Selector) throws { - self.target = target - self.selector = selector - - log(.start) - log("# Invocation") - log("[\(type(of: target)) \(selector)]") - log("Selector:", selector) - - try initialize() - } - - // swiftlint:disable function_body_length - private func initialize() throws { - /// `NSMethodSignature *methodSignature = [target methodSignatureForSelector: selector]` - let methodSignature: NSObject - do { - let selector = NSSelectorFromString("methodSignatureForSelector:") - let signature = (@convention(c)(NSObject, Selector, Selector) -> Any).self - let method = unsafeBitCast(target.method(for: selector), to: signature) - guard let result = method(target, selector, self.selector) as? NSObject else { - let error = InvocationError.unrecognizedSelector(type(of: target), self.selector) - log("ERROR:", error) - throw error - } - methodSignature = result - } - - /// `numberOfArguments = methodSignature.numberOfArguments` - self.numberOfArguments = methodSignature.value(forKeyPath: "numberOfArguments") as? Int ?? 0 - log("NumberOfArguments:", numberOfArguments) - - /// `methodReturnLength = methodSignature.methodReturnLength` - self.returnLength = methodSignature.value(forKeyPath: "methodReturnLength") as? Int ?? 0 - log("ReturnLength:", returnLength) - - /// `methodReturnType = methodSignature.methodReturnType` - let methodReturnType: UnsafePointer - do { - let selector = NSSelectorFromString("methodReturnType") - let signature = (@convention(c)(NSObject, Selector) -> UnsafePointer).self - let method = unsafeBitCast(methodSignature.method(for: selector), to: signature) - methodReturnType = method(methodSignature, selector) - } - self.returnType = methodReturnType - log("ReturnType:", self.returnTypeString ?? "?") - - /// `NSInvocation *invocation = [NSInvocation invocationWithMethodSignature: methodSignature]` - let invocation: NSObject - do { - let NSInvocation = NSClassFromString("NSInvocation") as AnyObject - let selector = NSSelectorFromString("invocationWithMethodSignature:") - let signature = (@convention(c)(AnyObject, Selector, AnyObject) -> AnyObject).self - let method = unsafeBitCast(NSInvocation.method(for: selector), to: signature) - guard let result = method(NSInvocation, selector, methodSignature) as? NSObject else { - let error = InvocationError.unrecognizedSelector(type(of: target), self.selector) - log("ERROR:", error) - throw error - } - invocation = result - } - self.invocation = invocation - - /// `invocation.selector = selector` - do { - let selector = NSSelectorFromString("setSelector:") - let signature = (@convention(c)(NSObject, Selector, Selector) -> Void).self - let method = unsafeBitCast(invocation.method(for: selector), to: signature) - method(invocation, selector, self.selector) - } - - /// `[invocation retainArguments]` - do { - let selector = NSSelectorFromString("retainArguments") - let signature = (@convention(c)(NSObject, Selector) -> Void).self - let method = unsafeBitCast(invocation.method(for: selector), to: signature) - method(invocation, selector) - } - } - - func setArgument(_ argument: Any?, at index: NSInteger) { - guard let invocation = invocation else { return } - - log("Argument #\(index - 1):", argument ?? "") - - /// `[invocation setArgument:&argument atIndex:i + 2]` - let selector = NSSelectorFromString("setArgument:atIndex:") - let signature = (@convention(c)(NSObject, Selector, UnsafeRawPointer, Int) -> Void).self - let method = unsafeBitCast(invocation.method(for: selector), to: signature) - - if let valueArgument = argument as? NSValue { - /// Get the type byte size - let typeSize = UnsafeMutablePointer.allocate(capacity: 1) - defer { typeSize.deallocate() } - NSGetSizeAndAlignment(valueArgument.objCType, typeSize, nil) - - /// Get the actual value - let buffer = UnsafeMutablePointer.allocate(capacity: typeSize.pointee) - defer { buffer.deallocate() } - valueArgument.getValue(buffer) - - method(invocation, selector, buffer, index) - } else { - withUnsafePointer(to: argument) { pointer in - method(invocation, selector, pointer, index) - } - } - } - - func invoke() { - guard let invocation = invocation, !isInvoked else { return } - - log("Invoking...") - - isInvoked = true - - /// `[invocation invokeWithTarget: target]` - do { - let selector = NSSelectorFromString("invokeWithTarget:") - let signature = (@convention(c)(NSObject, Selector, AnyObject) -> Void).self - let method = unsafeBitCast(invocation.method(for: selector), to: signature) - method(invocation, selector, target) - } - - log(.end) - } - - func getReturnValue(result: inout T) { - guard let invocation = invocation else { return } - - /// `[invocation getReturnValue: returnValue]` - do { - let selector = NSSelectorFromString("getReturnValue:") - let signature = (@convention(c)(NSObject, Selector, UnsafeMutableRawPointer) -> Void).self - let method = unsafeBitCast(invocation.method(for: selector), to: signature) - withUnsafeMutablePointer(to: &result) { pointer in - method(invocation, selector, pointer) - } - } - - if NSStringFromSelector(self.selector) == "alloc" { - log("getReturnValue() -> ") - } else { - log("getReturnValue() ->", result) - } - } - - private func returnedObjectValue() -> AnyObject? { - guard returnsObject, returnLength > 0 else { - return nil - } - - var result: AnyObject? - - getReturnValue(result: &result) - - guard let object = result else { - return nil - } - - /// Take the ownership of the initialized objects to ensure they're deallocated properly. - if isRetainingMethod() { - return Unmanaged.passRetained(object).takeRetainedValue() - } - - /// `NSInvocation.getReturnValue()` doesn't give us the ownership of the returned object, but the compiler - /// tries to release this object anyway. So, we are retaining it to balance with the compiler's release. - return Unmanaged.passRetained(object).takeUnretainedValue() - } - - private func isRetainingMethod() -> Bool { - /// Refer to: https://bit.ly/308okXm - let selector = NSStringFromSelector(self.selector) - return selector == "alloc" || - selector.hasPrefix("new") || - selector.hasPrefix("copy") || - selector.hasPrefix("mutableCopy") - } -} - -public enum InvocationError: CustomNSError { - case unrecognizedSelector(_ classType: AnyClass, _ selector: Selector) - - public static var errorDomain: String { String(describing: Invocation.self) } - - public var errorCode: Int { - switch self { - case .unrecognizedSelector: - return 404 - } - } - - public var errorUserInfo: [String: Any] { - var message: String - switch self { - case .unrecognizedSelector(let classType, let selector): - message = "'\(String(describing: classType))' doesn't recognize selector '\(selector)'" - } - return [NSLocalizedDescriptionKey: message] - } -} diff --git a/PlayTools/Utils/Dynamic/Logger.swift b/PlayTools/Utils/Dynamic/Logger.swift deleted file mode 100644 index 52077cc6..00000000 --- a/PlayTools/Utils/Dynamic/Logger.swift +++ /dev/null @@ -1,106 +0,0 @@ -// -// Dynamic -// Created by Mhd Hejazi on 4/15/20. -// Copyright © 2020 Samabox. All rights reserved. -// - -import Foundation - -protocol Loggable: AnyObject { - var loggingEnabled: Bool { get } -} - -extension Loggable { - var loggingEnabled: Bool { false } - var logUsingPrint: Bool { true } - - @discardableResult - func log(_ items: Any...) -> Logger { - guard loggingEnabled else { return Logger.dummy } - return Logger.logger(for: self).log(items) - } - - @discardableResult - func log(_ group: Logger.Group) -> Logger { - guard loggingEnabled else { return Logger.dummy } - return Logger.logger(for: self).log(group) - } -} - -class Logger { - enum Group { - case start, end - } - - static let dummy = DummyLogger() - static var enabled = true - - private static var loggers: [ObjectIdentifier: Logger] = [:] - private static var level: Int = 0 - - static func logger(for object: AnyObject) -> Logger { - let id = ObjectIdentifier(object) - if let logger = Self.loggers[id] { - return logger - } - - let logger = Logger() - Self.loggers[id] = logger - - return logger - } - - @discardableResult - func log(_ items: Any..., withBullet: Bool = true) -> Logger { - log(items, withBullet: withBullet) - } - - @discardableResult - func log(_ items: [Any], withBullet: Bool = true) -> Logger { - guard Self.enabled else { return self } - - let message = items.lazy.map { String(describing: $0) }.joined(separator: " ") - var indent = String(repeating: " ╷ ", count: Self.level) - if !indent.isEmpty, withBullet { - indent = indent.dropLast(2) + "‣ " - } - print(indent + message) - return self - } - - @discardableResult - func log(_ group: Group) -> Logger { - switch group { - case .start: logGroupStart() - case .end: logGroupEnd() - } - return self - } - - private func logGroupStart() { - guard Self.enabled else { return } - - log([" ╭╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴"], withBullet: false) - Self.level += 1 - } - - private func logGroupEnd() { - guard Self.enabled else { return } - - guard Self.level > 0 else { return } - Self.level -= 1 - log([" ╰╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴"], withBullet: false) - } -} - -class DummyLogger: Logger { - @discardableResult - override func log(_ items: [Any], withBullet: Bool = true) -> Logger { - self - } - - @discardableResult - override func log(_ group: Group) -> Logger { - self - } -} diff --git a/PlayTools/Utils/Dynamic/TypeMapping.swift b/PlayTools/Utils/Dynamic/TypeMapping.swift deleted file mode 100644 index 5cab4528..00000000 --- a/PlayTools/Utils/Dynamic/TypeMapping.swift +++ /dev/null @@ -1,125 +0,0 @@ -// -// Dynamic -// Created by Mhd Hejazi on 4/18/20. -// Copyright © 2020 Samabox. All rights reserved. -// - -// swiftlint:disable cyclomatic_complexity syntactic_sugar - -import Foundation - -/// The type mapping table can be found here: -/// https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/working_with_foundation_types -class TypeMapping { - private static let typePairs: [(swiftType: Any.Type, objCType: AnyObject.Type)] = [ - (Array.self, NSArray.self), - (Calendar.self, NSCalendar.self), - (CharacterSet.self, NSCharacterSet.self), - (Data.self, NSData.self), - (DateComponents.self, NSDateComponents.self), - (DateInterval.self, NSDateInterval.self), - (Date.self, NSDate.self), - (Decimal.self, NSDecimalNumber.self), - (Dictionary.self, NSDictionary.self), - (IndexPath.self, NSIndexPath.self), - (IndexSet.self, NSIndexSet.self), - (Locale.self, NSLocale.self), - (Notification.self, NSNotification.self), - (PersonNameComponents.self, NSPersonNameComponents.self), - (Set.self, NSSet.self), - (String.self, NSString.self), - (TimeZone.self, NSTimeZone.self), - (URL.self, NSURL.self), - (URLComponents.self, NSURLComponents.self), - (URLQueryItem.self, NSURLQueryItem.self), - (URLRequest.self, NSURLRequest.self), - (UUID.self, NSUUID.self) - ] - - private static let swiftToObjCTypes: [ObjectIdentifier: AnyObject.Type] = { - let pairs = typePairs.map { - (ObjectIdentifier($0.swiftType), $0.objCType) - } - return [ObjectIdentifier: AnyObject.Type](uniqueKeysWithValues: pairs) - }() - - private static let objCToSwiftTypes: [ObjectIdentifier: Any.Type] = { - let pairs = typePairs.map { - (ObjectIdentifier($0.objCType), $0.swiftType) - } - return [ObjectIdentifier: Any.Type](uniqueKeysWithValues: pairs) - }() - - static func swiftType(for type: Any.Type) -> Any.Type? { - objCToSwiftTypes[ObjectIdentifier(type)] - } - - static func objCType(for type: Any.Type) -> Any.Type? { - swiftToObjCTypes[ObjectIdentifier(type)] - } - - static func mappedType(for type: Any.Type) -> Any.Type? { - swiftType(for: type) ?? objCType(for: type) - } - - static func convertToObjCType(_ object: Any?) -> Any? { - switch object { - case is Array: return object as? NSArray - case is Calendar: return object as? NSCalendar - case is CharacterSet: return object as? NSCharacterSet - case is Data: return object as? NSData - case is DateComponents: return object as? NSDateComponents - case is DateInterval: return object as? NSDateInterval - case is Date: return object as? NSDate - case is Decimal: return object as? NSDecimalNumber - case is Dictionary: return object as? NSDictionary - case is IndexPath: return object as? NSIndexPath - case is IndexSet: return object as? NSIndexSet - case is Locale: return object as? NSLocale - case is Notification: return object as? NSNotification - case is PersonNameComponents: return object as? NSPersonNameComponents - case is Set: return object as? NSSet - case is String: return object as? NSString - case is TimeZone: return object as? NSTimeZone - case is URL: return object as? NSURL - case is URLComponents: return object as? NSURLComponents - case is URLQueryItem: return object as? NSURLQueryItem - case is URLRequest: return object as? NSURLRequest - case is UUID: return object as? NSUUID - default: return nil - } - } - - static func convertToSwiftType(_ object: Any?) -> Any? { - switch object { - case is NSArray: return object as? Array - case is NSCalendar: return object as? Calendar - case is NSCharacterSet: return object as? CharacterSet - case is NSData: return object as? Data - case is NSDateComponents: return object as? DateComponents - case is NSDateInterval: return object as? DateInterval - case is NSDate: return object as? Date - case is NSDecimalNumber: return object as? Decimal - case is NSDictionary: return object as? Dictionary - case is NSIndexPath: return object as? IndexPath - case is NSIndexSet: return object as? IndexSet - case is NSLocale: return object as? Locale - case is NSMeasurement: return object as? Measurement - case is NSNotification: return object as? Notification - case is NSPersonNameComponents: return object as? PersonNameComponents - case is NSSet: return object as? Set - case is NSString: return object as? String - case is NSTimeZone: return object as? TimeZone - case is NSURL: return object as? URL - case is NSURLComponents: return object as? URLComponents - case is NSURLQueryItem: return object as? URLQueryItem - case is NSURLRequest: return object as? URLRequest - case is NSUUID: return object as? UUID - default: return nil - } - } - - static func convertType(of object: Any?) -> Any? { - convertToObjCType(object) ?? convertToSwiftType(object) - } -} diff --git a/PlayTools/Utils/PlayInfo.swift b/PlayTools/Utils/PlayInfo.swift index 30cd9b64..830dbbb9 100644 --- a/PlayTools/Utils/PlayInfo.swift +++ b/PlayTools/Utils/PlayInfo.swift @@ -4,12 +4,11 @@ // import Foundation -import GameController class PlayInfo { static var isLauncherInstalled: Bool { - return Dynamic.NSWorkspace.sharedWorkspace - .URLForApplicationWithBundleIdentifier("io.playcover.PlayCover").asAnyObject != nil + return AKInterface.shared! + .urlForApplicationWithBundleIdentifier("io.playcover.PlayCover") != nil } } diff --git a/Plugin.swift b/Plugin.swift new file mode 100644 index 00000000..07962480 --- /dev/null +++ b/Plugin.swift @@ -0,0 +1,28 @@ +// +// Plugin.swift +// PlayTools +// +// Created by Isaac Marovitz on 13/09/2022. +// + +import Foundation + +@objc(Plugin) +public protocol Plugin: NSObjectProtocol { + init() + + var screenCount: Int { get } + var mousePoint: CGPoint { get } + var windowFrame: CGRect { get } + var mainScreenFrame: CGRect { get } + var isMainScreenEqualToFirst: Bool { get } + var isFullscreen: Bool { get } + + func hideCursor() + func unhideCursor() + func terminateApplication() + func eliminateRedundantKeyPressEvents(_ dontIgnore: @escaping() -> Bool) + func setupMouseButton(_ _up: Int, _ _down: Int, _ dontIgnore: @escaping(Int, Bool, Bool) -> Bool) + func urlForApplicationWithBundleIdentifier(_ value: String) -> URL? + func setMenuBarVisible(_ value: Bool) +}