From 48e684335ee7da31b8fdd775b63dea6cb5686e81 Mon Sep 17 00:00:00 2001 From: fufesou Date: Tue, 27 Dec 2022 16:45:13 +0800 Subject: [PATCH 1/3] choose keyboard layout type, mid commit Signed-off-by: fufesou --- flutter/lib/desktop/pages/remote_page.dart | 5 + .../widgets/kb_layout_type_chooser.dart | 227 ++++++++++++++++++ flutter/lib/desktop/widgets/login.dart | 10 +- .../lib/desktop/widgets/remote_menubar.dart | 53 +++- flutter/lib/models/model.dart | 10 + libs/hbb_common/src/config.rs | 10 + src/flutter_ffi.rs | 8 + src/keyboard.rs | 18 +- src/lang/cn.rs | 2 + src/lang/tw.rs | 2 + src/ui_interface.rs | 10 + 11 files changed, 345 insertions(+), 10 deletions(-) create mode 100644 flutter/lib/desktop/widgets/kb_layout_type_chooser.dart diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index dd569a110..5da5c066c 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -23,6 +23,7 @@ import '../../models/model.dart'; import '../../models/platform_model.dart'; import '../../common/shared_state.dart'; import '../widgets/remote_menubar.dart'; +import '../widgets/kb_layout_type_chooser.dart'; bool _isCustomCursorInited = false; final SimpleWrapper _firstEnterImage = SimpleWrapper(false); @@ -95,6 +96,10 @@ class _RemotePageState extends State _initStates(widget.id); _ffi = FFI(); Get.put(_ffi, tag: widget.id); + _ffi.imageModel.addCallbackOnFirstImage((String peerId) { + showKBLayoutTypeChooserIfNeeded( + _ffi.ffiModel.pi.platform, _ffi.dialogManager); + }); _ffi.start(widget.id); WidgetsBinding.instance.addPostFrameCallback((_) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); diff --git a/flutter/lib/desktop/widgets/kb_layout_type_chooser.dart b/flutter/lib/desktop/widgets/kb_layout_type_chooser.dart new file mode 100644 index 000000000..35ab4c81e --- /dev/null +++ b/flutter/lib/desktop/widgets/kb_layout_type_chooser.dart @@ -0,0 +1,227 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_hbb/models/platform_model.dart'; + +import '../../common.dart'; + +typedef KBChoosedCallback = bool Function(String); + +const double _kImageMarginVertical = 6.0; +const double _kImageMarginHorizental = 10.0; +const double _kImageBoarderWidth = 4.0; +const double _kImagePaddingWidth = 4.0; +const Color _kImageBorderColor = Color.fromARGB(125, 202, 247, 2); +const double _kBorderRadius = 6.0; +const String _kKBLayoutTypeISO = 'ISO'; +const String _kKBLayoutTypeNotISO = 'Not ISO'; + +const _kKBLayoutImageMap = { + _kKBLayoutTypeISO: 'KB_LAYOUT_ISO', + _kKBLayoutTypeNotISO: 'KB_LAYOUT_NOT_ISO', +}; + +class _KBImage extends StatelessWidget { + final String kbLayoutType; + final double imageWidth; + final RxString choosedType; + const _KBImage({ + Key? key, + required this.kbLayoutType, + required this.imageWidth, + required this.choosedType, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Obx(() { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(_kBorderRadius), + border: Border.all( + color: choosedType.value == kbLayoutType + ? _kImageBorderColor + : Colors.transparent, + width: _kImageBoarderWidth, + ), + ), + margin: EdgeInsets.symmetric( + horizontal: _kImageMarginHorizental, + vertical: _kImageMarginVertical, + ), + padding: EdgeInsets.all(_kImagePaddingWidth), + child: SvgPicture.asset( + 'assets/${_kKBLayoutImageMap[kbLayoutType] ?? ""}.svg', + width: imageWidth - + _kImageMarginHorizental * 2 - + _kImagePaddingWidth * 2 - + _kImageBoarderWidth * 2, + ), + ); + }); + } +} + +class _KBChooser extends StatelessWidget { + final String kbLayoutType; + final double imageWidth; + final RxString choosedType; + final KBChoosedCallback cb; + const _KBChooser({ + Key? key, + required this.kbLayoutType, + required this.imageWidth, + required this.choosedType, + required this.cb, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + TextButton( + onPressed: () { + choosedType.value = kbLayoutType; + }, + child: _KBImage( + kbLayoutType: kbLayoutType, + imageWidth: imageWidth, + choosedType: choosedType, + ), + style: TextButton.styleFrom(padding: EdgeInsets.zero), + ), + TextButton( + child: Row( + children: [ + Obx(() => Radio( + splashRadius: 0, + value: kbLayoutType, + groupValue: choosedType.value, + onChanged: (String? newValue) { + if (newValue != null) { + if (cb(newValue)) { + choosedType.value = newValue; + } + } + }, + )), + Text(kbLayoutType), + ], + ), + onPressed: () { + if (cb(kbLayoutType)) { + choosedType.value = kbLayoutType; + } + }, + ), + ], + ); + } +} + +class KBLayoutTypeChooser extends StatelessWidget { + final RxString choosedType; + final double width; + final double height; + final double dividerWidth; + final KBChoosedCallback cb; + KBLayoutTypeChooser({ + Key? key, + required this.choosedType, + required this.width, + required this.height, + required this.dividerWidth, + required this.cb, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final imageWidth = width / 2 - dividerWidth; + return Container( + color: Colors.white, + child: SizedBox( + width: width, + height: height, + child: Center( + child: Row( + children: [ + _KBChooser( + kbLayoutType: _kKBLayoutTypeISO, + imageWidth: imageWidth, + choosedType: choosedType, + cb: cb, + ), + VerticalDivider( + width: dividerWidth * 2, + ), + _KBChooser( + kbLayoutType: _kKBLayoutTypeNotISO, + imageWidth: imageWidth, + choosedType: choosedType, + cb: cb, + ), + ], + ), + ), + ), + ); + } +} + +RxString KBLayoutType = ''.obs; + +String getLocalPlatformForKBLayoutType(String peerPlatform) { + String localPlatform = ''; + if (peerPlatform != 'Mac OS') { + return localPlatform; + } + + if (Platform.isWindows) { + localPlatform = 'Windows'; + } else if (Platform.isLinux) { + localPlatform = 'Linux'; + } + // to-do: web desktop support ? + return localPlatform; +} + +showKBLayoutTypeChooserIfNeeded( + String peerPlatform, + OverlayDialogManager dialogManager, +) async { + final localPlatform = getLocalPlatformForKBLayoutType(peerPlatform); + if (localPlatform == '') { + return; + } + KBLayoutType.value = bind.getLocalKbLayoutType(); + if (KBLayoutType.value == _kKBLayoutTypeISO || + KBLayoutType.value == _kKBLayoutTypeNotISO) { + return; + } + showKBLayoutTypeChooser(localPlatform, dialogManager); +} + +showKBLayoutTypeChooser( + String localPlatform, + OverlayDialogManager dialogManager, +) { + dialogManager.show((setState, close) { + return CustomAlertDialog( + title: + Text('${translate('Select local keyboard type')} ($localPlatform)'), + content: KBLayoutTypeChooser( + choosedType: KBLayoutType, + width: 360, + height: 200, + dividerWidth: 4.0, + cb: (String v) { + bind.setLocalKbLayoutType(kbLayoutType: v); + KBLayoutType.value = bind.getLocalKbLayoutType(); + return v == KBLayoutType.value; + }), + actions: [msgBoxButton(translate('Close'), close)], + onCancel: close, + ); + }); +} diff --git a/flutter/lib/desktop/widgets/login.dart b/flutter/lib/desktop/widgets/login.dart index 053653ab3..0736f0864 100644 --- a/flutter/lib/desktop/widgets/login.dart +++ b/flutter/lib/desktop/widgets/login.dart @@ -9,7 +9,7 @@ import 'package:url_launcher/url_launcher.dart'; import '../../common.dart'; -final kMidButtonPadding = const EdgeInsets.fromLTRB(15, 0, 15, 0); +final _kMidButtonPadding = const EdgeInsets.fromLTRB(15, 0, 15, 0); class _IconOP extends StatelessWidget { final String icon; @@ -53,7 +53,7 @@ class ButtonOP extends StatelessWidget { Expanded( child: Container( height: height, - padding: kMidButtonPadding, + padding: _kMidButtonPadding, child: Obx(() => ElevatedButton( style: ElevatedButton.styleFrom( primary: curOP.value.isEmpty || curOP.value == op @@ -315,7 +315,7 @@ class LoginWidgetUserPass extends StatelessWidget { height: 8.0, ), Container( - padding: kMidButtonPadding, + padding: _kMidButtonPadding, child: Row( children: [ ConstrainedBox( @@ -343,7 +343,7 @@ class LoginWidgetUserPass extends StatelessWidget { height: 8.0, ), Container( - padding: kMidButtonPadding, + padding: _kMidButtonPadding, child: Row( children: [ ConstrainedBox( @@ -377,7 +377,7 @@ class LoginWidgetUserPass extends StatelessWidget { Expanded( child: Container( height: 38, - padding: kMidButtonPadding, + padding: _kMidButtonPadding, child: Obx(() => ElevatedButton( style: curOP.value.isEmpty || curOP.value == 'rustdesk' ? null diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index b60fa7128..0cd4ee00f 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -22,6 +22,7 @@ import '../../models/platform_model.dart'; import '../../common/shared_state.dart'; import './popup_menu.dart'; import './material_mod_popup_menu.dart' as mod_menu; +import './kb_layout_type_chooser.dart'; class MenubarState { final kStoreKey = 'remoteMenubarState'; @@ -1187,7 +1188,7 @@ class _RemoteMenubarState extends State { } List> _getKeyboardMenu() { - final keyboardMenu = [ + final List> keyboardMenu = [ MenuEntryRadios( text: translate('Ratio'), optionsGetter: () => [ @@ -1203,7 +1204,55 @@ class _RemoteMenubarState extends State { }, ) ]; - + final localPlatform = + getLocalPlatformForKBLayoutType(widget.ffi.ffiModel.pi.platform); + if (localPlatform != '') { + keyboardMenu.add(MenuEntryDivider()); + keyboardMenu.add( + MenuEntryButton( + childBuilder: (TextStyle? style) => Container( + alignment: AlignmentDirectional.center, + height: _MenubarTheme.height, + child: Row( + children: [ + Obx(() => RichText( + text: TextSpan( + text: '${translate('Local keyboard type')}: ', + style: DefaultTextStyle.of(context).style, + children: [ + TextSpan( + text: KBLayoutType.value, + style: TextStyle(fontWeight: FontWeight.bold), + ), + ], + ), + )), + Expanded( + child: Align( + alignment: Alignment.centerRight, + child: Transform.scale( + scale: 0.8, + child: IconButton( + padding: EdgeInsets.zero, + icon: const Icon(Icons.settings), + onPressed: () { + if (Navigator.canPop(context)) { + Navigator.pop(context); + } + showKBLayoutTypeChooser( + localPlatform, widget.ffi.dialogManager); + }, + ), + ), + )) + ], + )), + proc: () {}, + padding: EdgeInsets.zero, + dismissOnClicked: false, + ), + ); + } return keyboardMenu; } diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 3659e8d58..63062a928 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -381,12 +381,22 @@ class ImageModel with ChangeNotifier { WeakReference parent; + final List _callbacksOnFirstImage = []; + ImageModel(this.parent); + addCallbackOnFirstImage(Function(String) cb) => + _callbacksOnFirstImage.add(cb); + onRgba(Uint8List rgba) { if (_waitForImage[id]!) { _waitForImage[id] = false; parent.target?.dialogManager.dismissAll(); + if (isDesktop) { + for (final cb in _callbacksOnFirstImage) { + cb(id); + } + } } final pid = parent.target?.id; ui.decodeImageFromPixels( diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index e2592bbea..d86ac3463 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -998,6 +998,8 @@ pub struct LocalConfig { #[serde(default)] remote_id: String, // latest used one #[serde(default)] + kb_layout_type: String, + #[serde(default)] size: Size, #[serde(default)] pub fav: Vec, @@ -1017,6 +1019,14 @@ impl LocalConfig { Config::store_(self, "_local"); } + pub fn get_kb_layout_type() -> String { + LOCAL_CONFIG.read().unwrap().kb_layout_type.clone() + } + + pub fn set_kb_layout_type(kb_layout_type: String) { + LOCAL_CONFIG.write().unwrap().kb_layout_type = kb_layout_type + } + pub fn get_size() -> Size { LOCAL_CONFIG.read().unwrap().size } diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index dc9d7a04a..3be0c9fed 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -181,6 +181,14 @@ pub fn set_local_flutter_config(k: String, v: String) { ui_interface::set_local_flutter_config(k, v); } +pub fn get_local_kb_layout_type() -> SyncReturn { + SyncReturn(ui_interface::get_kb_layout_type()) +} + +pub fn set_local_kb_layout_type(kb_layout_type: String) { + ui_interface::set_kb_layout_type(kb_layout_type) +} + pub fn session_get_view_style(id: String) -> Option { if let Some(session) = SESSIONS.read().unwrap().get(&id) { Some(session.get_view_style()) diff --git a/src/keyboard.rs b/src/keyboard.rs index 4b42bdf5d..d22573fbc 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -6,7 +6,7 @@ use crate::flutter::FlutterHandler; #[cfg(not(feature = "flutter"))] use crate::ui::remote::SciterHandler; use crate::ui_session_interface::Session; -use hbb_common::{log, message_proto::*}; +use hbb_common::{log, message_proto::*, config::LocalConfig}; use rdev::{Event, EventType, Key}; #[cfg(any(target_os = "windows", target_os = "macos"))] use std::sync::atomic::{AtomicBool, Ordering}; @@ -620,7 +620,13 @@ pub fn map_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option event.scan_code, - "macos" => rdev::win_scancode_to_macos_code(event.scan_code)?, + "macos" => { + if LocalConfig::get_kb_layout_type() == "ISO" { + rdev::win_scancode_to_macos_iso_code(event.scan_code)? + } else { + rdev::win_scancode_to_macos_code(event.scan_code)? + } + }, _ => rdev::win_scancode_to_linux_code(event.scan_code)?, }; #[cfg(target_os = "macos")] @@ -632,7 +638,13 @@ pub fn map_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option rdev::linux_code_to_win_scancode(event.code as _)?, - "macos" => rdev::linux_code_to_macos_code(event.code as _)?, + "macos" => { + if LocalConfig::get_kb_layout_type() == "ISO" { + rdev::linux_code_to_macos_iso_code(event.scan_code)? + } else { + rdev::linux_code_to_macos_code(event.code as _)? + } + }, _ => event.code as _, }; #[cfg(any(target_os = "android", target_os = "ios"))] diff --git a/src/lang/cn.rs b/src/lang/cn.rs index be0d7803e..5d04268b0 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -407,5 +407,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Group", "小组"), ("Search", "搜索"), ("Closed manually by the web console", "被web控制台手动关闭"), + ("Local keyboard type", "本地键盘类型"), + ("Select local keyboard type", "请选择本地键盘类型"), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 6eef3656c..301384ea3 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -406,5 +406,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Group", "小組"), ("Search", "搜索"), ("Closed manually by the web console", "被web控制台手動關閉"), + ("Local keyboard type", "本地鍵盤類型"), + ("Select local keyboard type", "請選擇本地鍵盤類型"), ].iter().cloned().collect(); } diff --git a/src/ui_interface.rs b/src/ui_interface.rs index 604d2e222..3e4cd681f 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -202,6 +202,16 @@ pub fn set_local_flutter_config(key: String, value: String) { LocalConfig::set_flutter_config(key, value); } +#[inline] +pub fn get_kb_layout_type() -> String { + LocalConfig::get_kb_layout_type() +} + +#[inline] +pub fn set_kb_layout_type(kb_layout_type: String) { + LocalConfig::set_kb_layout_type(kb_layout_type); +} + #[inline] pub fn peer_has_password(id: String) -> bool { !PeerConfig::load(&id).password.is_empty() From ebdead8766b5ace96922c3a39bb57034f0d07c41 Mon Sep 17 00:00:00 2001 From: fufesou Date: Tue, 27 Dec 2022 16:46:56 +0800 Subject: [PATCH 2/3] add svg Signed-off-by: fufesou --- flutter/assets/kb_layout_iso.svg | 1 + flutter/assets/kb_layout_not_iso.svg | 1 + flutter/lib/desktop/widgets/kb_layout_type_chooser.dart | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 flutter/assets/kb_layout_iso.svg create mode 100644 flutter/assets/kb_layout_not_iso.svg diff --git a/flutter/assets/kb_layout_iso.svg b/flutter/assets/kb_layout_iso.svg new file mode 100644 index 000000000..69f0c96cb --- /dev/null +++ b/flutter/assets/kb_layout_iso.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/flutter/assets/kb_layout_not_iso.svg b/flutter/assets/kb_layout_not_iso.svg new file mode 100644 index 000000000..09a055be3 --- /dev/null +++ b/flutter/assets/kb_layout_not_iso.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/flutter/lib/desktop/widgets/kb_layout_type_chooser.dart b/flutter/lib/desktop/widgets/kb_layout_type_chooser.dart index 35ab4c81e..9269d1a60 100644 --- a/flutter/lib/desktop/widgets/kb_layout_type_chooser.dart +++ b/flutter/lib/desktop/widgets/kb_layout_type_chooser.dart @@ -18,8 +18,8 @@ const String _kKBLayoutTypeISO = 'ISO'; const String _kKBLayoutTypeNotISO = 'Not ISO'; const _kKBLayoutImageMap = { - _kKBLayoutTypeISO: 'KB_LAYOUT_ISO', - _kKBLayoutTypeNotISO: 'KB_LAYOUT_NOT_ISO', + _kKBLayoutTypeISO: 'kb_layout_iso', + _kKBLayoutTypeNotISO: 'kb_layout_not_iso', }; class _KBImage extends StatelessWidget { From 50c33450b94de574d9259f44fe69c7eded6a6ef3 Mon Sep 17 00:00:00 2001 From: fufesou Date: Tue, 27 Dec 2022 17:49:32 +0800 Subject: [PATCH 3/3] fix keyboard type store Signed-off-by: fufesou --- .../widgets/kb_layout_type_chooser.dart | 75 +++++++++---------- libs/hbb_common/src/config.rs | 4 +- 2 files changed, 39 insertions(+), 40 deletions(-) diff --git a/flutter/lib/desktop/widgets/kb_layout_type_chooser.dart b/flutter/lib/desktop/widgets/kb_layout_type_chooser.dart index 9269d1a60..6601160a7 100644 --- a/flutter/lib/desktop/widgets/kb_layout_type_chooser.dart +++ b/flutter/lib/desktop/widgets/kb_layout_type_chooser.dart @@ -6,7 +6,7 @@ import 'package:flutter_hbb/models/platform_model.dart'; import '../../common.dart'; -typedef KBChoosedCallback = bool Function(String); +typedef KBChoosedCallback = Future Function(String); const double _kImageMarginVertical = 6.0; const double _kImageMarginHorizental = 10.0; @@ -78,11 +78,19 @@ class _KBChooser extends StatelessWidget { @override Widget build(BuildContext context) { + onChanged(String? v) async { + if (v != null) { + if (await cb(v)) { + choosedType.value = v; + } + } + } + return Column( children: [ TextButton( onPressed: () { - choosedType.value = kbLayoutType; + onChanged(kbLayoutType); }, child: _KBImage( kbLayoutType: kbLayoutType, @@ -98,21 +106,13 @@ class _KBChooser extends StatelessWidget { splashRadius: 0, value: kbLayoutType, groupValue: choosedType.value, - onChanged: (String? newValue) { - if (newValue != null) { - if (cb(newValue)) { - choosedType.value = newValue; - } - } - }, + onChanged: onChanged, )), Text(kbLayoutType), ], ), onPressed: () { - if (cb(kbLayoutType)) { - choosedType.value = kbLayoutType; - } + onChanged(kbLayoutType); }, ), ], @@ -138,31 +138,28 @@ class KBLayoutTypeChooser extends StatelessWidget { @override Widget build(BuildContext context) { final imageWidth = width / 2 - dividerWidth; - return Container( - color: Colors.white, - child: SizedBox( - width: width, - height: height, - child: Center( - child: Row( - children: [ - _KBChooser( - kbLayoutType: _kKBLayoutTypeISO, - imageWidth: imageWidth, - choosedType: choosedType, - cb: cb, - ), - VerticalDivider( - width: dividerWidth * 2, - ), - _KBChooser( - kbLayoutType: _kKBLayoutTypeNotISO, - imageWidth: imageWidth, - choosedType: choosedType, - cb: cb, - ), - ], - ), + return SizedBox( + width: width, + height: height, + child: Center( + child: Row( + children: [ + _KBChooser( + kbLayoutType: _kKBLayoutTypeISO, + imageWidth: imageWidth, + choosedType: choosedType, + cb: cb, + ), + VerticalDivider( + width: dividerWidth * 2, + ), + _KBChooser( + kbLayoutType: _kKBLayoutTypeNotISO, + imageWidth: imageWidth, + choosedType: choosedType, + cb: cb, + ), + ], ), ), ); @@ -215,8 +212,8 @@ showKBLayoutTypeChooser( width: 360, height: 200, dividerWidth: 4.0, - cb: (String v) { - bind.setLocalKbLayoutType(kbLayoutType: v); + cb: (String v) async { + await bind.setLocalKbLayoutType(kbLayoutType: v); KBLayoutType.value = bind.getLocalKbLayoutType(); return v == KBLayoutType.value; }), diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index d86ac3463..4bc33cda5 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -1024,7 +1024,9 @@ impl LocalConfig { } pub fn set_kb_layout_type(kb_layout_type: String) { - LOCAL_CONFIG.write().unwrap().kb_layout_type = kb_layout_type + let mut config = LOCAL_CONFIG.write().unwrap(); + config.kb_layout_type = kb_layout_type; + config.store(); } pub fn get_size() -> Size {