JoyKeyMapper
JoyKeyMapper copied to clipboard
Can't contribute, so here is my edit
I did a small edit so I can use diagonal mvmt on joy con switchs, here is the code if you're interested ;)
//
// GameController.swift
// JoyKeyMapper
//
// Created by magicien on 2019/07/14.
// Copyright © 2019 DarkHorse. All rights reserved.
//
import JoyConSwift
import InputMethodKit
extension JoyCon.BatteryStatus {
static let stringMap: [JoyCon.BatteryStatus: String] = [
.empty: "Empty",
.critical: "Critical",
.low: "Low",
.medium: "Medium",
.full: "Full",
.unknown: "Unknown"
]
var string: String {
return JoyCon.BatteryStatus.stringMap[self] ?? "Unknown"
}
var localizedString: String {
return NSLocalizedString(self.string, comment: "BatteryStatus localized string")
}
}
extension JoyCon.StickDirection {
static let correspStickDiag: [JoyCon.StickDirection: [JoyCon.StickDirection]] = [
.DownLeft: [.Down, .Left],
.DownRight: [.Down, .Right],
.UpLeft: [.Up, .Left],
.UpRight: [.Up, .Right]
]
}
class GameController {
let data: ControllerData
var type: JoyCon.ControllerType
var bodyColor: NSColor
var buttonColor: NSColor
var leftGripColor: NSColor?
var rightGripColor: NSColor?
var controller: JoyConSwift.Controller? {
didSet {
self.setControllerHandler()
}
}
var currentConfigData: KeyConfig {
didSet { self.updateKeyMap() }
}
var currentConfig: [JoyCon.Button:KeyMap] = [:]
var currentLStickMode: StickType = .None
var currentLStickConfig: [JoyCon.StickDirection:KeyMap] = [:]
var currentRStickMode: StickType = .None
var currentRStickConfig: [JoyCon.StickDirection:KeyMap] = [:]
var isEnabled: Bool = true {
didSet {
self.updateControllerIcon()
}
}
var isLeftDragging: Bool = false
var isRightDragging: Bool = false
var isCenterDragging: Bool = false
var lastAccess: Date? = nil
var timer: Timer? = nil
var icon: NSImage? {
if self._icon == nil {
self.updateControllerIcon()
}
return self._icon
}
private var _icon: NSImage?
var localizedBatteryString: String {
return (self.controller?.battery ?? .unknown).localizedString
}
init(data: ControllerData) {
self.data = data
guard let defaultConfig = self.data.defaultConfig else {
fatalError("Failed to get defaultConfig")
}
self.currentConfigData = defaultConfig
let type = JoyCon.ControllerType(rawValue: data.type ?? "")
self.type = type ?? JoyCon.ControllerType(rawValue: "unknown")!
let defaultColor = NSColor(red: 55.0 / 255, green: 55.0 / 255, blue: 55.0 / 255, alpha: 55.0 / 255)
self.bodyColor = defaultColor
if let bodyColorData = data.bodyColor {
if let bodyColor = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSColor.self, from: bodyColorData) {
self.bodyColor = bodyColor
}
}
self.buttonColor = defaultColor
if let buttonColorData = data.buttonColor {
if let buttonColor = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSColor.self, from: buttonColorData) {
self.buttonColor = buttonColor
}
}
self.leftGripColor = nil
if let leftGripColorData = data.leftGripColor {
if let leftGripColor = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSColor.self, from: leftGripColorData) {
self.leftGripColor = leftGripColor
}
}
self.rightGripColor = nil
if let rightGripColorData = data.rightGripColor {
if let rightGripColor = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSColor.self, from: rightGripColorData) {
self.rightGripColor = rightGripColor
}
}
}
// MARK: - Controller event handlers
func setControllerHandler() {
guard let controller = self.controller else { return }
controller.setPlayerLights(l1: .on, l2: .off, l3: .off, l4: .off)
controller.enableIMU(enable: true)
controller.setInputMode(mode: .standardFull)
controller.buttonPressHandler = { [weak self] button in
self?.buttonPressHandler(button: button)
}
controller.buttonReleaseHandler = { [weak self] button in
if !(self?.isEnabled ?? false) { return }
self?.buttonReleaseHandler(button: button)
}
controller.leftStickHandler = { [weak self] (newDir, oldDir) in
if !(self?.isEnabled ?? false) { return }
self?.leftStickHandler(newDirection: newDir, oldDirection: oldDir)
}
controller.rightStickHandler = { [weak self] (newDir, oldDir) in
if !(self?.isEnabled ?? false) { return }
self?.rightStickHandler(newDirection: newDir, oldDirection: oldDir)
}
controller.leftStickPosHandler = { [weak self] pos in
if !(self?.isEnabled ?? false) { return }
self?.leftStickPosHandler(pos: pos)
}
controller.rightStickPosHandler = { [weak self] pos in
if !(self?.isEnabled ?? false) { return }
self?.rightStickPosHandler(pos: pos)
}
controller.batteryChangeHandler = { [weak self] newState, oldState in
self?.batteryChangeHandler(newState: newState, oldState: oldState)
}
controller.isChargingChangeHandler = { [weak self] isCharging in
self?.isChargingChangeHandler(isCharging: isCharging)
}
// Update Controller data
self.data.type = controller.type.rawValue
self.type = controller.type
let bodyColor = NSColor(cgColor: controller.bodyColor)!
self.data.bodyColor = try! NSKeyedArchiver.archivedData(withRootObject: bodyColor, requiringSecureCoding: false)
self.bodyColor = bodyColor
let buttonColor = NSColor(cgColor: controller.buttonColor)!
self.data.buttonColor = try! NSKeyedArchiver.archivedData(withRootObject: buttonColor, requiringSecureCoding: false)
self.buttonColor = buttonColor
self.data.leftGripColor = nil
if let leftGripColor = controller.leftGripColor {
if let nsLeftGripColor = NSColor(cgColor: leftGripColor) {
self.data.leftGripColor = try? NSKeyedArchiver.archivedData(withRootObject: nsLeftGripColor, requiringSecureCoding: false)
self.leftGripColor = nsLeftGripColor
}
}
self.data.rightGripColor = nil
if let rightGripColor = controller.rightGripColor {
if let nsRightGripColor = NSColor(cgColor: rightGripColor) {
self.data.rightGripColor = try? NSKeyedArchiver.archivedData(withRootObject: nsRightGripColor, requiringSecureCoding: false)
self.rightGripColor = nsRightGripColor
}
}
self.updateControllerIcon()
}
func buttonPressHandler(button: JoyCon.Button) {
guard let config = self.currentConfig[button] else { return }
self.buttonPressHandler(config: config)
}
func buttonPressHandler(config: KeyMap) {
DispatchQueue.main.async {
let source = CGEventSource(stateID: .hidSystemState)
if config.keyCode >= 0 {
metaKeyEvent(config: config, keyDown: true)
if let systemKey = systemDefinedKey[Int(config.keyCode)] {
let mousePos = NSEvent.mouseLocation
let flags = NSEvent.ModifierFlags(rawValue: 0x0a00)
let data1 = Int((systemKey << 16) | 0x0a00)
let ev = NSEvent.otherEvent(
with: .systemDefined,
location: mousePos,
modifierFlags: flags,
timestamp: ProcessInfo().systemUptime,
windowNumber: 0,
context: nil,
subtype: Int16(NX_SUBTYPE_AUX_CONTROL_BUTTONS),
data1: data1,
data2: -1)
ev?.cgEvent?.post(tap: .cghidEventTap)
} else {
let event = CGEvent(keyboardEventSource: source, virtualKey: CGKeyCode(config.keyCode), keyDown: true)
event?.flags = CGEventFlags(rawValue: CGEventFlags.RawValue(config.modifiers))
event?.post(tap: .cghidEventTap)
}
}
if config.mouseButton >= 0 {
let mousePos = NSEvent.mouseLocation
let cursorPos = CGPoint(x: mousePos.x, y: NSScreen.main!.frame.maxY - mousePos.y)
metaKeyEvent(config: config, keyDown: true)
var event: CGEvent?
if config.mouseButton == 0 {
event = CGEvent(mouseEventSource: source, mouseType: .leftMouseDown, mouseCursorPosition: cursorPos, mouseButton: .left)
self.isLeftDragging = true
} else if config.mouseButton == 1 {
event = CGEvent(mouseEventSource: source, mouseType: .rightMouseDown, mouseCursorPosition: cursorPos, mouseButton: .right)
self.isRightDragging = true
} else if config.mouseButton == 2 {
event = CGEvent(mouseEventSource: source, mouseType: .otherMouseDown, mouseCursorPosition: cursorPos, mouseButton: .center)
self.isCenterDragging = true
}
event?.flags = CGEventFlags(rawValue: CGEventFlags.RawValue(config.modifiers))
event?.post(tap: .cghidEventTap)
}
}
}
func buttonReleaseHandler(button: JoyCon.Button) {
guard let config = self.currentConfig[button] else { return }
self.buttonReleaseHandler(config: config)
}
func buttonReleaseHandler(config: KeyMap) {
DispatchQueue.main.async {
let source = CGEventSource(stateID: .hidSystemState)
if config.keyCode >= 0 {
if let systemKey = systemDefinedKey[Int(config.keyCode)] {
let mousePos = NSEvent.mouseLocation
let flags = NSEvent.ModifierFlags(rawValue: 0x0b00)
let data1 = Int((systemKey << 16) | 0x0b00)
let ev = NSEvent.otherEvent(
with: .systemDefined,
location: mousePos,
modifierFlags: flags,
timestamp: ProcessInfo().systemUptime,
windowNumber: 0,
context: nil,
subtype: Int16(NX_SUBTYPE_AUX_CONTROL_BUTTONS),
data1: data1,
data2: -1)
ev?.cgEvent?.post(tap: .cghidEventTap)
} else {
let event = CGEvent(keyboardEventSource: source, virtualKey: CGKeyCode(config.keyCode), keyDown: false)
event?.flags = CGEventFlags(rawValue: CGEventFlags.RawValue(config.modifiers))
event?.post(tap: .cghidEventTap)
}
metaKeyEvent(config: config, keyDown: false)
}
if config.mouseButton >= 0 {
let mousePos = NSEvent.mouseLocation
let cursorPos = CGPoint(x: mousePos.x, y: NSScreen.main!.frame.maxY - mousePos.y)
var event: CGEvent?
if config.mouseButton == 0 {
event = CGEvent(mouseEventSource: source, mouseType: .leftMouseUp, mouseCursorPosition: cursorPos, mouseButton: .left)
self.isLeftDragging = false
} else if config.mouseButton == 1 {
event = CGEvent(mouseEventSource: source, mouseType: .rightMouseUp, mouseCursorPosition: cursorPos, mouseButton: .right)
self.isRightDragging = false
} else if config.mouseButton == 2 {
event = CGEvent(mouseEventSource: source, mouseType: .otherMouseUp, mouseCursorPosition: cursorPos, mouseButton: .center)
self.isCenterDragging = false
}
event?.post(tap: .cghidEventTap)
}
}
}
func stickMouseHandler(pos: CGPoint, speed: CGFloat) {
if pos.x == 0 && pos.y == 0 {
return
}
let mousePos = NSEvent.mouseLocation
let newX = mousePos.x + pos.x * speed
let newY = NSScreen.main!.frame.maxY - mousePos.y - pos.y * speed
let newPos = CGPoint(x: newX, y: newY)
let source = CGEventSource(stateID: .hidSystemState)
if self.isLeftDragging {
let event = CGEvent(mouseEventSource: source, mouseType: .leftMouseDragged, mouseCursorPosition: newPos, mouseButton: .left)
event?.post(tap: .cghidEventTap)
} else if self.isRightDragging {
let event = CGEvent(mouseEventSource: source, mouseType: .rightMouseDragged, mouseCursorPosition: newPos, mouseButton: .right)
event?.post(tap: .cghidEventTap)
} else if self.isCenterDragging {
let event = CGEvent(mouseEventSource: source, mouseType: .otherMouseDragged, mouseCursorPosition: newPos, mouseButton: .center)
event?.post(tap: .cghidEventTap)
} else {
CGDisplayMoveCursorToPoint(CGMainDisplayID(), newPos)
}
}
func stickMouseWheelHandler(pos: CGPoint, speed: CGFloat) {
if pos.x == 0 && pos.y == 0 {
return
}
let wheelX = Int32(pos.x * speed)
let wheelY = Int32(pos.y * speed)
let source = CGEventSource(stateID: .hidSystemState)
let event = CGEvent(scrollWheelEvent2Source: source, units: .pixel, wheelCount: 2, wheel1: wheelY, wheel2: wheelX, wheel3: 0)
event?.post(tap: .cghidEventTap)
}
func leftStickHandler(newDirection: JoyCon.StickDirection, oldDirection: JoyCon.StickDirection) {
if self.currentLStickMode == .Key {
if JoyCon.StickDirection.correspStickDiag.keys.contains(oldDirection) {
for direction in JoyCon.StickDirection.correspStickDiag[oldDirection] ?? [] {
if let config = self.currentLStickConfig[direction] {
self.buttonReleaseHandler(config: config)
}
}
} else if let config = self.currentLStickConfig[oldDirection] {
self.buttonReleaseHandler(config: config)
}
if JoyCon.StickDirection.correspStickDiag.keys.contains(newDirection) {
for direction in JoyCon.StickDirection.correspStickDiag[newDirection] ?? [] {
if let config = self.currentLStickConfig[direction] {
self.buttonPressHandler(config: config)
}
}
} else if let config = self.currentLStickConfig[newDirection] {
self.buttonPressHandler(config: config)
}
}
}
func rightStickHandler(newDirection: JoyCon.StickDirection, oldDirection: JoyCon.StickDirection) {
if self.currentRStickMode == .Key {
if JoyCon.StickDirection.correspStickDiag.keys.contains(oldDirection) {
for direction in JoyCon.StickDirection.correspStickDiag[oldDirection] ?? [] {
if let config = self.currentRStickConfig[direction] {
self.buttonReleaseHandler(config: config)
}
}
} else if let config = self.currentRStickConfig[oldDirection] {
self.buttonReleaseHandler(config: config)
}
if JoyCon.StickDirection.correspStickDiag.keys.contains(newDirection) {
for direction in JoyCon.StickDirection.correspStickDiag[newDirection] ?? [] {
if let config = self.currentRStickConfig[direction] {
self.buttonPressHandler(config: config)
}
}
} else if let config = self.currentRStickConfig[newDirection] {
self.buttonPressHandler(config: config)
}
}
}
func leftStickPosHandler(pos: CGPoint) {
let speed = CGFloat(self.currentConfigData.leftStick?.speed ?? 0)
if self.currentLStickMode == .Mouse {
self.stickMouseHandler(pos: pos, speed: speed)
} else if self.currentLStickMode == .MouseWheel {
self.stickMouseWheelHandler(pos: pos, speed: speed)
}
}
func rightStickPosHandler(pos: CGPoint) {
let speed = CGFloat(self.currentConfigData.rightStick?.speed ?? 0)
if self.currentRStickMode == .Mouse {
self.stickMouseHandler(pos: pos, speed: speed)
} else if self.currentRStickMode == .MouseWheel {
self.stickMouseWheelHandler(pos: pos, speed: speed)
}
}
func batteryChangeHandler(newState: JoyCon.BatteryStatus, oldState: JoyCon.BatteryStatus) {
self.updateControllerIcon()
if newState == .full && oldState != .unknown {
AppNotifications.notifyBatteryFullCharge(self)
}
if newState == .empty {
AppNotifications.notifyBatteryLevel(self)
}
if newState == .critical && oldState != .empty {
AppNotifications.notifyBatteryLevel(self)
}
if newState == .low && oldState != .critical && oldState != .empty {
AppNotifications.notifyBatteryLevel(self)
}
DispatchQueue.main.async {
guard let delegate = NSApplication.shared.delegate as? AppDelegate else { return }
delegate.updateControllersMenu()
}
}
func isChargingChangeHandler(isCharging: Bool) {
self.updateControllerIcon()
if isCharging {
AppNotifications.notifyStartCharge(self)
} else {
AppNotifications.notifyStopCharge(self)
}
DispatchQueue.main.async {
guard let delegate = NSApplication.shared.delegate as? AppDelegate else { return }
delegate.updateControllersMenu()
}
}
// MARK: - Controller Icon
func updateControllerIcon() {
self._icon = GameControllerIcon(for: self)
NotificationCenter.default.post(name: .controllerIconChanged, object: self)
DispatchQueue.main.async {
guard let delegate = NSApplication.shared.delegate as? AppDelegate else { return }
delegate.updateControllersMenu()
}
}
// MARK: -
func switchApp(bundleID: String) {
let appConfig = self.data.appConfigs?.first(where: {
guard let appConfig = $0 as? AppConfig else { return false }
return appConfig.app?.bundleID == bundleID
}) as? AppConfig
if let keyConfig = appConfig?.config {
self.currentConfigData = keyConfig
return
}
guard let defaultConfig = self.data.defaultConfig else {
fatalError("Failed to get defaultConfig")
}
self.currentConfigData = defaultConfig
}
func updateKeyMap() {
var newKeyMap: [JoyCon.Button:KeyMap] = [:]
self.currentConfigData.keyMaps?.enumerateObjects { (map, _) in
guard let keyMap = map as? KeyMap else { return }
guard let buttonStr = keyMap.button else { return }
let buttonName = buttonNames.first { (_, name) in
return name == buttonStr
}
guard let button = buttonName?.key else { return }
newKeyMap[button] = keyMap
}
self.currentConfig = newKeyMap
self.currentLStickMode = .None
if let stickTypeStr = self.currentConfigData.leftStick?.type,
let stickType = StickType(rawValue: stickTypeStr) {
self.currentLStickMode = stickType
}
var newLeftStickMap: [JoyCon.StickDirection:KeyMap] = [:]
self.currentConfigData.leftStick?.keyMaps?.enumerateObjects { (map, _) in
guard let keyMap = map as? KeyMap else { return }
guard let buttonStr = keyMap.button else { return }
let directionName = directionNames.first { (_, name) in
return name == buttonStr
}
guard let direction = directionName?.key else { return }
newLeftStickMap[direction] = keyMap
}
self.currentLStickConfig = newLeftStickMap
self.currentRStickMode = .None
if let stickTypeStr = self.currentConfigData.rightStick?.type,
let stickType = StickType(rawValue: stickTypeStr) {
self.currentRStickMode = stickType
}
var newRightStickMap: [JoyCon.StickDirection:KeyMap] = [:]
self.currentConfigData.rightStick?.keyMaps?.enumerateObjects { (map, _) in
guard let keyMap = map as? KeyMap else { return }
guard let buttonStr = keyMap.button else { return }
let directionName = directionNames.first { (_, name) in
return name == buttonStr
}
guard let direction = directionName?.key else { return }
newRightStickMap[direction] = keyMap
}
self.currentRStickConfig = newRightStickMap
}
func addApp(url: URL) {
guard let delegate = NSApplication.shared.delegate as? AppDelegate else { return }
guard let manager = delegate.dataManager else { return }
guard let bundle = Bundle(url: url) else { return }
guard let info = bundle.infoDictionary else { return }
let bundleID = info["CFBundleIdentifier"] as? String ?? ""
let appIndex = self.data.appConfigs?.index(ofObjectPassingTest: { (obj, index, stop) in
guard let appConfig = obj as? AppConfig else { return false }
return appConfig.app?.bundleID == bundleID
})
if appIndex != nil && appIndex != NSNotFound {
// The selected app has been already added.
return
}
let appConfig = manager.createAppConfig(type: self.type)
// appConfig.config = manager.createKeyConfig()
let displayName = FileManager.default.displayName(atPath: url.absoluteString)
let iconFile = info["CFBundleIconFile"] as? String ?? ""
if let iconURL = bundle.url(forResource: iconFile, withExtension: nil) {
do {
let iconData = try Data(contentsOf: iconURL)
appConfig.app?.icon = iconData
} catch {}
} else if let iconURL = bundle.url(forResource: "\(iconFile).icns", withExtension: nil) {
do {
let iconData = try Data(contentsOf: iconURL)
appConfig.app?.icon = iconData
} catch {}
}
appConfig.app?.bundleID = bundleID
appConfig.app?.displayName = displayName
self.data.addToAppConfigs(appConfig)
}
func removeApp(_ app: AppConfig) {
self.data.removeFromAppConfigs(app)
}
@objc func toggleEnableKeyMappings() {
self.isEnabled = !self.isEnabled
}
@objc func disconnect() {
self.stopTimer()
self.controller?.setHCIState(state: .disconnect)
}
// MARK: - Timer
func updateAccessTime() {
self.lastAccess = Date(timeIntervalSinceNow: 0)
}
func startTimer() {
self.stopTimer()
let checkInterval: TimeInterval = 1 * 60 // 1 min
self.timer = Timer.scheduledTimer(withTimeInterval: checkInterval, repeats: true) { [weak self] _ in
if AppSettings.disconnectTime <= 0 { return }
guard let lastAccess = self?.lastAccess else { return }
let disconnectTime = TimeInterval(AppSettings.disconnectTime * 60)
let now = Date(timeIntervalSinceNow: 0)
if now.timeIntervalSince(lastAccess) > disconnectTime {
self?.disconnect()
}
}
self.updateAccessTime()
}
func stopTimer() {
self.timer?.invalidate()
self.timer = nil
}
}
@antoine-nedelec Can you teach me how to implement this code by my self?
Download the project, change the file with the code in here, compile your own version and launch it :)