Skip to content

Commit

Permalink
Merge pull request #88 from XuYicong/auto-keyboard
Browse files Browse the repository at this point in the history
Intelligent keyboard mapping
  • Loading branch information
JoseMoreville committed May 3, 2023
2 parents 4b0b6b0 + 6f6ef3b commit b5f69c2
Show file tree
Hide file tree
Showing 14 changed files with 655 additions and 489 deletions.
66 changes: 51 additions & 15 deletions AKPlugin.swift
Expand Up @@ -8,7 +8,6 @@
import AppKit
import CoreGraphics
import Foundation
import GameController

class AKPlugin: NSObject, Plugin {
required override init() {
Expand All @@ -19,11 +18,11 @@ class AKPlugin: NSObject, Plugin {
}

var mousePoint: CGPoint {
NSApplication.shared.windows.first!.mouseLocationOutsideOfEventStream as CGPoint
NSApplication.shared.windows.first?.mouseLocationOutsideOfEventStream ?? CGPoint()
}

var windowFrame: CGRect {
NSApplication.shared.windows.first!.frame as CGRect
NSApplication.shared.windows.first?.frame ?? CGRect()
}

var isMainScreenEqualToFirst: Bool {
Expand All @@ -38,15 +37,19 @@ class AKPlugin: NSObject, Plugin {
NSApplication.shared.windows.first!.styleMask.contains(.fullScreen)
}

var cmdPressed: Bool = false

func hideCursor() {
NSCursor.hide()
CGAssociateMouseAndMouseCursorPosition(0)
warpCursor()
}

func warpCursor() {
guard let firstScreen = NSScreen.screens.first else {return}
let frame = windowFrame
CGWarpMouseCursorPosition(CGPoint(x: frame.midX, y: frame.midY))
// Convert from NS coordinates to CG coordinates
CGWarpMouseCursorPosition(CGPoint(x: frame.midX, y: firstScreen.frame.height - frame.midY))
}

func unhideCursor() {
Expand All @@ -59,56 +62,89 @@ class AKPlugin: NSObject, Plugin {
}

private var modifierFlag: UInt = 0
func initialize(keyboard: @escaping(UInt16, Bool, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool,
swapMode: @escaping() -> Void) {
func setupKeyboard(keyboard: @escaping(UInt16, Bool, Bool) -> Bool,
swapMode: @escaping() -> Bool) {
func checkCmd(modifier: NSEvent.ModifierFlags) -> Bool {
if modifier.contains(.command) {
self.cmdPressed = true
return true
} else if self.cmdPressed {
self.cmdPressed = false
}
return false
}
NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: { event in
if checkCmd(modifier: event.modifierFlags) {
return event
}
let consumed = keyboard(event.keyCode, true, event.isARepeat)
if consumed {
return nil
}
return event
})
NSEvent.addLocalMonitorForEvents(matching: .keyUp, handler: { event in
if checkCmd(modifier: event.modifierFlags) {
return event
}
let consumed = keyboard(event.keyCode, false, false)
if consumed {
return nil
}
return event
})
NSEvent.addLocalMonitorForEvents(matching: .flagsChanged, handler: { event in
if checkCmd(modifier: event.modifierFlags) {
return event
}
let pressed = self.modifierFlag < event.modifierFlags.rawValue
let changed = self.modifierFlag ^ event.modifierFlags.rawValue
self.modifierFlag = event.modifierFlags.rawValue
if pressed && NSEvent.ModifierFlags(rawValue: changed).contains(.option) {
swapMode()
return nil
if swapMode() {
return nil
}
return event
}
let consumed = keyboard(event.keyCode, pressed, false)
if consumed {
return nil
}
return event
})
let mask: NSEvent.EventTypeMask = [.leftMouseDragged, .otherMouseDragged, .rightMouseDragged, .mouseMoved]
}

func setupMouseMoved(mouseMoved: @escaping(CGFloat, CGFloat) -> Bool) {
let mask: NSEvent.EventTypeMask = [.leftMouseDragged, .otherMouseDragged, .rightMouseDragged]
NSEvent.addLocalMonitorForEvents(matching: mask, handler: { event in
let consumed = mouseMoved(event.deltaX, event.deltaY)
if consumed {
return nil
}
return event
})
// transpass mouse moved event when no button pressed, for traffic light button to light up
NSEvent.addLocalMonitorForEvents(matching: .mouseMoved, handler: { event in
_ = mouseMoved(event.deltaX, event.deltaY)
return event
})
}

func setupMouseButton(_ _up: Int, _ _down: Int, _ dontIgnore: @escaping(Int, Bool, Bool) -> 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) {
func setupMouseButton(left: Bool, right: Bool, _ dontIgnore: @escaping(Bool) -> Bool) {
let downType: NSEvent.EventTypeMask = left ? .leftMouseDown : right ? .rightMouseDown : .otherMouseDown
let upType: NSEvent.EventTypeMask = left ? .leftMouseUp : right ? .rightMouseUp : .otherMouseUp
NSEvent.addLocalMonitorForEvents(matching: downType, handler: { event in
// For traffic light buttons when fullscreen
if event.window != NSApplication.shared.windows.first! {
return event
}
if dontIgnore(true) {
return event
}
return nil
})
NSEvent.addLocalMonitorForEvents(matching: NSEvent.EventTypeMask(rawValue: UInt64(_down)), handler: { event in
if dontIgnore(_up, false, true) {
NSEvent.addLocalMonitorForEvents(matching: upType, handler: { event in
if dontIgnore(false) {
return event
}
return nil
Expand Down
4 changes: 4 additions & 0 deletions PlayTools.xcodeproj/project.pbxproj
Expand Up @@ -15,6 +15,7 @@
6E84A14528D0F94E00BF7495 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA818CBA287ABFD5000BEE9D /* UIKit.framework */; };
6E84A15028D0F97500BF7495 /* AKInterface.bundle in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6E84A14C28D0F96D00BF7495 /* AKInterface.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
951D8275299D097C00D35B20 /* Playtools.strings in Resources */ = {isa = PBXBuildFile; fileRef = 951D8277299D097C00D35B20 /* Playtools.strings */; };
95A553E729F2BBB200E34C26 /* PlayController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95A553E629F2BBB200E34C26 /* PlayController.swift */; };
AA71970B287A44D200623C15 /* PlayLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = AA719702287A44D200623C15 /* PlayLoader.m */; };
AA71970D287A44D200623C15 /* PlaySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719704287A44D200623C15 /* PlaySettings.swift */; };
AA71970E287A44D200623C15 /* PlayLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = AA719705287A44D200623C15 /* PlayLoader.h */; };
Expand Down Expand Up @@ -78,6 +79,7 @@
6E84A14C28D0F96D00BF7495 /* AKInterface.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AKInterface.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
951D8276299D097C00D35B20 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Playtools.strings; sourceTree = "<group>"; };
951D8278299D098000D35B20 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Playtools.strings"; sourceTree = "<group>"; };
95A553E629F2BBB200E34C26 /* PlayController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayController.swift; sourceTree = "<group>"; };
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 = "<group>"; };
AA719704287A44D200623C15 /* PlaySettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaySettings.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -202,6 +204,7 @@
AA719755287A480C00623C15 /* PlayInput.swift */,
AA719756287A480C00623C15 /* ControlMode.swift */,
AA719757287A480C00623C15 /* MenuController.swift */,
95A553E629F2BBB200E34C26 /* PlayController.swift */,
);
path = Controls;
sourceTree = "<group>";
Expand Down Expand Up @@ -449,6 +452,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
95A553E729F2BBB200E34C26 /* PlayController.swift in Sources */,
AA71975A287A480D00623C15 /* Toucher.swift in Sources */,
AA7197A1287A481500623C15 /* CircleMenuLoader.swift in Sources */,
6E76639B28D0FAE700DE4AF9 /* Plugin.swift in Sources */,
Expand Down
26 changes: 16 additions & 10 deletions PlayTools/Controls/ControlMode.swift
Expand Up @@ -10,7 +10,17 @@ let mode = ControlMode.mode
public class ControlMode {

static public let mode = ControlMode()
public var visible: Bool = PlaySettings.shared.mouseMapping
public var visible: Bool = true
public var keyboardMapped = true

public static func trySwap() -> Bool {
if PlayInput.shouldLockCursor {
mode.show(!mode.visible)
return true
}
mode.show(true)
return false
}

func show(_ show: Bool) {
if !editor.editorMode {
Expand All @@ -21,25 +31,21 @@ public class ControlMode {
if screen.fullscreen {
screen.switchDock(true)
}
if PlaySettings.shared.mouseMapping {
AKInterface.shared!.unhideCursor()
}
PlayInput.shared.invalidate()
AKInterface.shared!.unhideCursor()
// PlayInput.shared.invalidate()
}
} else {
if visible {
NotificationCenter.default.post(name: NSNotification.Name.playtoolsKeymappingWillEnable,
object: nil, userInfo: [:])
if PlaySettings.shared.mouseMapping {
AKInterface.shared!.hideCursor()
}
AKInterface.shared!.hideCursor()
if screen.fullscreen {
screen.switchDock(false)
}

PlayInput.shared.setup()
// PlayInput.shared.setup()
}
}
Toucher.writeLog(logMessage: "cursor show switched to \(show)")
visible = show
}
}
Expand Down
48 changes: 45 additions & 3 deletions PlayTools/Controls/MenuController.swift
Expand Up @@ -44,6 +44,13 @@ extension UIApplication {
func downscaleElement(_ sender: AnyObject) {
EditorController.shared.focusedControl?.resize(down: true)
}

// put a mark in the toucher log, so as to align with tester description
@objc
func markToucherLog(_ sender: AnyObject) {
Toucher.writeLog(logMessage: "mark")
Toast.showHint(title: "Log marked")
}
}

extension UIViewController {
Expand Down Expand Up @@ -78,16 +85,49 @@ var keymappingSelectors = [#selector(UIApplication.switchEditorMode(_:)),
#selector(UIApplication.removeElement(_:)),
#selector(UIApplication.upscaleElement(_:)),
#selector(UIApplication.downscaleElement(_:)),
#selector(UIViewController.rotateView(_:))]
#selector(UIViewController.rotateView(_:))
]

class MenuController {
init(with builder: UIMenuBuilder) {
if Toucher.logEnabled {
builder.insertSibling(MenuController.debuggingMenu(), afterMenu: .view)
}
builder.insertSibling(MenuController.keymappingMenu(), afterMenu: .view)
}

class func keymappingMenu() -> UIMenu {
let keyCommands = [ "K", UIKeyCommand.inputDelete, UIKeyCommand.inputUpArrow, UIKeyCommand.inputDownArrow, "R" ]
static func debuggingMenu() -> UIMenu {
let menuTitle = [
"Put a mark in toucher log"
]
let keyCommands = ["L"]
let selectors = [
#selector(UIApplication.markToucherLog)
]
let arrowKeyChildrenCommands = zip(keyCommands, menuTitle).map { (command, btn) in
UIKeyCommand(title: btn,
image: nil,
action: selectors[menuTitle.firstIndex(of: btn)!],
input: command,
modifierFlags: .command,
propertyList: [CommandsList.KeymappingToolbox: btn]
)
}
return UIMenu(title: "Debug",
image: nil,
identifier: .debuggingMenu,
options: [],
children: [
UIMenu(title: "",
image: nil,
identifier: .debuggingOptionsMenu,
options: .displayInline,
children: arrowKeyChildrenCommands)])
}

class func keymappingMenu() -> UIMenu {
let keyCommands = [ "K", UIKeyCommand.inputDelete,
UIKeyCommand.inputUpArrow, UIKeyCommand.inputDownArrow, "R", "L"]
let arrowKeyChildrenCommands = zip(keyCommands, keymapping).map { (command, btn) in
UIKeyCommand(title: btn,
image: nil,
Expand Down Expand Up @@ -116,4 +156,6 @@ class MenuController {
extension UIMenu.Identifier {
static var keymappingMenu: UIMenu.Identifier { UIMenu.Identifier("io.playcover.PlayTools.menus.editor") }
static var keymappingOptionsMenu: UIMenu.Identifier { UIMenu.Identifier("io.playcover.PlayTools.menus.keymapping") }
static var debuggingMenu: UIMenu.Identifier { UIMenu.Identifier("io.playcover.PlayTools.menus.debug") }
static var debuggingOptionsMenu: UIMenu.Identifier { UIMenu.Identifier("io.playcover.PlayTools.menus.debugging") }
}

0 comments on commit b5f69c2

Please sign in to comment.