From 9b9b8472f705122871eedd7337c07af03bb6bd20 Mon Sep 17 00:00:00 2001 From: beijixing Date: Tue, 4 Mar 2025 21:48:31 +0800 Subject: [PATCH] game mode ctrl --- flutter/lib/common/widgets/toolbar.dart | 15 ++ .../lib/desktop/widgets/remote_toolbar.dart | 28 +++ flutter/lib/main.dart | 3 + flutter/lib/models/game_mode_state.dart | 41 ++++ src/client.rs | 33 +++- src/game_mode.rs | 176 ++++++++++++++++++ src/ui_session_interface.rs | 12 ++ 7 files changed, 300 insertions(+), 8 deletions(-) create mode 100644 flutter/lib/models/game_mode_state.dart diff --git a/flutter/lib/common/widgets/toolbar.dart b/flutter/lib/common/widgets/toolbar.dart index 153121057e5..5eca3479201 100644 --- a/flutter/lib/common/widgets/toolbar.dart +++ b/flutter/lib/common/widgets/toolbar.dart @@ -11,6 +11,9 @@ import 'package:flutter_hbb/models/model.dart'; import 'package:flutter_hbb/models/platform_model.dart'; import 'package:get/get.dart'; +import '../../models/game_mode_state.dart'; +const String kOptionGameMode = 'game-mode'; + bool isEditOsPassword = false; class TTextMenu { @@ -235,6 +238,18 @@ List toolbarControls(BuildContext context, String id, FFI ffi) { blockInput.value = !blockInput.value; })); } + // gameMode + if (ffi.ffiModel.keyboard && pi.platform == kPeerPlatformWindows) { + v.add(TTextMenu( + child: Obx(() => Text(translate('Game Mode'))), + onPressed: () { + bind.sessionToggleOption( + sessionId: sessionId, value: kOptionGameMode); + gameModeState.gameModeEnabled.value = + bind.sessionGetToggleOptionSync( + sessionId: sessionId, arg: kOptionGameMode); + })); + } // switchSides if (isDesktop && ffiModel.keyboard && diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index d826ea8c6b6..ef2f9a7f502 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -24,6 +24,7 @@ import '../../models/platform_model.dart'; import '../../common/shared_state.dart'; import './popup_menu.dart'; import './kb_layout_type_chooser.dart'; +import 'package:flutter_hbb/models/game_mode_state.dart'; class ToolbarState { late RxBool _pin; @@ -319,6 +320,33 @@ class RemoteMenuEntry { dismissCallback: dismissCallback, ); } + + // 添加游戏模式菜单项 + static MenuEntrySwitch gameMode( + SessionID sessionId, + EdgeInsets padding, { + DismissFunc? dismissFunc, + DismissCallback? dismissCallback, + }) { + return MenuEntrySwitch( + switchType: SwitchType.scheckbox, + text: translate('Game Mode'), + getter: () async { + return gameModeState.gameModeEnabled.value; + }, + setter: (bool v) async { + await bind.sessionToggleOption(sessionId: sessionId, value: kOptionGameMode); + gameModeState.gameModeEnabled.value = bind.sessionGetToggleOptionSync( + sessionId: sessionId, arg: kOptionGameMode); + if (dismissFunc != null) { + dismissFunc(); + } + }, + padding: padding, + dismissOnClicked: true, + dismissCallback: dismissCallback, + ); + } } class RemoteToolbar extends StatefulWidget { diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index b5a0af71144..559ef9d2404 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -26,6 +26,7 @@ import 'consts.dart'; import 'mobile/pages/home_page.dart'; import 'mobile/pages/server_page.dart'; import 'models/platform_model.dart'; +import 'models/game_mode_state.dart'; import 'package:flutter_hbb/plugin/handlers.dart' if (dart.library.html) 'package:flutter_hbb/web/plugin/handlers.dart'; @@ -526,6 +527,8 @@ _registerEventHandler() { platformFFI.registerEventHandler('native_ui', 'native_ui', (evt) async { NativeUiHandler.instance.onEvent(evt); }); + // 初始化游戏模式状态 + gameModeState.init(); } } diff --git a/flutter/lib/models/game_mode_state.dart b/flutter/lib/models/game_mode_state.dart new file mode 100644 index 00000000000..bcb7e1e2a4a --- /dev/null +++ b/flutter/lib/models/game_mode_state.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:flutter_hbb/common.dart'; +import 'package:flutter_hbb/models/platform_model.dart'; + +// 游戏模式状态管理类 +class GameModeState { + // 单例模式 + static final GameModeState instance = GameModeState._internal(); + GameModeState._internal(); + + // 游戏模式是否启用的状态 + final RxBool gameModeEnabled = false.obs; + + // 注册事件处理器,监听游戏模式状态变化 + void registerEventListener() { + // 注册游戏模式状态变化事件 + platformFFI.registerEventHandler('on_game_mode_changed', 'game_mode', (evt) async { + bool enabled = evt['enabled'] ?? false; + gameModeEnabled.value = enabled; + debugPrint('Game mode changed: $enabled'); + + // 显示游戏模式状态提示 + if (enabled) { + showToast('游戏模式已启用'); + } else { + showToast('游戏模式已禁用'); + } + }); + } + + // 初始化游戏模式状态 + void init() { + // 读取当前游戏模式状态 + gameModeEnabled.value = bind.sessionGetOption(id: ''.obs, arg: kOptionGameMode) == 'Y'; + registerEventListener(); + } +} + +// 全局访问点 +final gameModeState = GameModeState.instance; diff --git a/src/client.rs b/src/client.rs index d2ceffd3cca..be84dcda425 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1585,10 +1585,11 @@ impl LoginConfigHandler { self.force_relay = config::option2bool("force-always-relay", &self.get_option("force-always-relay")) || force_relay; - if let Some((real_id, server, key)) = &self.other_server { - let other_server_key = self.get_option("other-server-key"); - if !other_server_key.is_empty() && key.is_empty() { - self.other_server = Some((real_id.to_owned(), server.to_owned(), other_server_key)); + if let Some((real_id, server, key)) = self.other_server.as_ref() { + if server != PUBLIC_SERVER { + self.config + .options + .insert("other-server-key".to_owned(), key.clone()); } } self.direct = None; @@ -1806,6 +1807,21 @@ impl LoginConfigHandler { BoolOption::No }) .into(); + } else if name == "game-mode" { + // 处理游戏模式选项 + let mut game_mode_enabled = self.get_toggle_option("game-mode"); + game_mode_enabled = !game_mode_enabled; + + if game_mode_enabled { + #[cfg(target_os = "windows")] + crate::game_mode::enable(); + } else { + #[cfg(target_os = "windows")] + crate::game_mode::disable(); + } + + // 保存游戏模式状态 + config.options.insert("game-mode".to_owned(), if game_mode_enabled { "Y".to_owned() } else { "N".to_owned() }); } else if name == "privacy-mode" { // try toggle privacy mode option.privacy_mode = (if config.privacy_mode.v { @@ -3085,10 +3101,10 @@ pub async fn handle_hash( interface.msgbox("input-password", "Password Required", "", ""); Vec::new() } else { - let mut hasher = Sha256::new(); - hasher.update(&password); - hasher.update(&hash.challenge); - hasher.finalize()[..].into() + let mut hasher2 = Sha256::new(); + hasher2.update(&password[..]); + hasher2.update(&hash.challenge); + hasher2.finalize()[..].into() }; let os_username = lc.read().unwrap().get_option("os-username"); @@ -3189,6 +3205,7 @@ pub async fn handle_login_from_ui( hash_password = hasher2.finalize()[..].to_vec(); send_login(lc.clone(), os_username, os_password, hash_password, peer).await; + lc.write().unwrap().hash = lc.read().unwrap().hash.clone(); } async fn send_switch_login_request( diff --git a/src/game_mode.rs b/src/game_mode.rs index 6e9ead09466..497d79f50ca 100644 --- a/src/game_mode.rs +++ b/src/game_mode.rs @@ -2,7 +2,10 @@ use crate::common::config::{Config, LocalConfig}; use hbb_common::{ log, message_proto::OptionMessage, + allow_err, }; +#[cfg(feature = "flutter")] +use crate::flutter::{self, flutter_channel, Value, StreamSink}; use std::{ sync::{ atomic::{AtomicU8, Ordering}, @@ -10,11 +13,19 @@ use std::{ }, time::{Duration, Instant}, }; +#[cfg(windows)] +use winapi::{ + shared::{minwindef::*, windef::*}, + um::{winuser::*}, +}; lazy_static::lazy_static! { static ref GAME_MODE_ENABLED: RwLock = RwLock::new(false); static ref LAST_KEY_9_TIME: Mutex = Mutex::new(Instant::now()); static ref KEY_9_COUNT: AtomicU8 = AtomicU8::new(0); + static ref CURSOR_LOCKED: RwLock = RwLock::new(false); + static ref CURSOR_HIDDEN: RwLock = RwLock::new(false); + static ref SCREEN_CENTER: Mutex<(i32, i32)> = Mutex::new((0, 0)); // 屏幕中心坐标 } // 检查是否应该切换游戏模式(连续按三次9键) @@ -52,9 +63,28 @@ pub fn toggle_game_mode() { let mut enabled = GAME_MODE_ENABLED.write().unwrap(); *enabled = !*enabled; + // 根据游戏模式状态切换鼠标锁定和隐藏 + if *enabled { + #[cfg(windows)] + { + lock_cursor_to_screen_center(); + hide_cursor(); + } + } else { + #[cfg(windows)] + { + unlock_cursor(); + show_cursor(); + } + } + // 保存设置到配置文件 let _res = LocalConfig::set_option("game_mode".to_owned(), if *enabled { "Y".to_owned() } else { "".to_owned() }); + // 通知Flutter端游戏模式状态变化 + #[cfg(feature = "flutter")] + notify_game_mode_state(); + log::info!("Game mode {}", if *enabled { "enabled" } else { "disabled" }); } @@ -66,7 +96,29 @@ pub fn is_game_mode_enabled() -> bool { // 设置游戏模式状态 pub fn set_game_mode_enabled(enabled: bool) { let mut game_mode = GAME_MODE_ENABLED.write().unwrap(); + let changed = *game_mode != enabled; *game_mode = enabled; + + // 根据游戏模式状态切换鼠标锁定和隐藏 + if *game_mode { + #[cfg(windows)] + { + lock_cursor_to_screen_center(); + hide_cursor(); + } + } else { + #[cfg(windows)] + { + unlock_cursor(); + show_cursor(); + } + } + + // 通知Flutter端游戏模式状态变化(如果状态有变化) + #[cfg(feature = "flutter")] + if changed { + notify_game_mode_state(); + } } // 从配置加载游戏模式状态 @@ -84,3 +136,127 @@ pub fn add_game_mode_option(options: &mut OptionMessage) { options.game_mode = "".to_owned(); } } + +// Windows平台下锁定鼠标到屏幕中心 +#[cfg(windows)] +pub fn lock_cursor_to_screen_center() { + unsafe { + // 获取主屏幕尺寸 + let screen_width = GetSystemMetrics(SM_CXSCREEN); + let screen_height = GetSystemMetrics(SM_CYSCREEN); + + // 计算屏幕中心坐标 + let center_x = screen_width / 2; + let center_y = screen_height / 2; + + // 保存屏幕中心坐标 + let mut screen_center = SCREEN_CENTER.lock().unwrap(); + *screen_center = (center_x, center_y); + + // 设置鼠标位置到屏幕中心 + SetCursorPos(center_x, center_y); + + // 锁定鼠标范围到屏幕中心附近的小区域 + let rect = RECT { + left: center_x - 1, + top: center_y - 1, + right: center_x + 1, + bottom: center_y + 1, + }; + + // 锁定鼠标 + ClipCursor(&rect); + + // 标记鼠标已锁定 + let mut cursor_locked = CURSOR_LOCKED.write().unwrap(); + *cursor_locked = true; + + log::debug!("Cursor locked to screen center: ({}, {})", center_x, center_y); + } +} + +// Windows平台下解锁鼠标 +#[cfg(windows)] +pub fn unlock_cursor() { + unsafe { + // 解锁鼠标 + ClipCursor(std::ptr::null()); + + // 标记鼠标已解锁 + let mut cursor_locked = CURSOR_LOCKED.write().unwrap(); + *cursor_locked = false; + + log::debug!("Cursor unlocked"); + } +} + +// Windows平台下隐藏鼠标 +#[cfg(windows)] +pub fn hide_cursor() { + unsafe { + // 隐藏鼠标 + ShowCursor(FALSE); + + // 标记鼠标已隐藏 + let mut cursor_hidden = CURSOR_HIDDEN.write().unwrap(); + *cursor_hidden = true; + + log::debug!("Cursor hidden"); + } +} + +// Windows平台下显示鼠标 +#[cfg(windows)] +pub fn show_cursor() { + unsafe { + // 显示鼠标 + ShowCursor(TRUE); + + // 标记鼠标已显示 + let mut cursor_hidden = CURSOR_HIDDEN.write().unwrap(); + *cursor_hidden = false; + + log::debug!("Cursor shown"); + } +} + +// 鼠标是否已锁定 +pub fn is_cursor_locked() -> bool { + *CURSOR_LOCKED.read().unwrap() +} + +// 鼠标是否已隐藏 +pub fn is_cursor_hidden() -> bool { + *CURSOR_HIDDEN.read().unwrap() +} + +// 处理相对鼠标移动(游戏模式下使用相对移动而不是绝对位置) +#[cfg(windows)] +pub fn handle_relative_mouse_move(dx: i32, dy: i32) -> (i32, i32) { + if !is_game_mode_enabled() || !is_cursor_locked() { + return (dx, dy); + } + + unsafe { + // 这里需要返回相对移动值 + // 在游戏模式下,我们使用相对鼠标移动值,而不是绝对位置 + // 这样我们可以捕获鼠标移动,同时保持鼠标居中 + + // 每次移动后重置鼠标到屏幕中心 + let center = *SCREEN_CENTER.lock().unwrap(); + SetCursorPos(center.0, center.1); + + // 返回相对移动值 + (dx, dy) + } +} + +// 添加一个用于通知Flutter端游戏模式状态变化的FFI函数 +#[cfg(feature = "flutter")] +pub fn notify_game_mode_state() { + if let Some(s) = flutter_channel() { + let enabled = is_game_mode_enabled(); + let v = vec![Value::Bool(enabled)]; + allow_err!(s.send(&mut StreamSink::new(), "on_game_mode_changed", v)); + } +} diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 7cb00523398..531e2cdba85 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -1122,6 +1122,18 @@ impl Session { } } + // 处理游戏模式下的鼠标移动 + #[cfg(windows)] + let (x, y) = if crate::game_mode::is_game_mode_enabled() && mask == MOUSE_TYPE_MOVE { + // 在游戏模式下使用相对鼠标移动 + crate::game_mode::handle_relative_mouse_move(x, y) + } else if mask == MOUSE_TYPE_WHEEL || mask == MOUSE_TYPE_TRACKPAD { + self.get_scroll_xy((x, y)) + } else { + (x, y) + }; + + #[cfg(not(windows))] let (x, y) = if mask == MOUSE_TYPE_WHEEL || mask == MOUSE_TYPE_TRACKPAD { self.get_scroll_xy((x, y)) } else {