From 80804ee7ac4de6a7528bdc49588b95f3bbb7d0f8 Mon Sep 17 00:00:00 2001 From: TrueZhuangjia Date: Sun, 23 Feb 2025 16:12:23 +0800 Subject: [PATCH] super combo custom on-screen button connected by "-" in key string --- Limelight/Input/CommandManager.swift | 109 +++++++++++-- Limelight/Input/OnScreenWidgetView.swift | 146 ++++++++++++------ .../CommandManagerViewController.swift | 4 +- .../LayoutOnScreenControlsViewController.m | 5 +- 4 files changed, 200 insertions(+), 64 deletions(-) diff --git a/Limelight/Input/CommandManager.swift b/Limelight/Input/CommandManager.swift index 8767ffef..3e21bd3b 100644 --- a/Limelight/Input/CommandManager.swift +++ b/Limelight/Input/CommandManager.swift @@ -297,7 +297,7 @@ import UIKit } // extractKeyStrings from keyboardCMDString - @objc public func extractKeyStrings(from input: String) -> [String]? { + @objc public func extractKeyStringsFromComboCommand(from input: String) -> [String]? { let keys = CommandManager.keyboardButtonMappings.keys.joined(separator: "|") let pattern = "^(?:(\(keys))(?:\\+(\(keys))*)*)$" @@ -344,10 +344,60 @@ import UIKit } + //super combo key button strings + @objc public func extractKeyStringsFromComboKeys(from input: String) -> [String]? { + let combinedStrings = [CommandManager.keyboardButtonMappings.keys.map { $0 as String }, + CommandManager.oscButtonMappings.keys.map { $0 as String }, + CommandManager.mouseButtonMappings.keys.map { $0 as String }] + .lazy // 零内存开销 + .flatMap { $0 } // 三维展开 + .map(String.init(describing:)) // 安全类型转换 + + let keys = combinedStrings.joined(separator: "|") + // print(keys) + let pattern = "^(?:(\(keys))(?:\\-(\(keys))*)*)$" + + guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { + print("Failed to create regex") + return nil + } + let range = NSRange(location: 0, length: input.utf16.count) + guard let match = regex.firstMatch(in: input, options: [], range: range) else { + print("No match found for input: \(input)") + return nil + } + print("Regex matched for input: \(input)") + + let matchedString = (input as NSString).substring(with: match.range(at: 0)) + let keyStrings = matchedString.split(separator: "-").map { String($0) } + + guard !keyStrings.isEmpty else { + print("No key strings found in the matched string") + return nil + } + + var validKeyStrings: [String] = [] + + for key in keyStrings { + validKeyStrings.append(key) + } + + if validKeyStrings.isEmpty { + print("No valid key strings found in the matched string") + return nil + } + + for (index, key) in validKeyStrings.enumerated() { + print("Valid Key \(index): \(key)") + } + + return validKeyStrings + } + @objc public func addCommand(_ command: RemoteCommand) -> Bool { command.keyboardCmdString = command.keyboardCmdString.uppercased() // convert all letters to upper case if(command.alias.trimmingCharacters(in: .whitespacesAndNewlines).count == 0) {command.alias = command.keyboardCmdString} // copy cmd string as alias when alias is empty - let keyStrings = extractKeyStrings(from: command.keyboardCmdString) + let keyStrings = extractKeyStringsFromComboCommand(from: command.keyboardCmdString) if (keyStrings == nil) {return false} // in case of non-keyboard command strings, return false commands.append(command) saveCommands() @@ -393,25 +443,50 @@ import UIKit } } - @objc public func sendKeyDownEventWithDelay(keyboardCmdStrings: [String], delay: TimeInterval = 0.2, index: Int = 0) { // we need a large delay for WAN streaming + @objc public func sendKeyComboCommand(keyboardCmdStrings: [String], delay: TimeInterval = 0.2, index: Int = 0) { // we need a large delay for WAN streaming + // 如果已处理完所有按键,则开始释放按键 guard index < keyboardCmdStrings.count else { - for keyStr in keyboardCmdStrings { - LiSendKeyboardEvent(CommandManager.keyboardButtonMappings[keyStr]!,Int8(KEY_ACTION_UP), 0) + // 释放按键 + for keyStr in keyboardCmdStrings.reversed() { // 从后往前释放按键 + if let keyCode = CommandManager.keyboardButtonMappings[keyStr] { + LiSendKeyboardEvent(keyCode, Int8(KEY_ACTION_UP), 0) // 释放按键 + } } return - } // 如果已经处理完所有键,释放所有按键 - - let keyCode = CommandManager.keyboardButtonMappings[keyboardCmdStrings[index]] - guard let keyCode = keyCode else { - print("No mapping found for \(keyboardCmdStrings[index])") - sendKeyDownEventWithDelay(keyboardCmdStrings: keyboardCmdStrings, delay: delay, index: index+1) // 跳过当前键,继续下一个 - return } - // 发送当前键的键盘按下事件 - LiSendKeyboardEvent(keyCode,Int8(KEY_ACTION_DOWN), 0) - // 使用 asyncAfter 在指定异步延迟后发送下一个键的键盘按下事件 - DispatchQueue.main.asyncAfter(deadline: .now() + delay) { - self.sendKeyDownEventWithDelay(keyboardCmdStrings: keyboardCmdStrings, delay: delay, index: index+1) // 跳过当前键,继续下一个 + + // 获取当前按键的映射值 + if let keyCode = CommandManager.keyboardButtonMappings[keyboardCmdStrings[index]] { + // 发送当前按键的按下事件 + LiSendKeyboardEvent(keyCode, Int8(KEY_ACTION_DOWN), 0) + + // 延迟后递归处理下一个按键 + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { + self.sendKeyComboCommand(keyboardCmdStrings: keyboardCmdStrings, delay: delay, index: index + 1) + } + } else { + print("No mapping found for \(keyboardCmdStrings[index])") + // 如果当前按键没有映射,跳过当前按键并继续下一个 + self.sendKeyComboCommand(keyboardCmdStrings: keyboardCmdStrings, delay: delay, index: index + 1) } } + + @objc public func sendKeyComboDown(keyboardCmdStrings: [String]) { // we need a large delay for WAN streaming + for keyStr in keyboardCmdStrings { + if let keyCode = CommandManager.keyboardButtonMappings[keyStr] { + LiSendKeyboardEvent(keyCode, Int8(KEY_ACTION_DOWN), 0) // 释放按键 + } + } + } + + @objc public func sendKeyComboUp(keyboardCmdStrings: [String]) { // we need a large delay for WAN streaming + for keyStr in keyboardCmdStrings { + if let keyCode = CommandManager.keyboardButtonMappings[keyStr] { + LiSendKeyboardEvent(keyCode, Int8(KEY_ACTION_UP), 0) // 释放按键 + } + } + return + } + + } diff --git a/Limelight/Input/OnScreenWidgetView.swift b/Limelight/Input/OnScreenWidgetView.swift index c1d0c42f..9a2b29c3 100644 --- a/Limelight/Input/OnScreenWidgetView.swift +++ b/Limelight/Input/OnScreenWidgetView.swift @@ -659,7 +659,7 @@ import UIKit private func setupButtonDownVisualEffectLayer() { - self.buttonDownVisualEffectWidth = 8.6 + self.buttonDownVisualEffectWidth = 8 if CommandManager.oscButtonMappings.keys.contains(self.keyString) && !CommandManager.oscRectangleButtonCmds.contains(self.keyString){ if widthFactor < 1.3 {self.buttonDownVisualEffectWidth = 15.3} // wider visual effect for osc buttons else {self.buttonDownVisualEffectWidth = 9} @@ -713,6 +713,60 @@ import UIKit } //========================================================================================================== + private func sendOscButtonDownEvent(keyString: String){ + let buttonFlag = CommandManager.oscButtonMappings[keyString] + if buttonFlag != 0 {self.onScreenControls.pressDownControllerButton(buttonFlag!)} + if keyString == "OSCL2" { + self.onScreenControls.updateLeftTrigger(0xFF) + } + if keyString == "OSCR2" {self.onScreenControls.updateRightTrigger(0xFF)} + } + + private func sendOscButtonUpEvent(keyString: String){ + let buttonFlag = CommandManager.oscButtonMappings[keyString] + if buttonFlag != 0 {self.onScreenControls.releaseControllerButton(buttonFlag!)} + if keyString == "OSCL2" {self.onScreenControls.updateLeftTrigger(0x00)} + if keyString == "OSCR2" {self.onScreenControls.updateRightTrigger(0x00)} + } + +//============================================================================== + private func sendComboButtonsDownEvent(comboStrings: [String]) { + DispatchQueue.global(qos: .userInitiated).async { + for comboString in comboStrings { + if CommandManager.oscButtonMappings.keys.contains(comboString) { + self.sendOscButtonDownEvent(keyString: comboString) + } + if CommandManager.keyboardButtonMappings.keys.contains(comboString) { + LiSendKeyboardEvent(CommandManager.keyboardButtonMappings[comboString]!,Int8(KEY_ACTION_DOWN), 0) + } + if CommandManager.mouseButtonMappings.keys.contains(comboString) { + LiSendMouseButtonEvent(CChar(BUTTON_ACTION_PRESS), Int32(CommandManager.mouseButtonMappings[comboString]!)) + } + usleep(10000) // delay 10ms + } + } + } + + private func sendComboButtonsUpEvent(comboStrings: [String]) { + DispatchQueue.global(qos: .userInitiated).async { + for comboString in comboStrings { + if CommandManager.oscButtonMappings.keys.contains(comboString) { + self.sendOscButtonUpEvent(keyString: comboString) + } + if CommandManager.keyboardButtonMappings.keys.contains(comboString) { + LiSendKeyboardEvent(CommandManager.keyboardButtonMappings[comboString]!,Int8(KEY_ACTION_UP), 0) + } + if CommandManager.mouseButtonMappings.keys.contains(comboString) { + LiSendMouseButtonEvent(CChar(BUTTON_ACTION_RELEASE), Int32(CommandManager.mouseButtonMappings[comboString]!)) + } + usleep(10000) //delay 10ms + } + } + } + +//============================================================================== + + // Touch event handling override func touchesBegan(_ touches: Set, with event: UIEvent?) { @@ -739,6 +793,7 @@ import UIKit self.pressed = true if !OnScreenWidgetView.editMode { + if CommandManager.touchPadCmds.contains(self.keyString) && touches.count == 1{ // don't use event?.allTouches?.count here, it will counts all touches including the ones captured by other UIViews switch self.keyString { case "LSPAD": @@ -770,32 +825,37 @@ import UIKit break } } - - if !CommandManager.touchPadCmds.contains(self.keyString) { - self.buttonDownVisualEffect() + + if !self.keyString.contains("+") && !self.keyString.contains("-") { + // if there's no "+" in the keystring, treat it as a regular button: + if CommandManager.oscButtonMappings.keys.contains(self.keyString) { + self.sendOscButtonDownEvent(keyString: self.keyString) + } + if CommandManager.keyboardButtonMappings.keys.contains(self.keyString) { + LiSendKeyboardEvent(CommandManager.keyboardButtonMappings[self.keyString]!,Int8(KEY_ACTION_DOWN), 0) + } + if CommandManager.mouseButtonMappings.keys.contains(self.keyString) { + LiSendMouseButtonEvent(CChar(BUTTON_ACTION_PRESS), Int32(CommandManager.mouseButtonMappings[self.keyString]!)) + } } - - if CommandManager.oscButtonMappings.keys.contains(self.keyString) { - let buttonFlag = CommandManager.oscButtonMappings[self.keyString] - if buttonFlag != 0 {self.onScreenControls.pressDownControllerButton(buttonFlag!)} - if self.keyString == "OSCL2" {self.onScreenControls.updateLeftTrigger(0xFF)} - if self.keyString == "OSCR2" {self.onScreenControls.updateRightTrigger(0xFF)} + + // if the command(keystring contains "-", it's a multi-type super combo button + if self.keyString.contains("-"){ + let comboStrings = CommandManager.shared.extractKeyStringsFromComboKeys(from: self.keyString)! + self.sendComboButtonsDownEvent(comboStrings: comboStrings) } // if the command(keystring contains "+", it's a multi-key command or a quick triggering key, rather than a physical button - if self.keyString.contains("+"){ - let keyboardCmdStrings = CommandManager.shared.extractKeyStrings(from: self.keyString)! - CommandManager.shared.sendKeyDownEventWithDelay(keyboardCmdStrings: keyboardCmdStrings) // send multi-key command + if self.keyString.contains("+") && !self.keyString.contains("-"){ + let keyboardCmdStrings = CommandManager.shared.extractKeyStringsFromComboCommand(from: self.keyString)! + CommandManager.shared.sendKeyComboCommand(keyboardCmdStrings: keyboardCmdStrings) // send multi-key command DispatchQueue.main.asyncAfter(deadline: .now() + 0.03) { // reset shadow color immediately 50ms later self.buttonUpVisualEffect() } } - // if there's no "+" in the keystring, treat it as a regular button: - if CommandManager.keyboardButtonMappings.keys.contains(self.keyString) { - LiSendKeyboardEvent(CommandManager.keyboardButtonMappings[self.keyString]!,Int8(KEY_ACTION_DOWN), 0) - } - if CommandManager.mouseButtonMappings.keys.contains(self.keyString) { - LiSendMouseButtonEvent(CChar(BUTTON_ACTION_PRESS), Int32(CommandManager.mouseButtonMappings[self.keyString]!)) + + if !CommandManager.touchPadCmds.contains(self.keyString) { + self.buttonDownVisualEffect() } } // here is in edit mode: @@ -949,6 +1009,30 @@ import UIKit break } } + self.l3r3Indicator.removeFromSuperlayer() + self.upIndicator.removeFromSuperlayer() + self.downIndicator.removeFromSuperlayer() + self.leftIndicator.removeFromSuperlayer() + self.rightIndicator.removeFromSuperlayer() + self.lrudIndicatorBall.removeFromSuperlayer() + + if !OnScreenWidgetView.editMode && !self.keyString.contains("+") && !self.keyString.contains("-") { // if the command(keystring contains "+", it's a multi-key command rather than a single key button + if CommandManager.oscButtonMappings.keys.contains(self.keyString) { + sendOscButtonUpEvent(keyString: self.keyString) + } + if CommandManager.keyboardButtonMappings.keys.contains(self.keyString) { + LiSendKeyboardEvent(CommandManager.keyboardButtonMappings[self.keyString]!,Int8(KEY_ACTION_UP), 0) + } + if CommandManager.mouseButtonMappings.keys.contains(self.keyString){ + LiSendMouseButtonEvent(CChar(BUTTON_ACTION_RELEASE), Int32(CommandManager.mouseButtonMappings[self.keyString]!)) + } + } + + // if the command(keystring contains "-", it's a multi-type super combo button + if !OnScreenWidgetView.editMode && self.keyString.contains("-"){ + let comboStrings = CommandManager.shared.extractKeyStringsFromComboKeys(from: self.keyString)! + self.sendComboButtonsUpEvent(comboStrings: comboStrings) + } if !OnScreenWidgetView.editMode && CommandManager.specialOverlayButtonCmds.contains(self.keyString){ if CACurrentMediaTime() - self.touchTapTimeStamp < 0.3 { @@ -960,33 +1044,9 @@ import UIKit } } } - - self.buttonUpVisualEffect() - self.l3r3Indicator.removeFromSuperlayer() - self.upIndicator.removeFromSuperlayer() - self.downIndicator.removeFromSuperlayer() - self.leftIndicator.removeFromSuperlayer() - self.rightIndicator.removeFromSuperlayer() - self.lrudIndicatorBall.removeFromSuperlayer() - if CommandManager.oscButtonMappings.keys.contains(self.keyString) { - let buttonFlag = CommandManager.oscButtonMappings[self.keyString] - if buttonFlag != 0 {self.onScreenControls.releaseControllerButton(buttonFlag!)} - if self.keyString == "OSCL2" {self.onScreenControls.updateLeftTrigger(0x00)} - if self.keyString == "OSCR2" {self.onScreenControls.updateRightTrigger(0x00)} - } - - if !OnScreenWidgetView.editMode && !self.keyString.contains("+") { // if the command(keystring contains "+", it's a multi-key command rather than a single key button - - if CommandManager.mouseButtonMappings.keys.contains(self.keyString){ - LiSendMouseButtonEvent(CChar(BUTTON_ACTION_RELEASE), Int32(CommandManager.mouseButtonMappings[self.keyString]!)) - } - if CommandManager.keyboardButtonMappings.keys.contains(self.keyString) { - LiSendKeyboardEvent(CommandManager.keyboardButtonMappings[self.keyString]!,Int8(KEY_ACTION_UP), 0) - } - } if OnScreenWidgetView.editMode { guard let superview = superview else { return } diff --git a/Limelight/ViewControllers/CommandManagerViewController.swift b/Limelight/ViewControllers/CommandManagerViewController.swift index adf0fdfa..d0fc030f 100644 --- a/Limelight/ViewControllers/CommandManagerViewController.swift +++ b/Limelight/ViewControllers/CommandManagerViewController.swift @@ -303,8 +303,8 @@ import UIKit private func sendKeyboardCommand(_ cmd: RemoteCommand) { print("Sending key-value") - let keyboardCmdStrings = CommandManager.shared.extractKeyStrings(from: cmd.keyboardCmdString) - CommandManager.shared.sendKeyDownEventWithDelay(keyboardCmdStrings: keyboardCmdStrings!) + let keyboardCmdStrings = CommandManager.shared.extractKeyStringsFromComboCommand(from: cmd.keyboardCmdString) + CommandManager.shared.sendKeyComboCommand(keyboardCmdStrings: keyboardCmdStrings!) } } diff --git a/Limelight/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m b/Limelight/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m index bac09f59..042cc7c0 100644 --- a/Limelight/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m +++ b/Limelight/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m @@ -362,13 +362,14 @@ NSString *cmdString = [alertController.textFields[0].text uppercaseString]; // convert to uppercase NSString *keyLabel = alertController.textFields[1].text; if([keyLabel isEqualToString:@""]) keyLabel = [[cmdString lowercaseString] capitalizedString]; - bool noValidKeyboardString = [CommandManager.shared extractKeyStringsFrom:cmdString] == nil; // this is a invalid string. + bool noValidKeyboardString = [CommandManager.shared extractKeyStringsFromComboCommandFrom:cmdString] == nil; // this is a invalid string. + bool noValidSuperComboButtonString = [CommandManager.shared extractKeyStringsFromComboKeysFrom:cmdString] == nil; // this is a invalid string. bool noValidMouseButtonString = ![CommandManager.mouseButtonMappings.allKeys containsObject:cmdString]; bool noValidTouchPadString = ![CommandManager.touchPadCmds containsObject:cmdString]; bool noValidOscButtonString = ![CommandManager.oscButtonMappings.allKeys containsObject:cmdString]; bool noValidSpecialButtonString = ![CommandManager.specialOverlayButtonCmds containsObject:cmdString]; - if(noValidKeyboardString && noValidMouseButtonString && noValidTouchPadString && noValidOscButtonString && noValidSpecialButtonString) return; + if(noValidKeyboardString && noValidMouseButtonString && noValidTouchPadString && noValidOscButtonString && noValidSpecialButtonString && noValidSuperComboButtonString) return; //saving & present the keyboard button: OnScreenWidgetView* widgetView = [[OnScreenWidgetView alloc] initWithKeyString:cmdString keyLabel:keyLabel];