From 289076aa705870f3849dd5c4f571b2a7d14512bd Mon Sep 17 00:00:00 2001 From: solokot Date: Mon, 21 Oct 2024 09:12:58 +0300 Subject: [PATCH 1/2] Update ru.rs (#9712) --- src/lang/ru.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lang/ru.rs b/src/lang/ru.rs index d011eae60068..00ae750f27db 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -147,7 +147,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("OS Password", "Пароль входа в ОС"), ("install_tip", "В некоторых случаях из-за UAC RustDesk может работать неправильно на удалённом узле. Чтобы избежать возможных проблем с UAC, нажмите кнопку ниже для установки RustDesk в системе."), ("Click to upgrade", "Нажмите, чтобы обновить"), - ("Click to download", "Нажмите, чтобы загрузить"), + ("Click to download", "Нажмите, чтобы скачать"), ("Click to update", "Нажмите, чтобы обновить"), ("Configure", "Настроить"), ("config_acc", "Чтобы удалённо управлять своим рабочим столом, вы должны предоставить RustDesk права \"доступа\""), @@ -648,9 +648,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", "Требуется аутентификация"), ("Authenticate", "Аутентификация"), ("web_id_input_tip", "Можно ввести ID на том же сервере, прямой доступ по IP в веб-клиенте не поддерживается.\nЕсли вы хотите получить доступ к устройству на другом сервере, добавьте адрес сервера (@<адрес_сервера>?key=<ключ>), например,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nЕсли вы хотите получить доступ к устройству на публичном сервере, введите \"@public\", для публичного сервера ключ не нужен."), - ("Download", ""), - ("Upload folder", ""), - ("Upload files", ""), - ("Clipboard is synchronized", ""), + ("Download", "Скачать"), + ("Upload folder", "Загрузить папку"), + ("Upload files", "Загрузить файлы"), + ("Clipboard is synchronized", "Буфер обмена синхронизирован"), ].iter().cloned().collect(); } From e8187588c14150d1b8102650ca33d2052a1c434b Mon Sep 17 00:00:00 2001 From: 21pages Date: Mon, 21 Oct 2024 14:34:06 +0800 Subject: [PATCH 2/2] auto record outgoing (#9711) * Add option auto record outgoing session * In the same connection, all displays and all windows share the same recording state. todo: Android check external storage permission Known issue: * Sciter old issue, stop the process directly without stop record, the record file can't play. Signed-off-by: 21pages --- Cargo.lock | 2 +- flutter/lib/consts.dart | 1 + .../desktop/pages/desktop_setting_page.dart | 93 +++---- flutter/lib/desktop/pages/remote_page.dart | 3 +- .../lib/desktop/widgets/remote_toolbar.dart | 3 +- flutter/lib/mobile/pages/remote_page.dart | 9 +- flutter/lib/mobile/pages/settings_page.dart | 73 ++++-- flutter/lib/models/model.dart | 73 +----- libs/hbb_common/src/config.rs | 6 + libs/scrap/Cargo.toml | 1 - libs/scrap/src/common/codec.rs | 12 +- libs/scrap/src/common/mod.rs | 29 +++ libs/scrap/src/common/record.rs | 234 ++++++++++-------- src/client.rs | 72 +++--- src/client/io_loop.rs | 8 +- src/flutter.rs | 7 +- src/flutter_ffi.rs | 16 +- src/lang/ar.rs | 1 + src/lang/be.rs | 1 + src/lang/bg.rs | 1 + src/lang/ca.rs | 1 + src/lang/cn.rs | 3 +- src/lang/cs.rs | 1 + src/lang/da.rs | 1 + src/lang/de.rs | 1 + src/lang/el.rs | 1 + src/lang/eo.rs | 1 + src/lang/es.rs | 1 + src/lang/et.rs | 1 + src/lang/eu.rs | 1 + src/lang/fa.rs | 1 + src/lang/fr.rs | 1 + src/lang/he.rs | 1 + src/lang/hr.rs | 1 + src/lang/hu.rs | 1 + src/lang/id.rs | 1 + src/lang/it.rs | 1 + src/lang/ja.rs | 1 + src/lang/ko.rs | 1 + src/lang/kz.rs | 1 + src/lang/lt.rs | 1 + src/lang/lv.rs | 1 + src/lang/nb.rs | 1 + src/lang/nl.rs | 1 + src/lang/pl.rs | 1 + src/lang/pt_PT.rs | 1 + src/lang/ptbr.rs | 1 + src/lang/ro.rs | 1 + src/lang/ru.rs | 1 + src/lang/sk.rs | 1 + src/lang/sl.rs | 1 + src/lang/sq.rs | 1 + src/lang/sr.rs | 1 + src/lang/sv.rs | 1 + src/lang/template.rs | 1 + src/lang/th.rs | 1 + src/lang/tr.rs | 1 + src/lang/tw.rs | 1 + src/lang/uk.rs | 1 + src/lang/vn.rs | 1 + src/server/video_service.rs | 24 +- src/ui/header.tis | 22 +- src/ui/index.tis | 3 + src/ui/remote.rs | 8 +- src/ui_session_interface.rs | 20 +- 65 files changed, 442 insertions(+), 322 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e1474f2f3a58..923325c4ea79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3051,7 +3051,7 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hwcodec" version = "0.7.0" -source = "git+https://github.com/rustdesk-org/hwcodec#f74410edec91435252b8394c38f8eeca87ad2a26" +source = "git+https://github.com/rustdesk-org/hwcodec#8bbd05bb300ad07cc345356ad85570f9ea99fbfa" dependencies = [ "bindgen 0.59.2", "cc", diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 1be9c7712c79..89306bb7ae89 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -89,6 +89,7 @@ const String kOptionAllowAutoDisconnect = "allow-auto-disconnect"; const String kOptionAutoDisconnectTimeout = "auto-disconnect-timeout"; const String kOptionEnableHwcodec = "enable-hwcodec"; const String kOptionAllowAutoRecordIncoming = "allow-auto-record-incoming"; +const String kOptionAllowAutoRecordOutgoing = "allow-auto-record-outgoing"; const String kOptionVideoSaveDirectory = "video-save-directory"; const String kOptionAccessMode = "access-mode"; const String kOptionEnableKeyboard = "enable-keyboard"; diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index ada38ecf025e..766c160e042c 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -575,12 +575,17 @@ class _GeneralState extends State<_General> { bool root_dir_exists = map['root_dir_exists']!; bool user_dir_exists = map['user_dir_exists']!; return _Card(title: 'Recording', children: [ - _OptionCheckBox(context, 'Automatically record incoming sessions', - kOptionAllowAutoRecordIncoming), - if (showRootDir) + if (!bind.isOutgoingOnly()) + _OptionCheckBox(context, 'Automatically record incoming sessions', + kOptionAllowAutoRecordIncoming), + if (!bind.isIncomingOnly()) + _OptionCheckBox(context, 'Automatically record outgoing sessions', + kOptionAllowAutoRecordOutgoing), + if (showRootDir && !bind.isOutgoingOnly()) Row( children: [ - Text('${translate("Incoming")}:'), + Text( + '${translate(bind.isIncomingOnly() ? "Directory" : "Incoming")}:'), Expanded( child: GestureDetector( onTap: root_dir_exists @@ -597,45 +602,49 @@ class _GeneralState extends State<_General> { ), ], ).marginOnly(left: _kContentHMargin), - Row( - children: [ - Text('${translate(showRootDir ? "Outgoing" : "Directory")}:'), - Expanded( - child: GestureDetector( - onTap: user_dir_exists - ? () => launchUrl(Uri.file(user_dir)) - : null, - child: Text( - user_dir, - softWrap: true, - style: user_dir_exists - ? const TextStyle(decoration: TextDecoration.underline) + if (!(showRootDir && bind.isIncomingOnly())) + Row( + children: [ + Text( + '${translate((showRootDir && !bind.isOutgoingOnly()) ? "Outgoing" : "Directory")}:'), + Expanded( + child: GestureDetector( + onTap: user_dir_exists + ? () => launchUrl(Uri.file(user_dir)) : null, - )).marginOnly(left: 10), - ), - ElevatedButton( - onPressed: isOptionFixed(kOptionVideoSaveDirectory) - ? null - : () async { - String? initialDirectory; - if (await Directory.fromUri(Uri.directory(user_dir)) - .exists()) { - initialDirectory = user_dir; - } - String? selectedDirectory = - await FilePicker.platform.getDirectoryPath( - initialDirectory: initialDirectory); - if (selectedDirectory != null) { - await bind.mainSetOption( - key: kOptionVideoSaveDirectory, - value: selectedDirectory); - setState(() {}); - } - }, - child: Text(translate('Change'))) - .marginOnly(left: 5), - ], - ).marginOnly(left: _kContentHMargin), + child: Text( + user_dir, + softWrap: true, + style: user_dir_exists + ? const TextStyle( + decoration: TextDecoration.underline) + : null, + )).marginOnly(left: 10), + ), + ElevatedButton( + onPressed: isOptionFixed(kOptionVideoSaveDirectory) + ? null + : () async { + String? initialDirectory; + if (await Directory.fromUri( + Uri.directory(user_dir)) + .exists()) { + initialDirectory = user_dir; + } + String? selectedDirectory = + await FilePicker.platform.getDirectoryPath( + initialDirectory: initialDirectory); + if (selectedDirectory != null) { + await bind.mainSetOption( + key: kOptionVideoSaveDirectory, + value: selectedDirectory); + setState(() {}); + } + }, + child: Text(translate('Change'))) + .marginOnly(left: 5), + ], + ).marginOnly(left: _kContentHMargin), ]); }); } diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index 4ef8157da19b..cca2074a242c 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -115,6 +115,8 @@ class _RemotePageState extends State _ffi.imageModel.addCallbackOnFirstImage((String peerId) { showKBLayoutTypeChooserIfNeeded( _ffi.ffiModel.pi.platform, _ffi.dialogManager); + _ffi.recordingModel + .updateStatus(bind.sessionGetIsRecording(sessionId: _ffi.sessionId)); }); _ffi.start( widget.id, @@ -253,7 +255,6 @@ class _RemotePageState extends State _ffi.dialogManager.hideMobileActionsOverlay(); _ffi.imageModel.disposeImage(); _ffi.cursorModel.disposeImages(); - _ffi.recordingModel.onClose(); _rawKeyFocusNode.dispose(); await _ffi.close(closeSession: closeSession); _timer?.cancel(); diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index 3d8ca5e13149..75791ad093cc 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -1924,8 +1924,7 @@ class _RecordMenu extends StatelessWidget { var ffi = Provider.of(context); var recordingModel = Provider.of(context); final visible = - (recordingModel.start || ffi.permissions['recording'] != false) && - ffi.pi.currentDisplay != kAllDisplayValue; + (recordingModel.start || ffi.permissions['recording'] != false); if (!visible) return Offstage(); return _IconMenuButton( assetName: 'assets/rec.svg', diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index 395ce333365b..40890f228e6b 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -92,6 +92,13 @@ class _RemotePageState extends State { gFFI.chatModel .changeCurrentKey(MessageKey(widget.id, ChatModel.clientModeID)); _blockableOverlayState.applyFfi(gFFI); + gFFI.imageModel.addCallbackOnFirstImage((String peerId) { + gFFI.recordingModel + .updateStatus(bind.sessionGetIsRecording(sessionId: gFFI.sessionId)); + if (gFFI.recordingModel.start) { + showToast(translate('Automatically record outgoing sessions')); + } + }); } @override @@ -207,7 +214,7 @@ class _RemotePageState extends State { } void _handleNonIOSSoftKeyboardInput(String newValue) { - _composingTimer?.cancel(); + _composingTimer?.cancel(); if (_textController.value.isComposingRangeValid) { _composingTimer = Timer(Duration(milliseconds: 25), () { _handleNonIOSSoftKeyboardInput(_textController.value.text); diff --git a/flutter/lib/mobile/pages/settings_page.dart b/flutter/lib/mobile/pages/settings_page.dart index 8fac2ea2a040..e70ee5b35c7a 100644 --- a/flutter/lib/mobile/pages/settings_page.dart +++ b/flutter/lib/mobile/pages/settings_page.dart @@ -79,6 +79,7 @@ class _SettingsState extends State with WidgetsBindingObserver { var _enableRecordSession = false; var _enableHardwareCodec = false; var _autoRecordIncomingSession = false; + var _autoRecordOutgoingSession = false; var _allowAutoDisconnect = false; var _localIP = ""; var _directAccessPort = ""; @@ -104,6 +105,8 @@ class _SettingsState extends State with WidgetsBindingObserver { bind.mainGetOptionSync(key: kOptionEnableHwcodec)); _autoRecordIncomingSession = option2bool(kOptionAllowAutoRecordIncoming, bind.mainGetOptionSync(key: kOptionAllowAutoRecordIncoming)); + _autoRecordOutgoingSession = option2bool(kOptionAllowAutoRecordOutgoing, + bind.mainGetOptionSync(key: kOptionAllowAutoRecordOutgoing)); _localIP = bind.mainGetOptionSync(key: 'local-ip-addr'); _directAccessPort = bind.mainGetOptionSync(key: kOptionDirectAccessPort); _allowAutoDisconnect = option2bool(kOptionAllowAutoDisconnect, @@ -231,6 +234,7 @@ class _SettingsState extends State with WidgetsBindingObserver { Widget build(BuildContext context) { Provider.of(context); final outgoingOnly = bind.isOutgoingOnly(); + final incommingOnly = bind.isIncomingOnly(); final customClientSection = CustomSettingsSection( child: Column( children: [ @@ -674,32 +678,55 @@ class _SettingsState extends State with WidgetsBindingObserver { }, ), ]), - if (isAndroid && !outgoingOnly) + if (isAndroid) SettingsSection( title: Text(translate("Recording")), tiles: [ - SettingsTile.switchTile( - title: - Text(translate('Automatically record incoming sessions')), - leading: Icon(Icons.videocam), - description: Text( - "${translate("Directory")}: ${bind.mainVideoSaveDirectory(root: false)}"), - initialValue: _autoRecordIncomingSession, - onToggle: isOptionFixed(kOptionAllowAutoRecordIncoming) - ? null - : (v) async { - await bind.mainSetOption( - key: kOptionAllowAutoRecordIncoming, - value: - bool2option(kOptionAllowAutoRecordIncoming, v)); - final newValue = option2bool( - kOptionAllowAutoRecordIncoming, - await bind.mainGetOption( - key: kOptionAllowAutoRecordIncoming)); - setState(() { - _autoRecordIncomingSession = newValue; - }); - }, + if (!outgoingOnly) + SettingsTile.switchTile( + title: + Text(translate('Automatically record incoming sessions')), + initialValue: _autoRecordIncomingSession, + onToggle: isOptionFixed(kOptionAllowAutoRecordIncoming) + ? null + : (v) async { + await bind.mainSetOption( + key: kOptionAllowAutoRecordIncoming, + value: bool2option( + kOptionAllowAutoRecordIncoming, v)); + final newValue = option2bool( + kOptionAllowAutoRecordIncoming, + await bind.mainGetOption( + key: kOptionAllowAutoRecordIncoming)); + setState(() { + _autoRecordIncomingSession = newValue; + }); + }, + ), + if (!incommingOnly) + SettingsTile.switchTile( + title: + Text(translate('Automatically record outgoing sessions')), + initialValue: _autoRecordOutgoingSession, + onToggle: isOptionFixed(kOptionAllowAutoRecordOutgoing) + ? null + : (v) async { + await bind.mainSetOption( + key: kOptionAllowAutoRecordOutgoing, + value: bool2option( + kOptionAllowAutoRecordOutgoing, v)); + final newValue = option2bool( + kOptionAllowAutoRecordOutgoing, + await bind.mainGetOption( + key: kOptionAllowAutoRecordOutgoing)); + setState(() { + _autoRecordOutgoingSession = newValue; + }); + }, + ), + SettingsTile( + title: Text(translate("Directory")), + description: Text(bind.mainVideoSaveDirectory(root: false)), ), ], ), diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index d836f62a6e8a..f0e4cd75f9e7 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -397,6 +397,10 @@ class FfiModel with ChangeNotifier { if (isWeb) { parent.target?.fileModel.onSelectedFiles(evt); } + } else if (name == "record_status") { + if (desktopType == DesktopType.remote || isMobile) { + parent.target?.recordingModel.updateStatus(evt['start'] == 'true'); + } } else { debugPrint('Event is not handled in the fixed branch: $name'); } @@ -527,7 +531,6 @@ class FfiModel with ChangeNotifier { } } - parent.target?.recordingModel.onSwitchDisplay(); if (!_pi.isSupportMultiUiSession || _pi.currentDisplay == display) { handleResolutions(peerId, evt['resolutions']); } @@ -1135,8 +1138,6 @@ class FfiModel with ChangeNotifier { // Directly switch to the new display without waiting for the response. switchToNewDisplay(int display, SessionID sessionId, String peerId, {bool updateCursorPos = false}) { - // VideoHandler creation is upon when video frames are received, so either caching commands(don't know next width/height) or stopping recording when switching displays. - parent.target?.recordingModel.onClose(); // no need to wait for the response pi.currentDisplay = display; updateCurDisplay(sessionId, updateCursorPos: updateCursorPos); @@ -2342,25 +2343,7 @@ class RecordingModel with ChangeNotifier { WeakReference parent; RecordingModel(this.parent); bool _start = false; - get start => _start; - - onSwitchDisplay() { - if (isIOS || !_start) return; - final sessionId = parent.target?.sessionId; - int? width = parent.target?.canvasModel.getDisplayWidth(); - int? height = parent.target?.canvasModel.getDisplayHeight(); - if (sessionId == null || width == null || height == null) return; - final pi = parent.target?.ffiModel.pi; - if (pi == null) return; - final currentDisplay = pi.currentDisplay; - if (currentDisplay == kAllDisplayValue) return; - bind.sessionRecordScreen( - sessionId: sessionId, - start: true, - display: currentDisplay, - width: width, - height: height); - } + bool get start => _start; toggle() async { if (isIOS) return; @@ -2368,48 +2351,16 @@ class RecordingModel with ChangeNotifier { if (sessionId == null) return; final pi = parent.target?.ffiModel.pi; if (pi == null) return; - final currentDisplay = pi.currentDisplay; - if (currentDisplay == kAllDisplayValue) return; - _start = !_start; - notifyListeners(); - await _sendStatusMessage(sessionId, pi, _start); - if (_start) { - sessionRefreshVideo(sessionId, pi); - if (versionCmp(pi.version, '1.2.4') >= 0) { - // will not receive SwitchDisplay since 1.2.4 - onSwitchDisplay(); - } - } else { - bind.sessionRecordScreen( - sessionId: sessionId, - start: false, - display: currentDisplay, - width: 0, - height: 0); + bool value = !_start; + if (value) { + await sessionRefreshVideo(sessionId, pi); } + await bind.sessionRecordScreen(sessionId: sessionId, start: value); } - onClose() async { - if (isIOS) return; - final sessionId = parent.target?.sessionId; - if (sessionId == null) return; - if (!_start) return; - _start = false; - final pi = parent.target?.ffiModel.pi; - if (pi == null) return; - final currentDisplay = pi.currentDisplay; - if (currentDisplay == kAllDisplayValue) return; - await _sendStatusMessage(sessionId, pi, false); - bind.sessionRecordScreen( - sessionId: sessionId, - start: false, - display: currentDisplay, - width: 0, - height: 0); - } - - _sendStatusMessage(SessionID sessionId, PeerInfo pi, bool status) async { - await bind.sessionRecordStatus(sessionId: sessionId, status: status); + updateStatus(bool status) { + _start = status; + notifyListeners(); } } diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index f0f7ec7317ec..94f4ec9d988d 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -965,6 +965,10 @@ impl Config { .unwrap_or_default() } + pub fn get_bool_option(k: &str) -> bool { + option2bool(k, &Self::get_option(k)) + } + pub fn set_option(k: String, v: String) { if !is_option_can_save(&OVERWRITE_SETTINGS, &k, &DEFAULT_SETTINGS, &v) { return; @@ -2198,6 +2202,7 @@ pub mod keys { pub const OPTION_AUTO_DISCONNECT_TIMEOUT: &str = "auto-disconnect-timeout"; pub const OPTION_ALLOW_ONLY_CONN_WINDOW_OPEN: &str = "allow-only-conn-window-open"; pub const OPTION_ALLOW_AUTO_RECORD_INCOMING: &str = "allow-auto-record-incoming"; + pub const OPTION_ALLOW_AUTO_RECORD_OUTGOING: &str = "allow-auto-record-outgoing"; pub const OPTION_VIDEO_SAVE_DIRECTORY: &str = "video-save-directory"; pub const OPTION_ENABLE_ABR: &str = "enable-abr"; pub const OPTION_ALLOW_REMOVE_WALLPAPER: &str = "allow-remove-wallpaper"; @@ -2342,6 +2347,7 @@ pub mod keys { OPTION_AUTO_DISCONNECT_TIMEOUT, OPTION_ALLOW_ONLY_CONN_WINDOW_OPEN, OPTION_ALLOW_AUTO_RECORD_INCOMING, + OPTION_ALLOW_AUTO_RECORD_OUTGOING, OPTION_VIDEO_SAVE_DIRECTORY, OPTION_ENABLE_ABR, OPTION_ALLOW_REMOVE_WALLPAPER, diff --git a/libs/scrap/Cargo.toml b/libs/scrap/Cargo.toml index 3a0784e7cca4..529010f16072 100644 --- a/libs/scrap/Cargo.toml +++ b/libs/scrap/Cargo.toml @@ -62,4 +62,3 @@ gstreamer-video = { version = "0.16", optional = true } git = "https://github.com/rustdesk-org/hwcodec" optional = true - diff --git a/libs/scrap/src/common/codec.rs b/libs/scrap/src/common/codec.rs index 07ff0f91d243..dad924c862cd 100644 --- a/libs/scrap/src/common/codec.rs +++ b/libs/scrap/src/common/codec.rs @@ -15,7 +15,7 @@ use crate::{ aom::{self, AomDecoder, AomEncoder, AomEncoderConfig}, common::GoogleImage, vpxcodec::{self, VpxDecoder, VpxDecoderConfig, VpxEncoder, VpxEncoderConfig, VpxVideoCodecId}, - CodecFormat, EncodeInput, EncodeYuvFormat, ImageRgb, + CodecFormat, EncodeInput, EncodeYuvFormat, ImageRgb, ImageTexture, }; use hbb_common::{ @@ -623,7 +623,7 @@ impl Decoder { &mut self, frame: &video_frame::Union, rgb: &mut ImageRgb, - _texture: &mut *mut c_void, + _texture: &mut ImageTexture, _pixelbuffer: &mut bool, chroma: &mut Option, ) -> ResultType { @@ -777,12 +777,16 @@ impl Decoder { fn handle_vram_video_frame( decoder: &mut VRamDecoder, frames: &EncodedVideoFrames, - texture: &mut *mut c_void, + texture: &mut ImageTexture, ) -> ResultType { let mut ret = false; for h26x in frames.frames.iter() { for image in decoder.decode(&h26x.data)? { - *texture = image.frame.texture; + *texture = ImageTexture { + texture: image.frame.texture, + w: image.frame.width as _, + h: image.frame.height as _, + }; ret = true; } } diff --git a/libs/scrap/src/common/mod.rs b/libs/scrap/src/common/mod.rs index 635f0ec26d45..ee96f57c8514 100644 --- a/libs/scrap/src/common/mod.rs +++ b/libs/scrap/src/common/mod.rs @@ -96,6 +96,22 @@ impl ImageRgb { } } +pub struct ImageTexture { + pub texture: *mut c_void, + pub w: usize, + pub h: usize, +} + +impl Default for ImageTexture { + fn default() -> Self { + Self { + texture: std::ptr::null_mut(), + w: 0, + h: 0, + } + } +} + #[inline] pub fn would_block_if_equal(old: &mut Vec, b: &[u8]) -> std::io::Result<()> { // does this really help? @@ -296,6 +312,19 @@ impl From<&VideoFrame> for CodecFormat { } } +impl From<&video_frame::Union> for CodecFormat { + fn from(it: &video_frame::Union) -> Self { + match it { + video_frame::Union::Vp8s(_) => CodecFormat::VP8, + video_frame::Union::Vp9s(_) => CodecFormat::VP9, + video_frame::Union::Av1s(_) => CodecFormat::AV1, + video_frame::Union::H264s(_) => CodecFormat::H264, + video_frame::Union::H265s(_) => CodecFormat::H265, + _ => CodecFormat::Unknown, + } + } +} + impl From<&CodecName> for CodecFormat { fn from(value: &CodecName) -> Self { match value { diff --git a/libs/scrap/src/common/record.rs b/libs/scrap/src/common/record.rs index 52973c2b244b..c53b7743147f 100644 --- a/libs/scrap/src/common/record.rs +++ b/libs/scrap/src/common/record.rs @@ -25,22 +25,28 @@ pub struct RecorderContext { pub server: bool, pub id: String, pub dir: String, + pub display: usize, + pub tx: Option>, +} + +#[derive(Debug, Clone)] +pub struct RecorderContext2 { pub filename: String, pub width: usize, pub height: usize, pub format: CodecFormat, - pub tx: Option>, } -impl RecorderContext { - pub fn set_filename(&mut self) -> ResultType<()> { - if !PathBuf::from(&self.dir).exists() { - std::fs::create_dir_all(&self.dir)?; +impl RecorderContext2 { + pub fn set_filename(&mut self, ctx: &RecorderContext) -> ResultType<()> { + if !PathBuf::from(&ctx.dir).exists() { + std::fs::create_dir_all(&ctx.dir)?; } - let file = if self.server { "incoming" } else { "outgoing" }.to_string() + let file = if ctx.server { "incoming" } else { "outgoing" }.to_string() + "_" - + &self.id.clone() + + &ctx.id.clone() + &chrono::Local::now().format("_%Y%m%d%H%M%S%3f_").to_string() + + &format!("display{}_", ctx.display) + &self.format.to_string().to_lowercase() + if self.format == CodecFormat::VP9 || self.format == CodecFormat::VP8 @@ -50,11 +56,10 @@ impl RecorderContext { } else { ".mp4" }; - self.filename = PathBuf::from(&self.dir) + self.filename = PathBuf::from(&ctx.dir) .join(file) .to_string_lossy() .to_string(); - log::info!("video will save to {}", self.filename); Ok(()) } } @@ -63,7 +68,7 @@ unsafe impl Send for Recorder {} unsafe impl Sync for Recorder {} pub trait RecorderApi { - fn new(ctx: RecorderContext) -> ResultType + fn new(ctx: RecorderContext, ctx2: RecorderContext2) -> ResultType where Self: Sized; fn write_video(&mut self, frame: &EncodedVideoFrame) -> bool; @@ -78,13 +83,15 @@ pub enum RecordState { } pub struct Recorder { - pub inner: Box, + pub inner: Option>, ctx: RecorderContext, + ctx2: Option, pts: Option, + check_failed: bool, } impl Deref for Recorder { - type Target = Box; + type Target = Option>; fn deref(&self) -> &Self::Target { &self.inner @@ -98,114 +105,123 @@ impl DerefMut for Recorder { } impl Recorder { - pub fn new(mut ctx: RecorderContext) -> ResultType { - ctx.set_filename()?; - let recorder = match ctx.format { - CodecFormat::VP8 | CodecFormat::VP9 | CodecFormat::AV1 => Recorder { - inner: Box::new(WebmRecorder::new(ctx.clone())?), - ctx, - pts: None, - }, - #[cfg(feature = "hwcodec")] - _ => Recorder { - inner: Box::new(HwRecorder::new(ctx.clone())?), - ctx, - pts: None, - }, - #[cfg(not(feature = "hwcodec"))] - _ => bail!("unsupported codec type"), - }; - recorder.send_state(RecordState::NewFile(recorder.ctx.filename.clone())); - Ok(recorder) + pub fn new(ctx: RecorderContext) -> ResultType { + Ok(Self { + inner: None, + ctx, + ctx2: None, + pts: None, + check_failed: false, + }) } - fn change(&mut self, mut ctx: RecorderContext) -> ResultType<()> { - ctx.set_filename()?; - self.inner = match ctx.format { - CodecFormat::VP8 | CodecFormat::VP9 | CodecFormat::AV1 => { - Box::new(WebmRecorder::new(ctx.clone())?) + fn check(&mut self, w: usize, h: usize, format: CodecFormat) -> ResultType<()> { + match self.ctx2 { + Some(ref ctx2) => { + if ctx2.width != w || ctx2.height != h || ctx2.format != format { + let mut ctx2 = RecorderContext2 { + width: w, + height: h, + format, + filename: Default::default(), + }; + ctx2.set_filename(&self.ctx)?; + self.ctx2 = Some(ctx2); + self.inner = None; + } } - #[cfg(feature = "hwcodec")] - _ => Box::new(HwRecorder::new(ctx.clone())?), - #[cfg(not(feature = "hwcodec"))] - _ => bail!("unsupported codec type"), + None => { + let mut ctx2 = RecorderContext2 { + width: w, + height: h, + format, + filename: Default::default(), + }; + ctx2.set_filename(&self.ctx)?; + self.ctx2 = Some(ctx2); + self.inner = None; + } + } + let Some(ctx2) = &self.ctx2 else { + bail!("ctx2 is None"); }; - self.ctx = ctx; - self.pts = None; - self.send_state(RecordState::NewFile(self.ctx.filename.clone())); + if self.inner.is_none() { + self.inner = match format { + CodecFormat::VP8 | CodecFormat::VP9 | CodecFormat::AV1 => Some(Box::new( + WebmRecorder::new(self.ctx.clone(), (*ctx2).clone())?, + )), + #[cfg(feature = "hwcodec")] + _ => Some(Box::new(HwRecorder::new( + self.ctx.clone(), + (*ctx2).clone(), + )?)), + #[cfg(not(feature = "hwcodec"))] + _ => bail!("unsupported codec type"), + }; + self.pts = None; + self.send_state(RecordState::NewFile(ctx2.filename.clone())); + } Ok(()) } - pub fn write_message(&mut self, msg: &Message) { + pub fn write_message(&mut self, msg: &Message, w: usize, h: usize) { if let Some(message::Union::VideoFrame(vf)) = &msg.union { if let Some(frame) = &vf.union { - self.write_frame(frame).ok(); + self.write_frame(frame, w, h).ok(); } } } - pub fn write_frame(&mut self, frame: &video_frame::Union) -> ResultType<()> { + pub fn write_frame( + &mut self, + frame: &video_frame::Union, + w: usize, + h: usize, + ) -> ResultType<()> { + if self.check_failed { + bail!("check failed"); + } + let format = CodecFormat::from(frame); + if format == CodecFormat::Unknown { + bail!("unsupported frame type"); + } + let res = self.check(w, h, format); + if res.is_err() { + self.check_failed = true; + log::error!("check failed: {:?}", res); + res?; + } match frame { video_frame::Union::Vp8s(vp8s) => { - if self.ctx.format != CodecFormat::VP8 { - self.change(RecorderContext { - format: CodecFormat::VP8, - ..self.ctx.clone() - })?; - } for f in vp8s.frames.iter() { - self.check_pts(f.pts)?; - self.write_video(f); + self.check_pts(f.pts, w, h, format)?; + self.as_mut().map(|x| x.write_video(f)); } } video_frame::Union::Vp9s(vp9s) => { - if self.ctx.format != CodecFormat::VP9 { - self.change(RecorderContext { - format: CodecFormat::VP9, - ..self.ctx.clone() - })?; - } for f in vp9s.frames.iter() { - self.check_pts(f.pts)?; - self.write_video(f); + self.check_pts(f.pts, w, h, format)?; + self.as_mut().map(|x| x.write_video(f)); } } video_frame::Union::Av1s(av1s) => { - if self.ctx.format != CodecFormat::AV1 { - self.change(RecorderContext { - format: CodecFormat::AV1, - ..self.ctx.clone() - })?; - } for f in av1s.frames.iter() { - self.check_pts(f.pts)?; - self.write_video(f); + self.check_pts(f.pts, w, h, format)?; + self.as_mut().map(|x| x.write_video(f)); } } #[cfg(feature = "hwcodec")] video_frame::Union::H264s(h264s) => { - if self.ctx.format != CodecFormat::H264 { - self.change(RecorderContext { - format: CodecFormat::H264, - ..self.ctx.clone() - })?; - } for f in h264s.frames.iter() { - self.check_pts(f.pts)?; - self.write_video(f); + self.check_pts(f.pts, w, h, format)?; + self.as_mut().map(|x| x.write_video(f)); } } #[cfg(feature = "hwcodec")] video_frame::Union::H265s(h265s) => { - if self.ctx.format != CodecFormat::H265 { - self.change(RecorderContext { - format: CodecFormat::H265, - ..self.ctx.clone() - })?; - } for f in h265s.frames.iter() { - self.check_pts(f.pts)?; - self.write_video(f); + self.check_pts(f.pts, w, h, format)?; + self.as_mut().map(|x| x.write_video(f)); } } _ => bail!("unsupported frame type"), @@ -214,13 +230,21 @@ impl Recorder { Ok(()) } - fn check_pts(&mut self, pts: i64) -> ResultType<()> { + fn check_pts(&mut self, pts: i64, w: usize, h: usize, format: CodecFormat) -> ResultType<()> { // https://stackoverflow.com/questions/76379101/how-to-create-one-playable-webm-file-from-two-different-video-tracks-with-same-c let old_pts = self.pts; self.pts = Some(pts); if old_pts.clone().unwrap_or_default() > pts { log::info!("pts {:?} -> {}, change record filename", old_pts, pts); - self.change(self.ctx.clone())?; + self.inner = None; + self.ctx2 = None; + let res = self.check(w, h, format); + if res.is_err() { + self.check_failed = true; + log::error!("check failed: {:?}", res); + res?; + } + self.pts = Some(pts); } Ok(()) } @@ -234,21 +258,22 @@ struct WebmRecorder { vt: VideoTrack, webm: Option>>, ctx: RecorderContext, + ctx2: RecorderContext2, key: bool, written: bool, start: Instant, } impl RecorderApi for WebmRecorder { - fn new(ctx: RecorderContext) -> ResultType { + fn new(ctx: RecorderContext, ctx2: RecorderContext2) -> ResultType { let out = match { OpenOptions::new() .write(true) .create_new(true) - .open(&ctx.filename) + .open(&ctx2.filename) } { Ok(file) => file, - Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => File::create(&ctx.filename)?, + Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => File::create(&ctx2.filename)?, Err(e) => return Err(e.into()), }; let mut webm = match mux::Segment::new(mux::Writer::new(out)) { @@ -256,18 +281,18 @@ impl RecorderApi for WebmRecorder { None => bail!("Failed to create webm mux"), }; let vt = webm.add_video_track( - ctx.width as _, - ctx.height as _, + ctx2.width as _, + ctx2.height as _, None, - if ctx.format == CodecFormat::VP9 { + if ctx2.format == CodecFormat::VP9 { mux::VideoCodecId::VP9 - } else if ctx.format == CodecFormat::VP8 { + } else if ctx2.format == CodecFormat::VP8 { mux::VideoCodecId::VP8 } else { mux::VideoCodecId::AV1 }, ); - if ctx.format == CodecFormat::AV1 { + if ctx2.format == CodecFormat::AV1 { // [129, 8, 12, 0] in 3.6.0, but zero works let codec_private = vec![0, 0, 0, 0]; if !webm.set_codec_private(vt.track_number(), &codec_private) { @@ -278,6 +303,7 @@ impl RecorderApi for WebmRecorder { vt, webm: Some(webm), ctx, + ctx2, key: false, written: false, start: Instant::now(), @@ -307,7 +333,7 @@ impl Drop for WebmRecorder { let _ = std::mem::replace(&mut self.webm, None).map_or(false, |webm| webm.finalize(None)); let mut state = RecordState::WriteTail; if !self.written || self.start.elapsed().as_secs() < MIN_SECS { - std::fs::remove_file(&self.ctx.filename).ok(); + std::fs::remove_file(&self.ctx2.filename).ok(); state = RecordState::RemoveFile; } self.ctx.tx.as_ref().map(|tx| tx.send(state)); @@ -318,6 +344,7 @@ impl Drop for WebmRecorder { struct HwRecorder { muxer: Muxer, ctx: RecorderContext, + ctx2: RecorderContext2, written: bool, key: bool, start: Instant, @@ -325,18 +352,19 @@ struct HwRecorder { #[cfg(feature = "hwcodec")] impl RecorderApi for HwRecorder { - fn new(ctx: RecorderContext) -> ResultType { + fn new(ctx: RecorderContext, ctx2: RecorderContext2) -> ResultType { let muxer = Muxer::new(MuxContext { - filename: ctx.filename.clone(), - width: ctx.width, - height: ctx.height, - is265: ctx.format == CodecFormat::H265, + filename: ctx2.filename.clone(), + width: ctx2.width, + height: ctx2.height, + is265: ctx2.format == CodecFormat::H265, framerate: crate::hwcodec::DEFAULT_FPS as _, }) .map_err(|_| anyhow!("Failed to create hardware muxer"))?; Ok(HwRecorder { muxer, ctx, + ctx2, written: false, key: false, start: Instant::now(), @@ -365,7 +393,7 @@ impl Drop for HwRecorder { self.muxer.write_tail().ok(); let mut state = RecordState::WriteTail; if !self.written || self.start.elapsed().as_secs() < MIN_SECS { - std::fs::remove_file(&self.ctx.filename).ok(); + std::fs::remove_file(&self.ctx2.filename).ok(); state = RecordState::RemoveFile; } self.ctx.tx.as_ref().map(|tx| tx.send(state)); diff --git a/src/client.rs b/src/client.rs index 9e49b84e2e27..a1b2b83d4366 100644 --- a/src/client.rs +++ b/src/client.rs @@ -30,7 +30,6 @@ pub use file_trait::FileManager; #[cfg(not(feature = "flutter"))] #[cfg(not(any(target_os = "android", target_os = "ios")))] use hbb_common::tokio::sync::mpsc::UnboundedSender; -use hbb_common::tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver}; use hbb_common::{ allow_err, anyhow::{anyhow, Context}, @@ -54,11 +53,15 @@ use hbb_common::{ }, AddrMangle, ResultType, Stream, }; +use hbb_common::{ + config::keys::OPTION_ALLOW_AUTO_RECORD_OUTGOING, + tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver}, +}; pub use helper::*; use scrap::{ codec::Decoder, record::{Recorder, RecorderContext}, - CodecFormat, ImageFormat, ImageRgb, + CodecFormat, ImageFormat, ImageRgb, ImageTexture, }; use crate::{ @@ -1146,7 +1149,7 @@ impl AudioHandler { pub struct VideoHandler { decoder: Decoder, pub rgb: ImageRgb, - pub texture: *mut c_void, + pub texture: ImageTexture, recorder: Arc>>, record: bool, _display: usize, // useful for debug @@ -1172,7 +1175,7 @@ impl VideoHandler { VideoHandler { decoder: Decoder::new(format, luid), rgb: ImageRgb::new(ImageFormat::ARGB, crate::get_dst_align_rgba()), - texture: std::ptr::null_mut(), + texture: Default::default(), recorder: Default::default(), record: false, _display, @@ -1220,11 +1223,14 @@ impl VideoHandler { } self.first_frame = false; if self.record { - self.recorder - .lock() - .unwrap() - .as_mut() - .map(|r| r.write_frame(frame)); + self.recorder.lock().unwrap().as_mut().map(|r| { + let (w, h) = if *pixelbuffer { + (self.rgb.w, self.rgb.h) + } else { + (self.texture.w, self.texture.h) + }; + r.write_frame(frame, w, h).ok(); + }); } res } @@ -1248,17 +1254,14 @@ impl VideoHandler { } /// Start or stop screen record. - pub fn record_screen(&mut self, start: bool, w: i32, h: i32, id: String) { + pub fn record_screen(&mut self, start: bool, id: String, display: usize) { self.record = false; if start { self.recorder = Recorder::new(RecorderContext { server: false, id, dir: crate::ui_interface::video_save_directory(false), - filename: "".to_owned(), - width: w as _, - height: h as _, - format: scrap::CodecFormat::VP9, + display, tx: None, }) .map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r)))); @@ -1347,6 +1350,7 @@ pub struct LoginConfigHandler { password_source: PasswordSource, // where the sent password comes from shared_password: Option, // Store the shared password pub enable_trusted_devices: bool, + pub record: bool, } impl Deref for LoginConfigHandler { @@ -1438,6 +1442,7 @@ impl LoginConfigHandler { self.adapter_luid = adapter_luid; self.selected_windows_session_id = None; self.shared_password = shared_password; + self.record = Config::get_bool_option(OPTION_ALLOW_AUTO_RECORD_OUTGOING); } /// Check if the client should auto login. @@ -2227,7 +2232,7 @@ pub enum MediaData { AudioFrame(Box), AudioFormat(AudioFormat), Reset(Option), - RecordScreen(bool, usize, i32, i32, String), + RecordScreen(bool), } pub type MediaSender = mpsc::Sender; @@ -2303,10 +2308,16 @@ where let start = std::time::Instant::now(); let format = CodecFormat::from(&vf); if !handler_controller_map.contains_key(&display) { + let mut handler = VideoHandler::new(format, display); + let record = session.lc.read().unwrap().record; + let id = session.lc.read().unwrap().id.clone(); + if record { + handler.record_screen(record, id, display); + } handler_controller_map.insert( display, VideoHandlerController { - handler: VideoHandler::new(format, display), + handler, skip_beginning: 0, }, ); @@ -2325,7 +2336,7 @@ where video_callback( display, &mut handler_controller.handler.rgb, - handler_controller.handler.texture, + handler_controller.handler.texture.texture, pixelbuffer, ); @@ -2399,18 +2410,19 @@ where } } } - MediaData::RecordScreen(start, display, w, h, id) => { - log::info!("record screen command: start: {start}, display: {display}"); - // Compatible with the sciter version(single ui session). - // For the sciter version, there're no multi-ui-sessions for one connection. - // The display is always 0, video_handler_controllers.len() is always 1. So we use the first video handler. - if let Some(handler_controler) = handler_controller_map.get_mut(&display) { - handler_controler.handler.record_screen(start, w, h, id); - } else if handler_controller_map.len() == 1 { - if let Some(handler_controler) = - handler_controller_map.values_mut().next() - { - handler_controler.handler.record_screen(start, w, h, id); + MediaData::RecordScreen(start) => { + log::info!("record screen command: start: {start}"); + let record = session.lc.read().unwrap().record; + session.update_record_status(start); + if record != start { + session.lc.write().unwrap().record = start; + let id = session.lc.read().unwrap().id.clone(); + for (display, handler_controler) in handler_controller_map.iter_mut() { + handler_controler.handler.record_screen( + start, + id.clone(), + *display, + ); } } } @@ -3169,7 +3181,7 @@ pub enum Data { SetConfirmOverrideFile((i32, i32, bool, bool, bool)), AddJob((i32, String, String, i32, bool, bool)), ResumeJob((i32, bool)), - RecordScreen(bool, usize, i32, i32, String), + RecordScreen(bool), ElevateDirect, ElevateWithLogon(String, String), NewVoiceCall, diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 84d8a897cf58..cc74c96edd14 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -837,10 +837,8 @@ impl Remote { self.handle_job_status(id, -1, err); } } - Data::RecordScreen(start, display, w, h, id) => { - let _ = self - .video_sender - .send(MediaData::RecordScreen(start, display, w, h, id)); + Data::RecordScreen(start) => { + let _ = self.video_sender.send(MediaData::RecordScreen(start)); } Data::ElevateDirect => { let mut request = ElevationRequest::new(); @@ -1218,7 +1216,7 @@ impl Remote { crate::plugin::handle_listen_event( crate::plugin::EVENT_ON_CONN_CLIENT.to_owned(), self.handler.get_id(), - ) + ); } if self.handler.is_file_transfer() { diff --git a/src/flutter.rs b/src/flutter.rs index cbeb3e2c3d3a..69266f51c1ce 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -17,7 +17,7 @@ use serde::Serialize; use serde_json::json; use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, ffi::CString, os::raw::{c_char, c_int, c_void}, str::FromStr, @@ -1010,6 +1010,10 @@ impl InvokeUiSession for FlutterHandler { rgba_data.valid = false; } } + + fn update_record_status(&self, start: bool) { + self.push_event("record_status", &[("start", &start.to_string())], &[]); + } } impl FlutterHandler { @@ -1830,7 +1834,6 @@ pub(super) fn session_update_virtual_display(session: &FlutterSession, index: i3 // sessions mod is used to avoid the big lock of sessions' map. pub mod sessions { - use std::collections::HashSet; use super::*; diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index d029de1d2560..979e25e8d267 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -241,21 +241,17 @@ pub fn session_is_multi_ui_session(session_id: SessionID) -> SyncReturn { } } -pub fn session_record_screen( - session_id: SessionID, - start: bool, - display: usize, - width: usize, - height: usize, -) { +pub fn session_record_screen(session_id: SessionID, start: bool) { if let Some(session) = sessions::get_session_by_session_id(&session_id) { - session.record_screen(start, display as _, width as _, height as _); + session.record_screen(start); } } -pub fn session_record_status(session_id: SessionID, status: bool) { +pub fn session_get_is_recording(session_id: SessionID) -> SyncReturn { if let Some(session) = sessions::get_session_by_session_id(&session_id) { - session.record_status(status); + SyncReturn(session.is_recording()) + } else { + SyncReturn(false) } } diff --git a/src/lang/ar.rs b/src/lang/ar.rs index 6cae020b6e45..fe7e853815b6 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "التسجيل"), ("Directory", "المسار"), ("Automatically record incoming sessions", "تسجيل الجلسات القادمة تلقائيا"), + ("Automatically record outgoing sessions", ""), ("Change", "تغيير"), ("Start session recording", "بدء تسجيل الجلسة"), ("Stop session recording", "ايقاف تسجيل الجلسة"), diff --git a/src/lang/be.rs b/src/lang/be.rs index 7e9f928ae573..da9be46401c3 100644 --- a/src/lang/be.rs +++ b/src/lang/be.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Запіс"), ("Directory", "Тэчка"), ("Automatically record incoming sessions", "Аўтаматычна запісваць уваходныя сесіі"), + ("Automatically record outgoing sessions", ""), ("Change", "Змяніць"), ("Start session recording", "Пачаць запіс сесіі"), ("Stop session recording", "Спыніць запіс сесіі"), diff --git a/src/lang/bg.rs b/src/lang/bg.rs index 9f6282e75234..72b5fcf19717 100644 --- a/src/lang/bg.rs +++ b/src/lang/bg.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Записване"), ("Directory", "Директория"), ("Automatically record incoming sessions", "Автоматичен запис на входящи сесии"), + ("Automatically record outgoing sessions", ""), ("Change", "Промяна"), ("Start session recording", "Започванена запис"), ("Stop session recording", "Край на запис"), diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 64748c9ac093..fbe3cde5fcd5 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Gravació"), ("Directory", "Contactes"), ("Automatically record incoming sessions", "Enregistrament automàtic de sessions entrants"), + ("Automatically record outgoing sessions", ""), ("Change", "Canvia"), ("Start session recording", "Inicia la gravació de la sessió"), ("Stop session recording", "Atura la gravació de la sessió"), diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 5ad91ada84f7..f57d100d5e58 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -363,7 +363,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Unpin Toolbar", "取消固定工具栏"), ("Recording", "录屏"), ("Directory", "目录"), - ("Automatically record incoming sessions", "自动录制来访会话"), + ("Automatically record incoming sessions", "自动录制传入会话"), + ("Automatically record outgoing sessions", "自动录制传出会话"), ("Change", "更改"), ("Start session recording", "开始录屏"), ("Stop session recording", "结束录屏"), diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 8db1470b98a5..d72b85a0cf50 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Nahrávání"), ("Directory", "Adresář"), ("Automatically record incoming sessions", "Automaticky nahrávat příchozí relace"), + ("Automatically record outgoing sessions", ""), ("Change", "Změnit"), ("Start session recording", "Spustit záznam relace"), ("Stop session recording", "Zastavit záznam relace"), diff --git a/src/lang/da.rs b/src/lang/da.rs index f867cac467a8..558b2fa45410 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Optager"), ("Directory", "Mappe"), ("Automatically record incoming sessions", "Optag automatisk indgående sessioner"), + ("Automatically record outgoing sessions", ""), ("Change", "Ændr"), ("Start session recording", "Start sessionsoptagelse"), ("Stop session recording", "Stop sessionsoptagelse"), diff --git a/src/lang/de.rs b/src/lang/de.rs index 4213c86de740..0a78ee2b55e8 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Aufnahme"), ("Directory", "Verzeichnis"), ("Automatically record incoming sessions", "Eingehende Sitzungen automatisch aufzeichnen"), + ("Automatically record outgoing sessions", ""), ("Change", "Ändern"), ("Start session recording", "Sitzungsaufzeichnung starten"), ("Stop session recording", "Sitzungsaufzeichnung beenden"), diff --git a/src/lang/el.rs b/src/lang/el.rs index 625867c343e9..9725ecc78864 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Εγγραφή"), ("Directory", "Φάκελος εγγραφών"), ("Automatically record incoming sessions", "Αυτόματη εγγραφή εισερχόμενων συνεδριών"), + ("Automatically record outgoing sessions", ""), ("Change", "Αλλαγή"), ("Start session recording", "Έναρξη εγγραφής συνεδρίας"), ("Stop session recording", "Διακοπή εγγραφής συνεδρίας"), diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 217b37a6a5e6..2f585b1c6e6f 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", ""), ("Directory", ""), ("Automatically record incoming sessions", ""), + ("Automatically record outgoing sessions", ""), ("Change", ""), ("Start session recording", ""), ("Stop session recording", ""), diff --git a/src/lang/es.rs b/src/lang/es.rs index 25cd7bf283c2..92015df0b20b 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Grabando"), ("Directory", "Directorio"), ("Automatically record incoming sessions", "Grabación automática de sesiones entrantes"), + ("Automatically record outgoing sessions", ""), ("Change", "Cambiar"), ("Start session recording", "Comenzar grabación de sesión"), ("Stop session recording", "Detener grabación de sesión"), diff --git a/src/lang/et.rs b/src/lang/et.rs index 8187759d5755..96ca16f964f6 100644 --- a/src/lang/et.rs +++ b/src/lang/et.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", ""), ("Directory", ""), ("Automatically record incoming sessions", ""), + ("Automatically record outgoing sessions", ""), ("Change", ""), ("Start session recording", ""), ("Stop session recording", ""), diff --git a/src/lang/eu.rs b/src/lang/eu.rs index f8ba679a756d..d68e5c42aeb2 100644 --- a/src/lang/eu.rs +++ b/src/lang/eu.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Grabatzen"), ("Directory", "Direktorioa"), ("Automatically record incoming sessions", "Automatikoki grabatu sarrerako saioak"), + ("Automatically record outgoing sessions", ""), ("Change", "Aldatu"), ("Start session recording", "Hasi saioaren grabaketa"), ("Stop session recording", "Gelditu saioaren grabaketa"), diff --git a/src/lang/fa.rs b/src/lang/fa.rs index cf5ce0f0f70e..207dfbbdbd98 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "در حال ضبط"), ("Directory", "مسیر"), ("Automatically record incoming sessions", "ضبط خودکار جلسات ورودی"), + ("Automatically record outgoing sessions", ""), ("Change", "تغییر"), ("Start session recording", "شروع ضبط جلسه"), ("Stop session recording", "توقف ضبط جلسه"), diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 0fd093688b1f..9844167404ab 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Enregistrement"), ("Directory", "Répertoire"), ("Automatically record incoming sessions", "Enregistrement automatique des sessions entrantes"), + ("Automatically record outgoing sessions", ""), ("Change", "Modifier"), ("Start session recording", "Commencer l'enregistrement"), ("Stop session recording", "Stopper l'enregistrement"), diff --git a/src/lang/he.rs b/src/lang/he.rs index e36252afe903..408829b6c641 100644 --- a/src/lang/he.rs +++ b/src/lang/he.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", ""), ("Directory", ""), ("Automatically record incoming sessions", ""), + ("Automatically record outgoing sessions", ""), ("Change", ""), ("Start session recording", ""), ("Stop session recording", ""), diff --git a/src/lang/hr.rs b/src/lang/hr.rs index e2480eb63502..b9f9409fc394 100644 --- a/src/lang/hr.rs +++ b/src/lang/hr.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Snimanje"), ("Directory", "Mapa"), ("Automatically record incoming sessions", "Automatski snimi dolazne sesije"), + ("Automatically record outgoing sessions", ""), ("Change", "Promijeni"), ("Start session recording", "Započni snimanje sesije"), ("Stop session recording", "Zaustavi snimanje sesije"), diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 00c56edfbcad..e9caf1917f45 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Felvétel"), ("Directory", "Könyvtár"), ("Automatically record incoming sessions", "A bejövő munkamenetek automatikus rögzítése"), + ("Automatically record outgoing sessions", ""), ("Change", "Változtatás"), ("Start session recording", "Munkamenet rögzítés indítása"), ("Stop session recording", "Munkamenet rögzítés leállítása"), diff --git a/src/lang/id.rs b/src/lang/id.rs index 2f95411f0823..52c1741915e0 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Perekaman"), ("Directory", "Direktori"), ("Automatically record incoming sessions", "Otomatis merekam sesi masuk"), + ("Automatically record outgoing sessions", ""), ("Change", "Ubah"), ("Start session recording", "Mulai sesi perekaman"), ("Stop session recording", "Hentikan sesi perekaman"), diff --git a/src/lang/it.rs b/src/lang/it.rs index baaa1d10d31e..4f85db09a150 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Registrazione"), ("Directory", "Cartella"), ("Automatically record incoming sessions", "Registra automaticamente le sessioni in entrata"), + ("Automatically record outgoing sessions", ""), ("Change", "Modifica"), ("Start session recording", "Inizia registrazione sessione"), ("Stop session recording", "Ferma registrazione sessione"), diff --git a/src/lang/ja.rs b/src/lang/ja.rs index dbc40b789796..0bca730dc7c2 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "録画"), ("Directory", "ディレクトリ"), ("Automatically record incoming sessions", "受信したセッションを自動で記録する"), + ("Automatically record outgoing sessions", ""), ("Change", "変更"), ("Start session recording", "セッションの録画を開始"), ("Stop session recording", "セッションの録画を停止"), diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 966ac90097e8..dc9a0a69d595 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "녹화"), ("Directory", "경로"), ("Automatically record incoming sessions", "들어오는 세션을 자동으로 녹화"), + ("Automatically record outgoing sessions", ""), ("Change", "변경"), ("Start session recording", "세션 녹화 시작"), ("Stop session recording", "세션 녹화 중지"), diff --git a/src/lang/kz.rs b/src/lang/kz.rs index d62bbc393bf8..9ea54b975407 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", ""), ("Directory", ""), ("Automatically record incoming sessions", ""), + ("Automatically record outgoing sessions", ""), ("Change", ""), ("Start session recording", ""), ("Stop session recording", ""), diff --git a/src/lang/lt.rs b/src/lang/lt.rs index bc7d856b1e8e..df795401b762 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Įrašymas"), ("Directory", "Katalogas"), ("Automatically record incoming sessions", "Automatiškai įrašyti įeinančius seansus"), + ("Automatically record outgoing sessions", ""), ("Change", "Keisti"), ("Start session recording", "Pradėti seanso įrašinėjimą"), ("Stop session recording", "Sustabdyti seanso įrašinėjimą"), diff --git a/src/lang/lv.rs b/src/lang/lv.rs index bf67345c3a41..9a54daa005ba 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Ierakstīšana"), ("Directory", "Direktorija"), ("Automatically record incoming sessions", "Automātiski ierakstīt ienākošās sesijas"), + ("Automatically record outgoing sessions", ""), ("Change", "Mainīt"), ("Start session recording", "Sākt sesijas ierakstīšanu"), ("Stop session recording", "Apturēt sesijas ierakstīšanu"), diff --git a/src/lang/nb.rs b/src/lang/nb.rs index d91eb93258d5..4c8b1550c5e8 100644 --- a/src/lang/nb.rs +++ b/src/lang/nb.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Opptak"), ("Directory", "Mappe"), ("Automatically record incoming sessions", "Ta opp innkommende sesjoner automatisk"), + ("Automatically record outgoing sessions", ""), ("Change", "Rediger"), ("Start session recording", "Start sesjonsopptak"), ("Stop session recording", "Stopp sesjonsopptak"), diff --git a/src/lang/nl.rs b/src/lang/nl.rs index b780ce7daf72..2728f2edc899 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Opnemen"), ("Directory", "Map"), ("Automatically record incoming sessions", "Automatisch inkomende sessies opnemen"), + ("Automatically record outgoing sessions", ""), ("Change", "Wissel"), ("Start session recording", "Start de sessieopname"), ("Stop session recording", "Stop de sessieopname"), diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 8e9989084491..caf992978c9d 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Nagrywanie"), ("Directory", "Folder"), ("Automatically record incoming sessions", "Automatycznie nagrywaj sesje przychodzące"), + ("Automatically record outgoing sessions", ""), ("Change", "Zmień"), ("Start session recording", "Zacznij nagrywać sesję"), ("Stop session recording", "Zatrzymaj nagrywanie sesji"), diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 4c01d0b628a2..1194c11ec947 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", ""), ("Directory", ""), ("Automatically record incoming sessions", ""), + ("Automatically record outgoing sessions", ""), ("Change", ""), ("Start session recording", ""), ("Stop session recording", ""), diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 240fae99ae42..012ca3538fab 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Gravando"), ("Directory", "Diretório"), ("Automatically record incoming sessions", "Gravar automaticamente sessões de entrada"), + ("Automatically record outgoing sessions", ""), ("Change", "Alterar"), ("Start session recording", "Iniciar gravação da sessão"), ("Stop session recording", "Parar gravação da sessão"), diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 26858b134363..e09888c58a87 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Înregistrare"), ("Directory", "Director"), ("Automatically record incoming sessions", "Înregistrează automat sesiunile viitoare"), + ("Automatically record outgoing sessions", ""), ("Change", "Modifică"), ("Start session recording", "Începe înregistrarea"), ("Stop session recording", "Oprește înregistrarea"), diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 00ae750f27db..b547208d7ab7 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Запись"), ("Directory", "Папка"), ("Automatically record incoming sessions", "Автоматически записывать входящие сеансы"), + ("Automatically record outgoing sessions", ""), ("Change", "Изменить"), ("Start session recording", "Начать запись сеанса"), ("Stop session recording", "Остановить запись сеанса"), diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 28e322460df7..bb8e872c7cf6 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Nahrávanie"), ("Directory", "Adresár"), ("Automatically record incoming sessions", "Automaticky nahrávať prichádzajúce relácie"), + ("Automatically record outgoing sessions", ""), ("Change", "Zmeniť"), ("Start session recording", "Spustiť záznam relácie"), ("Stop session recording", "Zastaviť záznam relácie"), diff --git a/src/lang/sl.rs b/src/lang/sl.rs index e7f6248160be..1563a02c5737 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Snemanje"), ("Directory", "Imenik"), ("Automatically record incoming sessions", "Samodejno snemaj vhodne seje"), + ("Automatically record outgoing sessions", ""), ("Change", "Spremeni"), ("Start session recording", "Začni snemanje seje"), ("Stop session recording", "Ustavi snemanje seje"), diff --git a/src/lang/sq.rs b/src/lang/sq.rs index 58dc1ed5502d..ccc4b805fac7 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Regjistrimi"), ("Directory", "Direktoria"), ("Automatically record incoming sessions", "Regjistro automatikisht seancat hyrëse"), + ("Automatically record outgoing sessions", ""), ("Change", "Ndrysho"), ("Start session recording", "Fillo regjistrimin e sesionit"), ("Stop session recording", "Ndalo regjistrimin e sesionit"), diff --git a/src/lang/sr.rs b/src/lang/sr.rs index d38d20e9ef34..6df6b7ad83b4 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Snimanje"), ("Directory", "Direktorijum"), ("Automatically record incoming sessions", "Automatski snimaj dolazne sesije"), + ("Automatically record outgoing sessions", ""), ("Change", "Promeni"), ("Start session recording", "Započni snimanje sesije"), ("Stop session recording", "Zaustavi snimanje sesije"), diff --git a/src/lang/sv.rs b/src/lang/sv.rs index cfbac29b9034..2f95488bd42d 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Spelar in"), ("Directory", "Katalog"), ("Automatically record incoming sessions", "Spela in inkommande sessioner automatiskt"), + ("Automatically record outgoing sessions", ""), ("Change", "Byt"), ("Start session recording", "Starta inspelning"), ("Stop session recording", "Avsluta inspelning"), diff --git a/src/lang/template.rs b/src/lang/template.rs index 1c4606e94797..962506b99eca 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", ""), ("Directory", ""), ("Automatically record incoming sessions", ""), + ("Automatically record outgoing sessions", ""), ("Change", ""), ("Start session recording", ""), ("Stop session recording", ""), diff --git a/src/lang/th.rs b/src/lang/th.rs index 2dbb564e870e..673ebf319812 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "การบันทึก"), ("Directory", "ไดเรกทอรี่"), ("Automatically record incoming sessions", "บันทึกเซสชันขาเข้าโดยอัตโนมัติ"), + ("Automatically record outgoing sessions", ""), ("Change", "เปลี่ยน"), ("Start session recording", "เริ่มต้นการบันทึกเซสชัน"), ("Stop session recording", "หยุดการบันทึกเซสซัน"), diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 9bdbec110c60..046872878938 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Kayıt Ediliyor"), ("Directory", "Klasör"), ("Automatically record incoming sessions", "Gelen oturumları otomatik olarak kayıt et"), + ("Automatically record outgoing sessions", ""), ("Change", "Değiştir"), ("Start session recording", "Oturum kaydını başlat"), ("Stop session recording", "Oturum kaydını sonlandır"), diff --git a/src/lang/tw.rs b/src/lang/tw.rs index b37949206ace..25da736b8374 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "錄製"), ("Directory", "路徑"), ("Automatically record incoming sessions", "自動錄製連入的工作階段"), + ("Automatically record outgoing sessions", ""), ("Change", "變更"), ("Start session recording", "開始錄影"), ("Stop session recording", "停止錄影"), diff --git a/src/lang/uk.rs b/src/lang/uk.rs index 2bce8cc81735..97743266cad1 100644 --- a/src/lang/uk.rs +++ b/src/lang/uk.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Запис"), ("Directory", "Директорія"), ("Automatically record incoming sessions", "Автоматично записувати вхідні сеанси"), + ("Automatically record outgoing sessions", ""), ("Change", "Змінити"), ("Start session recording", "Розпочати запис сеансу"), ("Stop session recording", "Закінчити запис сеансу"), diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 9693d35c3c8e..b49aea67bb76 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Đang ghi hình"), ("Directory", "Thư mục"), ("Automatically record incoming sessions", "Tự động ghi những phiên kết nối vào"), + ("Automatically record outgoing sessions", ""), ("Change", "Thay đổi"), ("Start session recording", "Bắt đầu ghi hình phiên kết nối"), ("Stop session recording", "Dừng ghi hình phiên kết nối"), diff --git a/src/server/video_service.rs b/src/server/video_service.rs index aeff1911e017..55bfa08f0e69 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -487,6 +487,8 @@ fn run(vs: VideoService) -> ResultType<()> { let repeat_encode_max = 10; let mut encode_fail_counter = 0; let mut first_frame = true; + let capture_width = c.width; + let capture_height = c.height; while sp.ok() { #[cfg(windows)] @@ -576,6 +578,8 @@ fn run(vs: VideoService) -> ResultType<()> { recorder.clone(), &mut encode_fail_counter, &mut first_frame, + capture_width, + capture_height, )?; frame_controller.set_send(now, send_conn_ids); } @@ -632,6 +636,8 @@ fn run(vs: VideoService) -> ResultType<()> { recorder.clone(), &mut encode_fail_counter, &mut first_frame, + capture_width, + capture_height, )?; frame_controller.set_send(now, send_conn_ids); } @@ -722,7 +728,13 @@ fn setup_encoder( ); Encoder::set_fallback(&encoder_cfg); let codec_format = Encoder::negotiated_codec(); - let recorder = get_recorder(c.width, c.height, &codec_format, record_incoming); + let recorder = get_recorder( + c.width, + c.height, + &codec_format, + record_incoming, + display_idx, + ); let use_i444 = Encoder::use_i444(&encoder_cfg); let encoder = Encoder::new(encoder_cfg.clone(), use_i444)?; Ok((encoder, encoder_cfg, codec_format, use_i444, recorder)) @@ -809,6 +821,7 @@ fn get_recorder( height: usize, codec_format: &CodecFormat, record_incoming: bool, + display: usize, ) -> Arc>> { #[cfg(windows)] let root = crate::platform::is_root(); @@ -828,10 +841,7 @@ fn get_recorder( server: true, id: Config::get_id(), dir: crate::ui_interface::video_save_directory(root), - filename: "".to_owned(), - width, - height, - format: codec_format.clone(), + display, tx, }) .map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r)))) @@ -910,6 +920,8 @@ fn handle_one_frame( recorder: Arc>>, encode_fail_counter: &mut usize, first_frame: &mut bool, + width: usize, + height: usize, ) -> ResultType> { sp.snapshot(|sps| { // so that new sub and old sub share the same encoder after switch @@ -933,7 +945,7 @@ fn handle_one_frame( .lock() .unwrap() .as_mut() - .map(|r| r.write_message(&msg)); + .map(|r| r.write_message(&msg, width, height)); send_conn_ids = sp.send_video_frame(msg); } Err(e) => { diff --git a/src/ui/header.tis b/src/ui/header.tis index 36aa624b48f4..3116f1f542fa 100644 --- a/src/ui/header.tis +++ b/src/ui/header.tis @@ -301,26 +301,12 @@ class Header: Reactor.Component { } event click $(span#recording) (_, me) { - recording = !recording; header.update(); - handler.record_status(recording); - // 0 is just a dummy value. It will be ignored by the handler. - if (recording) { - handler.refresh_video(0); - if (handler.version_cmp(pi.version, '1.2.4') >= 0) handler.record_screen(recording, pi.current_display, display_width, display_height); - } - else { - handler.record_screen(recording, pi.current_display, display_width, display_height); - } + handler.record_screen(!recording) } event click $(#screen) (_, me) { if (pi.current_display == me.index) return; - if (recording) { - recording = false; - handler.record_screen(false, pi.current_display, display_width, display_height); - handler.record_status(false); - } handler.switch_display(me.index); } @@ -518,6 +504,7 @@ if (!(is_file_transfer || is_port_forward)) { handler.updatePi = function(v) { pi = v; + recording = handler.is_recording(); header.update(); if (is_port_forward) { view.windowState = View.WINDOW_MINIMIZED; @@ -682,3 +669,8 @@ handler.setConnectionType = function(secured, direct) { direct_connection: direct, }); } + +handler.updateRecordStatus = function(status) { + recording = status; + header.update(); +} \ No newline at end of file diff --git a/src/ui/index.tis b/src/ui/index.tis index b10df76233eb..daf2c10201e8 100644 --- a/src/ui/index.tis +++ b/src/ui/index.tis @@ -253,10 +253,12 @@ class Enhancements: Reactor.Component { var root_dir = show_root_dir ? handler.video_save_directory(true) : ""; var ts0 = handler.get_option("enable-record-session") == '' ? { checked: true } : {}; var ts1 = handler.get_option("allow-auto-record-incoming") == 'Y' ? { checked: true } : {}; + var ts2 = handler.get_option("allow-auto-record-outgoing") == 'Y' ? { checked: true } : {}; msgbox("custom-recording", translate('Recording'),
{translate('Enable recording session')}
{translate('Automatically record incoming sessions')}
+
{translate('Automatically record outgoing sessions')}
{show_root_dir ?
{translate("Incoming")}:  {root_dir}
: ""}
{translate(show_root_dir ? "Outgoing" : "Directory")}:  {user_dir}
@@ -267,6 +269,7 @@ class Enhancements: Reactor.Component { if (!res) return; handler.set_option("enable-record-session", res.enable_record_session ? '' : 'N'); handler.set_option("allow-auto-record-incoming", res.auto_record_incoming ? 'Y' : ''); + handler.set_option("allow-auto-record-outgoing", res.auto_record_outgoing ? 'Y' : ''); handler.set_option("video-save-directory", $(#folderPath).text); }); } diff --git a/src/ui/remote.rs b/src/ui/remote.rs index baf9d1f64512..f0829e75eee4 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -335,6 +335,10 @@ impl InvokeUiSession for SciterHandler { } fn next_rgba(&self, _display: usize) {} + + fn update_record_status(&self, start: bool) { + self.call("updateRecordStatus", &make_args!(start)); + } } pub struct SciterSession(Session); @@ -478,8 +482,7 @@ impl sciter::EventHandler for SciterSession { fn save_image_quality(String); fn save_custom_image_quality(i32); fn refresh_video(i32); - fn record_screen(bool, i32, i32, i32); - fn record_status(bool); + fn record_screen(bool); fn get_toggle_option(String); fn is_privacy_mode_supported(); fn toggle_option(String); @@ -496,6 +499,7 @@ impl sciter::EventHandler for SciterSession { fn close_voice_call(); fn version_cmp(String, String); fn set_selected_windows_session_id(String); + fn is_recording(); } } diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index ce28b78d8e95..00e9459db47f 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -389,22 +389,17 @@ impl Session { self.send(Data::Message(LoginConfigHandler::refresh())); } - pub fn record_screen(&self, start: bool, display: i32, w: i32, h: i32) { - self.send(Data::RecordScreen( - start, - display as usize, - w, - h, - self.get_id(), - )); - } - - pub fn record_status(&self, status: bool) { + pub fn record_screen(&self, start: bool) { let mut misc = Misc::new(); - misc.set_client_record_status(status); + misc.set_client_record_status(start); let mut msg = Message::new(); msg.set_misc(misc); self.send(Data::Message(msg)); + self.send(Data::RecordScreen(start)); + } + + pub fn is_recording(&self) -> bool { + self.lc.read().unwrap().record } pub fn save_custom_image_quality(&self, custom_image_quality: i32) { @@ -1557,6 +1552,7 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default { fn set_current_display(&self, disp_idx: i32); #[cfg(feature = "flutter")] fn is_multi_ui_session(&self) -> bool; + fn update_record_status(&self, start: bool); } impl Deref for Session {