From e3a5218eb18a3430577e87627223e3790bcdf9d5 Mon Sep 17 00:00:00 2001 From: csf Date: Fri, 16 Sep 2022 19:41:04 +0800 Subject: [PATCH 1/6] global HW_CODEC_CONFIG --- libs/hbb_common/src/config.rs | 11 +++++++++++ libs/scrap/src/common/hwcodec.rs | 5 +++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index 9354b4079..5fc974462 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -50,6 +50,7 @@ lazy_static::lazy_static! { pub static ref PROD_RENDEZVOUS_SERVER: Arc> = Default::default(); pub static ref APP_NAME: Arc> = Arc::new(RwLock::new("RustDesk".to_owned())); static ref KEY_PAIR: Arc, Vec)>>> = Default::default(); + static ref HW_CODEC_CONFIG: Arc> = Arc::new(RwLock::new(HwCodecConfig::load())); } // #[cfg(any(target_os = "android", target_os = "ios"))] @@ -1023,6 +1024,16 @@ impl HwCodecConfig { pub fn remove() { std::fs::remove_file(Config::file_("_hwcodec")).ok(); } + + /// refresh current global HW_CODEC_CONFIG, usually uesd after HwCodecConfig::remove() + pub fn refresh() { + *HW_CODEC_CONFIG.write().unwrap() = HwCodecConfig::load(); + log::debug!("HW_CODEC_CONFIG refreshed successfully"); + } + + pub fn get() -> HwCodecConfig { + return HW_CODEC_CONFIG.read().unwrap().clone(); + } } #[cfg(test)] diff --git a/libs/scrap/src/common/hwcodec.rs b/libs/scrap/src/common/hwcodec.rs index ee81627d8..4fdef5462 100644 --- a/libs/scrap/src/common/hwcodec.rs +++ b/libs/scrap/src/common/hwcodec.rs @@ -267,7 +267,7 @@ impl HwDecoderImage<'_> { } fn get_config(k: &str) -> ResultType { - let v = HwCodecConfig::load() + let v = HwCodecConfig::get() .options .get(k) .unwrap_or(&"".to_owned()) @@ -323,7 +323,8 @@ pub fn check_config_process(force_reset: bool) { std::process::Command::new(exe) .arg("--check-hwcodec-config") .status() - .ok() + .ok(); + HwCodecConfig::refresh(); }); }; } From c6e1e84c72524904519b7b64653b3ddf359b310d Mon Sep 17 00:00:00 2001 From: csf Date: Fri, 16 Sep 2022 19:43:28 +0800 Subject: [PATCH 2/6] flutter desktop Codec Preference --- .../desktop/pages/desktop_setting_page.dart | 17 +-- .../lib/desktop/widgets/remote_menubar.dart | 138 ++++++++++++------ src/flutter_ffi.rs | 20 ++- src/ui/remote.rs | 28 +--- src/ui_session_interface.rs | 33 ++++- 5 files changed, 152 insertions(+), 84 deletions(-) diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index effc26b39..0693554d9 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -219,17 +219,12 @@ class _GeneralState extends State<_General> { } Widget hwcodec() { - return _futureBuilder( - future: bind.mainHasHwcodec(), - hasData: (data) { - return Offstage( - offstage: !(data as bool), - child: _Card(title: 'Hardware Codec', children: [ - _OptionCheckBox( - context, 'Enable hardware codec', 'enable-hwcodec'), - ]), - ); - }); + return Offstage( + offstage: !bind.mainHasHwcodec(), + child: _Card(title: 'Hardware Codec', children: [ + _OptionCheckBox(context, 'Enable hardware codec', 'enable-hwcodec'), + ]), + ); } Widget audio(BuildContext context) { diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index ef20352dc..092ea7da8 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:io'; import 'dart:math' as math; @@ -298,25 +299,35 @@ class _RemoteMenubarState extends State { } Widget _buildDisplay(BuildContext context) { - return mod_menu.PopupMenuButton( - padding: EdgeInsets.zero, - icon: const Icon( - Icons.tv, - color: _MenubarTheme.commonColor, - ), - tooltip: translate('Display Settings'), - position: mod_menu.PopupMenuPosition.under, - itemBuilder: (BuildContext context) => _getDisplayMenu() - .map((entry) => entry.build( - context, - const MenuConfig( - commonColor: _MenubarTheme.commonColor, - height: _MenubarTheme.height, - dividerHeight: _MenubarTheme.dividerHeight, - ))) - .expand((i) => i) - .toList(), - ); + return FutureBuilder(future: () async { + final supportedHwcodec = + await bind.sessionSupportedHwcodec(id: widget.id); + return {'supportedHwcodec': supportedHwcodec}; + }(), builder: (context, snapshot) { + if (snapshot.hasData) { + return mod_menu.PopupMenuButton( + padding: EdgeInsets.zero, + icon: const Icon( + Icons.tv, + color: _MenubarTheme.commonColor, + ), + tooltip: translate('Display Settings'), + position: mod_menu.PopupMenuPosition.under, + itemBuilder: (BuildContext context) => _getDisplayMenu(snapshot.data!) + .map((entry) => entry.build( + context, + const MenuConfig( + commonColor: _MenubarTheme.commonColor, + height: _MenubarTheme.height, + dividerHeight: _MenubarTheme.dividerHeight, + ))) + .expand((i) => i) + .toList(), + ); + } else { + return const Offstage(); + } + }); } Widget _buildKeyboard(BuildContext context) { @@ -532,7 +543,7 @@ class _RemoteMenubarState extends State { return displayMenu; } - List> _getDisplayMenu() { + List> _getDisplayMenu(dynamic futureData) { final displayMenu = [ MenuEntryRadios( text: translate('Ratio'), @@ -653,33 +664,74 @@ class _RemoteMenubarState extends State { } }), MenuEntryDivider(), - // {show_codec ?
- // MenuEntryDivider(), - () { - final state = ShowRemoteCursorState.find(widget.id); - return MenuEntrySwitch2( - text: translate('Show remote cursor'), - getter: () { - return state; + ]; + + /// Show Codec Preference + if (bind.mainHasHwcodec()) { + final List codecs = []; + try { + final Map codecsJson = jsonDecode(futureData['supportedHwcodec']); + final h264 = codecsJson['h264'] ?? false; + final h265 = codecsJson['h265'] ?? false; + codecs.add(h264); + codecs.add(h265); + } finally {} + if (codecs.length == 2 && (codecs[0] || codecs[1])) { + displayMenu.add(MenuEntryRadios( + text: translate('Codec Preference'), + optionsGetter: () { + final list = [ + MenuEntryRadioOption(text: translate('Auto'), value: 'auto'), + MenuEntryRadioOption(text: 'VP9', value: 'vp9'), + ]; + if (codecs[0]) { + list.add(MenuEntryRadioOption(text: 'H264', value: 'h264')); + } + if (codecs[1]) { + list.add(MenuEntryRadioOption(text: 'H265', value: 'h265')); + } + return list; }, - setter: (bool v) async { - state.value = v; - await bind.sessionToggleOption( - id: widget.id, value: 'show-remote-cursor'); - }); - }(), - MenuEntrySwitch( - text: translate('Show quality monitor'), - getter: () async { - return bind.sessionGetToggleOptionSync( - id: widget.id, arg: 'show-quality-monitor'); + curOptionGetter: () async { + return await bind.sessionGetOption( + id: widget.id, arg: 'codec-preference') ?? + 'auto'; + }, + optionSetter: (String oldValue, String newValue) async { + await bind.sessionPeerOption( + id: widget.id, name: "codec-preference", value: newValue); + bind.sessionChangePreferCodec(id: widget.id); + })); + } + } + + /// Show remote cursor + displayMenu.add(() { + final state = ShowRemoteCursorState.find(widget.id); + return MenuEntrySwitch2( + text: translate('Show remote cursor'), + getter: () { + return state; }, setter: (bool v) async { + state.value = v; await bind.sessionToggleOption( - id: widget.id, value: 'show-quality-monitor'); - widget.ffi.qualityMonitorModel.checkShowQualityMonitor(widget.id); - }), - ]; + id: widget.id, value: 'show-remote-cursor'); + }); + }()); + + /// Show quality monitor + displayMenu.add(MenuEntrySwitch( + text: translate('Show quality monitor'), + getter: () async { + return bind.sessionGetToggleOptionSync( + id: widget.id, arg: 'show-quality-monitor'); + }, + setter: (bool v) async { + await bind.sessionToggleOption( + id: widget.id, value: 'show-quality-monitor'); + widget.ffi.qualityMonitorModel.checkShowQualityMonitor(widget.id); + })); final perms = widget.ffi.ffiModel.permissions; final pi = widget.ffi.ffiModel.pi; diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index e1a806bf4..d68d030be 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -754,8 +754,8 @@ pub fn main_remove_peer(id: String) { PeerConfig::remove(&id); } -pub fn main_has_hwcodec() -> bool { - has_hwcodec() +pub fn main_has_hwcodec() -> SyncReturn { + SyncReturn(has_hwcodec()) } pub fn session_send_mouse(id: String, msg: String) { @@ -816,6 +816,22 @@ pub fn session_send_note(id: String, note: String) { } } +pub fn session_supported_hwcodec(id: String) -> String { + if let Some(session) = SESSIONS.read().unwrap().get(&id) { + let (h264, h265) = session.supported_hwcodec(); + let msg = HashMap::from([("h264", h264), ("h265", h265)]); + serde_json::ser::to_string(&msg).unwrap_or("".to_owned()) + } else { + String::new() + } +} + +pub fn session_change_prefer_codec(id: String) { + if let Some(session) = SESSIONS.read().unwrap().get(&id) { + session.change_prefer_codec(); + } +} + pub fn main_set_home_dir(home: String) { *config::APP_HOME_DIR.write().unwrap() = home; } diff --git a/src/ui/remote.rs b/src/ui/remote.rs index 76e1ee96c..97dfe1d02 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -449,27 +449,11 @@ impl SciterSession { } fn supported_hwcodec(&self) -> Value { - #[cfg(feature = "hwcodec")] - { - let mut v = Value::array(0); - let decoder = scrap::codec::Decoder::video_codec_state(&self.id); - let mut h264 = decoder.score_h264 > 0; - let mut h265 = decoder.score_h265 > 0; - if let Some((encoding_264, encoding_265)) = self.lc.read().unwrap().supported_encoding { - h264 = h264 && encoding_264; - h265 = h265 && encoding_265; - } - v.push(h264); - v.push(h265); - v - } - #[cfg(not(feature = "hwcodec"))] - { - let mut v = Value::array(0); - v.push(false); - v.push(false); - v - } + let (h264, h265) = self.0.supported_hwcodec(); + let mut v = Value::array(0); + v.push(h264); + v.push(h265); + v } fn save_size(&mut self, x: i32, y: i32, w: i32, h: i32) { @@ -721,4 +705,4 @@ pub fn make_fd(id: i32, entries: &Vec, only_count: bool) -> Value { m.set_item("num_entries", entries.len() as i32); m.set_item("total_size", n as f64); m -} \ No newline at end of file +} diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 692b592d4..c6254b719 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -11,9 +11,9 @@ use async_trait::async_trait; use hbb_common::config::{Config, LocalConfig, PeerConfig}; use hbb_common::rendezvous_proto::ConnType; use hbb_common::tokio::{self, sync::mpsc}; -use rdev::{Event, EventType::*, Key as RdevKey, KeyboardState}; #[cfg(not(any(target_os = "android", target_os = "ios")))] use rdev::Keyboard as RdevKeyboard; +use rdev::{Event, EventType::*, Key as RdevKey, KeyboardState}; use hbb_common::{allow_err, message_proto::*}; use hbb_common::{fs, get_version_number, log, Stream}; @@ -143,6 +143,25 @@ impl Session { return true; } + pub fn supported_hwcodec(&self) -> (bool, bool) { + #[cfg(feature = "hwcodec")] + { + let decoder = scrap::codec::Decoder::video_codec_state(&self.id); + let mut h264 = decoder.score_h264 > 0; + let mut h265 = decoder.score_h265 > 0; + if let Some((encoding_264, encoding_265)) = self.lc.read().unwrap().supported_encoding { + h264 = h264 && encoding_264; + h265 = h265 && encoding_265; + } + return (h264, h265); + } + #[cfg(feature = "mediacodec")] + { + todo!(); + } + (false, false) + } + pub fn change_prefer_codec(&self) { let msg = self.lc.write().unwrap().change_prefer_codec(); self.send(Data::Message(msg)); @@ -658,18 +677,20 @@ impl Session { } self.map_keyboard_mode(down_or_up, key, Some(evt)); } - KeyboardMode::Legacy => { + KeyboardMode::Legacy => + { #[cfg(not(any(target_os = "android", target_os = "ios")))] self.legacy_keyboard_mode(down_or_up, key, evt) - }, + } KeyboardMode::Translate => { #[cfg(not(any(target_os = "android", target_os = "ios")))] self.translate_keyboard_mode(down_or_up, key, evt); } - _ => { + _ => + { #[cfg(not(any(target_os = "android", target_os = "ios")))] self.legacy_keyboard_mode(down_or_up, key, evt) - }, + } } } @@ -1178,7 +1199,7 @@ impl Session { if self.is_port_forward() || self.is_file_transfer() { return; } - if !KEYBOARD_HOOKED.load(Ordering::SeqCst){ + if !KEYBOARD_HOOKED.load(Ordering::SeqCst) { return; } log::info!("keyboard hooked"); From 6f92edca5c44ebdd74a6b1c94bb4cda4ec99be91 Mon Sep 17 00:00:00 2001 From: csf Date: Fri, 16 Sep 2022 20:31:01 +0800 Subject: [PATCH 3/6] feat: Android Codec Preference --- flutter/lib/mobile/pages/remote_page.dart | 100 ++++++++++++++++------ src/ui_interface.rs | 4 +- src/ui_session_interface.rs | 13 +-- 3 files changed, 76 insertions(+), 41 deletions(-) diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index d9943a62d..94f584109 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:ui' as ui; import 'package:flutter/gestures.dart'; @@ -1011,17 +1012,22 @@ class QualityMonitor extends StatelessWidget { void showOptions(String id, OverlayDialogManager dialogManager) async { String quality = await bind.sessionGetImageQuality(id: id) ?? 'balanced'; if (quality == '') quality = 'balanced'; + String codec = + await bind.sessionGetOption(id: id, arg: 'codec-preference') ?? 'auto'; + if (codec == '') codec = 'auto'; String viewStyle = await bind.sessionGetOption(id: id, arg: 'view-style') ?? ''; + var displays = []; final pi = gFFI.ffiModel.pi; final image = gFFI.ffiModel.getConnectionImage(); - if (image != null) + if (image != null) { displays.add(Padding(padding: const EdgeInsets.only(top: 8), child: image)); + } if (pi.displays.length > 1) { final cur = pi.currentDisplay; final children = []; - for (var i = 0; i < pi.displays.length; ++i) + for (var i = 0; i < pi.displays.length; ++i) { children.add(InkWell( onTap: () { if (i == cur) return; @@ -1038,6 +1044,7 @@ void showOptions(String id, OverlayDialogManager dialogManager) async { child: Text((i + 1).toString(), style: TextStyle( color: i == cur ? Colors.white : Colors.black87)))))); + } displays.add(Padding( padding: const EdgeInsets.only(top: 8), child: Wrap( @@ -1047,9 +1054,21 @@ void showOptions(String id, OverlayDialogManager dialogManager) async { ))); } if (displays.isNotEmpty) { - displays.add(Divider(color: MyTheme.border)); + displays.add(const Divider(color: MyTheme.border)); } final perms = gFFI.ffiModel.permissions; + final hasHwcodec = bind.mainHasHwcodec(); + final List codecs = []; + if (hasHwcodec) { + try { + final Map codecsJson = + jsonDecode(await bind.sessionSupportedHwcodec(id: id)); + final h264 = codecsJson['h264'] ?? false; + final h265 = codecsJson['h265'] ?? false; + codecs.add(h264); + codecs.add(h265); + } finally {} + } dialogManager.show((setState, close) { final more = []; @@ -1057,50 +1076,77 @@ void showOptions(String id, OverlayDialogManager dialogManager) async { more.add(getToggle(id, setState, 'disable-audio', 'Mute')); } if (perms['keyboard'] != false) { - if (perms['clipboard'] != false) + if (perms['clipboard'] != false) { more.add( getToggle(id, setState, 'disable-clipboard', 'Disable clipboard')); + } more.add(getToggle( id, setState, 'lock-after-session-end', 'Lock after session end')); if (pi.platform == 'Windows') { more.add(getToggle(id, setState, 'privacy-mode', 'Privacy mode')); } } - var setQuality = (String? value) { + setQuality(String? value) { if (value == null) return; setState(() { quality = value; bind.sessionSetImageQuality(id: id, value: value); }); - }; - var setViewStyle = (String? value) { + } + + setViewStyle(String? value) { if (value == null) return; setState(() { viewStyle = value; - bind.sessionPeerOption(id: id, name: "view-style", value: value); - gFFI.canvasModel.updateViewStyle(); + bind + .sessionPeerOption(id: id, name: "view-style", value: value) + .then((_) => gFFI.canvasModel.updateViewStyle()); }); - }; + } + + setCodec(String? value) { + if (value == null) return; + setState(() { + codec = value; + bind + .sessionPeerOption(id: id, name: "codec-preference", value: value) + .then((_) => bind.sessionChangePreferCodec(id: id)); + }); + } + + final radios = [ + getRadio('Scale original', 'original', viewStyle, setViewStyle), + getRadio('Scale adaptive', 'adaptive', viewStyle, setViewStyle), + const Divider(color: MyTheme.border), + getRadio('Good image quality', 'best', quality, setQuality), + getRadio('Balanced', 'balanced', quality, setQuality), + getRadio('Optimize reaction time', 'low', quality, setQuality), + const Divider(color: MyTheme.border) + ]; + + if (hasHwcodec && codecs.length == 2 && (codecs[0] || codecs[1])) { + radios.addAll([ + getRadio(translate('Auto'), 'auto', codec, setCodec), + getRadio('VP9', 'vp9', codec, setCodec), + ]); + if (codecs[0]) { + radios.add(getRadio('H264', 'h264', codec, setCodec)); + } + if (codecs[1]) { + radios.add(getRadio('H265', 'h265', codec, setCodec)); + } + radios.add(const Divider(color: MyTheme.border)); + } + + final toggles = [ + getToggle(id, setState, 'show-remote-cursor', 'Show remote cursor'), + getToggle(id, setState, 'show-quality-monitor', 'Show quality monitor'), + ]; + return CustomAlertDialog( - title: SizedBox.shrink(), content: Column( mainAxisSize: MainAxisSize.min, - children: displays + - [ - getRadio('Scale Original', 'original', viewStyle, setViewStyle), - getRadio('Scale adaptive', 'adaptive', viewStyle, setViewStyle), - Divider(color: MyTheme.border), - getRadio('Good image quality', 'best', quality, setQuality), - getRadio('Balanced', 'balanced', quality, setQuality), - getRadio('Optimize reaction time', 'low', quality, setQuality), - Divider(color: MyTheme.border), - getToggle( - id, setState, 'show-remote-cursor', 'Show remote cursor'), - getToggle(id, setState, 'show-quality-monitor', - 'Show quality monitor'), - ] + - more), - actions: [], + children: displays + radios + toggles + more), contentPadding: 0, ); }, clickMaskDismiss: true, backDismiss: true); diff --git a/src/ui_interface.rs b/src/ui_interface.rs index 90afb8c43..fb2ab7472 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -744,9 +744,9 @@ pub fn get_api_server() -> String { #[inline] pub fn has_hwcodec() -> bool { - #[cfg(not(feature = "hwcodec"))] + #[cfg(not(any(feature = "hwcodec", feature = "mediacodec")))] return false; - #[cfg(feature = "hwcodec")] + #[cfg(any(feature = "hwcodec", feature = "mediacodec"))] return true; } diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index c6254b719..2feceb8fe 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -136,15 +136,8 @@ impl Session { true } - pub fn has_hwcodec(&self) -> bool { - #[cfg(not(feature = "hwcodec"))] - return false; - #[cfg(feature = "hwcodec")] - return true; - } - pub fn supported_hwcodec(&self) -> (bool, bool) { - #[cfg(feature = "hwcodec")] + #[cfg(any(feature = "hwcodec", feature = "mediacodec"))] { let decoder = scrap::codec::Decoder::video_codec_state(&self.id); let mut h264 = decoder.score_h264 > 0; @@ -155,10 +148,6 @@ impl Session { } return (h264, h265); } - #[cfg(feature = "mediacodec")] - { - todo!(); - } (false, false) } From e0302de8082375227620353905dbb217c25748bf Mon Sep 17 00:00:00 2001 From: csf Date: Fri, 16 Sep 2022 20:43:15 +0800 Subject: [PATCH 4/6] Android server_page.dart fix verificationMethod onSelected color & follow lint --- flutter/lib/mobile/pages/server_page.dart | 152 ++++++++++++---------- 1 file changed, 82 insertions(+), 70 deletions(-) diff --git a/flutter/lib/mobile/pages/server_page.dart b/flutter/lib/mobile/pages/server_page.dart index 00c433fd8..e3be5060f 100644 --- a/flutter/lib/mobile/pages/server_page.dart +++ b/flutter/lib/mobile/pages/server_page.dart @@ -12,51 +12,50 @@ class ServerPage extends StatefulWidget implements PageShape { final title = translate("Share Screen"); @override - final icon = Icon(Icons.mobile_screen_share); + final icon = const Icon(Icons.mobile_screen_share); @override final appBarActions = [ PopupMenuButton( - icon: Icon(Icons.more_vert), + icon: const Icon(Icons.more_vert), itemBuilder: (context) { return [ PopupMenuItem( - child: Text(translate("Change ID")), - padding: EdgeInsets.symmetric(horizontal: 16.0), + padding: const EdgeInsets.symmetric(horizontal: 16.0), value: "changeID", enabled: false, + child: Text(translate("Change ID")), ), PopupMenuItem( - child: Text(translate("Set permanent password")), - padding: EdgeInsets.symmetric(horizontal: 16.0), + padding: const EdgeInsets.symmetric(horizontal: 16.0), value: "setPermanentPassword", enabled: gFFI.serverModel.verificationMethod != kUseTemporaryPassword, + child: Text(translate("Set permanent password")), ), PopupMenuItem( - child: Text(translate("Set temporary password length")), - padding: EdgeInsets.symmetric(horizontal: 16.0), + padding: const EdgeInsets.symmetric(horizontal: 16.0), value: "setTemporaryPasswordLength", enabled: gFFI.serverModel.verificationMethod != kUsePermanentPassword, + child: Text(translate("Set temporary password length")), ), const PopupMenuDivider(), PopupMenuItem( - padding: EdgeInsets.symmetric(horizontal: 0.0), + padding: const EdgeInsets.symmetric(horizontal: 0.0), value: kUseTemporaryPassword, - child: Container( - child: ListTile( - title: Text(translate("Use temporary password")), - trailing: Icon( - Icons.check, - color: gFFI.serverModel.verificationMethod == - kUseTemporaryPassword - ? null - : Color(0xFFFFFFFF), - ))), + child: ListTile( + title: Text(translate("Use temporary password")), + trailing: Icon( + Icons.check, + color: gFFI.serverModel.verificationMethod == + kUseTemporaryPassword + ? null + : Colors.transparent, + )), ), PopupMenuItem( - padding: EdgeInsets.symmetric(horizontal: 0.0), + padding: const EdgeInsets.symmetric(horizontal: 0.0), value: kUsePermanentPassword, child: ListTile( title: Text(translate("Use permanent password")), @@ -65,11 +64,11 @@ class ServerPage extends StatefulWidget implements PageShape { color: gFFI.serverModel.verificationMethod == kUsePermanentPassword ? null - : Color(0xFFFFFFFF), + : Colors.transparent, )), ), PopupMenuItem( - padding: EdgeInsets.symmetric(horizontal: 0.0), + padding: const EdgeInsets.symmetric(horizontal: 0.0), value: kUseBothPasswords, child: ListTile( title: Text(translate("Use both passwords")), @@ -80,7 +79,7 @@ class ServerPage extends StatefulWidget implements PageShape { gFFI.serverModel.verificationMethod != kUsePermanentPassword ? null - : Color(0xFFFFFFFF), + : Colors.transparent, )), ), ]; @@ -101,6 +100,8 @@ class ServerPage extends StatefulWidget implements PageShape { }) ]; + ServerPage({Key? key}) : super(key: key); + @override State createState() => _ServerPageState(); } @@ -125,9 +126,9 @@ class _ServerPageState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ ServerInfo(), - PermissionChecker(), - ConnectionManager(), - SizedBox.fromSize(size: Size(0, 15.0)), + const PermissionChecker(), + const ConnectionManager(), + SizedBox.fromSize(size: const Size(0, 15.0)), ], ), ), @@ -148,6 +149,8 @@ class ServerInfo extends StatelessWidget { final model = gFFI.serverModel; final emptyController = TextEditingController(text: "-"); + ServerInfo({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { final isPermanent = model.verificationMethod == kUsePermanentPassword; @@ -158,7 +161,7 @@ class ServerInfo extends StatelessWidget { children: [ TextFormField( readOnly: true, - style: TextStyle( + style: const TextStyle( fontSize: 25.0, fontWeight: FontWeight.bold, color: MyTheme.accent), @@ -166,14 +169,14 @@ class ServerInfo extends StatelessWidget { decoration: InputDecoration( icon: const Icon(Icons.perm_identity), labelText: translate("ID"), - labelStyle: TextStyle( + labelStyle: const TextStyle( fontWeight: FontWeight.bold, color: MyTheme.accent50), ), onSaved: (String? value) {}, ), TextFormField( readOnly: true, - style: TextStyle( + style: const TextStyle( fontSize: 25.0, fontWeight: FontWeight.bold, color: MyTheme.accent), @@ -181,7 +184,7 @@ class ServerInfo extends StatelessWidget { decoration: InputDecoration( icon: const Icon(Icons.lock), labelText: translate("Password"), - labelStyle: TextStyle( + labelStyle: const TextStyle( fontWeight: FontWeight.bold, color: MyTheme.accent50), suffix: isPermanent ? null @@ -200,13 +203,13 @@ class ServerInfo extends StatelessWidget { Center( child: Row( children: [ - Icon(Icons.warning_amber_sharp, + const Icon(Icons.warning_amber_sharp, color: Colors.redAccent, size: 24), - SizedBox(width: 10), + const SizedBox(width: 10), Expanded( child: Text( translate("Service is not running"), - style: TextStyle( + style: const TextStyle( fontFamily: 'WorkSans', fontWeight: FontWeight.bold, fontSize: 18, @@ -215,11 +218,11 @@ class ServerInfo extends StatelessWidget { )) ], )), - SizedBox(height: 5), + const SizedBox(height: 5), Center( child: Text( translate("android_start_service_tip"), - style: TextStyle(fontSize: 12, color: MyTheme.darkGray), + style: const TextStyle(fontSize: 12, color: MyTheme.darkGray), )) ], )); @@ -227,8 +230,10 @@ class ServerInfo extends StatelessWidget { } class PermissionChecker extends StatefulWidget { + const PermissionChecker({Key? key}) : super(key: key); + @override - _PermissionCheckerState createState() => _PermissionCheckerState(); + State createState() => _PermissionCheckerState(); } class _PermissionCheckerState extends State { @@ -236,7 +241,7 @@ class _PermissionCheckerState extends State { Widget build(BuildContext context) { final serverModel = Provider.of(context); final hasAudioPermission = androidVersion >= 30; - final status; + final String status; if (serverModel.connectStatus == -1) { status = 'not_ready_status'; } else if (serverModel.connectStatus == 0) { @@ -260,9 +265,9 @@ class _PermissionCheckerState extends State { serverModel.toggleAudio) : Text( "* ${translate("android_version_audio_tip")}", - style: TextStyle(color: MyTheme.darkGray), + style: const TextStyle(color: MyTheme.darkGray), ), - SizedBox(height: 8), + const SizedBox(height: 8), Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -273,11 +278,11 @@ class _PermissionCheckerState extends State { style: ButtonStyle( backgroundColor: MaterialStateProperty.all(Colors.red)), - icon: Icon(Icons.stop), + icon: const Icon(Icons.stop), onPressed: serverModel.toggleService, label: Text(translate("Stop service"))) : ElevatedButton.icon( - icon: Icon(Icons.play_arrow), + icon: const Icon(Icons.play_arrow), onPressed: serverModel.toggleService, label: Text(translate("Start Service")))), Expanded( @@ -287,8 +292,8 @@ class _PermissionCheckerState extends State { Expanded( flex: 0, child: Padding( - padding: - EdgeInsets.only(left: 20, right: 5), + padding: const EdgeInsets.only( + left: 20, right: 5), child: Icon(Icons.circle, color: serverModel.connectStatus > 0 ? Colors.greenAccent @@ -297,12 +302,12 @@ class _PermissionCheckerState extends State { Expanded( child: Text(translate(status), softWrap: true, - style: TextStyle( + style: const TextStyle( fontSize: 14.0, color: MyTheme.accent50))) ], ) - : SizedBox.shrink()) + : const SizedBox.shrink()) ], ), ], @@ -311,7 +316,8 @@ class _PermissionCheckerState extends State { } class PermissionRow extends StatelessWidget { - PermissionRow(this.name, this.isOk, this.onPressed); + const PermissionRow(this.name, this.isOk, this.onPressed, {Key? key}) + : super(key: key); final String name; final bool isOk; @@ -327,8 +333,8 @@ class PermissionRow extends StatelessWidget { fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text(name, - style: - TextStyle(fontSize: 16.0, color: MyTheme.accent50)))), + style: const TextStyle( + fontSize: 16.0, color: MyTheme.accent50)))), Expanded( flex: 2, child: FittedBox( @@ -347,7 +353,7 @@ class PermissionRow extends StatelessWidget { onPressed: onPressed, child: Text( translate(isOk ? "CLOSE" : "OPEN"), - style: TextStyle(fontWeight: FontWeight.bold), + style: const TextStyle(fontWeight: FontWeight.bold), )))), ], ); @@ -355,6 +361,8 @@ class PermissionRow extends StatelessWidget { } class ConnectionManager extends StatelessWidget { + const ConnectionManager({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { final serverModel = Provider.of(context); @@ -377,7 +385,7 @@ class ConnectionManager extends StatelessWidget { Expanded( flex: -1, child: client.isFileTransfer || !client.authorized - ? SizedBox.shrink() + ? const SizedBox.shrink() : IconButton( onPressed: () { gFFI.chatModel.changeCurrentID(client.id); @@ -388,24 +396,24 @@ class ConnectionManager extends StatelessWidget { bar.onTap!(1); } }, - icon: Icon( + icon: const Icon( Icons.chat, color: MyTheme.accent80, ))) ], ), client.authorized - ? SizedBox.shrink() + ? const SizedBox.shrink() : Text( translate("android_new_connection_tip"), - style: TextStyle(color: Colors.black54), + style: const TextStyle(color: Colors.black54), ), client.authorized ? ElevatedButton.icon( style: ButtonStyle( backgroundColor: MaterialStateProperty.all(Colors.red)), - icon: Icon(Icons.close), + icon: const Icon(Icons.close), onPressed: () { bind.cmCloseConnection(connId: client.id); gFFI.invokeMethod( @@ -418,7 +426,7 @@ class ConnectionManager extends StatelessWidget { onPressed: () { serverModel.sendLoginResponse(client, false); }), - SizedBox(width: 20), + const SizedBox(width: 20), ElevatedButton( child: Text(translate("Accept")), onPressed: () { @@ -432,7 +440,8 @@ class ConnectionManager extends StatelessWidget { } class PaddingCard extends StatelessWidget { - PaddingCard({required this.child, this.title, this.titleIcon}); + const PaddingCard({Key? key, required this.child, this.title, this.titleIcon}) + : super(key: key); final String? title; final IconData? titleIcon; @@ -445,18 +454,18 @@ class PaddingCard extends StatelessWidget { children.insert( 0, Padding( - padding: EdgeInsets.symmetric(vertical: 5.0), + padding: const EdgeInsets.symmetric(vertical: 5.0), child: Row( children: [ titleIcon != null ? Padding( - padding: EdgeInsets.only(right: 10), + padding: const EdgeInsets.only(right: 10), child: Icon(titleIcon, color: MyTheme.accent80, size: 30)) - : SizedBox.shrink(), + : const SizedBox.shrink(), Text( title!, - style: TextStyle( + style: const TextStyle( fontFamily: 'WorkSans', fontWeight: FontWeight.bold, fontSize: 20, @@ -466,12 +475,13 @@ class PaddingCard extends StatelessWidget { ], ))); } - return Container( + return SizedBox( width: double.maxFinite, child: Card( - margin: EdgeInsets.fromLTRB(15.0, 15.0, 15.0, 0), + margin: const EdgeInsets.fromLTRB(15.0, 15.0, 15.0, 0), child: Padding( - padding: EdgeInsets.symmetric(vertical: 15.0, horizontal: 30.0), + padding: + const EdgeInsets.symmetric(vertical: 15.0, horizontal: 30.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: children, @@ -483,27 +493,29 @@ class PaddingCard extends StatelessWidget { Widget clientInfo(Client client) { return Padding( - padding: EdgeInsets.symmetric(vertical: 8), + padding: const EdgeInsets.symmetric(vertical: 8), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( flex: -1, child: Padding( - padding: EdgeInsets.only(right: 12), + padding: const EdgeInsets.only(right: 12), child: CircleAvatar( - child: Text(client.name[0]), - backgroundColor: MyTheme.border))), + backgroundColor: MyTheme.border, + child: Text(client.name[0])))), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Text(client.name, - style: TextStyle(color: MyTheme.idColor, fontSize: 18)), - SizedBox(width: 8), + style: const TextStyle( + color: MyTheme.idColor, fontSize: 18)), + const SizedBox(width: 8), Text(client.peerId, - style: TextStyle(color: MyTheme.idColor, fontSize: 10)) + style: + const TextStyle(color: MyTheme.idColor, fontSize: 10)) ])) ], ), From e32a019a29a72c80d1f31c0808616bdb508e6f93 Mon Sep 17 00:00:00 2001 From: csf Date: Fri, 16 Sep 2022 21:52:08 +0800 Subject: [PATCH 5/6] feat: Android change id --- flutter/lib/common/widgets/dialog.dart | 74 +++++++++++++++++ .../lib/desktop/pages/desktop_home_page.dart | 2 + .../desktop/pages/desktop_setting_page.dart | 80 +------------------ flutter/lib/mobile/pages/server_page.dart | 4 +- src/flutter_ffi.rs | 17 ++-- src/ui_interface.rs | 22 ++++- 6 files changed, 108 insertions(+), 91 deletions(-) create mode 100644 flutter/lib/common/widgets/dialog.dart diff --git a/flutter/lib/common/widgets/dialog.dart b/flutter/lib/common/widgets/dialog.dart new file mode 100644 index 000000000..82e4fb5cc --- /dev/null +++ b/flutter/lib/common/widgets/dialog.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import '../../common.dart'; +import '../../models/platform_model.dart'; + +void changeIdDialog() { + var newId = ""; + var msg = ""; + var isInProgress = false; + TextEditingController controller = TextEditingController(); + gFFI.dialogManager.show((setState, close) { + submit() async { + debugPrint("onSubmit"); + newId = controller.text.trim(); + setState(() { + msg = ""; + isInProgress = true; + bind.mainChangeId(newId: newId); + }); + + var status = await bind.mainGetAsyncStatus(); + while (status == " ") { + await Future.delayed(const Duration(milliseconds: 100)); + status = await bind.mainGetAsyncStatus(); + } + if (status.isEmpty) { + // ok + close(); + return; + } + setState(() { + isInProgress = false; + msg = translate(status); + }); + } + + return CustomAlertDialog( + title: Text(translate("Change ID")), + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(translate("id_change_tip")), + const SizedBox( + height: 12.0, + ), + TextField( + decoration: InputDecoration( + border: const OutlineInputBorder(), + errorText: msg.isEmpty ? null : translate(msg)), + inputFormatters: [ + LengthLimitingTextInputFormatter(16), + // FilteringTextInputFormatter(RegExp(r"[a-zA-z][a-zA-z0-9\_]*"), allow: true) + ], + maxLength: 16, + controller: controller, + focusNode: FocusNode()..requestFocus(), + ), + const SizedBox( + height: 4.0, + ), + Offstage( + offstage: !isInProgress, child: const LinearProgressIndicator()) + ], + ), + actions: [ + TextButton(onPressed: close, child: Text(translate("Cancel"))), + TextButton(onPressed: submit, child: Text(translate("OK"))), + ], + onSubmit: submit, + onCancel: close, + ); + }); +} diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index bbdd89b15..c8706d5a0 100644 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ b/flutter/lib/desktop/pages/desktop_home_page.dart @@ -19,6 +19,8 @@ import 'package:tray_manager/tray_manager.dart'; import 'package:url_launcher/url_launcher_string.dart'; import 'package:window_manager/window_manager.dart'; +import '../../common/widgets/dialog.dart'; + class _MenubarTheme { static const Color commonColor = MyTheme.accent; // kMinInteractiveDimension diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 0693554d9..4aedc1385 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -11,6 +11,8 @@ import 'package:get/get.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher_string.dart'; +import '../../common/widgets/dialog.dart'; + const double _kTabWidth = 235; const double _kTabHeight = 42; const double _kCardFixedWidth = 560; @@ -1496,82 +1498,4 @@ void changeSocks5Proxy() async { }); } -void changeIdDialog() { - var newId = ""; - var msg = ""; - var isInProgress = false; - TextEditingController controller = TextEditingController(); - gFFI.dialogManager.show((setState, close) { - submit() async { - newId = controller.text.trim(); - setState(() { - msg = ""; - isInProgress = true; - bind.mainChangeId(newId: newId); - }); - - var status = await bind.mainGetAsyncStatus(); - while (status == " ") { - await Future.delayed(const Duration(milliseconds: 100)); - status = await bind.mainGetAsyncStatus(); - } - if (status.isEmpty) { - // ok - close(); - return; - } - setState(() { - isInProgress = false; - msg = translate(status); - }); - } - - return CustomAlertDialog( - title: Text(translate("Change ID")), - content: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(translate("id_change_tip")), - const SizedBox( - height: 8.0, - ), - Row( - children: [ - const Text("ID:").marginOnly(bottom: 16.0), - const SizedBox( - width: 24.0, - ), - Expanded( - child: TextField( - decoration: InputDecoration( - border: const OutlineInputBorder(), - errorText: msg.isEmpty ? null : translate(msg)), - inputFormatters: [ - LengthLimitingTextInputFormatter(16), - // FilteringTextInputFormatter(RegExp(r"[a-zA-z][a-zA-z0-9\_]*"), allow: true) - ], - maxLength: 16, - controller: controller, - focusNode: FocusNode()..requestFocus(), - ), - ), - ], - ), - const SizedBox( - height: 4.0, - ), - Offstage( - offstage: !isInProgress, child: const LinearProgressIndicator()) - ], - ), - actions: [ - TextButton(onPressed: close, child: Text(translate("Cancel"))), - TextButton(onPressed: submit, child: Text(translate("OK"))), - ], - onSubmit: submit, - onCancel: close, - ); - }); -} - //#endregion diff --git a/flutter/lib/mobile/pages/server_page.dart b/flutter/lib/mobile/pages/server_page.dart index e3be5060f..4fdd00ede 100644 --- a/flutter/lib/mobile/pages/server_page.dart +++ b/flutter/lib/mobile/pages/server_page.dart @@ -3,6 +3,7 @@ import 'package:flutter_hbb/mobile/widgets/dialog.dart'; import 'package:provider/provider.dart'; import '../../common.dart'; +import '../../common/widgets/dialog.dart'; import '../../models/platform_model.dart'; import '../../models/server_model.dart'; import 'home_page.dart'; @@ -23,7 +24,6 @@ class ServerPage extends StatefulWidget implements PageShape { PopupMenuItem( padding: const EdgeInsets.symmetric(horizontal: 16.0), value: "changeID", - enabled: false, child: Text(translate("Change ID")), ), PopupMenuItem( @@ -86,7 +86,7 @@ class ServerPage extends StatefulWidget implements PageShape { }, onSelected: (value) { if (value == "changeID") { - // TODO + changeIdDialog(); } else if (value == "setPermanentPassword") { setPermanentPasswordDialog(gFFI.dialogManager); } else if (value == "setTemporaryPasswordLength") { diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index d68d030be..253855e3f 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -17,15 +17,15 @@ use crate::flutter::{self, SESSIONS}; use crate::start_server; use crate::ui_interface; #[cfg(not(any(target_os = "android", target_os = "ios")))] -use crate::ui_interface::{change_id, get_sound_inputs}; +use crate::ui_interface::get_sound_inputs; use crate::ui_interface::{ - check_mouse_time, check_super_user_permission, discover, forget_password, get_api_server, - get_app_name, get_async_job_status, get_connect_status, get_fav, get_id, get_lan_peers, - get_langs, get_license, get_local_option, get_mouse_time, get_option, get_options, get_peer, - get_peer_option, get_socks, get_uuid, get_version, has_hwcodec, has_rendezvous_service, - post_request, send_to_cm, set_local_option, set_option, set_options, set_peer_option, - set_permanent_password, set_socks, store_fav, test_if_valid_server, update_temporary_password, - using_public_server, + change_id, check_mouse_time, check_super_user_permission, discover, forget_password, + get_api_server, get_app_name, get_async_job_status, get_connect_status, get_fav, get_id, + get_lan_peers, get_langs, get_license, get_local_option, get_mouse_time, get_option, + get_options, get_peer, get_peer_option, get_socks, get_uuid, get_version, has_hwcodec, + has_rendezvous_service, post_request, send_to_cm, set_local_option, set_option, set_options, + set_peer_option, set_permanent_password, set_socks, store_fav, test_if_valid_server, + update_temporary_password, using_public_server, }; use crate::{ client::file_trait::FileManager, @@ -426,7 +426,6 @@ pub fn main_get_sound_inputs() -> Vec { } pub fn main_change_id(new_id: String) { - #[cfg(not(any(target_os = "android", target_os = "ios")))] change_id(new_id) } diff --git a/src/ui_interface.rs b/src/ui_interface.rs index fb2ab7472..7242a35dc 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -687,7 +687,6 @@ pub fn open_url(url: String) { } #[inline] -#[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn change_id(id: String) { *ASYNC_JOB_STATUS.lock().unwrap() = " ".to_owned(); let old_id = get_id(); @@ -864,17 +863,27 @@ pub(crate) async fn send_to_cm(data: &ipc::Data) { const INVALID_FORMAT: &'static str = "Invalid format"; const UNKNOWN_ERROR: &'static str = "Unknown error"; -#[cfg(not(any(target_os = "android", target_os = "ios")))] #[tokio::main(flavor = "current_thread")] async fn change_id_(id: String, old_id: String) -> &'static str { if !hbb_common::is_valid_custom_id(&id) { return INVALID_FORMAT; } + + #[cfg(not(any(target_os = "android", target_os = "ios")))] let uuid = machine_uid::get().unwrap_or("".to_owned()); + #[cfg(any(target_os = "android", target_os = "ios"))] + let uuid = base64::encode(hbb_common::get_uuid()); + if uuid.is_empty() { + log::error!("Failed to change id, uuid is_empty"); return UNKNOWN_ERROR; } + + #[cfg(not(any(target_os = "android", target_os = "ios")))] let rendezvous_servers = crate::ipc::get_rendezvous_servers(1_000).await; + #[cfg(any(target_os = "android", target_os = "ios"))] + let rendezvous_servers = Config::get_rendezvous_servers(); + let mut futs = Vec::new(); let err: Arc> = Default::default(); for rendezvous_server in rendezvous_servers { @@ -892,7 +901,13 @@ async fn change_id_(id: String, old_id: String) -> &'static str { join_all(futs).await; let err = *err.lock().unwrap(); if err.is_empty() { + #[cfg(not(any(target_os = "android", target_os = "ios")))] crate::ipc::set_config_async("id", id.to_owned()).await.ok(); + #[cfg(any(target_os = "android", target_os = "ios"))] + { + Config::set_key_confirmed(false); + Config::set_id(&id); + } } err } @@ -937,6 +952,9 @@ async fn check_id( register_pk_response::Result::NOT_SUPPORT => { return "server_not_support"; } + register_pk_response::Result::SERVER_ERROR => { + return "Server error"; + } register_pk_response::Result::INVALID_ID_FORMAT => { return INVALID_FORMAT; } From 2e0f71fb35b9753c1684d424a32626957ada468f Mon Sep 17 00:00:00 2001 From: csf Date: Fri, 16 Sep 2022 21:55:51 +0800 Subject: [PATCH 6/6] fix sciter has_hwcodec --- src/ui/remote.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ui/remote.rs b/src/ui/remote.rs index 97dfe1d02..97e130d09 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -28,6 +28,7 @@ use hbb_common::{ use crate::clipboard_file::*; use crate::{ client::*, + ui_interface::has_hwcodec, ui_session_interface::{InvokeUiSession, Session}, }; @@ -440,6 +441,10 @@ impl SciterSession { v } + fn has_hwcodec(&self) -> bool { + has_hwcodec() + } + pub fn t(&self, name: String) -> String { crate::client::translate(name) }