mirror of
https://github.com/rustdesk/rustdesk.git
synced 2025-01-23 02:23:01 +08:00
auto record outgoing (#9711)
* Add option auto record outgoing session * In the same connection, all displays and all windows share the same recording state. todo: Android check external storage permission Known issue: * Sciter old issue, stop the process directly without stop record, the record file can't play. Signed-off-by: 21pages <sunboeasy@gmail.com>
This commit is contained in:
parent
289076aa70
commit
e8187588c1
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -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",
|
||||||
|
@ -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";
|
||||||
|
@ -575,12 +575,17 @@ 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',
|
if (!bind.isOutgoingOnly())
|
||||||
kOptionAllowAutoRecordIncoming),
|
_OptionCheckBox(context, 'Automatically record incoming sessions',
|
||||||
if (showRootDir)
|
kOptionAllowAutoRecordIncoming),
|
||||||
|
if (!bind.isIncomingOnly())
|
||||||
|
_OptionCheckBox(context, 'Automatically record outgoing sessions',
|
||||||
|
kOptionAllowAutoRecordOutgoing),
|
||||||
|
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 +602,49 @@ class _GeneralState extends State<_General> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
).marginOnly(left: _kContentHMargin),
|
).marginOnly(left: _kContentHMargin),
|
||||||
Row(
|
if (!(showRootDir && bind.isIncomingOnly()))
|
||||||
children: [
|
Row(
|
||||||
Text('${translate(showRootDir ? "Outgoing" : "Directory")}:'),
|
children: [
|
||||||
Expanded(
|
Text(
|
||||||
child: GestureDetector(
|
'${translate((showRootDir && !bind.isOutgoingOnly()) ? "Outgoing" : "Directory")}:'),
|
||||||
onTap: user_dir_exists
|
Expanded(
|
||||||
? () => launchUrl(Uri.file(user_dir))
|
child: GestureDetector(
|
||||||
: null,
|
onTap: user_dir_exists
|
||||||
child: Text(
|
? () => launchUrl(Uri.file(user_dir))
|
||||||
user_dir,
|
|
||||||
softWrap: true,
|
|
||||||
style: user_dir_exists
|
|
||||||
? const TextStyle(decoration: TextDecoration.underline)
|
|
||||||
: null,
|
: null,
|
||||||
)).marginOnly(left: 10),
|
child: Text(
|
||||||
),
|
user_dir,
|
||||||
ElevatedButton(
|
softWrap: true,
|
||||||
onPressed: isOptionFixed(kOptionVideoSaveDirectory)
|
style: user_dir_exists
|
||||||
? null
|
? const TextStyle(
|
||||||
: () async {
|
decoration: TextDecoration.underline)
|
||||||
String? initialDirectory;
|
: null,
|
||||||
if (await Directory.fromUri(Uri.directory(user_dir))
|
)).marginOnly(left: 10),
|
||||||
.exists()) {
|
),
|
||||||
initialDirectory = user_dir;
|
ElevatedButton(
|
||||||
}
|
onPressed: isOptionFixed(kOptionVideoSaveDirectory)
|
||||||
String? selectedDirectory =
|
? null
|
||||||
await FilePicker.platform.getDirectoryPath(
|
: () async {
|
||||||
initialDirectory: initialDirectory);
|
String? initialDirectory;
|
||||||
if (selectedDirectory != null) {
|
if (await Directory.fromUri(
|
||||||
await bind.mainSetOption(
|
Uri.directory(user_dir))
|
||||||
key: kOptionVideoSaveDirectory,
|
.exists()) {
|
||||||
value: selectedDirectory);
|
initialDirectory = user_dir;
|
||||||
setState(() {});
|
}
|
||||||
}
|
String? selectedDirectory =
|
||||||
},
|
await FilePicker.platform.getDirectoryPath(
|
||||||
child: Text(translate('Change')))
|
initialDirectory: initialDirectory);
|
||||||
.marginOnly(left: 5),
|
if (selectedDirectory != null) {
|
||||||
],
|
await bind.mainSetOption(
|
||||||
).marginOnly(left: _kContentHMargin),
|
key: kOptionVideoSaveDirectory,
|
||||||
|
value: selectedDirectory);
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(translate('Change')))
|
||||||
|
.marginOnly(left: 5),
|
||||||
|
],
|
||||||
|
).marginOnly(left: _kContentHMargin),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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) &&
|
(recordingModel.start || ffi.permissions['recording'] != false);
|
||||||
ffi.pi.currentDisplay != kAllDisplayValue;
|
|
||||||
if (!visible) return Offstage();
|
if (!visible) return Offstage();
|
||||||
return _IconMenuButton(
|
return _IconMenuButton(
|
||||||
assetName: 'assets/rec.svg',
|
assetName: 'assets/rec.svg',
|
||||||
|
@ -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);
|
||||||
|
@ -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.mainGetOptionSync(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(
|
if (!outgoingOnly)
|
||||||
title:
|
SettingsTile.switchTile(
|
||||||
Text(translate('Automatically record incoming sessions')),
|
title:
|
||||||
leading: Icon(Icons.videocam),
|
Text(translate('Automatically record incoming sessions')),
|
||||||
description: Text(
|
initialValue: _autoRecordIncomingSession,
|
||||||
"${translate("Directory")}: ${bind.mainVideoSaveDirectory(root: false)}"),
|
onToggle: isOptionFixed(kOptionAllowAutoRecordIncoming)
|
||||||
initialValue: _autoRecordIncomingSession,
|
? null
|
||||||
onToggle: isOptionFixed(kOptionAllowAutoRecordIncoming)
|
: (v) async {
|
||||||
? null
|
await bind.mainSetOption(
|
||||||
: (v) async {
|
key: kOptionAllowAutoRecordIncoming,
|
||||||
await bind.mainSetOption(
|
value: bool2option(
|
||||||
key: kOptionAllowAutoRecordIncoming,
|
kOptionAllowAutoRecordIncoming, v));
|
||||||
value:
|
final newValue = option2bool(
|
||||||
bool2option(kOptionAllowAutoRecordIncoming, v));
|
kOptionAllowAutoRecordIncoming,
|
||||||
final newValue = option2bool(
|
await bind.mainGetOption(
|
||||||
kOptionAllowAutoRecordIncoming,
|
key: kOptionAllowAutoRecordIncoming));
|
||||||
await bind.mainGetOption(
|
setState(() {
|
||||||
key: kOptionAllowAutoRecordIncoming));
|
_autoRecordIncomingSession = newValue;
|
||||||
setState(() {
|
});
|
||||||
_autoRecordIncomingSession = newValue;
|
},
|
||||||
});
|
),
|
||||||
},
|
if (!incommingOnly)
|
||||||
|
SettingsTile.switchTile(
|
||||||
|
title:
|
||||||
|
Text(translate('Automatically record outgoing sessions')),
|
||||||
|
initialValue: _autoRecordOutgoingSession,
|
||||||
|
onToggle: isOptionFixed(kOptionAllowAutoRecordOutgoing)
|
||||||
|
? null
|
||||||
|
: (v) async {
|
||||||
|
await bind.mainSetOption(
|
||||||
|
key: kOptionAllowAutoRecordOutgoing,
|
||||||
|
value: bool2option(
|
||||||
|
kOptionAllowAutoRecordOutgoing, v));
|
||||||
|
final newValue = option2bool(
|
||||||
|
kOptionAllowAutoRecordOutgoing,
|
||||||
|
await bind.mainGetOption(
|
||||||
|
key: kOptionAllowAutoRecordOutgoing));
|
||||||
|
setState(() {
|
||||||
|
_autoRecordOutgoingSession = newValue;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SettingsTile(
|
||||||
|
title: Text(translate("Directory")),
|
||||||
|
description: Text(bind.mainVideoSaveDirectory(root: false)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -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;
|
bool 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
bool value = !_start;
|
||||||
if (currentDisplay == kAllDisplayValue) return;
|
if (value) {
|
||||||
_start = !_start;
|
await sessionRefreshVideo(sessionId, pi);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
await bind.sessionRecordScreen(sessionId: sessionId, start: value);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClose() async {
|
updateStatus(bool status) {
|
||||||
if (isIOS) return;
|
_start = status;
|
||||||
final sessionId = parent.target?.sessionId;
|
notifyListeners();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
@ -2198,6 +2202,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";
|
||||||
@ -2342,6 +2347,7 @@ 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_ALLOW_AUTO_RECORD_OUTGOING,
|
||||||
OPTION_VIDEO_SAVE_DIRECTORY,
|
OPTION_VIDEO_SAVE_DIRECTORY,
|
||||||
OPTION_ENABLE_ABR,
|
OPTION_ENABLE_ABR,
|
||||||
OPTION_ALLOW_REMOVE_WALLPAPER,
|
OPTION_ALLOW_REMOVE_WALLPAPER,
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
impl RecorderContext2 {
|
||||||
pub fn set_filename(&mut self) -> ResultType<()> {
|
pub fn set_filename(&mut self, ctx: &RecorderContext) -> ResultType<()> {
|
||||||
if !PathBuf::from(&self.dir).exists() {
|
if !PathBuf::from(&ctx.dir).exists() {
|
||||||
std::fs::create_dir_all(&self.dir)?;
|
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> {
|
pub fn new(ctx: RecorderContext) -> ResultType<Self> {
|
||||||
ctx.set_filename()?;
|
Ok(Self {
|
||||||
let recorder = match ctx.format {
|
inner: None,
|
||||||
CodecFormat::VP8 | CodecFormat::VP9 | CodecFormat::AV1 => Recorder {
|
ctx,
|
||||||
inner: Box::new(WebmRecorder::new(ctx.clone())?),
|
ctx2: None,
|
||||||
ctx,
|
pts: None,
|
||||||
pts: None,
|
check_failed: false,
|
||||||
},
|
})
|
||||||
#[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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn change(&mut self, mut ctx: RecorderContext) -> ResultType<()> {
|
fn check(&mut self, w: usize, h: usize, format: CodecFormat) -> ResultType<()> {
|
||||||
ctx.set_filename()?;
|
match self.ctx2 {
|
||||||
self.inner = match ctx.format {
|
Some(ref ctx2) => {
|
||||||
CodecFormat::VP8 | CodecFormat::VP9 | CodecFormat::AV1 => {
|
if ctx2.width != w || ctx2.height != h || ctx2.format != format {
|
||||||
Box::new(WebmRecorder::new(ctx.clone())?)
|
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")]
|
None => {
|
||||||
_ => Box::new(HwRecorder::new(ctx.clone())?),
|
let mut ctx2 = RecorderContext2 {
|
||||||
#[cfg(not(feature = "hwcodec"))]
|
width: w,
|
||||||
_ => bail!("unsupported codec type"),
|
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;
|
if self.inner.is_none() {
|
||||||
self.pts = None;
|
self.inner = match format {
|
||||||
self.send_state(RecordState::NewFile(self.ctx.filename.clone()));
|
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.check_pts(f.pts, w, h, format)?;
|
||||||
self.write_video(f);
|
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.check_pts(f.pts, w, h, format)?;
|
||||||
self.write_video(f);
|
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.check_pts(f.pts, w, h, format)?;
|
||||||
self.write_video(f);
|
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.check_pts(f.pts, w, h, format)?;
|
||||||
self.write_video(f);
|
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.check_pts(f.pts, w, h, format)?;
|
||||||
self.write_video(f);
|
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 _,
|
ctx2.width as _,
|
||||||
ctx.height 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(),
|
filename: ctx2.filename.clone(),
|
||||||
width: ctx.width,
|
width: ctx2.width,
|
||||||
height: ctx.height,
|
height: ctx2.height,
|
||||||
is265: ctx.format == CodecFormat::H265,
|
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));
|
||||||
|
@ -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
|
self.recorder.lock().unwrap().as_mut().map(|r| {
|
||||||
.lock()
|
let (w, h) = if *pixelbuffer {
|
||||||
.unwrap()
|
(self.rgb.w, self.rgb.h)
|
||||||
.as_mut()
|
} else {
|
||||||
.map(|r| r.write_frame(frame));
|
(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(),
|
display,
|
||||||
width: w as _,
|
|
||||||
height: h as _,
|
|
||||||
format: scrap::CodecFormat::VP9,
|
|
||||||
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 = Config::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) => {
|
MediaData::RecordScreen(start) => {
|
||||||
log::info!("record screen command: start: {start}, display: {display}");
|
log::info!("record screen command: start: {start}");
|
||||||
// Compatible with the sciter version(single ui session).
|
let record = session.lc.read().unwrap().record;
|
||||||
// For the sciter version, there're no multi-ui-sessions for one connection.
|
session.update_record_status(start);
|
||||||
// The display is always 0, video_handler_controllers.len() is always 1. So we use the first video handler.
|
if record != start {
|
||||||
if let Some(handler_controler) = handler_controller_map.get_mut(&display) {
|
session.lc.write().unwrap().record = start;
|
||||||
handler_controler.handler.record_screen(start, w, h, id);
|
let id = session.lc.read().unwrap().id.clone();
|
||||||
} else if handler_controller_map.len() == 1 {
|
for (display, handler_controler) in handler_controller_map.iter_mut() {
|
||||||
if let Some(handler_controler) =
|
handler_controler.handler.record_screen(
|
||||||
handler_controller_map.values_mut().next()
|
start,
|
||||||
{
|
id.clone(),
|
||||||
handler_controler.handler.record_screen(start, w, h, id);
|
*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,
|
||||||
|
@ -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) => {
|
Data::RecordScreen(start) => {
|
||||||
let _ = self
|
let _ = self.video_sender.send(MediaData::RecordScreen(start));
|
||||||
.video_sender
|
|
||||||
.send(MediaData::RecordScreen(start, display, w, h, id));
|
|
||||||
}
|
}
|
||||||
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() {
|
||||||
|
@ -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::*;
|
||||||
|
|
||||||
|
@ -241,21 +241,17 @@ pub fn session_is_multi_ui_session(session_id: SessionID) -> SyncReturn<bool> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_record_screen(
|
pub fn session_record_screen(session_id: SessionID, start: bool) {
|
||||||
session_id: SessionID,
|
|
||||||
start: bool,
|
|
||||||
display: usize,
|
|
||||||
width: usize,
|
|
||||||
height: usize,
|
|
||||||
) {
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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", "ايقاف تسجيل الجلسة"),
|
||||||
|
@ -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", "Спыніць запіс сесіі"),
|
||||||
|
@ -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", "Край на запис"),
|
||||||
|
@ -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ó"),
|
||||||
|
@ -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", "结束录屏"),
|
||||||
|
@ -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"),
|
||||||
|
@ -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"),
|
||||||
|
@ -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"),
|
||||||
|
@ -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", "Διακοπή εγγραφής συνεδρίας"),
|
||||||
|
@ -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", ""),
|
||||||
|
@ -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"),
|
||||||
|
@ -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", ""),
|
||||||
|
@ -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"),
|
||||||
|
@ -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", "توقف ضبط جلسه"),
|
||||||
|
@ -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"),
|
||||||
|
@ -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", ""),
|
||||||
|
@ -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"),
|
||||||
|
@ -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"),
|
||||||
|
@ -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"),
|
||||||
|
@ -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"),
|
||||||
|
@ -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", "セッションの録画を停止"),
|
||||||
|
@ -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", "세션 녹화 중지"),
|
||||||
|
@ -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", ""),
|
||||||
|
@ -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ą"),
|
||||||
|
@ -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"),
|
||||||
|
@ -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"),
|
||||||
|
@ -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"),
|
||||||
|
@ -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", ""),
|
||||||
("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"),
|
||||||
|
@ -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", ""),
|
||||||
|
@ -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"),
|
||||||
|
@ -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"),
|
||||||
|
@ -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", "Остановить запись сеанса"),
|
||||||
|
@ -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"),
|
||||||
|
@ -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"),
|
||||||
|
@ -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"),
|
||||||
|
@ -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"),
|
||||||
|
@ -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"),
|
||||||
|
@ -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", ""),
|
||||||
|
@ -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", "หยุดการบันทึกเซสซัน"),
|
||||||
|
@ -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"),
|
||||||
|
@ -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", "停止錄影"),
|
||||||
|
@ -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", "Закінчити запис сеансу"),
|
||||||
|
@ -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"),
|
||||||
|
@ -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(),
|
display,
|
||||||
width,
|
|
||||||
height,
|
|
||||||
format: codec_format.clone(),
|
|
||||||
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) => {
|
||||||
|
@ -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);
|
handler.record_screen(!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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
}
|
@ -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_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")}: </span><span>{root_dir}</span></div> : ""}
|
{show_root_dir ? <div style="word-wrap:break-word"><span>{translate("Incoming")}: </span><span>{root_dir}</span></div> : ""}
|
||||||
<div style="word-wrap:break-word"><span>{translate(show_root_dir ? "Outgoing" : "Directory")}: </span><span #folderPath>{user_dir}</span></div>
|
<div style="word-wrap:break-word"><span>{translate(show_root_dir ? "Outgoing" : "Directory")}: </span><span #folderPath>{user_dir}</span></div>
|
||||||
@ -267,6 +269,7 @@ 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("allow-auto-record-outgoing", res.auto_record_outgoing ? 'Y' : '');
|
||||||
handler.set_option("video-save-directory", $(#folderPath).text);
|
handler.set_option("video-save-directory", $(#folderPath).text);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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_screen(bool);
|
||||||
fn record_status(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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) {
|
pub fn record_screen(&self, start: bool) {
|
||||||
self.send(Data::RecordScreen(
|
|
||||||
start,
|
|
||||||
display as usize,
|
|
||||||
w,
|
|
||||||
h,
|
|
||||||
self.get_id(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn record_status(&self, status: 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> {
|
||||||
|
Loading…
Reference in New Issue
Block a user