diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index ef53b2c41..1a0d59e16 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -194,14 +194,20 @@ void msgBox(String type, String title, String text, {bool? hasCancel}) { style: TextStyle(color: MyTheme.accent)))); SmartDialog.dismiss(); - final buttons = [ - wrap(Translator.call('OK'), () { - SmartDialog.dismiss(); - backToHome(); - }) - ]; + List<Widget> buttons = []; + if (type != "connecting" && type != "success" && type.indexOf("nook") < 0) { + buttons.insert( + 0, + wrap(Translator.call('OK'), () { + SmartDialog.dismiss(); + backToHome(); + })); + } if (hasCancel == null) { - hasCancel = type != 'error'; + // hasCancel = type != 'error'; + hasCancel = type.indexOf("error") < 0 && + type.indexOf("nocancel") < 0 && + type != "restarting"; } if (hasCancel) { buttons.insert( @@ -210,6 +216,14 @@ void msgBox(String type, String title, String text, {bool? hasCancel}) { SmartDialog.dismiss(); })); } + // TODO: test this button + if (type.indexOf("hasclose") >= 0) { + buttons.insert( + 0, + wrap(Translator.call('Close'), () { + SmartDialog.dismiss(); + })); + } DialogManager.show((setState, close) => CustomAlertDialog( title: Text(translate(title), style: TextStyle(fontSize: 21)), content: Text(Translator.call(text), style: TextStyle(fontSize: 15)), diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index da5ad1455..da7a317a8 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -604,8 +604,12 @@ class _RemotePageState extends State<RemotePage> await bind.getSessionToggleOption(id: id, arg: 'privacy-mode') != true) { more.add(PopupMenuItem<String>( - child: Text(translate((_ffi.ffiModel.inputBlocked ? 'Unb' : 'B') + - 'lock user input')), + child: Consumer<FfiModel>( + builder: (_context, ffiModel, _child) => () { + return Text(translate( + (ffiModel.inputBlocked ? 'Unb' : 'B') + + 'lock user input')); + }()), value: 'block-input')); } } @@ -951,7 +955,11 @@ void showOptions(String id) async { 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')); + more.add(Consumer<FfiModel>( + builder: (_context, _ffiModel, _child) => () { + return getToggle( + id, setState, 'privacy-mode', 'Privacy mode'); + }())); } } var setQuality = (String? value) { diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index c7295f57e..4f295e377 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -173,6 +173,10 @@ class FfiModel with ChangeNotifier { parent.target?.serverModel.onClientRemove(evt); } else if (name == 'update_quality_status') { parent.target?.qualityMonitorModel.updateQualityStatus(evt); + } else if (name == 'update_block_input_state') { + updateBlockInputState(evt); + } else if (name == 'update_privacy_mode') { + updatePrivacyMode(evt); } }; } @@ -228,6 +232,10 @@ class FfiModel with ChangeNotifier { parent.target?.serverModel.onClientRemove(evt); } else if (name == 'update_quality_status') { parent.target?.qualityMonitorModel.updateQualityStatus(evt); + } else if (name == 'update_block_input_state') { + updateBlockInputState(evt); + } else if (name == 'update_privacy_mode') { + updatePrivacyMode(evt); } }; platformFFI.setEventCallback(cb); @@ -331,6 +339,15 @@ class FfiModel with ChangeNotifier { } notifyListeners(); } + + updateBlockInputState(Map<String, dynamic> evt) { + _inputBlocked = evt['input_state'] == 'on'; + notifyListeners(); + } + + updatePrivacyMode(Map<String, dynamic> evt) { + notifyListeners(); + } } class ImageModel with ChangeNotifier { diff --git a/flutter/lib/utils/multi_window_manager.dart b/flutter/lib/utils/multi_window_manager.dart index bea110dab..4da0dca7f 100644 --- a/flutter/lib/utils/multi_window_manager.dart +++ b/flutter/lib/utils/multi_window_manager.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:ui'; import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:flutter/services.dart'; diff --git a/src/client.rs b/src/client.rs index 89d66c6ca..3c1e5c3c3 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1004,6 +1004,13 @@ impl LoginConfigHandler { Some(msg_out) } + /// Get [`PeerConfig`] of the current [`LoginConfigHandler`]. + /// + /// # Arguments + pub fn get_config(&mut self) -> &mut PeerConfig { + &mut self.config + } + /// Get [`OptionMessage`] of the current [`LoginConfigHandler`]. /// Return `None` if there's no option, for example, when the session is only for file transfer. /// diff --git a/src/common.rs b/src/common.rs index 605435956..5c387c07e 100644 --- a/src/common.rs +++ b/src/common.rs @@ -104,6 +104,19 @@ pub fn update_clipboard(clipboard: Clipboard, old: Option<&Arc<Mutex<String>>>) } } +pub async fn send_opts_after_login( + config: &crate::client::LoginConfigHandler, + peer: &mut hbb_common::tcp::FramedStream, +) { + if let Some(opts) = config.get_option_message_after_login() { + let mut misc = Misc::new(); + misc.set_option(opts); + let mut msg_out = Message::new(); + msg_out.set_misc(misc); + allow_err!(peer.send(&msg_out).await); + } +} + #[cfg(feature = "use_rubato")] pub fn resample_channels( data: &[f32], diff --git a/src/flutter.rs b/src/flutter.rs index d83e37b4e..bb8881c58 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -76,7 +76,7 @@ impl Session { // TODO close // Self::close(); let events2ui = Arc::new(RwLock::new(events2ui)); - let mut session = Session { + let session = Session { id: session_id.clone(), sender: Default::default(), lc: Default::default(), @@ -663,6 +663,8 @@ struct Connection { } impl Connection { + // TODO: Similar to remote::start_clipboard + // merge the code fn start_clipboard( tx_protobuf: mpsc::UnboundedSender<Data>, lc: Arc<RwLock<LoginConfigHandler>>, @@ -842,6 +844,7 @@ impl Connection { Some(message::Union::VideoFrame(vf)) => { if !self.first_frame { self.first_frame = true; + common::send_opts_after_login(&self.session.lc.read().unwrap(), peer).await; } let incomming_format = CodecFormat::from(&vf); if self.video_format != incomming_format { @@ -1083,6 +1086,11 @@ impl Connection { self.session.msgbox("error", "Connection Error", &c); return false; } + Some(misc::Union::BackNotification(notification)) => { + if !self.handle_back_notification(notification).await { + return false; + } + } _ => {} }, Some(message::Union::TestDelay(t)) => { @@ -1107,6 +1115,130 @@ impl Connection { true } + async fn handle_back_notification(&mut self, notification: BackNotification) -> bool { + match notification.union { + Some(back_notification::Union::BlockInputState(state)) => { + self.handle_back_msg_block_input( + state.enum_value_or(back_notification::BlockInputState::BlkStateUnknown), + ) + .await; + } + Some(back_notification::Union::PrivacyModeState(state)) => { + if !self + .handle_back_msg_privacy_mode( + state.enum_value_or(back_notification::PrivacyModeState::PrvStateUnknown), + ) + .await + { + return false; + } + } + _ => {} + } + true + } + + #[inline(always)] + fn update_block_input_state(&mut self, on: bool) { + self.session.push_event( + "update_block_input_state", + [("input_state", if on { "on" } else { "off" })].into(), + ); + } + + async fn handle_back_msg_block_input(&mut self, state: back_notification::BlockInputState) { + match state { + back_notification::BlockInputState::BlkOnSucceeded => { + self.update_block_input_state(true); + } + back_notification::BlockInputState::BlkOnFailed => { + self.session + .msgbox("custom-error", "Block user input", "Failed"); + self.update_block_input_state(false); + } + back_notification::BlockInputState::BlkOffSucceeded => { + self.update_block_input_state(false); + } + back_notification::BlockInputState::BlkOffFailed => { + self.session + .msgbox("custom-error", "Unblock user input", "Failed"); + } + _ => {} + } + } + + #[inline(always)] + fn update_privacy_mode(&mut self, on: bool) { + let mut config = self.session.load_config(); + config.privacy_mode = on; + self.session.save_config(&config); + self.session.lc.write().unwrap().get_config().privacy_mode = on; + self.session.push_event("update_privacy_mode", [].into()); + } + + async fn handle_back_msg_privacy_mode( + &mut self, + state: back_notification::PrivacyModeState, + ) -> bool { + match state { + back_notification::PrivacyModeState::PrvOnByOther => { + self.session.msgbox( + "error", + "Connecting...", + "Someone turns on privacy mode, exit", + ); + return false; + } + back_notification::PrivacyModeState::PrvNotSupported => { + self.session + .msgbox("custom-error", "Privacy mode", "Unsupported"); + self.update_privacy_mode(false); + } + back_notification::PrivacyModeState::PrvOnSucceeded => { + self.session + .msgbox("custom-nocancel", "Privacy mode", "In privacy mode"); + self.update_privacy_mode(true); + } + back_notification::PrivacyModeState::PrvOnFailedDenied => { + self.session + .msgbox("custom-error", "Privacy mode", "Peer denied"); + self.update_privacy_mode(false); + } + back_notification::PrivacyModeState::PrvOnFailedPlugin => { + self.session + .msgbox("custom-error", "Privacy mode", "Please install plugins"); + self.update_privacy_mode(false); + } + back_notification::PrivacyModeState::PrvOnFailed => { + self.session + .msgbox("custom-error", "Privacy mode", "Failed"); + self.update_privacy_mode(false); + } + back_notification::PrivacyModeState::PrvOffSucceeded => { + self.session + .msgbox("custom-nocancel", "Privacy mode", "Out privacy mode"); + self.update_privacy_mode(false); + } + back_notification::PrivacyModeState::PrvOffByPeer => { + self.session + .msgbox("custom-error", "Privacy mode", "Peer exit"); + self.update_privacy_mode(false); + } + back_notification::PrivacyModeState::PrvOffFailed => { + self.session + .msgbox("custom-error", "Privacy mode", "Failed to turn off"); + } + back_notification::PrivacyModeState::PrvOffUnknown => { + self.session + .msgbox("custom-error", "Privacy mode", "Turned off"); + // log::error!("Privacy mode is turned off with unknown reason"); + self.update_privacy_mode(false); + } + _ => {} + } + true + } + async fn handle_msg_from_ui(&mut self, data: Data, peer: &mut Stream) -> bool { match data { Data::Close => { diff --git a/src/ui/remote.rs b/src/ui/remote.rs index 5d036dee2..060aa59db 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -25,8 +25,12 @@ use clipboard::{ use enigo::{self, Enigo, KeyboardControllable}; use hbb_common::{ allow_err, - config::{Config, LocalConfig, PeerConfig}, - fs, log, + config::{Config, LocalConfig, PeerConfig, TransferSerde}, + fs::{ + self, can_enable_overwrite_detection, get_job, get_string, new_send_confirm, + DigestCheckResult, RemoveJobMeta, TransferJobMeta, + }, + get_version_number, log, message_proto::{permission_info::Permission, *}, protobuf::Message as _, rendezvous_proto::ConnType, @@ -38,14 +42,6 @@ use hbb_common::{ }, Stream, }; -use hbb_common::{config::TransferSerde, fs::TransferJobMeta}; -use hbb_common::{ - fs::{ - can_enable_overwrite_detection, get_job, get_string, new_send_confirm, DigestCheckResult, - RemoveJobMeta, - }, - get_version_number, -}; #[cfg(windows)] use crate::clipboard_file::*; @@ -2071,22 +2067,6 @@ impl Remote { true } - async fn send_opts_after_login(&self, peer: &mut Stream) { - if let Some(opts) = self - .handler - .lc - .read() - .unwrap() - .get_option_message_after_login() - { - let mut misc = Misc::new(); - misc.set_option(opts); - let mut msg_out = Message::new(); - msg_out.set_misc(misc); - allow_err!(peer.send(&msg_out).await); - } - } - async fn handle_msg_from_peer(&mut self, data: &[u8], peer: &mut Stream) -> bool { if let Ok(msg_in) = Message::parse_from_bytes(&data) { match msg_in.union { @@ -2095,7 +2075,7 @@ impl Remote { self.first_frame = true; self.handler.call2("closeSuccess", &make_args!()); self.handler.call("adaptSize", &make_args!()); - self.send_opts_after_login(peer).await; + common::send_opts_after_login(&self.handler.lc.read().unwrap(), peer).await; } let incomming_format = CodecFormat::from(&vf); if self.video_format != incomming_format {