4 次代码提交 547da31095 ... 6159449eba

作者 SHA1 备注 提交日期
  21pages 6159449eba move option `video-save-directory` and `allow-auto-record-outgoing` to local (#9715) 17 小时之前
  Andrzej Rudnik 6088920f8d Update pl.rs (#9713) 1 天之前
  21pages e8187588c1 auto record outgoing (#9711) 1 天之前
  solokot 289076aa70 Update ru.rs (#9712) 1 天之前
共有 67 个文件被更改,包括 480 次插入340 次删除
  1. 1 1
      Cargo.lock
  2. 1 0
      flutter/lib/consts.dart
  3. 52 42
      flutter/lib/desktop/pages/desktop_setting_page.dart
  4. 2 1
      flutter/lib/desktop/pages/remote_page.dart
  5. 1 2
      flutter/lib/desktop/widgets/remote_toolbar.dart
  6. 8 1
      flutter/lib/mobile/pages/remote_page.dart
  7. 50 23
      flutter/lib/mobile/pages/settings_page.dart
  8. 12 61
      flutter/lib/models/model.dart
  9. 22 1
      libs/hbb_common/src/config.rs
  10. 0 1
      libs/scrap/Cargo.toml
  11. 8 4
      libs/scrap/src/common/codec.rs
  12. 29 0
      libs/scrap/src/common/mod.rs
  13. 131 103
      libs/scrap/src/common/record.rs
  14. 42 30
      src/client.rs
  15. 3 5
      src/client/io_loop.rs
  16. 5 2
      src/flutter.rs
  17. 6 10
      src/flutter_ffi.rs
  18. 1 1
      src/ipc.rs
  19. 1 0
      src/lang/ar.rs
  20. 1 0
      src/lang/be.rs
  21. 1 0
      src/lang/bg.rs
  22. 1 0
      src/lang/ca.rs
  23. 2 1
      src/lang/cn.rs
  24. 1 0
      src/lang/cs.rs
  25. 1 0
      src/lang/da.rs
  26. 1 0
      src/lang/de.rs
  27. 1 0
      src/lang/el.rs
  28. 1 0
      src/lang/eo.rs
  29. 1 0
      src/lang/es.rs
  30. 1 0
      src/lang/et.rs
  31. 1 0
      src/lang/eu.rs
  32. 1 0
      src/lang/fa.rs
  33. 1 0
      src/lang/fr.rs
  34. 1 0
      src/lang/he.rs
  35. 1 0
      src/lang/hr.rs
  36. 1 0
      src/lang/hu.rs
  37. 1 0
      src/lang/id.rs
  38. 1 0
      src/lang/it.rs
  39. 1 0
      src/lang/ja.rs
  40. 1 0
      src/lang/ko.rs
  41. 1 0
      src/lang/kz.rs
  42. 1 0
      src/lang/lt.rs
  43. 1 0
      src/lang/lv.rs
  44. 1 0
      src/lang/nb.rs
  45. 1 0
      src/lang/nl.rs
  46. 9 8
      src/lang/pl.rs
  47. 1 0
      src/lang/pt_PT.rs
  48. 1 0
      src/lang/ptbr.rs
  49. 1 0
      src/lang/ro.rs
  50. 6 5
      src/lang/ru.rs
  51. 1 0
      src/lang/sk.rs
  52. 1 0
      src/lang/sl.rs
  53. 1 0
      src/lang/sq.rs
  54. 1 0
      src/lang/sr.rs
  55. 1 0
      src/lang/sv.rs
  56. 1 0
      src/lang/template.rs
  57. 1 0
      src/lang/th.rs
  58. 1 0
      src/lang/tr.rs
  59. 1 0
      src/lang/tw.rs
  60. 1 0
      src/lang/uk.rs
  61. 1 0
      src/lang/vn.rs
  62. 18 6
      src/server/video_service.rs
  63. 7 15
      src/ui/header.tis
  64. 4 1
      src/ui/index.tis
  65. 6 2
      src/ui/remote.rs
  66. 6 2
      src/ui_interface.rs
  67. 8 12
      src/ui_session_interface.rs

+ 1 - 1
Cargo.lock

@@ -3051,7 +3051,7 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
 [[package]]
 [[package]]
 name = "hwcodec"
 name = "hwcodec"
 version = "0.7.0"
 version = "0.7.0"
-source = "git+https://github.com/rustdesk-org/hwcodec#f74410edec91435252b8394c38f8eeca87ad2a26"
+source = "git+https://github.com/rustdesk-org/hwcodec#8bbd05bb300ad07cc345356ad85570f9ea99fbfa"
 dependencies = [
 dependencies = [
  "bindgen 0.59.2",
  "bindgen 0.59.2",
  "cc",
  "cc",

+ 1 - 0
flutter/lib/consts.dart

@@ -89,6 +89,7 @@ const String kOptionAllowAutoDisconnect = "allow-auto-disconnect";
 const String kOptionAutoDisconnectTimeout = "auto-disconnect-timeout";
 const String kOptionAutoDisconnectTimeout = "auto-disconnect-timeout";
 const String kOptionEnableHwcodec = "enable-hwcodec";
 const String kOptionEnableHwcodec = "enable-hwcodec";
 const String kOptionAllowAutoRecordIncoming = "allow-auto-record-incoming";
 const String kOptionAllowAutoRecordIncoming = "allow-auto-record-incoming";
+const String kOptionAllowAutoRecordOutgoing = "allow-auto-record-outgoing";
 const String kOptionVideoSaveDirectory = "video-save-directory";
 const String kOptionVideoSaveDirectory = "video-save-directory";
 const String kOptionAccessMode = "access-mode";
 const String kOptionAccessMode = "access-mode";
 const String kOptionEnableKeyboard = "enable-keyboard";
 const String kOptionEnableKeyboard = "enable-keyboard";

+ 52 - 42
flutter/lib/desktop/pages/desktop_setting_page.dart

@@ -575,12 +575,18 @@ class _GeneralState extends State<_General> {
       bool root_dir_exists = map['root_dir_exists']!;
       bool root_dir_exists = map['root_dir_exists']!;
       bool user_dir_exists = map['user_dir_exists']!;
       bool user_dir_exists = map['user_dir_exists']!;
       return _Card(title: 'Recording', children: [
       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,
+              isServer: false),
+        if (showRootDir && !bind.isOutgoingOnly())
           Row(
           Row(
             children: [
             children: [
-              Text('${translate("Incoming")}:'),
+              Text(
+                  '${translate(bind.isIncomingOnly() ? "Directory" : "Incoming")}:'),
               Expanded(
               Expanded(
                 child: GestureDetector(
                 child: GestureDetector(
                     onTap: root_dir_exists
                     onTap: root_dir_exists
@@ -597,45 +603,49 @@ class _GeneralState extends State<_General> {
               ),
               ),
             ],
             ],
           ).marginOnly(left: _kContentHMargin),
           ).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,
                         : 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.mainSetLocalOption(
+                                    key: kOptionVideoSaveDirectory,
+                                    value: selectedDirectory);
+                                setState(() {});
+                              }
+                            },
+                      child: Text(translate('Change')))
+                  .marginOnly(left: 5),
+            ],
+          ).marginOnly(left: _kContentHMargin),
       ]);
       ]);
     });
     });
   }
   }

+ 2 - 1
flutter/lib/desktop/pages/remote_page.dart

