mirror of
https://github.com/rustdesk/rustdesk.git
synced 2024-11-24 04:12:20 +08:00
choose keyboard layout type, mid commit
Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
parent
5b3b7bd3c0
commit
48e684335e
@ -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<bool> _firstEnterImage = SimpleWrapper(false);
|
||||
@ -95,6 +96,10 @@ class _RemotePageState extends State<RemotePage>
|
||||
_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: []);
|
||||
|
227
flutter/lib/desktop/widgets/kb_layout_type_chooser.dart
Normal file
227
flutter/lib/desktop/widgets/kb_layout_type_chooser.dart
Normal file
@ -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,
|
||||
);
|
||||
});
|
||||
}
|
@ -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
|
||||
|
@ -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<RemoteMenubar> {
|
||||
}
|
||||
|
||||
List<MenuEntryBase<String>> _getKeyboardMenu() {
|
||||
final keyboardMenu = [
|
||||
final List<MenuEntryBase<String>> keyboardMenu = [
|
||||
MenuEntryRadios<String>(
|
||||
text: translate('Ratio'),
|
||||
optionsGetter: () => [
|
||||
@ -1203,7 +1204,55 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
)
|
||||
];
|
||||
|
||||
final localPlatform =
|
||||
getLocalPlatformForKBLayoutType(widget.ffi.ffiModel.pi.platform);
|
||||
if (localPlatform != '') {
|
||||
keyboardMenu.add(MenuEntryDivider());
|
||||
keyboardMenu.add(
|
||||
MenuEntryButton<String>(
|
||||
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>[
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -381,12 +381,22 @@ class ImageModel with ChangeNotifier {
|
||||
|
||||
WeakReference<FFI> parent;
|
||||
|
||||
final List<Function(String)> _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(
|
||||
|
@ -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<String>,
|
||||
@ -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
|
||||
}
|
||||
|
@ -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<String> {
|
||||
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<String> {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
Some(session.get_view_style())
|
||||
|
@ -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<KeyEv
|
||||
#[cfg(target_os = "windows")]
|
||||
let keycode = match peer.as_str() {
|
||||
"windows" => 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<KeyEv
|
||||
#[cfg(target_os = "linux")]
|
||||
let keycode = match peer.as_str() {
|
||||
"windows" => 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"))]
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user