diff --git a/flutter/android/build.gradle b/flutter/android/build.gradle index 92c53b7df..f7ab9782c 100644 --- a/flutter/android/build.gradle +++ b/flutter/android/build.gradle @@ -7,7 +7,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.3.0' + classpath 'com.android.tools.build:gradle:7.0.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.google.gms:google-services:4.3.14' } diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index c5775236a..0662fce2b 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -582,13 +582,35 @@ class _RemotePageState extends State { value: 'block-input')); } } - if (gFFI.ffiModel.permissions["restart"] != false && + if (perms["restart"] != false && (pi.platform == "Linux" || pi.platform == "Windows" || pi.platform == "Mac OS")) { more.add(PopupMenuItem( child: Text(translate('Restart Remote Device')), value: 'restart')); } + // Currently only support VP9 + if (gFFI.recordingModel.start || + (perms["recording"] != false && + gFFI.qualityMonitorModel.data.codecFormat == "VP9")) { + more.add(PopupMenuItem( + child: Row( + children: [ + Text(translate(gFFI.recordingModel.start + ? 'Stop session recording' + : 'Start session recording')), + Padding( + padding: EdgeInsets.only(left: 12), + child: Icon( + gFFI.recordingModel.start + ? Icons.pause_circle_filled + : Icons.videocam_outlined, + color: MyTheme.accent), + ) + ], + ), + value: 'record')); + } () async { var value = await showMenu( context: context, @@ -630,6 +652,8 @@ class _RemotePageState extends State { gFFI.cursorModel.reset(); } else if (value == 'restart') { showRestartRemoteDevice(pi, widget.id, gFFI.dialogManager); + } else if (value == 'record') { + gFFI.recordingModel.toggle(); } }(); } diff --git a/flutter/lib/mobile/pages/settings_page.dart b/flutter/lib/mobile/pages/settings_page.dart index c555314d0..7a82bcdd8 100644 --- a/flutter/lib/mobile/pages/settings_page.dart +++ b/flutter/lib/mobile/pages/settings_page.dart @@ -36,6 +36,8 @@ var _enableAbr = false; var _denyLANDiscovery = false; var _onlyWhiteList = false; var _enableDirectIPAccess = false; +var _enableRecordSession = false; +var _autoRecordIncomingSession = false; var _localIP = ""; var _directAccessPort = ""; @@ -78,6 +80,21 @@ class _SettingsState extends State with WidgetsBindingObserver { _enableDirectIPAccess = enableDirectIPAccess; } + final enableRecordSession = option2bool('enable-record-session', + await bind.mainGetOption(key: 'enable-record-session')); + if (enableRecordSession != _enableRecordSession) { + update = true; + _enableRecordSession = enableRecordSession; + } + + final autoRecordIncomingSession = option2bool( + 'allow-auto-record-incoming', + await bind.mainGetOption(key: 'allow-auto-record-incoming')); + if (autoRecordIncomingSession != _autoRecordIncomingSession) { + update = true; + _autoRecordIncomingSession = autoRecordIncomingSession; + } + final localIP = await bind.mainGetOption(key: 'local-ip-addr'); if (localIP != _localIP) { update = true; @@ -178,6 +195,19 @@ class _SettingsState extends State with WidgetsBindingObserver { }); }, ), + SettingsTile.switchTile( + title: Text(translate('Enable Recording Session')), + initialValue: _enableRecordSession, + onToggle: (v) async { + await bind.mainSetOption( + key: "enable-record-session", value: v ? "" : "N"); + final newValue = + await bind.mainGetOption(key: "enable-record-session") != "N"; + setState(() { + _enableRecordSession = newValue; + }); + }, + ), SettingsTile.switchTile( title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -299,6 +329,33 @@ class _SettingsState extends State with WidgetsBindingObserver { }, ) ]), + SettingsSection( + title: Text(translate("Recording")), + tiles: [ + SettingsTile.switchTile( + title: Text(translate('Automatically record incoming sessions')), + leading: Icon(Icons.videocam), + description: FutureBuilder( + builder: (ctx, data) => Offstage( + offstage: !data.hasData, + child: Text("${translate("Directory")}: ${data.data}")), + future: bind.mainDefaultVideoSaveDirectory()), + initialValue: _autoRecordIncomingSession, + onToggle: (v) async { + await bind.mainSetOption( + key: "allow-auto-record-incoming", + value: bool2option("allow-auto-record-incoming", v)); + final newValue = option2bool( + 'allow-auto-record-incoming', + await bind.mainGetOption( + key: 'allow-auto-record-incoming')); + setState(() { + _autoRecordIncomingSession = newValue; + }); + }, + ), + ], + ), SettingsSection( title: Text(translate("Share Screen")), tiles: shareScreenTiles, diff --git a/flutter/lib/models/file_model.dart b/flutter/lib/models/file_model.dart index ed52d03ee..4beac01be 100644 --- a/flutter/lib/models/file_model.dart +++ b/flutter/lib/models/file_model.dart @@ -515,7 +515,7 @@ class FileModel extends ChangeNotifier { items.items.forEach((from) async { _jobId++; await bind.sessionSendFiles( - id: await bind.mainGetLastRemoteId(), + id: '${parent.target?.id}', actId: _jobId, path: from.path, to: PathUtil.join(toPath, from.name, isWindows), diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 79cd7ad54..8e5723588 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -231,7 +231,8 @@ class FfiModel with ChangeNotifier { } else if (type == 'input-password') { enterPasswordDialog(id, dialogManager); } else if (type == 'restarting') { - showMsgBox(id, type, title, text, link, false, dialogManager, hasCancel: false); + showMsgBox(id, type, title, text, link, false, dialogManager, + hasCancel: false); } else { var hasRetry = evt['hasRetry'] == 'true'; showMsgBox(id, type, title, text, link, hasRetry, dialogManager); @@ -1003,16 +1004,16 @@ class RecordingModel with ChangeNotifier { get start => _start; onSwitchDisplay() { - if (!isDesktop || !_start) return; + if (isIOS || !_start) return; var id = parent.target?.id; int? width = parent.target?.canvasModel.getDisplayWidth(); - int? height = parent.target?.canvasModel.getDisplayWidth(); + int? height = parent.target?.canvasModel.getDisplayHeight(); if (id == null || width == null || height == null) return; bind.sessionRecordScreen(id: id, start: true, width: width, height: height); } toggle() { - if (!isDesktop) return; + if (isIOS) return; var id = parent.target?.id; if (id == null) return; _start = !_start; @@ -1025,7 +1026,7 @@ class RecordingModel with ChangeNotifier { } onClose() { - if (!isDesktop) return; + if (isIOS) return; var id = parent.target?.id; if (id == null) return; _start = false; diff --git a/src/server/video_service.rs b/src/server/video_service.rs index d43996559..b583a0ae3 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -449,7 +449,7 @@ fn run(sp: GenericService) -> ResultType<()> { #[cfg(windows)] log::info!("gdi: {}", c.is_gdi()); let codec_name = Encoder::current_hw_encoder_name(); - #[cfg(not(any(target_os = "android", target_os = "ios")))] + #[cfg(not(target_os = "ios"))] let recorder = if !Config::get_option("allow-auto-record-incoming").is_empty() { Recorder::new(RecorderContext { id: "local".to_owned(), @@ -463,7 +463,7 @@ fn run(sp: GenericService) -> ResultType<()> { } else { Default::default() }; - #[cfg(any(target_os = "android", target_os = "ios"))] + #[cfg(target_os = "ios")] let recorder: Arc>> = Default::default(); #[cfg(windows)] start_uac_elevation_check(); @@ -674,7 +674,7 @@ fn handle_one_frame( let mut send_conn_ids: HashSet = Default::default(); if let Ok(msg) = encoder.encode_to_message(frame, ms) { - #[cfg(not(any(target_os = "android", target_os = "ios")))] + #[cfg(not(target_os = "ios"))] recorder .lock() .unwrap() diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs index bd05f3bce..96fb84b6d 100644 --- a/src/ui_cm_interface.rs +++ b/src/ui_cm_interface.rs @@ -413,7 +413,7 @@ pub async fn start_listen( _ => {} } } - cm.remove_connection(current_id); + cm.remove_connection(current_id, true); } async fn handle_fs(fs: ipc::FS, write_jobs: &mut Vec, tx: &UnboundedSender) { diff --git a/src/ui_interface.rs b/src/ui_interface.rs index 9ef512fd7..142c9cb43 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -733,11 +733,21 @@ pub fn get_langs() -> String { #[inline] pub fn default_video_save_directory() -> String { let appname = crate::get_app_name(); + + #[cfg(any(target_os = "android", target_os = "ios"))] + if let Ok(home) = config::APP_HOME_DIR.read() { + let mut path = home.to_owned(); + path.push_str("/RustDesk/ScreenRecord"); + return path; + } + if let Some(user) = directories_next::UserDirs::new() { if let Some(video_dir) = user.video_dir() { return video_dir.join(appname).to_string_lossy().to_string(); } } + + #[cfg(not(any(target_os = "android", target_os = "ios")))] if let Some(home) = platform::get_active_user_home() { let name = if cfg!(target_os = "macos") { "Movies" @@ -746,6 +756,7 @@ pub fn default_video_save_directory() -> String { }; return home.join(name).join(appname).to_string_lossy().to_string(); } + if let Ok(exe) = std::env::current_exe() { if let Some(dir) = exe.parent() { return dir.join("videos").to_string_lossy().to_string();