@@ -115,6 +115,8 @@ class _RemotePageState extends State<RemotePage>
     _ffi.imageModel.addCallbackOnFirstImage((String peerId) {
     _ffi.imageModel.addCallbackOnFirstImage((String peerId) {
       showKBLayoutTypeChooserIfNeeded(
       showKBLayoutTypeChooserIfNeeded(
           _ffi.ffiModel.pi.platform, _ffi.dialogManager);
           _ffi.ffiModel.pi.platform, _ffi.dialogManager);
+      _ffi.recordingModel
+          .updateStatus(bind.sessionGetIsRecording(sessionId: _ffi.sessionId));
     });
     });
     _ffi.start(
     _ffi.start(
       widget.id,
       widget.id,
@@ -253,7 +255,6 @@ class _RemotePageState extends State<RemotePage>
     _ffi.dialogManager.hideMobileActionsOverlay();
     _ffi.dialogManager.hideMobileActionsOverlay();
     _ffi.imageModel.disposeImage();
     _ffi.imageModel.disposeImage();
     _ffi.cursorModel.disposeImages();
     _ffi.cursorModel.disposeImages();
-    _ffi.recordingModel.onClose();
     _rawKeyFocusNode.dispose();
     _rawKeyFocusNode.dispose();
     await _ffi.close(closeSession: closeSession);
     await _ffi.close(closeSession: closeSession);
     _timer?.cancel();
     _timer?.cancel();

+ 1 - 2
flutter/lib/desktop/widgets/remote_toolbar.dart

@@ -1924,8 +1924,7 @@ class _RecordMenu extends StatelessWidget {
     var ffi = Provider.of<FfiModel>(context);
     var ffi = Provider.of<FfiModel>(context);
     var recordingModel = Provider.of<RecordingModel>(context);
     var recordingModel = Provider.of<RecordingModel>(context);
     final visible =
     final visible =
-        (recordingModel.start || ffi.permissions['recording'] != false) &&
-            ffi.pi.currentDisplay != kAllDisplayValue;
+        (recordingModel.start || ffi.permissions['recording'] != false);
     if (!visible) return Offstage();
     if (!visible) return Offstage();
     return _IconMenuButton(
     return _IconMenuButton(
       assetName: 'assets/rec.svg',
       assetName: 'assets/rec.svg',

+ 8 - 1
flutter/lib/mobile/pages/remote_page.dart

@@ -92,6 +92,13 @@ class _RemotePageState extends State<RemotePage> {
     gFFI.chatModel
     gFFI.chatModel
         .changeCurrentKey(MessageKey(widget.id, ChatModel.clientModeID));
         .changeCurrentKey(MessageKey(widget.id, ChatModel.clientModeID));
     _blockableOverlayState.applyFfi(gFFI);
     _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
   @override
@@ -207,7 +214,7 @@ class _RemotePageState extends State<RemotePage> {
   }
   }
 
 
   void _handleNonIOSSoftKeyboardInput(String newValue) {
   void _handleNonIOSSoftKeyboardInput(String newValue) {
-        _composingTimer?.cancel();
+    _composingTimer?.cancel();
     if (_textController.value.isComposingRangeValid) {
     if (_textController.value.isComposingRangeValid) {
       _composingTimer = Timer(Duration(milliseconds: 25), () {
       _composingTimer = Timer(Duration(milliseconds: 25), () {
         _handleNonIOSSoftKeyboardInput(_textController.value.text);
         _handleNonIOSSoftKeyboardInput(_textController.value.text);

+ 50 - 23
flutter/lib/mobile/pages/settings_page.dart

@@ -79,6 +79,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
   var _enableRecordSession = false;
   var _enableRecordSession = false;
   var _enableHardwareCodec = false;
   var _enableHardwareCodec = false;
   var _autoRecordIncomingSession = false;
   var _autoRecordIncomingSession = false;
+  var _autoRecordOutgoingSession = false;
   var _allowAutoDisconnect = false;
   var _allowAutoDisconnect = false;
   var _localIP = "";
   var _localIP = "";
   var _directAccessPort = "";
   var _directAccessPort = "";
@@ -104,6 +105,8 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
         bind.mainGetOptionSync(key: kOptionEnableHwcodec));
         bind.mainGetOptionSync(key: kOptionEnableHwcodec));
     _autoRecordIncomingSession = option2bool(kOptionAllowAutoRecordIncoming,
     _autoRecordIncomingSession = option2bool(kOptionAllowAutoRecordIncoming,
         bind.mainGetOptionSync(key: kOptionAllowAutoRecordIncoming));
         bind.mainGetOptionSync(key: kOptionAllowAutoRecordIncoming));
+    _autoRecordOutgoingSession = option2bool(kOptionAllowAutoRecordOutgoing,
+        bind.mainGetLocalOption(key: kOptionAllowAutoRecordOutgoing));
     _localIP = bind.mainGetOptionSync(key: 'local-ip-addr');
     _localIP = bind.mainGetOptionSync(key: 'local-ip-addr');
     _directAccessPort = bind.mainGetOptionSync(key: kOptionDirectAccessPort);
     _directAccessPort = bind.mainGetOptionSync(key: kOptionDirectAccessPort);
     _allowAutoDisconnect = option2bool(kOptionAllowAutoDisconnect,
     _allowAutoDisconnect = option2bool(kOptionAllowAutoDisconnect,
@@ -231,6 +234,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     Provider.of<FfiModel>(context);
     Provider.of<FfiModel>(context);
     final outgoingOnly = bind.isOutgoingOnly();
     final outgoingOnly = bind.isOutgoingOnly();
+    final incommingOnly = bind.isIncomingOnly();
     final customClientSection = CustomSettingsSection(
     final customClientSection = CustomSettingsSection(
         child: Column(
         child: Column(
       children: [
       children: [
@@ -674,32 +678,55 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
                     },
                     },
             ),
             ),
           ]),
           ]),
-        if (isAndroid && !outgoingOnly)
+        if (isAndroid)
           SettingsSection(
           SettingsSection(
             title: Text(translate("Recording")),
             title: Text(translate("Recording")),
             tiles: [
             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.mainSetLocalOption(
+                              key: kOptionAllowAutoRecordOutgoing,
+                              value: bool2option(
+                                  kOptionAllowAutoRecordOutgoing, v));
+                          final newValue = option2bool(
+                              kOptionAllowAutoRecordOutgoing,
+                              bind.mainGetLocalOption(
+                                  key: kOptionAllowAutoRecordOutgoing));
+                          setState(() {
+                            _autoRecordOutgoingSession = newValue;
+                          });
+                        },
+                ),
+              SettingsTile(
+                title: Text(translate("Directory")),
+                description: Text(bind.mainVideoSaveDirectory(root: false)),
               ),
               ),
             ],
             ],
           ),
           ),

+ 12 - 61
flutter/lib/models/model.dart

@@ -397,6 +397,10 @@ class FfiModel with ChangeNotifier {
         if (isWeb) {
         if (isWeb) {
           parent.target?.fileModel.onSelectedFiles(evt);
           parent.target?.fileModel.onSelectedFiles(evt);
         }
         }
+      } else if (name == "record_status") {
+        if (desktopType == DesktopType.remote || isMobile) {
+          parent.target?.recordingModel.updateStatus(evt['start'] == 'true');
+        }
       } else {
       } else {
         debugPrint('Event is not handled in the fixed branch: $name');
         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) {
     if (!_pi.isSupportMultiUiSession || _pi.currentDisplay == display) {
       handleResolutions(peerId, evt['resolutions']);
       handleResolutions(peerId, evt['resolutions']);
     }
     }
@@ -1135,8 +1138,6 @@ class FfiModel with ChangeNotifier {
   // Directly switch to the new display without waiting for the response.
   // Directly switch to the new display without waiting for the response.
   switchToNewDisplay(int display, SessionID sessionId, String peerId,
   switchToNewDisplay(int display, SessionID sessionId, String peerId,
       {bool updateCursorPos = false}) {
       {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
     // no need to wait for the response
     pi.currentDisplay = display;
     pi.currentDisplay = display;
     updateCurDisplay(sessionId, updateCursorPos: updateCursorPos);
     updateCurDisplay(sessionId, updateCursorPos: updateCursorPos);
@@ -2342,25 +2343,7 @@ class RecordingModel with ChangeNotifier {
   WeakReference<FFI> parent;
   WeakReference<FFI> parent;
   RecordingModel(this.parent);
   RecordingModel(this.parent);
   bool _start = false;
   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 {
   toggle() async {
     if (isIOS) return;
     if (isIOS) return;
@@ -2368,48 +2351,16 @@ class RecordingModel with ChangeNotifier {
     if (sessionId == null) return;
     if (sessionId == null) return;
     final pi = parent.target?.ffiModel.pi;
     final pi = parent.target?.ffiModel.pi;
     if (pi == null) return;
     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();
   }
   }
 }
 }
 
 

+ 22 - 1
libs/hbb_common/src/config.rs

@@ -965,6 +965,10 @@ impl Config {
         .unwrap_or_default()
         .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) {
     pub fn set_option(k: String, v: String) {
         if !is_option_can_save(&OVERWRITE_SETTINGS, &k, &DEFAULT_SETTINGS, &v) {
         if !is_option_can_save(&OVERWRITE_SETTINGS, &k, &DEFAULT_SETTINGS, &v) {
             return;
             return;
@@ -1558,6 +1562,21 @@ impl LocalConfig {
         .unwrap_or_default()
         .unwrap_or_default()
     }
     }
 
 
+    // Usually get_option should be used.
+    pub fn get_option_from_file(k: &str) -> String {
+        get_or(
+            &OVERWRITE_LOCAL_SETTINGS,
+            &Self::load().options,
+            &DEFAULT_LOCAL_SETTINGS,
+            k,
+        )
+        .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) {
     pub fn set_option(k: String, v: String) {
         if !is_option_can_save(&OVERWRITE_LOCAL_SETTINGS, &k, &DEFAULT_LOCAL_SETTINGS, &v) {
         if !is_option_can_save(&OVERWRITE_LOCAL_SETTINGS, &k, &DEFAULT_LOCAL_SETTINGS, &v) {
             return;
             return;
@@ -2198,6 +2217,7 @@ pub mod keys {
     pub const OPTION_AUTO_DISCONNECT_TIMEOUT: &str = "auto-disconnect-timeout";
     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_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_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_VIDEO_SAVE_DIRECTORY: &str = "video-save-directory";
     pub const OPTION_ENABLE_ABR: &str = "enable-abr";
     pub const OPTION_ENABLE_ABR: &str = "enable-abr";
     pub const OPTION_ALLOW_REMOVE_WALLPAPER: &str = "allow-remove-wallpaper";
     pub const OPTION_ALLOW_REMOVE_WALLPAPER: &str = "allow-remove-wallpaper";
@@ -2321,6 +2341,8 @@ pub mod keys {
         OPTION_DISABLE_GROUP_PANEL,
         OPTION_DISABLE_GROUP_PANEL,
         OPTION_PRE_ELEVATE_SERVICE,
         OPTION_PRE_ELEVATE_SERVICE,
         OPTION_ALLOW_REMOTE_CM_MODIFICATION,
         OPTION_ALLOW_REMOTE_CM_MODIFICATION,
+        OPTION_ALLOW_AUTO_RECORD_OUTGOING,
+        OPTION_VIDEO_SAVE_DIRECTORY,
     ];
     ];
     // DEFAULT_SETTINGS, OVERWRITE_SETTINGS
     // DEFAULT_SETTINGS, OVERWRITE_SETTINGS
     pub const KEYS_SETTINGS: &[&str] = &[
     pub const KEYS_SETTINGS: &[&str] = &[
@@ -2342,7 +2364,6 @@ pub mod keys {
         OPTION_AUTO_DISCONNECT_TIMEOUT,
         OPTION_AUTO_DISCONNECT_TIMEOUT,
         OPTION_ALLOW_ONLY_CONN_WINDOW_OPEN,
         OPTION_ALLOW_ONLY_CONN_WINDOW_OPEN,
         OPTION_ALLOW_AUTO_RECORD_INCOMING,
         OPTION_ALLOW_AUTO_RECORD_INCOMING,
-        OPTION_VIDEO_SAVE_DIRECTORY,
         OPTION_ENABLE_ABR,
         OPTION_ENABLE_ABR,
         OPTION_ALLOW_REMOVE_WALLPAPER,
         OPTION_ALLOW_REMOVE_WALLPAPER,
         OPTION_ALLOW_ALWAYS_SOFTWARE_RENDER,
         OPTION_ALLOW_ALWAYS_SOFTWARE_RENDER,

+ 0 - 1
libs/scrap/Cargo.toml

@@ -62,4 +62,3 @@ gstreamer-video = { version = "0.16", optional = true }
 git = "https://github.com/rustdesk-org/hwcodec"
 git = "https://github.com/rustdesk-org/hwcodec"
 optional = true
 optional = true
 
 
-

+ 8 - 4
libs/scrap/src/common/codec.rs

@@ -15,7 +15,7 @@ use crate::{
     aom::{self, AomDecoder, AomEncoder, AomEncoderConfig},
     aom::{self, AomDecoder, AomEncoder, AomEncoderConfig},
     common::GoogleImage,
     common::GoogleImage,
     vpxcodec::{self, VpxDecoder, VpxDecoderConfig, VpxEncoder, VpxEncoderConfig, VpxVideoCodecId},
     vpxcodec::{self, VpxDecoder, VpxDecoderConfig, VpxEncoder, VpxEncoderConfig, VpxVideoCodecId},
-    CodecFormat, EncodeInput, EncodeYuvFormat, ImageRgb,
+    CodecFormat, EncodeInput, EncodeYuvFormat, ImageRgb, ImageTexture,
 };
 };
 
 
 use hbb_common::{
 use hbb_common::{
@@ -623,7 +623,7 @@ impl Decoder {
         &mut self,
         &mut self,
         frame: &video_frame::Union,
         frame: &video_frame::Union,
         rgb: &mut ImageRgb,
         rgb: &mut ImageRgb,
-        _texture: &mut *mut c_void,
+        _texture: &mut ImageTexture,
         _pixelbuffer: &mut bool,
         _pixelbuffer: &mut bool,
         chroma: &mut Option<Chroma>,
         chroma: &mut Option<Chroma>,
     ) -> ResultType<bool> {
     ) -> ResultType<bool> {
@@ -777,12 +777,16 @@ impl Decoder {
     fn handle_vram_video_frame(
     fn handle_vram_video_frame(
         decoder: &mut VRamDecoder,
         decoder: &mut VRamDecoder,
         frames: &EncodedVideoFrames,
         frames: &EncodedVideoFrames,
-        texture: &mut *mut c_void,
+        texture: &mut ImageTexture,
     ) -> ResultType<bool> {
     ) -> ResultType<bool> {
         let mut ret = false;
         let mut ret = false;
         for h26x in frames.frames.iter() {
         for h26x in frames.frames.iter() {
             for image in decoder.decode(&h26x.data)? {
             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;
                 ret = true;
             }
             }
         }
         }

+ 29 - 0
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]
 #[inline]
 pub fn would_block_if_equal(old: &mut Vec<u8>, b: &[u8]) -> std::io::Result<()> {
 pub fn would_block_if_equal(old: &mut Vec<u8>, b: &[u8]) -> std::io::Result<()> {
     // does this really help?
     // 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 {
 impl From<&CodecName> for CodecFormat {
     fn from(value: &CodecName) -> Self {
     fn from(value: &CodecName) -> Self {
         match value {
         match value {

+ 131 - 103
libs/scrap/src/common/record.rs

@@ -25,22 +25,28 @@ pub struct RecorderContext {
     pub server: bool,
     pub server: bool,
     pub id: String,
     pub id: String,
     pub dir: String,
     pub dir: String,
+    pub display: usize,
+    pub tx: Option<Sender<RecordState>>,
+}
+
+#[derive(Debug, Clone)]
+pub struct RecorderContext2 {
     pub filename: String,
     pub filename: String,
     pub width: usize,
     pub width: usize,
     pub height: usize,
     pub height: usize,
     pub format: CodecFormat,
     pub format: CodecFormat,
-    pub tx: Option<Sender<RecordState>>,
 }
 }
 
 
-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()
             + &chrono::Local::now().format("_%Y%m%d%H%M%S%3f_").to_string()
+            + &format!("display{}_", ctx.display)
             + &self.format.to_string().to_lowercase()
             + &self.format.to_string().to_lowercase()
             + if self.format == CodecFormat::VP9
             + if self.format == CodecFormat::VP9
                 || self.format == CodecFormat::VP8
                 || self.format == CodecFormat::VP8
@@ -50,11 +56,10 @@ impl RecorderContext {
             } else {
             } else {
                 ".mp4"
                 ".mp4"
             };
             };
-        self.filename = PathBuf::from(&self.dir)
+        self.filename = PathBuf::from(&ctx.dir)
             .join(file)
             .join(file)
             .to_string_lossy()
             .to_string_lossy()
             .to_string();
             .to_string();
-        log::info!("video will save to {}", self.filename);
         Ok(())
         Ok(())
     }
     }
 }
 }
@@ -63,7 +68,7 @@ unsafe impl Send for Recorder {}
 unsafe impl Sync for Recorder {}
 unsafe impl Sync for Recorder {}
 
 
 pub trait RecorderApi {
 pub trait RecorderApi {
-    fn new(ctx: RecorderContext) -> ResultType<Self>
+    fn new(ctx: RecorderContext, ctx2: RecorderContext2) -> ResultType<Self>
     where
     where
         Self: Sized;
         Self: Sized;
     fn write_video(&mut self, frame: &EncodedVideoFrame) -> bool;
     fn write_video(&mut self, frame: &EncodedVideoFrame) -> bool;
@@ -78,13 +83,15 @@ pub enum RecordState {
 }
 }
 
 
 pub struct Recorder {
 pub struct Recorder {
-    pub inner: Box<dyn RecorderApi>,
+    pub inner: Option<Box<dyn RecorderApi>>,
     ctx: RecorderContext,
     ctx: RecorderContext,
+    ctx2: Option<RecorderContext2>,
     pts: Option<i64>,
     pts: Option<i64>,
+    check_failed: bool,
 }
 }
 
 
 impl Deref for Recorder {
 impl Deref for Recorder {
-    type Target = Box<dyn RecorderApi>;
+    type Target = Option<Box<dyn RecorderApi>>;
 
 
     fn deref(&self) -> &Self::Target {
     fn deref(&self) -> &Self::Target {
         &self.inner
         &self.inner
@@ -98,114 +105,123 @@ impl DerefMut for Recorder {
 }
 }
 
 
 impl Recorder {
 impl Recorder {
-    pub fn new(mut ctx: RecorderContext) -> ResultType<Self> {
-        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<Self> {
+        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(())
         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(message::Union::VideoFrame(vf)) = &msg.union {
             if let Some(frame) = &vf.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 {
         match frame {
             video_frame::Union::Vp8s(vp8s) => {
             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() {
                 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) => {
             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() {
                 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) => {
             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() {
                 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")]
             #[cfg(feature = "hwcodec")]
             video_frame::Union::H264s(h264s) => {
             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() {
                 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")]
             #[cfg(feature = "hwcodec")]
             video_frame::Union::H265s(h265s) => {
             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() {
                 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"),
             _ => bail!("unsupported frame type"),
@@ -214,13 +230,21 @@ impl Recorder {
         Ok(())
         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
         // 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;
         let old_pts = self.pts;
         self.pts = Some(pts);
         self.pts = Some(pts);
         if old_pts.clone().unwrap_or_default() > pts {
         if old_pts.clone().unwrap_or_default() > pts {
             log::info!("pts {:?} -> {}, change record filename", old_pts, 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(())
         Ok(())
     }
     }
@@ -234,21 +258,22 @@ struct WebmRecorder {
     vt: VideoTrack,
     vt: VideoTrack,
     webm: Option<Segment<Writer<File>>>,
     webm: Option<Segment<Writer<File>>>,
     ctx: RecorderContext,
     ctx: RecorderContext,
+    ctx2: RecorderContext2,
     key: bool,
     key: bool,
     written: bool,
     written: bool,
     start: Instant,
     start: Instant,
 }
 }
 
 
 impl RecorderApi for WebmRecorder {
 impl RecorderApi for WebmRecorder {
-    fn new(ctx: RecorderContext) -> ResultType<Self> {
+    fn new(ctx: RecorderContext, ctx2: RecorderContext2) -> ResultType<Self> {
         let out = match {
         let out = match {
             OpenOptions::new()
             OpenOptions::new()
                 .write(true)
                 .write(true)
                 .create_new(true)
                 .create_new(true)
-                .open(&ctx.filename)
+                .open(&ctx2.filename)
         } {
         } {
             Ok(file) => file,
             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()),
             Err(e) => return Err(e.into()),
         };
         };
         let mut webm = match mux::Segment::new(mux::Writer::new(out)) {
         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"),
             None => bail!("Failed to create webm mux"),
         };
         };
         let vt = webm.add_video_track(
         let vt = webm.add_video_track(
-            ctx.width as _,
-            ctx.height as _,
+            ctx2.width as _,
+            ctx2.height as _,
             None,
             None,
-            if ctx.format == CodecFormat::VP9 {
+            if ctx2.format == CodecFormat::VP9 {
                 mux::VideoCodecId::VP9
                 mux::VideoCodecId::VP9
-            } else if ctx.format == CodecFormat::VP8 {
+            } else if ctx2.format == CodecFormat::VP8 {
                 mux::VideoCodecId::VP8
                 mux::VideoCodecId::VP8
             } else {
             } else {
                 mux::VideoCodecId::AV1
                 mux::VideoCodecId::AV1
             },
             },
         );
         );
-        if ctx.format == CodecFormat::AV1 {
+        if ctx2.format == CodecFormat::AV1 {
             // [129, 8, 12, 0] in 3.6.0, but zero works
             // [129, 8, 12, 0] in 3.6.0, but zero works
             let codec_private = vec![0, 0, 0, 0];
             let codec_private = vec![0, 0, 0, 0];
             if !webm.set_codec_private(vt.track_number(), &codec_private) {
             if !webm.set_codec_private(vt.track_number(), &codec_private) {
@@ -278,6 +303,7 @@ impl RecorderApi for WebmRecorder {
             vt,
             vt,
             webm: Some(webm),
             webm: Some(webm),
             ctx,
             ctx,
+            ctx2,
             key: false,
             key: false,
             written: false,
             written: false,
             start: Instant::now(),
             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 _ = std::mem::replace(&mut self.webm, None).map_or(false, |webm| webm.finalize(None));
         let mut state = RecordState::WriteTail;
         let mut state = RecordState::WriteTail;
         if !self.written || self.start.elapsed().as_secs() < MIN_SECS {
         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;
             state = RecordState::RemoveFile;
         }
         }
         self.ctx.tx.as_ref().map(|tx| tx.send(state));
         self.ctx.tx.as_ref().map(|tx| tx.send(state));
@@ -318,6 +344,7 @@ impl Drop for WebmRecorder {
 struct HwRecorder {
 struct HwRecorder {
     muxer: Muxer,
     muxer: Muxer,
     ctx: RecorderContext,
     ctx: RecorderContext,
+    ctx2: RecorderContext2,
     written: bool,
     written: bool,
     key: bool,
     key: bool,
     start: Instant,
     start: Instant,
@@ -325,18 +352,19 @@ struct HwRecorder {
 
 
 #[cfg(feature = "hwcodec")]
 #[cfg(feature = "hwcodec")]
 impl RecorderApi for HwRecorder {
 impl RecorderApi for HwRecorder {
-    fn new(ctx: RecorderContext) -> ResultType<Self> {
+    fn new(ctx: RecorderContext, ctx2: RecorderContext2) -> ResultType<Self> {
         let muxer = Muxer::new(MuxContext {
         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 _,
             framerate: crate::hwcodec::DEFAULT_FPS as _,
         })
         })
         .map_err(|_| anyhow!("Failed to create hardware muxer"))?;
         .map_err(|_| anyhow!("Failed to create hardware muxer"))?;
         Ok(HwRecorder {
         Ok(HwRecorder {
             muxer,
             muxer,
             ctx,
             ctx,
+            ctx2,
             written: false,
             written: false,
             key: false,
             key: false,
             start: Instant::now(),
             start: Instant::now(),
@@ -365,7 +393,7 @@ impl Drop for HwRecorder {
         self.muxer.write_tail().ok();
         self.muxer.write_tail().ok();
         let mut state = RecordState::WriteTail;
         let mut state = RecordState::WriteTail;
         if !self.written || self.start.elapsed().as_secs() < MIN_SECS {
         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;
             state = RecordState::RemoveFile;
         }
         }
         self.ctx.tx.as_ref().map(|tx| tx.send(state));
         self.ctx.tx.as_ref().map(|tx| tx.send(state));

+ 42 - 30
src/client.rs

@@ -30,7 +30,6 @@ pub use file_trait::FileManager;
 #[cfg(not(feature = "flutter"))]
 #[cfg(not(feature = "flutter"))]
 #[cfg(not(any(target_os = "android", target_os = "ios")))]
 #[cfg(not(any(target_os = "android", target_os = "ios")))]
 use hbb_common::tokio::sync::mpsc::UnboundedSender;
 use hbb_common::tokio::sync::mpsc::UnboundedSender;
-use hbb_common::tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver};
 use hbb_common::{
 use hbb_common::{
     allow_err,
     allow_err,
     anyhow::{anyhow, Context},
     anyhow::{anyhow, Context},
@@ -54,11 +53,15 @@ use hbb_common::{
     },
     },
     AddrMangle, ResultType, Stream,
     AddrMangle, ResultType, Stream,
 };
 };
+use hbb_common::{
+    config::keys::OPTION_ALLOW_AUTO_RECORD_OUTGOING,
+    tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver},
+};
 pub use helper::*;
 pub use helper::*;
 use scrap::{
 use scrap::{
     codec::Decoder,
     codec::Decoder,
     record::{Recorder, RecorderContext},
     record::{Recorder, RecorderContext},
-    CodecFormat, ImageFormat, ImageRgb,
+    CodecFormat, ImageFormat, ImageRgb, ImageTexture,
 };
 };
 
 
 use crate::{
 use crate::{
@@ -1146,7 +1149,7 @@ impl AudioHandler {
 pub struct VideoHandler {
 pub struct VideoHandler {
     decoder: Decoder,
     decoder: Decoder,
     pub rgb: ImageRgb,
     pub rgb: ImageRgb,
-    pub texture: *mut c_void,
+    pub texture: ImageTexture,
     recorder: Arc<Mutex<Option<Recorder>>>,
     recorder: Arc<Mutex<Option<Recorder>>>,
     record: bool,
     record: bool,
     _display: usize, // useful for debug
     _display: usize, // useful for debug
@@ -1172,7 +1175,7 @@ impl VideoHandler {
         VideoHandler {
         VideoHandler {
             decoder: Decoder::new(format, luid),
             decoder: Decoder::new(format, luid),
             rgb: ImageRgb::new(ImageFormat::ARGB, crate::get_dst_align_rgba()),
             rgb: ImageRgb::new(ImageFormat::ARGB, crate::get_dst_align_rgba()),
-            texture: std::ptr::null_mut(),
+            texture: Default::default(),
             recorder: Default::default(),
             recorder: Default::default(),
             record: false,
             record: false,
             _display,
             _display,
@@ -1220,11 +1223,14 @@ impl VideoHandler {
                 }
                 }
                 self.first_frame = false;
                 self.first_frame = false;
                 if self.record {
                 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
                 res
             }
             }
@@ -1248,17 +1254,14 @@ impl VideoHandler {
     }
     }
 
 
     /// Start or stop screen record.
     /// 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;
         self.record = false;
         if start {
         if start {
             self.recorder = Recorder::new(RecorderContext {
             self.recorder = Recorder::new(RecorderContext {
                 server: false,
                 server: false,
                 id,
                 id,
                 dir: crate::ui_interface::video_save_directory(false),
                 dir: crate::ui_interface::video_save_directory(false),
-                filename: "".to_owned(),
-                width: w as _,
-                height: h as _,
-                format: scrap::CodecFormat::VP9,
+                display,
                 tx: None,
                 tx: None,
             })
             })
             .map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r))));
             .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
     password_source: PasswordSource, // where the sent password comes from
     shared_password: Option<String>, // Store the shared password
     shared_password: Option<String>, // Store the shared password
     pub enable_trusted_devices: bool,
     pub enable_trusted_devices: bool,
+    pub record: bool,
 }
 }
 
 
 impl Deref for LoginConfigHandler {
 impl Deref for LoginConfigHandler {
@@ -1438,6 +1442,7 @@ impl LoginConfigHandler {
         self.adapter_luid = adapter_luid;
         self.adapter_luid = adapter_luid;
         self.selected_windows_session_id = None;
         self.selected_windows_session_id = None;
         self.shared_password = shared_password;
         self.shared_password = shared_password;
+        self.record = LocalConfig::get_bool_option(OPTION_ALLOW_AUTO_RECORD_OUTGOING);
     }
     }
 
 
     /// Check if the client should auto login.
     /// Check if the client should auto login.
@@ -2227,7 +2232,7 @@ pub enum MediaData {
     AudioFrame(Box<AudioFrame>),
     AudioFrame(Box<AudioFrame>),
     AudioFormat(AudioFormat),
     AudioFormat(AudioFormat),
     Reset(Option<usize>),
     Reset(Option<usize>),
-    RecordScreen(bool, usize, i32, i32, String),
+    RecordScreen(bool),
 }
 }
 
 
 pub type MediaSender = mpsc::Sender<MediaData>;
 pub type MediaSender = mpsc::Sender<MediaData>;
@@ -2303,10 +2308,16 @@ where
                         let start = std::time::Instant::now();
                         let start = std::time::Instant::now();
                         let format = CodecFormat::from(&vf);
                         let format = CodecFormat::from(&vf);
                         if !handler_controller_map.contains_key(&display) {
                         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(
                             handler_controller_map.insert(
                                 display,
                                 display,
                                 VideoHandlerController {
                                 VideoHandlerController {
-                                    handler: VideoHandler::new(format, display),
+                                    handler,
                                     skip_beginning: 0,
                                     skip_beginning: 0,
                                 },
                                 },
                             );
                             );
@@ -2325,7 +2336,7 @@ where
                                     video_callback(
                                     video_callback(
                                         display,
                                         display,
                                         &mut handler_controller.handler.rgb,
                                         &mut handler_controller.handler.rgb,
-                                        handler_controller.handler.texture,
+                                        handler_controller.handler.texture.texture,
                                         pixelbuffer,
                                         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)),
     SetConfirmOverrideFile((i32, i32, bool, bool, bool)),
     AddJob((i32, String, String, i32, bool, bool)),
     AddJob((i32, String, String, i32, bool, bool)),
     ResumeJob((i32, bool)),
     ResumeJob((i32, bool)),
-    RecordScreen(bool, usize, i32, i32, String),
+    RecordScreen(bool),
     ElevateDirect,
     ElevateDirect,
     ElevateWithLogon(String, String),
     ElevateWithLogon(String, String),
     NewVoiceCall,
     NewVoiceCall,

+ 3 - 5
src/client/io_loop.rs

@@ -837,10 +837,8 @@ impl<T: InvokeUiSession> Remote<T> {
                     self.handle_job_status(id, -1, err);
                     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 => {
             Data::ElevateDirect => {
                 let mut request = ElevationRequest::new();
                 let mut request = ElevationRequest::new();
@@ -1218,7 +1216,7 @@ impl<T: InvokeUiSession> Remote<T> {
                             crate::plugin::handle_listen_event(
                             crate::plugin::handle_listen_event(
                                 crate::plugin::EVENT_ON_CONN_CLIENT.to_owned(),
                                 crate::plugin::EVENT_ON_CONN_CLIENT.to_owned(),
                                 self.handler.get_id(),
                                 self.handler.get_id(),
-                            )
+                            );
                         }
                         }
 
 
                         if self.handler.is_file_transfer() {
                         if self.handler.is_file_transfer() {

+ 5 - 2
src/flutter.rs

@@ -17,7 +17,7 @@ use serde::Serialize;
 use serde_json::json;
 use serde_json::json;
 
 
 use std::{
 use std::{
-    collections::HashMap,
+    collections::{HashMap, HashSet},
     ffi::CString,
     ffi::CString,
     os::raw::{c_char, c_int, c_void},
     os::raw::{c_char, c_int, c_void},
     str::FromStr,
     str::FromStr,
@@ -1010,6 +1010,10 @@ impl InvokeUiSession for FlutterHandler {
             rgba_data.valid = false;
             rgba_data.valid = false;
         }
         }
     }
     }
+
+    fn update_record_status(&self, start: bool) {
+        self.push_event("record_status", &[("start", &start.to_string())], &[]);
+    }
 }
 }
 
 
 impl FlutterHandler {
 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.
 // sessions mod is used to avoid the big lock of sessions' map.
 pub mod sessions {
 pub mod sessions {
-    use std::collections::HashSet;
 
 
     use super::*;
     use super::*;
 
 

+ 6 - 10
src/flutter_ffi.rs

@@ -241,21 +241,17 @@ pub fn session_is_multi_ui_session(session_id: SessionID) -> SyncReturn<bool> {
     }
     }
 }
 }
 
 
-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) {
     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<bool> {
     if let Some(session) = sessions::get_session_by_session_id(&session_id) {
     if let Some(session) = sessions::get_session_by_session_id(&session_id) {
-        session.record_status(status);
+        SyncReturn(session.is_recording())
+    } else {
+        SyncReturn(false)
     }
     }
 }
 }
 
 

+ 1 - 1
src/ipc.rs

@@ -890,7 +890,7 @@ pub async fn set_data(data: &Data) -> ResultType<()> {
     set_data_async(data).await
     set_data_async(data).await
 }
 }
 
 
-pub async fn set_data_async(data: &Data) -> ResultType<()> {
+async fn set_data_async(data: &Data) -> ResultType<()> {
     let mut c = connect(1000, "").await?;
     let mut c = connect(1000, "").await?;
     c.send(data).await?;
     c.send(data).await?;
     Ok(())
     Ok(())

+ 1 - 0
src/lang/ar.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "التسجيل"),
         ("Recording", "التسجيل"),
         ("Directory", "المسار"),
         ("Directory", "المسار"),
         ("Automatically record incoming sessions", "تسجيل الجلسات القادمة تلقائيا"),
         ("Automatically record incoming sessions", "تسجيل الجلسات القادمة تلقائيا"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "تغيير"),
         ("Change", "تغيير"),
         ("Start session recording", "بدء تسجيل الجلسة"),
         ("Start session recording", "بدء تسجيل الجلسة"),
         ("Stop session recording", "ايقاف تسجيل الجلسة"),
         ("Stop session recording", "ايقاف تسجيل الجلسة"),

+ 1 - 0
src/lang/be.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Запіс"),
         ("Recording", "Запіс"),
         ("Directory", "Тэчка"),
         ("Directory", "Тэчка"),
         ("Automatically record incoming sessions", "Аўтаматычна запісваць уваходныя сесіі"),
         ("Automatically record incoming sessions", "Аўтаматычна запісваць уваходныя сесіі"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Змяніць"),
         ("Change", "Змяніць"),
         ("Start session recording", "Пачаць запіс сесіі"),
         ("Start session recording", "Пачаць запіс сесіі"),
         ("Stop session recording", "Спыніць запіс сесіі"),
         ("Stop session recording", "Спыніць запіс сесіі"),

+ 1 - 0
src/lang/bg.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Записване"),
         ("Recording", "Записване"),
         ("Directory", "Директория"),
         ("Directory", "Директория"),
         ("Automatically record incoming sessions", "Автоматичен запис на входящи сесии"),
         ("Automatically record incoming sessions", "Автоматичен запис на входящи сесии"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Промяна"),
         ("Change", "Промяна"),
         ("Start session recording", "Започванена  запис"),
         ("Start session recording", "Започванена  запис"),
         ("Stop session recording", "Край на запис"),
         ("Stop session recording", "Край на запис"),

+ 1 - 0
src/lang/ca.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Gravació"),
         ("Recording", "Gravació"),
         ("Directory", "Contactes"),
         ("Directory", "Contactes"),
         ("Automatically record incoming sessions", "Enregistrament automàtic de sessions entrants"),
         ("Automatically record incoming sessions", "Enregistrament automàtic de sessions entrants"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Canvia"),
         ("Change", "Canvia"),
         ("Start session recording", "Inicia la gravació de la sessió"),
         ("Start session recording", "Inicia la gravació de la sessió"),
         ("Stop session recording", "Atura la gravació de la sessió"),
         ("Stop session recording", "Atura la gravació de la sessió"),

+ 2 - 1
src/lang/cn.rs

@@ -363,7 +363,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Unpin Toolbar", "取消固定工具栏"),
         ("Unpin Toolbar", "取消固定工具栏"),
         ("Recording", "录屏"),
         ("Recording", "录屏"),
         ("Directory", "目录"),
         ("Directory", "目录"),
-        ("Automatically record incoming sessions", "自动录制来访会话"),
+        ("Automatically record incoming sessions", "自动录制传入会话"),
+        ("Automatically record outgoing sessions", "自动录制传出会话"),
         ("Change", "更改"),
         ("Change", "更改"),
         ("Start session recording", "开始录屏"),
         ("Start session recording", "开始录屏"),
         ("Stop session recording", "结束录屏"),
         ("Stop session recording", "结束录屏"),

+ 1 - 0
src/lang/cs.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Nahrávání"),
         ("Recording", "Nahrávání"),
         ("Directory", "Adresář"),
         ("Directory", "Adresář"),
         ("Automatically record incoming sessions", "Automaticky nahrávat příchozí relace"),
         ("Automatically record incoming sessions", "Automaticky nahrávat příchozí relace"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Změnit"),
         ("Change", "Změnit"),
         ("Start session recording", "Spustit záznam relace"),
         ("Start session recording", "Spustit záznam relace"),
         ("Stop session recording", "Zastavit záznam relace"),
         ("Stop session recording", "Zastavit záznam relace"),

+ 1 - 0
src/lang/da.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Optager"),
         ("Recording", "Optager"),
         ("Directory", "Mappe"),
         ("Directory", "Mappe"),
         ("Automatically record incoming sessions", "Optag automatisk indgående sessioner"),
         ("Automatically record incoming sessions", "Optag automatisk indgående sessioner"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Ændr"),
         ("Change", "Ændr"),
         ("Start session recording", "Start sessionsoptagelse"),
         ("Start session recording", "Start sessionsoptagelse"),
         ("Stop session recording", "Stop sessionsoptagelse"),
         ("Stop session recording", "Stop sessionsoptagelse"),

+ 1 - 0
src/lang/de.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Aufnahme"),
         ("Recording", "Aufnahme"),
         ("Directory", "Verzeichnis"),
         ("Directory", "Verzeichnis"),
         ("Automatically record incoming sessions", "Eingehende Sitzungen automatisch aufzeichnen"),
         ("Automatically record incoming sessions", "Eingehende Sitzungen automatisch aufzeichnen"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Ändern"),
         ("Change", "Ändern"),
         ("Start session recording", "Sitzungsaufzeichnung starten"),
         ("Start session recording", "Sitzungsaufzeichnung starten"),
         ("Stop session recording", "Sitzungsaufzeichnung beenden"),
         ("Stop session recording", "Sitzungsaufzeichnung beenden"),

+ 1 - 0
src/lang/el.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Εγγραφή"),
         ("Recording", "Εγγραφή"),
         ("Directory", "Φάκελος εγγραφών"),
         ("Directory", "Φάκελος εγγραφών"),
         ("Automatically record incoming sessions", "Αυτόματη εγγραφή εισερχόμενων συνεδριών"),
         ("Automatically record incoming sessions", "Αυτόματη εγγραφή εισερχόμενων συνεδριών"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Αλλαγή"),
         ("Change", "Αλλαγή"),
         ("Start session recording", "Έναρξη εγγραφής συνεδρίας"),
         ("Start session recording", "Έναρξη εγγραφής συνεδρίας"),
         ("Stop session recording", "Διακοπή εγγραφής συνεδρίας"),
         ("Stop session recording", "Διακοπή εγγραφής συνεδρίας"),

+ 1 - 0
src/lang/eo.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", ""),
         ("Recording", ""),
         ("Directory", ""),
         ("Directory", ""),
         ("Automatically record incoming sessions", ""),
         ("Automatically record incoming sessions", ""),
+        ("Automatically record outgoing sessions", ""),
         ("Change", ""),
         ("Change", ""),
         ("Start session recording", ""),
         ("Start session recording", ""),
         ("Stop session recording", ""),
         ("Stop session recording", ""),

+ 1 - 0
src/lang/es.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Grabando"),
         ("Recording", "Grabando"),
         ("Directory", "Directorio"),
         ("Directory", "Directorio"),
         ("Automatically record incoming sessions", "Grabación automática de sesiones entrantes"),
         ("Automatically record incoming sessions", "Grabación automática de sesiones entrantes"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Cambiar"),
         ("Change", "Cambiar"),
         ("Start session recording", "Comenzar grabación de sesión"),
         ("Start session recording", "Comenzar grabación de sesión"),
         ("Stop session recording", "Detener grabación de sesión"),
         ("Stop session recording", "Detener grabación de sesión"),

+ 1 - 0
src/lang/et.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", ""),
         ("Recording", ""),
         ("Directory", ""),
         ("Directory", ""),
         ("Automatically record incoming sessions", ""),
         ("Automatically record incoming sessions", ""),
+        ("Automatically record outgoing sessions", ""),
         ("Change", ""),
         ("Change", ""),
         ("Start session recording", ""),
         ("Start session recording", ""),
         ("Stop session recording", ""),
         ("Stop session recording", ""),

+ 1 - 0
src/lang/eu.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Grabatzen"),
         ("Recording", "Grabatzen"),
         ("Directory", "Direktorioa"),
         ("Directory", "Direktorioa"),
         ("Automatically record incoming sessions", "Automatikoki grabatu sarrerako saioak"),
         ("Automatically record incoming sessions", "Automatikoki grabatu sarrerako saioak"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Aldatu"),
         ("Change", "Aldatu"),
         ("Start session recording", "Hasi saioaren grabaketa"),
         ("Start session recording", "Hasi saioaren grabaketa"),
         ("Stop session recording", "Gelditu saioaren grabaketa"),
         ("Stop session recording", "Gelditu saioaren grabaketa"),

+ 1 - 0
src/lang/fa.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "در حال ضبط"),
         ("Recording", "در حال ضبط"),
         ("Directory", "مسیر"),
         ("Directory", "مسیر"),
         ("Automatically record incoming sessions", "ضبط خودکار جلسات ورودی"),
         ("Automatically record incoming sessions", "ضبط خودکار جلسات ورودی"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "تغییر"),
         ("Change", "تغییر"),
         ("Start session recording", "شروع ضبط جلسه"),
         ("Start session recording", "شروع ضبط جلسه"),
         ("Stop session recording", "توقف ضبط جلسه"),
         ("Stop session recording", "توقف ضبط جلسه"),

+ 1 - 0
src/lang/fr.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Enregistrement"),
         ("Recording", "Enregistrement"),
         ("Directory", "Répertoire"),
         ("Directory", "Répertoire"),
         ("Automatically record incoming sessions", "Enregistrement automatique des sessions entrantes"),
         ("Automatically record incoming sessions", "Enregistrement automatique des sessions entrantes"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Modifier"),
         ("Change", "Modifier"),
         ("Start session recording", "Commencer l'enregistrement"),
         ("Start session recording", "Commencer l'enregistrement"),
         ("Stop session recording", "Stopper l'enregistrement"),
         ("Stop session recording", "Stopper l'enregistrement"),

+ 1 - 0
src/lang/he.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", ""),
         ("Recording", ""),
         ("Directory", ""),
         ("Directory", ""),
         ("Automatically record incoming sessions", ""),
         ("Automatically record incoming sessions", ""),
+        ("Automatically record outgoing sessions", ""),
         ("Change", ""),
         ("Change", ""),
         ("Start session recording", ""),
         ("Start session recording", ""),
         ("Stop session recording", ""),
         ("Stop session recording", ""),

+ 1 - 0
src/lang/hr.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Snimanje"),
         ("Recording", "Snimanje"),
         ("Directory", "Mapa"),
         ("Directory", "Mapa"),
         ("Automatically record incoming sessions", "Automatski snimi dolazne sesije"),
         ("Automatically record incoming sessions", "Automatski snimi dolazne sesije"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Promijeni"),
         ("Change", "Promijeni"),
         ("Start session recording", "Započni snimanje sesije"),
         ("Start session recording", "Započni snimanje sesije"),
         ("Stop session recording", "Zaustavi snimanje sesije"),
         ("Stop session recording", "Zaustavi snimanje sesije"),

+ 1 - 0
src/lang/hu.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Felvétel"),
         ("Recording", "Felvétel"),
         ("Directory", "Könyvtár"),
         ("Directory", "Könyvtár"),
         ("Automatically record incoming sessions", "A bejövő munkamenetek automatikus rögzítése"),
         ("Automatically record incoming sessions", "A bejövő munkamenetek automatikus rögzítése"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Változtatás"),
         ("Change", "Változtatás"),
         ("Start session recording", "Munkamenet rögzítés indítása"),
         ("Start session recording", "Munkamenet rögzítés indítása"),
         ("Stop session recording", "Munkamenet rögzítés leállítása"),
         ("Stop session recording", "Munkamenet rögzítés leállítása"),

+ 1 - 0
src/lang/id.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Perekaman"),
         ("Recording", "Perekaman"),
         ("Directory", "Direktori"),
         ("Directory", "Direktori"),
         ("Automatically record incoming sessions", "Otomatis merekam sesi masuk"),
         ("Automatically record incoming sessions", "Otomatis merekam sesi masuk"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Ubah"),
         ("Change", "Ubah"),
         ("Start session recording", "Mulai sesi perekaman"),
         ("Start session recording", "Mulai sesi perekaman"),
         ("Stop session recording", "Hentikan sesi perekaman"),
         ("Stop session recording", "Hentikan sesi perekaman"),

+ 1 - 0
src/lang/it.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Registrazione"),
         ("Recording", "Registrazione"),
         ("Directory", "Cartella"),
         ("Directory", "Cartella"),
         ("Automatically record incoming sessions", "Registra automaticamente le sessioni in entrata"),
         ("Automatically record incoming sessions", "Registra automaticamente le sessioni in entrata"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Modifica"),
         ("Change", "Modifica"),
         ("Start session recording", "Inizia registrazione sessione"),
         ("Start session recording", "Inizia registrazione sessione"),
         ("Stop session recording", "Ferma registrazione sessione"),
         ("Stop session recording", "Ferma registrazione sessione"),

+ 1 - 0
src/lang/ja.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "録画"),
         ("Recording", "録画"),
         ("Directory", "ディレクトリ"),
         ("Directory", "ディレクトリ"),
         ("Automatically record incoming sessions", "受信したセッションを自動で記録する"),
         ("Automatically record incoming sessions", "受信したセッションを自動で記録する"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "変更"),
         ("Change", "変更"),
         ("Start session recording", "セッションの録画を開始"),
         ("Start session recording", "セッションの録画を開始"),
         ("Stop session recording", "セッションの録画を停止"),
         ("Stop session recording", "セッションの録画を停止"),

+ 1 - 0
src/lang/ko.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "녹화"),
         ("Recording", "녹화"),
         ("Directory", "경로"),
         ("Directory", "경로"),
         ("Automatically record incoming sessions", "들어오는 세션을 자동으로 녹화"),
         ("Automatically record incoming sessions", "들어오는 세션을 자동으로 녹화"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "변경"),
         ("Change", "변경"),
         ("Start session recording", "세션 녹화 시작"),
         ("Start session recording", "세션 녹화 시작"),
         ("Stop session recording", "세션 녹화 중지"),
         ("Stop session recording", "세션 녹화 중지"),

+ 1 - 0
src/lang/kz.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", ""),
         ("Recording", ""),
         ("Directory", ""),
         ("Directory", ""),
         ("Automatically record incoming sessions", ""),
         ("Automatically record incoming sessions", ""),
+        ("Automatically record outgoing sessions", ""),
         ("Change", ""),
         ("Change", ""),
         ("Start session recording", ""),
         ("Start session recording", ""),
         ("Stop session recording", ""),
         ("Stop session recording", ""),

+ 1 - 0
src/lang/lt.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Įrašymas"),
         ("Recording", "Įrašymas"),
         ("Directory", "Katalogas"),
         ("Directory", "Katalogas"),
         ("Automatically record incoming sessions", "Automatiškai įrašyti įeinančius seansus"),
         ("Automatically record incoming sessions", "Automatiškai įrašyti įeinančius seansus"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Keisti"),
         ("Change", "Keisti"),
         ("Start session recording", "Pradėti seanso įrašinėjimą"),
         ("Start session recording", "Pradėti seanso įrašinėjimą"),
         ("Stop session recording", "Sustabdyti seanso įrašinėjimą"),
         ("Stop session recording", "Sustabdyti seanso įrašinėjimą"),

+ 1 - 0
src/lang/lv.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Ierakstīšana"),
         ("Recording", "Ierakstīšana"),
         ("Directory", "Direktorija"),
         ("Directory", "Direktorija"),
         ("Automatically record incoming sessions", "Automātiski ierakstīt ienākošās sesijas"),
         ("Automatically record incoming sessions", "Automātiski ierakstīt ienākošās sesijas"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Mainīt"),
         ("Change", "Mainīt"),
         ("Start session recording", "Sākt sesijas ierakstīšanu"),
         ("Start session recording", "Sākt sesijas ierakstīšanu"),
         ("Stop session recording", "Apturēt sesijas ierakstīšanu"),
         ("Stop session recording", "Apturēt sesijas ierakstīšanu"),

+ 1 - 0
src/lang/nb.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Opptak"),
         ("Recording", "Opptak"),
         ("Directory", "Mappe"),
         ("Directory", "Mappe"),
         ("Automatically record incoming sessions", "Ta opp innkommende sesjoner automatisk"),
         ("Automatically record incoming sessions", "Ta opp innkommende sesjoner automatisk"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Rediger"),
         ("Change", "Rediger"),
         ("Start session recording", "Start sesjonsopptak"),
         ("Start session recording", "Start sesjonsopptak"),
         ("Stop session recording", "Stopp sesjonsopptak"),
         ("Stop session recording", "Stopp sesjonsopptak"),

+ 1 - 0
src/lang/nl.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Opnemen"),
         ("Recording", "Opnemen"),
         ("Directory", "Map"),
         ("Directory", "Map"),
         ("Automatically record incoming sessions", "Automatisch inkomende sessies opnemen"),
         ("Automatically record incoming sessions", "Automatisch inkomende sessies opnemen"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Wissel"),
         ("Change", "Wissel"),
         ("Start session recording", "Start de sessieopname"),
         ("Start session recording", "Start de sessieopname"),
         ("Stop session recording", "Stop de sessieopname"),
         ("Stop session recording", "Stop de sessieopname"),

+ 9 - 8
src/lang/pl.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Nagrywanie"),
         ("Recording", "Nagrywanie"),
         ("Directory", "Folder"),
         ("Directory", "Folder"),
         ("Automatically record incoming sessions", "Automatycznie nagrywaj sesje przychodzące"),
         ("Automatically record incoming sessions", "Automatycznie nagrywaj sesje przychodzące"),
+        ("Automatically record outgoing sessions", "Automatycznie nagrywaj sesje wychodzące"),
         ("Change", "Zmień"),
         ("Change", "Zmień"),
         ("Start session recording", "Zacznij nagrywać sesję"),
         ("Start session recording", "Zacznij nagrywać sesję"),
         ("Stop session recording", "Zatrzymaj nagrywanie sesji"),
         ("Stop session recording", "Zatrzymaj nagrywanie sesji"),
@@ -644,13 +645,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Parent directory", "Folder nadrzędny"),
         ("Parent directory", "Folder nadrzędny"),
         ("Resume", "Wznów"),
         ("Resume", "Wznów"),
         ("Invalid file name", "Nieprawidłowa nazwa pliku"),
         ("Invalid file name", "Nieprawidłowa nazwa pliku"),
-        ("one-way-file-transfer-tip", ""),
-        ("Authentication Required", ""),
-        ("Authenticate", ""),
-        ("web_id_input_tip", ""),
-        ("Download", ""),
-        ("Upload folder", ""),
-        ("Upload files", ""),
-        ("Clipboard is synchronized", ""),
+        ("one-way-file-transfer-tip", "Jednokierunkowy transfer plików jest włączony po stronie kontrolowanej."),
+        ("Authentication Required", "Wymagana autoryzacja"),
+        ("Authenticate", "Uwierzytelnienie"),
+        ("web_id_input_tip", "Jeśli chcesz uzyskać dostęp do urządzenia na innym serwerze, dodaj adres serwera (<id>@<adres_serwera>?key=<wartosc_klucza>) na przykład, \n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nJeśli chcesz uzyskać dostęp do urządzenia na serwerze publicznym, wprowadź \"<id>@public\", klucz nie jest wymagany dla serwera publicznego."),
+        ("Download", "Pobierz"),
+        ("Upload folder", "Wyślij folder"),
+        ("Upload files", "Wyślij pliki"),
+        ("Clipboard is synchronized", "Schowek jest zsynchronizowany"),
     ].iter().cloned().collect();
     ].iter().cloned().collect();
 }
 }

+ 1 - 0
src/lang/pt_PT.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", ""),
         ("Recording", ""),
         ("Directory", ""),
         ("Directory", ""),
         ("Automatically record incoming sessions", ""),
         ("Automatically record incoming sessions", ""),
+        ("Automatically record outgoing sessions", ""),
         ("Change", ""),
         ("Change", ""),
         ("Start session recording", ""),
         ("Start session recording", ""),
         ("Stop session recording", ""),
         ("Stop session recording", ""),

+ 1 - 0
src/lang/ptbr.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Gravando"),
         ("Recording", "Gravando"),
         ("Directory", "Diretório"),
         ("Directory", "Diretório"),
         ("Automatically record incoming sessions", "Gravar automaticamente sessões de entrada"),
         ("Automatically record incoming sessions", "Gravar automaticamente sessões de entrada"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Alterar"),
         ("Change", "Alterar"),
         ("Start session recording", "Iniciar gravação da sessão"),
         ("Start session recording", "Iniciar gravação da sessão"),
         ("Stop session recording", "Parar gravação da sessão"),
         ("Stop session recording", "Parar gravação da sessão"),

+ 1 - 0
src/lang/ro.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Înregistrare"),
         ("Recording", "Înregistrare"),
         ("Directory", "Director"),
         ("Directory", "Director"),
         ("Automatically record incoming sessions", "Înregistrează automat sesiunile viitoare"),
         ("Automatically record incoming sessions", "Înregistrează automat sesiunile viitoare"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Modifică"),
         ("Change", "Modifică"),
         ("Start session recording", "Începe înregistrarea"),
         ("Start session recording", "Începe înregistrarea"),
         ("Stop session recording", "Oprește înregistrarea"),
         ("Stop session recording", "Oprește înregistrarea"),

文件差异内容过多而无法显示
+ 6 - 5
src/lang/ru.rs


+ 1 - 0
src/lang/sk.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Nahrávanie"),
         ("Recording", "Nahrávanie"),
         ("Directory", "Adresár"),
         ("Directory", "Adresár"),
         ("Automatically record incoming sessions", "Automaticky nahrávať prichádzajúce relácie"),
         ("Automatically record incoming sessions", "Automaticky nahrávať prichádzajúce relácie"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Zmeniť"),
         ("Change", "Zmeniť"),
         ("Start session recording", "Spustiť záznam relácie"),
         ("Start session recording", "Spustiť záznam relácie"),
         ("Stop session recording", "Zastaviť záznam relácie"),
         ("Stop session recording", "Zastaviť záznam relácie"),

+ 1 - 0
src/lang/sl.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Snemanje"),
         ("Recording", "Snemanje"),
         ("Directory", "Imenik"),
         ("Directory", "Imenik"),
         ("Automatically record incoming sessions", "Samodejno snemaj vhodne seje"),
         ("Automatically record incoming sessions", "Samodejno snemaj vhodne seje"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Spremeni"),
         ("Change", "Spremeni"),
         ("Start session recording", "Začni snemanje seje"),
         ("Start session recording", "Začni snemanje seje"),
         ("Stop session recording", "Ustavi snemanje seje"),
         ("Stop session recording", "Ustavi snemanje seje"),

+ 1 - 0
src/lang/sq.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Regjistrimi"),
         ("Recording", "Regjistrimi"),
         ("Directory", "Direktoria"),
         ("Directory", "Direktoria"),
         ("Automatically record incoming sessions", "Regjistro automatikisht seancat hyrëse"),
         ("Automatically record incoming sessions", "Regjistro automatikisht seancat hyrëse"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Ndrysho"),
         ("Change", "Ndrysho"),
         ("Start session recording", "Fillo regjistrimin e sesionit"),
         ("Start session recording", "Fillo regjistrimin e sesionit"),
         ("Stop session recording", "Ndalo regjistrimin e sesionit"),
         ("Stop session recording", "Ndalo regjistrimin e sesionit"),

+ 1 - 0
src/lang/sr.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Snimanje"),
         ("Recording", "Snimanje"),
         ("Directory", "Direktorijum"),
         ("Directory", "Direktorijum"),
         ("Automatically record incoming sessions", "Automatski snimaj dolazne sesije"),
         ("Automatically record incoming sessions", "Automatski snimaj dolazne sesije"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Promeni"),
         ("Change", "Promeni"),
         ("Start session recording", "Započni snimanje sesije"),
         ("Start session recording", "Započni snimanje sesije"),
         ("Stop session recording", "Zaustavi snimanje sesije"),
         ("Stop session recording", "Zaustavi snimanje sesije"),

+ 1 - 0
src/lang/sv.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Spelar in"),
         ("Recording", "Spelar in"),
         ("Directory", "Katalog"),
         ("Directory", "Katalog"),
         ("Automatically record incoming sessions", "Spela in inkommande sessioner automatiskt"),
         ("Automatically record incoming sessions", "Spela in inkommande sessioner automatiskt"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Byt"),
         ("Change", "Byt"),
         ("Start session recording", "Starta inspelning"),
         ("Start session recording", "Starta inspelning"),
         ("Stop session recording", "Avsluta inspelning"),
         ("Stop session recording", "Avsluta inspelning"),

+ 1 - 0
src/lang/template.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", ""),
         ("Recording", ""),
         ("Directory", ""),
         ("Directory", ""),
         ("Automatically record incoming sessions", ""),
         ("Automatically record incoming sessions", ""),
+        ("Automatically record outgoing sessions", ""),
         ("Change", ""),
         ("Change", ""),
         ("Start session recording", ""),
         ("Start session recording", ""),
         ("Stop session recording", ""),
         ("Stop session recording", ""),

+ 1 - 0
src/lang/th.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "การบันทึก"),
         ("Recording", "การบันทึก"),
         ("Directory", "ไดเรกทอรี่"),
         ("Directory", "ไดเรกทอรี่"),
         ("Automatically record incoming sessions", "บันทึกเซสชันขาเข้าโดยอัตโนมัติ"),
         ("Automatically record incoming sessions", "บันทึกเซสชันขาเข้าโดยอัตโนมัติ"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "เปลี่ยน"),
         ("Change", "เปลี่ยน"),
         ("Start session recording", "เริ่มต้นการบันทึกเซสชัน"),
         ("Start session recording", "เริ่มต้นการบันทึกเซสชัน"),
         ("Stop session recording", "หยุดการบันทึกเซสซัน"),
         ("Stop session recording", "หยุดการบันทึกเซสซัน"),

+ 1 - 0
src/lang/tr.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Kayıt Ediliyor"),
         ("Recording", "Kayıt Ediliyor"),
         ("Directory", "Klasör"),
         ("Directory", "Klasör"),
         ("Automatically record incoming sessions", "Gelen oturumları otomatik olarak kayıt et"),
         ("Automatically record incoming sessions", "Gelen oturumları otomatik olarak kayıt et"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Değiştir"),
         ("Change", "Değiştir"),
         ("Start session recording", "Oturum kaydını başlat"),
         ("Start session recording", "Oturum kaydını başlat"),
         ("Stop session recording", "Oturum kaydını sonlandır"),
         ("Stop session recording", "Oturum kaydını sonlandır"),

+ 1 - 0
src/lang/tw.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "錄製"),
         ("Recording", "錄製"),
         ("Directory", "路徑"),
         ("Directory", "路徑"),
         ("Automatically record incoming sessions", "自動錄製連入的工作階段"),
         ("Automatically record incoming sessions", "自動錄製連入的工作階段"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "變更"),
         ("Change", "變更"),
         ("Start session recording", "開始錄影"),
         ("Start session recording", "開始錄影"),
         ("Stop session recording", "停止錄影"),
         ("Stop session recording", "停止錄影"),

+ 1 - 0
src/lang/uk.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Запис"),
         ("Recording", "Запис"),
         ("Directory", "Директорія"),
         ("Directory", "Директорія"),
         ("Automatically record incoming sessions", "Автоматично записувати вхідні сеанси"),
         ("Automatically record incoming sessions", "Автоматично записувати вхідні сеанси"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Змінити"),
         ("Change", "Змінити"),
         ("Start session recording", "Розпочати запис сеансу"),
         ("Start session recording", "Розпочати запис сеансу"),
         ("Stop session recording", "Закінчити запис сеансу"),
         ("Stop session recording", "Закінчити запис сеансу"),

+ 1 - 0
src/lang/vn.rs

@@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
         ("Recording", "Đang ghi hình"),
         ("Recording", "Đang ghi hình"),
         ("Directory", "Thư mục"),
         ("Directory", "Thư mục"),
         ("Automatically record incoming sessions", "Tự động ghi những phiên kết nối vào"),
         ("Automatically record incoming sessions", "Tự động ghi những phiên kết nối vào"),
+        ("Automatically record outgoing sessions", ""),
         ("Change", "Thay đổi"),
         ("Change", "Thay đổi"),
         ("Start session recording", "Bắt đầu ghi hình phiên kết nố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"),
         ("Stop session recording", "Dừng ghi hình phiên kết nối"),

+ 18 - 6
src/server/video_service.rs

@@ -487,6 +487,8 @@ fn run(vs: VideoService) -> ResultType<()> {
     let repeat_encode_max = 10;
     let repeat_encode_max = 10;
     let mut encode_fail_counter = 0;
     let mut encode_fail_counter = 0;
     let mut first_frame = true;
     let mut first_frame = true;
+    let capture_width = c.width;
+    let capture_height = c.height;
 
 
     while sp.ok() {
     while sp.ok() {
         #[cfg(windows)]
         #[cfg(windows)]
@@ -576,6 +578,8 @@ fn run(vs: VideoService) -> ResultType<()> {
                         recorder.clone(),
                         recorder.clone(),
                         &mut encode_fail_counter,
                         &mut encode_fail_counter,
                         &mut first_frame,
                         &mut first_frame,
+                        capture_width,
+                        capture_height,
                     )?;
                     )?;
                     frame_controller.set_send(now, send_conn_ids);
                     frame_controller.set_send(now, send_conn_ids);
                 }
                 }
@@ -632,6 +636,8 @@ fn run(vs: VideoService) -> ResultType<()> {
                             recorder.clone(),
                             recorder.clone(),
                             &mut encode_fail_counter,
                             &mut encode_fail_counter,
                             &mut first_frame,
                             &mut first_frame,
+                            capture_width,
+                            capture_height,
                         )?;
                         )?;
                         frame_controller.set_send(now, send_conn_ids);
                         frame_controller.set_send(now, send_conn_ids);
                     }
                     }
@@ -722,7 +728,13 @@ fn setup_encoder(
     );
     );
     Encoder::set_fallback(&encoder_cfg);
     Encoder::set_fallback(&encoder_cfg);
     let codec_format = Encoder::negotiated_codec();
     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 use_i444 = Encoder::use_i444(&encoder_cfg);
     let encoder = Encoder::new(encoder_cfg.clone(), use_i444)?;
     let encoder = Encoder::new(encoder_cfg.clone(), use_i444)?;
     Ok((encoder, encoder_cfg, codec_format, use_i444, recorder))
     Ok((encoder, encoder_cfg, codec_format, use_i444, recorder))
@@ -809,6 +821,7 @@ fn get_recorder(
     height: usize,
     height: usize,
     codec_format: &CodecFormat,
     codec_format: &CodecFormat,
     record_incoming: bool,
     record_incoming: bool,
+    display: usize,
 ) -> Arc<Mutex<Option<Recorder>>> {
 ) -> Arc<Mutex<Option<Recorder>>> {
     #[cfg(windows)]
     #[cfg(windows)]
     let root = crate::platform::is_root();
     let root = crate::platform::is_root();
@@ -828,10 +841,7 @@ fn get_recorder(
             server: true,
             server: true,
             id: Config::get_id(),
             id: Config::get_id(),
             dir: crate::ui_interface::video_save_directory(root),
             dir: crate::ui_interface::video_save_directory(root),
-            filename: "".to_owned(),
-            width,
-            height,
-            format: codec_format.clone(),
+            display,
             tx,
             tx,
         })
         })
         .map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r))))
         .map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r))))
@@ -910,6 +920,8 @@ fn handle_one_frame(
     recorder: Arc<Mutex<Option<Recorder>>>,
     recorder: Arc<Mutex<Option<Recorder>>>,
     encode_fail_counter: &mut usize,
     encode_fail_counter: &mut usize,
     first_frame: &mut bool,
     first_frame: &mut bool,
+    width: usize,
+    height: usize,
 ) -> ResultType<HashSet<i32>> {
 ) -> ResultType<HashSet<i32>> {
     sp.snapshot(|sps| {
     sp.snapshot(|sps| {
         // so that new sub and old sub share the same encoder after switch
         // so that new sub and old sub share the same encoder after switch
@@ -933,7 +945,7 @@ fn handle_one_frame(
                 .lock()
                 .lock()
                 .unwrap()
                 .unwrap()
                 .as_mut()
                 .as_mut()
-                .map(|r| r.write_message(&msg));
+                .map(|r| r.write_message(&msg, width, height));
             send_conn_ids = sp.send_video_frame(msg);
             send_conn_ids = sp.send_video_frame(msg);
         }
         }
         Err(e) => {
         Err(e) => {

+ 7 - 15
src/ui/header.tis

@@ -301,26 +301,12 @@ class Header: Reactor.Component {
     }
     }
 
 
     event click $(span#recording) (_, me) {
     event click $(span#recording) (_, me) {
-        recording = !recording;
         header.update();
         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) {
     event click $(#screen) (_, me) {
         if (pi.current_display == me.index) return;
         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);
         handler.switch_display(me.index);
     }
     }
     
     
@@ -518,6 +504,7 @@ if (!(is_file_transfer || is_port_forward)) {
 
 
 handler.updatePi = function(v) {
 handler.updatePi = function(v) {
     pi = v;
     pi = v;
+    recording = handler.is_recording();
     header.update();
     header.update();
     if (is_port_forward) {
     if (is_port_forward) {
         view.windowState = View.WINDOW_MINIMIZED;
         view.windowState = View.WINDOW_MINIMIZED;
@@ -682,3 +669,8 @@ handler.setConnectionType = function(secured, direct) {
        direct_connection: direct, 
        direct_connection: direct, 
     });
     });
 }
 }
+
+handler.updateRecordStatus = function(status) {
+    recording = status;
+    header.update();
+}

+ 4 - 1
src/ui/index.tis

@@ -253,10 +253,12 @@ class Enhancements: Reactor.Component {
             var root_dir = show_root_dir ? handler.video_save_directory(true) : "";
             var root_dir = show_root_dir ? handler.video_save_directory(true) : "";
             var ts0 = handler.get_option("enable-record-session") == '' ? { checked: true } : {};
             var ts0 = handler.get_option("enable-record-session") == '' ? { checked: true } : {};
             var ts1 = handler.get_option("allow-auto-record-incoming") == 'Y' ? { checked: true } : {};
             var ts1 = handler.get_option("allow-auto-record-incoming") == 'Y' ? { checked: true } : {};
+            var ts2 = handler.get_local_option("allow-auto-record-outgoing") == 'Y' ? { checked: true } : {};
             msgbox("custom-recording", translate('Recording'),
             msgbox("custom-recording", translate('Recording'),
                 <div .form>
                 <div .form>
                     <div><button|checkbox(enable_record_session) {ts0}>{translate('Enable recording session')}</button></div>
                     <div><button|checkbox(enable_record_session) {ts0}>{translate('Enable recording session')}</button></div>
                     <div><button|checkbox(auto_record_incoming) {ts1}>{translate('Automatically record incoming sessions')}</button></div>
                     <div><button|checkbox(auto_record_incoming) {ts1}>{translate('Automatically record incoming sessions')}</button></div>
+                    <div><button|checkbox(auto_record_outgoing) {ts2}>{translate('Automatically record outgoing sessions')}</button></div>
                     <div>
                     <div>
                         {show_root_dir ? <div style="word-wrap:break-word"><span>{translate("Incoming")}:&nbsp;&nbsp;</span><span>{root_dir}</span></div> : ""}
                         {show_root_dir ? <div style="word-wrap:break-word"><span>{translate("Incoming")}:&nbsp;&nbsp;</span><span>{root_dir}</span></div> : ""}
                         <div style="word-wrap:break-word"><span>{translate(show_root_dir ? "Outgoing" : "Directory")}:&nbsp;&nbsp;</span><span #folderPath>{user_dir}</span></div>
                         <div style="word-wrap:break-word"><span>{translate(show_root_dir ? "Outgoing" : "Directory")}:&nbsp;&nbsp;</span><span #folderPath>{user_dir}</span></div>
@@ -267,7 +269,8 @@ class Enhancements: Reactor.Component {
                 if (!res) return;
                 if (!res) return;
                 handler.set_option("enable-record-session", res.enable_record_session ? '' : 'N');
                 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-incoming", res.auto_record_incoming ? 'Y' : '');
-                handler.set_option("video-save-directory", $(#folderPath).text);
+                handler.set_local_option("allow-auto-record-outgoing", res.auto_record_outgoing ? 'Y' : '');
+                handler.set_local_option("video-save-directory", $(#folderPath).text);
             });
             });
         }
         }
         this.toggleMenuState();
         this.toggleMenuState();

+ 6 - 2
src/ui/remote.rs

@@ -335,6 +335,10 @@ impl InvokeUiSession for SciterHandler {
     }
     }
 
 
     fn next_rgba(&self, _display: usize) {}
     fn next_rgba(&self, _display: usize) {}
+
+    fn update_record_status(&self, start: bool) {
+        self.call("updateRecordStatus", &make_args!(start));
+    }
 }
 }
 
 
 pub struct SciterSession(Session<SciterHandler>);
 pub struct SciterSession(Session<SciterHandler>);
@@ -478,8 +482,7 @@ impl sciter::EventHandler for SciterSession {
         fn save_image_quality(String);
         fn save_image_quality(String);
         fn save_custom_image_quality(i32);
         fn save_custom_image_quality(i32);
         fn refresh_video(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 get_toggle_option(String);
         fn is_privacy_mode_supported();
         fn is_privacy_mode_supported();
         fn toggle_option(String);
         fn toggle_option(String);
@@ -496,6 +499,7 @@ impl sciter::EventHandler for SciterSession {
         fn close_voice_call();
         fn close_voice_call();
         fn version_cmp(String, String);
         fn version_cmp(String, String);
         fn set_selected_windows_session_id(String);
         fn set_selected_windows_session_id(String);
+        fn is_recording();
     }
     }
 }
 }
 
 

+ 6 - 2
src/ui_interface.rs

@@ -212,7 +212,7 @@ pub fn get_builtin_option(key: &str) -> String {
 
 
 #[inline]
 #[inline]
 pub fn set_local_option(key: String, value: String) {
 pub fn set_local_option(key: String, value: String) {
-    LocalConfig::set_option(key, value);
+    LocalConfig::set_option(key.clone(), value.clone());
 }
 }
 
 
 #[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
 #[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
@@ -844,7 +844,11 @@ pub fn video_save_directory(root: bool) -> String {
             return dir.to_string_lossy().to_string();
             return dir.to_string_lossy().to_string();
         }
         }
     }
     }
-    let dir = Config::get_option("video-save-directory");
+    // Get directory from config file otherwise --server will use the old value from global var.
+    #[cfg(any(target_os = "linux", target_os = "macos"))]
+    let dir = LocalConfig::get_option_from_file(OPTION_VIDEO_SAVE_DIRECTORY);
+    #[cfg(not(any(target_os = "linux", target_os = "macos")))]
+    let dir = LocalConfig::get_option(OPTION_VIDEO_SAVE_DIRECTORY);
     if !dir.is_empty() {
     if !dir.is_empty() {
         return dir;
         return dir;
     }
     }

+ 8 - 12
src/ui_session_interface.rs

@@ -389,22 +389,17 @@ impl<T: InvokeUiSession> Session<T> {
         self.send(Data::Message(LoginConfigHandler::refresh()));
         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();
         let mut misc = Misc::new();
-        misc.set_client_record_status(status);
+        misc.set_client_record_status(start);
         let mut msg = Message::new();
         let mut msg = Message::new();
         msg.set_misc(misc);
         msg.set_misc(misc);
         self.send(Data::Message(msg));
         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) {
     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);
     fn set_current_display(&self, disp_idx: i32);
     #[cfg(feature = "flutter")]
     #[cfg(feature = "flutter")]
     fn is_multi_ui_session(&self) -> bool;
     fn is_multi_ui_session(&self) -> bool;
+    fn update_record_status(&self, start: bool);
 }
 }
 
 
 impl<T: InvokeUiSession> Deref for Session<T> {
 impl<T: InvokeUiSession> Deref for Session<T> {