diff --git a/VoidLink/Input/ControllerSupport.h b/VoidLink/Input/ControllerSupport.h index ea961b2d..2d2242c3 100644 --- a/VoidLink/Input/ControllerSupport.h +++ b/VoidLink/Input/ControllerSupport.h @@ -15,6 +15,8 @@ - (void)gamepadPresenceChanged; - (void)mousePresenceChanged; +- (void)mouseConnected; +- (void)keyboardConnected; - (void)streamExitRequested; - (void)controllerArrivalWithPlayerIndex:(int8_t)index; diff --git a/VoidLink/Input/ControllerSupport.m b/VoidLink/Input/ControllerSupport.m index f86fb51a..16124c0f 100644 --- a/VoidLink/Input/ControllerSupport.m +++ b/VoidLink/Input/ControllerSupport.m @@ -1938,6 +1938,7 @@ double rc_expo(double x, double expo) { // Notify the delegate [self->_delegate mousePresenceChanged]; + [self->_delegate mouseConnected]; }]; _mouseDisconnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCMouseDidDisconnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { Log(LOG_I, @"Mouse disconnected!"); @@ -1956,6 +1957,8 @@ double rc_expo(double x, double expo) { _keyboardConnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCKeyboardDidConnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { Log(LOG_I, @"Keyboard connected!"); + [self->_delegate keyboardConnected]; + // Re-evaluate the on-screen control mode [self updateAutoOnScreenControlMode]; }]; diff --git a/VoidLink/Localization/Localizable.xcstrings b/VoidLink/Localization/Localizable.xcstrings index b96f58db..d9cf959e 100644 --- a/VoidLink/Localization/Localizable.xcstrings +++ b/VoidLink/Localization/Localizable.xcstrings @@ -6892,6 +6892,58 @@ } } }, + "Keyboard/Mouse Connected" : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "检测到键鼠接入" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "檢測到鍵鼠接入" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "檢測到鍵鼠接入" + } + } + } + }, + "keyboard&MouseStreamingTip" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "\nKeyboard Shortcuts for VoidLink (Desktop Mode):\n\nCtrl(Control) + Alt(Opt) + Shift + D — Disconnect the current session\n\nCtrl(Control) + Alt(Opt) + Shift + Q — Disconnect the current session and quit current app\n\nCtrl(Control) + Alt(Opt) + Shift + S — Expand the settings menu\n\nCtrl(Control) + Alt(Opt) + Shift + O — Toggle the statistics overlay\n\nCtrl(Control) + Alt(Opt) + Shift + C — Toggle mouse pointer visibility\n\nBy default, the mouse cannot control the camera in 3D games. To stream games, please disconnect the current session, then go to Settings → Peripheral Section → Local Mouse Cursor, and select “Capture”." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "\nVoidLink 桌面模式快捷键:\n\nCtrl(Control) + Alt(Opt) + Shift + D — 断开当前会话\n\nCtrl(Control) + Alt(Opt) + Shift + Q — 断开当前会话并退出当前app\n\nCtrl(Control) + Alt(Opt) + Shift + S — 展开设置菜单\n\nCtrl(Control) + Alt(Opt) + Shift + O — 切换性能信息叠加\n\nCtrl(Control) + Alt(Opt) + Shift + C — 切换鼠标指针可见性\n\n默认状态下鼠标无法在3D游戏中转动视角。如需串流游戏,请断开当前会话,前往设置菜单 -> 外设分区 -> 本地鼠标指针,选择“被窗口捕获”。" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "\nVoidLink 桌面模式快速鍵:\n\nCtrl(Control) + Alt(Opt) + Shift + D — 中斷目前連線\n\nCtrl(Control) + Alt(Opt) + Shift + Q — 中斷目前連線並退出目前app\n\nCtrl(Control) + Alt(Opt) + Shift + S — 展開設定選單\n\nCtrl(Control) + Alt(Opt) + Shift + O — 切換統計資訊覆蓋\n\nCtrl(Control) + Alt(Opt) + Shift + C — 切換滑鼠指標可見性\n\n預設狀態下,滑鼠無法在 3D 遊戲中轉動視角。如需進行串流遊戲,請中斷目前會話,前往設定選單 → 外設分區 → 本地滑鼠指標,選擇「被視窗捕獲」。" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "\nVoidLink 桌面模式快速鍵:\n\nCtrl(Control) + Alt(Opt) + Shift + D — 中斷目前連線\n\nCtrl(Control) + Alt(Opt) + Shift + Q — 中斷目前連線並退出目前app\n\nCtrl(Control) + Alt(Opt) + Shift + S — 展開設定選單\n\nCtrl(Control) + Alt(Opt) + Shift + O — 切換統計資訊覆蓋\n\nCtrl(Control) + Alt(Opt) + Shift + C — 切換滑鼠指標可見性\n\n預設狀態下,滑鼠無法在 3D 遊戲中轉動視角。如需進行串流遊戲,請中斷目前會話,前往設定選單 → 外設分區 → 本地滑鼠指標,選擇「被視窗捕獲」。" + } + } + } + }, "Label alpha" : { "extractionState" : "manual", "localizations" : { @@ -7554,35 +7606,6 @@ } } }, - "macFirstLaunchTip" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "\nKeyboard Shortcuts for VoidLink (Desktop Mode):\n\nCtrl + Alt + Shift + D — Disconnect the current session\n\nCtrl + Alt + Shift + Q — Disconnect the current session and quit current app\n\nCtrl + Alt + Shift + S — Expand the settings menu\n\nCtrl + Alt + Shift + O — Toggle the statistics overlay\n\nCtrl + Alt + Shift + C — Toggle mouse pointer visibility\n\nBy default, the mouse cannot control the camera in 3D games. To stream games, please disconnect the current session, then go to Settings → Peripheral Section → Local Mouse Cursor, and select “Capture”." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "\nVoidLink 桌面模式快捷键:\n\nCtrl + Alt + Shift + D — 断开当前会话\n\nCtrl + Alt + Shift + Q — 断开当前会话并退出当前app\n\nCtrl + Alt + Shift + S — 展开设置菜单\n\nCtrl + Alt + Shift + O — 切换性能信息叠加\n\nCtrl + Alt + Shift + C — 切换鼠标指针可见性\n\n默认状态下鼠标无法在3D游戏中转动视角。如需串流游戏,请断开当前会话,前往设置菜单 -> 外设分区 -> 本地鼠标指针,选择“被窗口捕获”。" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "\nVoidLink 桌面模式快速鍵:\n\nCtrl + Alt + Shift + D — 中斷目前連線\n\nCtrl + Alt + Shift + Q — 中斷目前連線並退出目前app\n\nCtrl + Alt + Shift + S — 展開設定選單\n\nCtrl + Alt + Shift + O — 切換統計資訊覆蓋\n\nCtrl + Alt + Shift + C — 切換滑鼠指標可見性\n\n預設狀態下,滑鼠無法在 3D 遊戲中轉動視角。如需進行串流遊戲,請中斷目前會話,前往設定選單 → 外設分區 → 本地滑鼠指標,選擇「被視窗捕獲」。" - } - }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "\nVoidLink 桌面模式快速鍵:\n\nCtrl + Alt + Shift + D — 中斷目前連線\n\nCtrl + Alt + Shift + Q — 中斷目前連線並退出目前app\n\nCtrl + Alt + Shift + S — 展開設定選單\n\nCtrl + Alt + Shift + O — 切換統計資訊覆蓋\n\nCtrl + Alt + Shift + C — 切換滑鼠指標可見性\n\n預設狀態下,滑鼠無法在 3D 遊戲中轉動視角。如需進行串流遊戲,請中斷目前會話,前往設定選單 → 外設分區 → 本地滑鼠指標,選擇「被視窗捕獲」。" - } - } - } - }, "Magnifier" : { "extractionState" : "manual", "localizations" : { diff --git a/VoidLink/Utility/GenericUtils.swift b/VoidLink/Utility/GenericUtils.swift index 5f900795..00fcc3e2 100644 --- a/VoidLink/Utility/GenericUtils.swift +++ b/VoidLink/Utility/GenericUtils.swift @@ -8,9 +8,49 @@ import Foundation +import GameController import UIKit @objc public class GenericUtils: NSObject { + + @objc static var hardwareKeyboardAlreadyDetected: Bool = false + + @objc static func isHardwareKeyboardConnected() -> Bool { + if #available(iOS 14.0, tvOS 14.0, *) { + hardwareKeyboardAlreadyDetected = GCKeyboard.coalesced != nil + return GCKeyboard.coalesced != nil + } + return false + } + + @objc static func isFirstHardwareKeyboardOrMouseConnection() -> Bool { + let key = "hasConnectedHardwareKeyboardOrMouse" + guard !UserDefaults.standard.bool(forKey: key) else { + return false + } + UserDefaults.standard.set(true, forKey: key) + return true + } + + @objc static func handleKeyboardOrMouseConnectionTip(in vc: UIViewController) { + if isRunningOnMacAsiPadApp { + return + } + if hardwareKeyboardAlreadyDetected { + _ = isFirstHardwareKeyboardOrMouseConnection() + return; + } + else if isFirstHardwareKeyboardOrMouseConnection() { + AlertControllerUtil.showAlert( + in: vc, + title: SwiftLocalizationHelper.localizedString(forKey: "Keyboard/Mouse Connected"), + message: SwiftLocalizationHelper.localizedString(forKey:"keyboard&MouseStreamingTip"), + withCancel: false, + buttonTitle: SwiftLocalizationHelper.localizedString(forKey: "This tip won't be shown again"), + countdown: 11, + completion: {}) + } + } @objc static func needUpdateDefaultSettings() -> Bool { // let key = "needUpdateDefaultSettings20260226-1" diff --git a/VoidLink/ViewControllers/StreamFrameViewController.m b/VoidLink/ViewControllers/StreamFrameViewController.m index 1cae4284..e7eaeb47 100644 --- a/VoidLink/ViewControllers/StreamFrameViewController.m +++ b/VoidLink/ViewControllers/StreamFrameViewController.m @@ -748,6 +748,18 @@ } #endif +- (void)popKeyboardAndMouseStreamingTip { + [AlertControllerUtil showAlertIn:self + title:[LocalizationHelper localizedStringForKey:@"Keyboard/Mouse Connected"] + message:[LocalizationHelper localizedStringForKey:@"keyboard&MouseStreamingTip"] + withCancel:NO + buttonTitle:[LocalizationHelper localizedStringForKey:@"This tip won't be shown again"] + countdown:5 + action:^{} + completion:^{ + }]; +} + - (void)popFirstStreamingTip { // 初始化倒计时秒数 @@ -756,17 +768,19 @@ uint8_t slideDist = (uint8_t)(_settings.slideToSettingsDistance.floatValue * 100); // 创建弹窗 NSString* tipText = (GenericUtils.isRunningOnMacAsiPadApp - ? [LocalizationHelper localizedStringForKey:@"macFirstLaunchTip"] - : [LocalizationHelper localizedStringForKey:@"firstLaunchTip", settingsEdgeSide, slideDist, cmdToolEdgeSide, slideDist]) ; - + ? [LocalizationHelper localizedStringForKey:@"keyboard&MouseStreamingTip"] + : [LocalizationHelper localizedStringForKey:@"firstLaunchTip", settingsEdgeSide, slideDist, cmdToolEdgeSide, slideDist]); + [AlertControllerUtil showAlertIn:self title:[LocalizationHelper localizedStringForKey:@"First Launch Tips"] message:tipText withCancel:NO - buttonTitle:[LocalizationHelper localizedStringForKey:@"Got it!"] + buttonTitle:[LocalizationHelper localizedStringForKey:@"This tip won't be shown again"] countdown:16 action:^{} - completion:^{}]; + completion:^{ + if(!GenericUtils.isRunningOnMacAsiPadApp && GenericUtils.isHardwareKeyboardConnected) [self popKeyboardAndMouseStreamingTip]; + }]; return; } @@ -903,7 +917,7 @@ selector:@selector(gameProfileSelectorClosed) name:@"GameProfileSelectorCloseNotification" object:nil]; - + #if 0 // FIXME: This doesn't work reliably on iPad for some reason. Showing and hiding the keyboard // several times in a row will not correctly restore the state of the UIScrollView. @@ -1819,6 +1833,14 @@ #endif } +- (void)keyboardConnected { + [GenericUtils handleKeyboardOrMouseConnectionTipIn:self]; +} + +- (void)mouseConnected { + [GenericUtils handleKeyboardOrMouseConnectionTipIn:self]; +} + - (void)mousePresenceChanged { #if !TARGET_OS_TV if (@available(iOS 14.0, *)) {