diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index fbeb2d469..6545f8556 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -1193,10 +1193,25 @@ class _RemoteMenubarState extends State { final List> keyboardMenu = [ MenuEntryRadios( text: translate('Ratio'), - optionsGetter: () => [ - MenuEntryRadioOption(text: translate('Legacy mode'), value: 'legacy'), - MenuEntryRadioOption(text: translate('Map mode'), value: 'map'), - ], + optionsGetter: () { + List list = []; + List modes = ["legacy"]; + + if (bind.sessionIsKeyboardModeSupported(id: widget.id, mode: "map")) { + modes.add("map"); + } + + for (String mode in modes) { + if (mode == "legacy") { + list.add(MenuEntryRadioOption( + text: translate('Legacy mode'), value: 'legacy')); + } else if (mode == "map") { + list.add(MenuEntryRadioOption( + text: translate('Map mode'), value: 'map')); + } + } + return list; + }, curOptionGetter: () async { return await bind.sessionGetKeyboardMode(id: widget.id) ?? "legacy"; }, diff --git a/libs/hbb_common/src/keyboard.rs b/libs/hbb_common/src/keyboard.rs new file mode 100644 index 000000000..10979f520 --- /dev/null +++ b/libs/hbb_common/src/keyboard.rs @@ -0,0 +1,39 @@ +use std::{fmt, slice::Iter, str::FromStr}; + +use crate::protos::message::KeyboardMode; + +impl fmt::Display for KeyboardMode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + KeyboardMode::Legacy => write!(f, "legacy"), + KeyboardMode::Map => write!(f, "map"), + KeyboardMode::Translate => write!(f, "translate"), + KeyboardMode::Auto => write!(f, "auto"), + } + } +} + +impl FromStr for KeyboardMode { + type Err = (); + fn from_str(s: &str) -> Result { + match s { + "legacy" => Ok(KeyboardMode::Legacy), + "map" => Ok(KeyboardMode::Map), + "translate" => Ok(KeyboardMode::Translate), + "auto" => Ok(KeyboardMode::Auto), + _ => Err(()), + } + } +} + +impl KeyboardMode { + pub fn iter() -> Iter<'static, KeyboardMode> { + static KEYBOARD_MODES: [KeyboardMode; 4] = [ + KeyboardMode::Legacy, + KeyboardMode::Map, + KeyboardMode::Translate, + KeyboardMode::Auto, + ]; + KEYBOARD_MODES.iter() + } +} diff --git a/libs/hbb_common/src/lib.rs b/libs/hbb_common/src/lib.rs index 85e0100d9..49be934fb 100644 --- a/libs/hbb_common/src/lib.rs +++ b/libs/hbb_common/src/lib.rs @@ -40,6 +40,7 @@ pub use tokio_socks::TargetAddr; pub mod password_security; pub use chrono; pub use directories_next; +pub mod keyboard; #[cfg(feature = "quic")] pub type Stream = quic::Connection; diff --git a/src/client.rs b/src/client.rs index cd05586fc..43ee5bf07 100644 --- a/src/client.rs +++ b/src/client.rs @@ -7,6 +7,7 @@ use cpal::{ use magnum_opus::{Channels::*, Decoder as AudioDecoder}; use sha2::{Digest, Sha256}; use std::{ + str::FromStr, collections::HashMap, net::SocketAddr, ops::{Deref, Not}, @@ -23,7 +24,7 @@ use hbb_common::{ Config, PeerConfig, PeerInfoSerde, CONNECT_TIMEOUT, READ_TIMEOUT, RELAY_PORT, RENDEZVOUS_TIMEOUT, }, - log, + get_version_number, log, message_proto::{option_message::BoolOption, *}, protobuf::Message as _, rand, @@ -47,7 +48,10 @@ pub use super::lang::*; pub mod file_trait; pub mod helper; pub mod io_loop; -use crate::server::video_service::{SCRAP_X11_REF_URL, SCRAP_X11_REQUIRED}; +use crate::{ + common::{self, is_keyboard_mode_supported}, + server::video_service::{SCRAP_X11_REF_URL, SCRAP_X11_REQUIRED}, +}; pub static SERVER_KEYBOARD_ENABLED: AtomicBool = AtomicBool::new(true); pub static SERVER_FILE_TRANSFER_ENABLED: AtomicBool = AtomicBool::new(true); pub static SERVER_CLIPBOARD_ENABLED: AtomicBool = AtomicBool::new(true); @@ -1392,12 +1396,18 @@ impl LoginConfigHandler { log::debug!("remove password of {}", self.id); } } - if config.keyboard_mode == "" { - if hbb_common::get_version_number(&pi.version) < hbb_common::get_version_number("1.2.0") - { - config.keyboard_mode = "legacy".to_string(); + if config.keyboard_mode.is_empty() { + if is_keyboard_mode_supported(&KeyboardMode::Map, get_version_number(&pi.version)) { + config.keyboard_mode = KeyboardMode::Map.to_string(); } else { - config.keyboard_mode = "map".to_string(); + config.keyboard_mode = KeyboardMode::Legacy.to_string(); + } + } else { + let keyboard_modes = + common::get_supported_keyboard_modes(get_version_number(&pi.version)); + let current_mode = &KeyboardMode::from_str(&config.keyboard_mode).unwrap_or_default(); + if !keyboard_modes.contains(current_mode) { + config.keyboard_mode = KeyboardMode::Legacy.to_string(); } } self.conn_id = pi.conn_id; diff --git a/src/common.rs b/src/common.rs index 163937479..cdf57ae3d 100644 --- a/src/common.rs +++ b/src/common.rs @@ -658,6 +658,22 @@ pub fn make_privacy_mode_msg(state: back_notification::PrivacyModeState) -> Mess msg_out } +pub fn is_keyboard_mode_supported(keyboard_mode: &KeyboardMode, version_number: i64) -> bool { + match keyboard_mode { + KeyboardMode::Legacy => true, + KeyboardMode::Map => version_number >= hbb_common::get_version_number("1.2.0"), + KeyboardMode::Translate => false, + KeyboardMode::Auto => false, + } +} + +pub fn get_supported_keyboard_modes(version: i64) -> Vec { + KeyboardMode::iter() + .filter(|&mode| is_keyboard_mode_supported(mode, version)) + .map(|&mode| mode) + .collect::>() +} + #[cfg(not(target_os = "linux"))] lazy_static::lazy_static! { pub static ref IS_X11: bool = false; diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index b30e1e108..594fe7767 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -7,11 +7,14 @@ use std::{ use flutter_rust_bridge::{StreamSink, SyncReturn, ZeroCopyBuffer}; use serde_json::json; +use crate::common::is_keyboard_mode_supported; +use hbb_common::message_proto::KeyboardMode; use hbb_common::ResultType; use hbb_common::{ config::{self, LocalConfig, PeerConfig, ONLINE}, fs, log, }; +use std::str::FromStr; // use crate::hbbs_http::account::AuthResult; @@ -254,6 +257,21 @@ pub fn session_get_custom_image_quality(id: String) -> Option> { } } +pub fn session_is_keyboard_mode_supported(id: String, mode: String) -> SyncReturn { + if let Some(session) = SESSIONS.read().unwrap().get(&id) { + if let Ok(mode) = KeyboardMode::from_str(&mode[..]) { + SyncReturn(is_keyboard_mode_supported( + &mode, + session.get_peer_version(), + )) + } else { + SyncReturn(false) + } + } else { + SyncReturn(false) + } +} + pub fn session_set_custom_image_quality(id: String, value: i32) { if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) { session.save_custom_image_quality(value); diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 7bacb9b21..46bf39b78 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -4,7 +4,7 @@ use crate::client::{ input_os_password, load_config, send_mouse, start_video_audio_threads, FileManager, Key, LoginConfigHandler, QualityStatus, KEY_MAP, }; -use crate::common::GrabState; +use crate::common::{self, is_keyboard_mode_supported, GrabState}; use crate::keyboard; use crate::{client::Data, client::Interface}; use async_trait::async_trait; @@ -74,6 +74,10 @@ impl Session { self.lc.read().unwrap().custom_image_quality.clone() } + pub fn get_peer_version(&self) -> i64 { + self.lc.read().unwrap().version.clone() + } + pub fn get_keyboard_mode(&self) -> String { self.lc.read().unwrap().keyboard_mode.clone() } @@ -225,6 +229,11 @@ impl Session { crate::platform::is_xfce() } + pub fn get_supported_keyboard_modes(&self) -> Vec { + let version = self.get_peer_version(); + common::get_supported_keyboard_modes(version) + } + pub fn remove_port_forward(&self, port: i32) { let mut config = self.load_config(); config.port_forwards = config