commit
dac851ace9
27
Cargo.lock
generated
27
Cargo.lock
generated
@ -625,8 +625,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
|
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"num-traits 0.2.15",
|
"num-traits 0.2.15",
|
||||||
|
"time 0.1.44",
|
||||||
|
"wasm-bindgen",
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1639,7 +1642,7 @@ dependencies = [
|
|||||||
"regex",
|
"regex",
|
||||||
"rustversion",
|
"rustversion",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"time",
|
"time 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1944,7 +1947,7 @@ checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2332,6 +2335,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"chrono",
|
||||||
"confy",
|
"confy",
|
||||||
"directories-next",
|
"directories-next",
|
||||||
"dirs-next",
|
"dirs-next",
|
||||||
@ -2974,7 +2978,7 @@ checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"wasi",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
"windows-sys 0.36.1",
|
"windows-sys 0.36.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -5124,6 +5128,17 @@ dependencies = [
|
|||||||
"weezl",
|
"weezl",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.1.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||||
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
@ -5495,6 +5510,12 @@ dependencies = [
|
|||||||
"try-lock",
|
"try-lock",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.10.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.0+wasi-snapshot-preview1"
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
@ -50,6 +50,8 @@ late final iconFile = MemoryImage(Uint8List.fromList(base64Decode(
|
|||||||
'iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAAUVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////8IN+deAAAAGnRSTlMAH+CAESEN8jyZkcIb5N/ONy3vmHhmiGjUm7UwS+YAAAHZSURBVGje7dnbboMwDIBhBwgQoFAO7Ta//4NOqCAXYZQstatq4r+r5ubrgQSpg8iyC4ZURa+PlIpQYGiwrzyeHtYZjAL8T05O4H8BbbKvFgRa4NoBU8pXeYEkDDgaaLQBcwJrmeErJQB/7wes3QBWGnCIX0+AQycL1PO6BMwPa0nA4ZxbgTvOjUYMGPHRnZkQAY4mxPZBjmy53E7ukSkFKYB/D4XsWZQx64sCeYebOogGsoOBYvv6/UCb8F0IOBZ0TlP6lEYdANY350AJqB9/qPVuOI5evw4A1hgLigAlepnyxW80bcCcwN++A2s82Vcu02ta+ceq9BoL5KGTTRwQPlpqA3gCnwWU2kCDgeWRQPj2jAPCDxgCMjhI6uZnToDpvd/BJeFrJQB/fsAa02gCt3mi1wNuy8GgBNDZlysBNNSrADVSjcJl6vCpUn6jOdx0kz0q6PMhQRa4465SFKhx35cgUCBTwj2/NHwZAb71qR8GEP2H1XcmAtBPTEO67GP6FUUAIKGABbDLQ0EArhN2sAIGesRO+iyy+RMAjckVTlMCKFVAbh/4Af9OPgG61SkDVco3BQGT3GXaDAnTIAcYZDuBTwGsAGDxuBFeAQqIqwoFMlAVLrHr/wId5MPt0nilGgAAAABJRU5ErkJggg==')));
|
'iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAAUVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////8IN+deAAAAGnRSTlMAH+CAESEN8jyZkcIb5N/ONy3vmHhmiGjUm7UwS+YAAAHZSURBVGje7dnbboMwDIBhBwgQoFAO7Ta//4NOqCAXYZQstatq4r+r5ubrgQSpg8iyC4ZURa+PlIpQYGiwrzyeHtYZjAL8T05O4H8BbbKvFgRa4NoBU8pXeYEkDDgaaLQBcwJrmeErJQB/7wes3QBWGnCIX0+AQycL1PO6BMwPa0nA4ZxbgTvOjUYMGPHRnZkQAY4mxPZBjmy53E7ukSkFKYB/D4XsWZQx64sCeYebOogGsoOBYvv6/UCb8F0IOBZ0TlP6lEYdANY350AJqB9/qPVuOI5evw4A1hgLigAlepnyxW80bcCcwN++A2s82Vcu02ta+ceq9BoL5KGTTRwQPlpqA3gCnwWU2kCDgeWRQPj2jAPCDxgCMjhI6uZnToDpvd/BJeFrJQB/fsAa02gCt3mi1wNuy8GgBNDZlysBNNSrADVSjcJl6vCpUn6jOdx0kz0q6PMhQRa4465SFKhx35cgUCBTwj2/NHwZAb71qR8GEP2H1XcmAtBPTEO67GP6FUUAIKGABbDLQ0EArhN2sAIGesRO+iyy+RMAjckVTlMCKFVAbh/4Af9OPgG61SkDVco3BQGT3GXaDAnTIAcYZDuBTwGsAGDxuBFeAQqIqwoFMlAVLrHr/wId5MPt0nilGgAAAABJRU5ErkJggg==')));
|
||||||
late final iconRestart = MemoryImage(Uint8List.fromList(base64Decode(
|
late final iconRestart = MemoryImage(Uint8List.fromList(base64Decode(
|
||||||
'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAB7BAAAewQHDaVRTAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAbhJREFUWIXVlrFqFGEUhb+7UYxaWCQKlrKKxaZSQVGDJih2tj6MD2DnMwiWvoAIRnENIpZiYxEro6IooiS7SPwsMgNLkk3mjmYmnmb45/73nMNwz/x/qH3gMu2gH6rAU+Blw+Lngau4jpmGxVF7qp1iPWjaQKnZ2WnXbuP/NqAeUPc3ZkA9XDwvqc+BVWCgPlJ7tRwUKThZce819b46VH+pfXVRXVO/q2cSul3VOgZUl0ejq86r39TXI8mqZKDuDEwCw3IREQvAbWAGmMsQZQ0sAl3gHPB1Q+0e8BuYzRDuy2yOiFVgaUxtRf0ETGc4syk4rc6PqU0Cx9j8Zf6dAeAK8Fi9sUXtFjABvEgxJlNwRP2svlNPjbw/q35U36oTFbnyMSwabxb/gB/qA3VBHagrauV7RW0DRfP1IvMlXqkXkhz1DYyQTKtHa/Z2VVMx3IiI+PI3/bCHjuOpFrSnAMpL6QfgTcMGesDx0kBr2BMzsNyi/vtQu8CJlgwsRbZDnWP90NkKaxHxJMOXMqAeAn5u0ydwMCKGY+qbkB3C2W3EKWoXk5zVoHbUZ+6Mh7tl4G4F8RJ3qvL+AfV3r5Vdpj70AAAAAElFTkSuQmCC')));
|
'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAB7BAAAewQHDaVRTAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAbhJREFUWIXVlrFqFGEUhb+7UYxaWCQKlrKKxaZSQVGDJih2tj6MD2DnMwiWvoAIRnENIpZiYxEro6IooiS7SPwsMgNLkk3mjmYmnmb45/73nMNwz/x/qH3gMu2gH6rAU+Blw+Lngau4jpmGxVF7qp1iPWjaQKnZ2WnXbuP/NqAeUPc3ZkA9XDwvqc+BVWCgPlJ7tRwUKThZce819b46VH+pfXVRXVO/q2cSul3VOgZUl0ejq86r39TXI8mqZKDuDEwCw3IREQvAbWAGmMsQZQ0sAl3gHPB1Q+0e8BuYzRDuy2yOiFVgaUxtRf0ETGc4syk4rc6PqU0Cx9j8Zf6dAeAK8Fi9sUXtFjABvEgxJlNwRP2svlNPjbw/q35U36oTFbnyMSwabxb/gB/qA3VBHagrauV7RW0DRfP1IvMlXqkXkhz1DYyQTKtHa/Z2VVMx3IiI+PI3/bCHjuOpFrSnAMpL6QfgTcMGesDx0kBr2BMzsNyi/vtQu8CJlgwsRbZDnWP90NkKaxHxJMOXMqAeAn5u0ydwMCKGY+qbkB3C2W3EKWoXk5zVoHbUZ+6Mh7tl4G4F8RJ3qvL+AfV3r5Vdpj70AAAAAElFTkSuQmCC')));
|
||||||
|
late final iconRecording = MemoryImage(Uint8List.fromList(base64Decode(
|
||||||
|
'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAANpJREFUWEftltENAiEMhtsJ1NcynG6gI+gGugEOR591gppeQoIYSDBILxEeydH/57u2FMF4obE+TAOTwLoIhBDOAHBExG2n6rgR0akW640AM0sn4SWMiDycc7s8JjN7Ijro/k8NqAAR5RoeAPZxv2ggP9hCJiWZxtGbq3hqbJiBVHy4gVx8qAER8Yi4JFy6huVAKXemgb8icI+1b5KEitq0DOO/Nm1EEX1TK27p/bVvv36MOhl4EtHHbFF7jq8AoG1z08OAiFycczrkFNe6RrIet26NMQlMAuYEXiayryF/QQktAAAAAElFTkSuQmCC')));
|
||||||
|
|
||||||
enum DesktopType {
|
enum DesktopType {
|
||||||
main,
|
main,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hbb/common.dart';
|
import 'package:flutter_hbb/common.dart';
|
||||||
@ -11,6 +12,7 @@ import 'package:flutter_hbb/models/platform_model.dart';
|
|||||||
import 'package:flutter_hbb/models/server_model.dart';
|
import 'package:flutter_hbb/models/server_model.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
|
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
|
||||||
|
|
||||||
@ -233,6 +235,7 @@ class _GeneralState extends State<_General> {
|
|||||||
abr(),
|
abr(),
|
||||||
hwcodec(),
|
hwcodec(),
|
||||||
audio(context),
|
audio(context),
|
||||||
|
record(context),
|
||||||
_Card(title: 'Language', children: [language()]),
|
_Card(title: 'Language', children: [language()]),
|
||||||
],
|
],
|
||||||
).marginOnly(bottom: _kListViewBottomMargin));
|
).marginOnly(bottom: _kListViewBottomMargin));
|
||||||
@ -324,6 +327,59 @@ class _GeneralState extends State<_General> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget record(BuildContext context) {
|
||||||
|
return _futureBuilder(future: () async {
|
||||||
|
String customDirectory =
|
||||||
|
await bind.mainGetOption(key: 'video-save-directory');
|
||||||
|
String defaultDirectory = await bind.mainDefaultVideoSaveDirectory();
|
||||||
|
String dir;
|
||||||
|
if (customDirectory.isNotEmpty) {
|
||||||
|
dir = customDirectory;
|
||||||
|
} else {
|
||||||
|
dir = defaultDirectory;
|
||||||
|
}
|
||||||
|
final canlaunch = await canLaunchUrl(Uri.file(dir));
|
||||||
|
return {'dir': dir, 'canlaunch': canlaunch};
|
||||||
|
}(), hasData: (data) {
|
||||||
|
Map<String, dynamic> map = data as Map<String, dynamic>;
|
||||||
|
String dir = map['dir']!;
|
||||||
|
bool canlaunch = map['canlaunch']! as bool;
|
||||||
|
|
||||||
|
return _Card(title: 'Recording', children: [
|
||||||
|
_OptionCheckBox(context, 'Automatically record incoming sessions',
|
||||||
|
'allow-auto-record-incoming'),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text('${translate('Directory')}:'),
|
||||||
|
Expanded(
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: canlaunch ? () => launchUrl(Uri.file(dir)) : null,
|
||||||
|
child: Text(
|
||||||
|
dir,
|
||||||
|
softWrap: true,
|
||||||
|
style:
|
||||||
|
const TextStyle(decoration: TextDecoration.underline),
|
||||||
|
)).marginOnly(left: 10),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
String? selectedDirectory = await FilePicker.platform
|
||||||
|
.getDirectoryPath(initialDirectory: dir);
|
||||||
|
if (selectedDirectory != null) {
|
||||||
|
await bind.mainSetOption(
|
||||||
|
key: 'video-save-directory',
|
||||||
|
value: selectedDirectory);
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(translate('Change')))
|
||||||
|
.marginOnly(left: 5),
|
||||||
|
],
|
||||||
|
).marginOnly(left: _kContentHMargin),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Widget language() {
|
Widget language() {
|
||||||
return _futureBuilder(future: () async {
|
return _futureBuilder(future: () async {
|
||||||
String langs = await bind.mainGetLangs();
|
String langs = await bind.mainGetLangs();
|
||||||
@ -414,6 +470,9 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
|||||||
enabled: enabled),
|
enabled: enabled),
|
||||||
_OptionCheckBox(context, 'Enable Remote Restart', 'enable-remote-restart',
|
_OptionCheckBox(context, 'Enable Remote Restart', 'enable-remote-restart',
|
||||||
enabled: enabled),
|
enabled: enabled),
|
||||||
|
_OptionCheckBox(
|
||||||
|
context, 'Enable Recording Session', 'enable-record-session',
|
||||||
|
enabled: enabled),
|
||||||
_OptionCheckBox(context, 'Enable remote configuration modification',
|
_OptionCheckBox(context, 'Enable remote configuration modification',
|
||||||
'allow-remote-config-modification',
|
'allow-remote-config-modification',
|
||||||
enabled: enabled),
|
enabled: enabled),
|
||||||
|
@ -116,6 +116,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
debugPrint("REMOTE PAGE dispose ${widget.id}");
|
debugPrint("REMOTE PAGE dispose ${widget.id}");
|
||||||
_ffi.dialogManager.hideMobileActionsOverlay();
|
_ffi.dialogManager.hideMobileActionsOverlay();
|
||||||
|
_ffi.recordingModel.onClose();
|
||||||
_rawKeyFocusNode.dispose();
|
_rawKeyFocusNode.dispose();
|
||||||
_ffi.close();
|
_ffi.close();
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
@ -164,6 +165,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
ChangeNotifierProvider.value(value: _ffi.imageModel),
|
ChangeNotifierProvider.value(value: _ffi.imageModel),
|
||||||
ChangeNotifierProvider.value(value: _ffi.cursorModel),
|
ChangeNotifierProvider.value(value: _ffi.cursorModel),
|
||||||
ChangeNotifierProvider.value(value: _ffi.canvasModel),
|
ChangeNotifierProvider.value(value: _ffi.canvasModel),
|
||||||
|
ChangeNotifierProvider.value(value: _ffi.recordingModel),
|
||||||
], child: buildBody(context)));
|
], child: buildBody(context)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -412,6 +412,13 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
|
|||||||
client.restart = enabled;
|
client.restart = enabled;
|
||||||
});
|
});
|
||||||
}, null),
|
}, null),
|
||||||
|
buildPermissionIcon(client.recording, iconRecording, (enabled) {
|
||||||
|
bind.cmSwitchPermission(
|
||||||
|
connId: client.id, name: "recording", enabled: enabled);
|
||||||
|
setState(() {
|
||||||
|
client.recording = enabled;
|
||||||
|
});
|
||||||
|
}, null),
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
|
@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hbb/models/chat_model.dart';
|
import 'package:flutter_hbb/models/chat_model.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'package:rxdart/rxdart.dart' as rxdart;
|
import 'package:rxdart/rxdart.dart' as rxdart;
|
||||||
|
|
||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
@ -134,6 +135,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
|||||||
if (!isWeb) {
|
if (!isWeb) {
|
||||||
menubarItems.add(_buildChat(context));
|
menubarItems.add(_buildChat(context));
|
||||||
}
|
}
|
||||||
|
menubarItems.add(_buildRecording(context));
|
||||||
menubarItems.add(_buildClose(context));
|
menubarItems.add(_buildClose(context));
|
||||||
return PopupMenuTheme(
|
return PopupMenuTheme(
|
||||||
data: const PopupMenuThemeData(
|
data: const PopupMenuThemeData(
|
||||||
@ -351,6 +353,28 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildRecording(BuildContext context) {
|
||||||
|
return Consumer<FfiModel>(builder: ((context, value, child) {
|
||||||
|
if (value.permissions['recording'] != false) {
|
||||||
|
return Consumer<RecordingModel>(
|
||||||
|
builder: (context, value, child) => IconButton(
|
||||||
|
tooltip: value.start
|
||||||
|
? translate('Stop session recording')
|
||||||
|
: translate('Start session recording'),
|
||||||
|
onPressed: () => value.toggle(),
|
||||||
|
icon: Icon(
|
||||||
|
value.start
|
||||||
|
? Icons.pause_circle_filled
|
||||||
|
: Icons.videocam_outlined,
|
||||||
|
color: _MenubarTheme.commonColor,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
return Offstage();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildClose(BuildContext context) {
|
Widget _buildClose(BuildContext context) {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
tooltip: translate('Close'),
|
tooltip: translate('Close'),
|
||||||
|
@ -203,6 +203,7 @@ class FfiModel with ChangeNotifier {
|
|||||||
if ((_display.width > _display.height) != oldOrientation) {
|
if ((_display.width > _display.height) != oldOrientation) {
|
||||||
gFFI.canvasModel.updateViewStyle();
|
gFFI.canvasModel.updateViewStyle();
|
||||||
}
|
}
|
||||||
|
parent.target?.recordingModel.onSwitchDisplay();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -972,6 +973,43 @@ class QualityMonitorModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RecordingModel with ChangeNotifier {
|
||||||
|
WeakReference<FFI> parent;
|
||||||
|
RecordingModel(this.parent);
|
||||||
|
bool _start = false;
|
||||||
|
get start => _start;
|
||||||
|
|
||||||
|
onSwitchDisplay() {
|
||||||
|
if (!isDesktop || !_start) return;
|
||||||
|
var id = parent.target?.id;
|
||||||
|
int? width = parent.target?.canvasModel.getDisplayWidth();
|
||||||
|
int? height = parent.target?.canvasModel.getDisplayWidth();
|
||||||
|
if (id == null || width == null || height == null) return;
|
||||||
|
bind.sessionRecordScreen(id: id, start: true, width: width, height: height);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle() {
|
||||||
|
if (!isDesktop) return;
|
||||||
|
var id = parent.target?.id;
|
||||||
|
if (id == null) return;
|
||||||
|
_start = !_start;
|
||||||
|
notifyListeners();
|
||||||
|
if (_start) {
|
||||||
|
bind.sessionRefresh(id: id);
|
||||||
|
} else {
|
||||||
|
bind.sessionRecordScreen(id: id, start: false, width: 0, height: 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose() {
|
||||||
|
if (!isDesktop) return;
|
||||||
|
var id = parent.target?.id;
|
||||||
|
if (id == null) return;
|
||||||
|
_start = false;
|
||||||
|
bind.sessionRecordScreen(id: id, start: false, width: 0, height: 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Mouse button enum.
|
/// Mouse button enum.
|
||||||
enum MouseButtons { left, right, wheel }
|
enum MouseButtons { left, right, wheel }
|
||||||
|
|
||||||
@ -1013,6 +1051,7 @@ class FFI {
|
|||||||
late final AbModel abModel; // global
|
late final AbModel abModel; // global
|
||||||
late final UserModel userModel; // global
|
late final UserModel userModel; // global
|
||||||
late final QualityMonitorModel qualityMonitorModel; // session
|
late final QualityMonitorModel qualityMonitorModel; // session
|
||||||
|
late final RecordingModel recordingModel; // recording
|
||||||
|
|
||||||
FFI() {
|
FFI() {
|
||||||
imageModel = ImageModel(WeakReference(this));
|
imageModel = ImageModel(WeakReference(this));
|
||||||
@ -1025,6 +1064,7 @@ class FFI {
|
|||||||
abModel = AbModel(WeakReference(this));
|
abModel = AbModel(WeakReference(this));
|
||||||
userModel = UserModel(WeakReference(this));
|
userModel = UserModel(WeakReference(this));
|
||||||
qualityMonitorModel = QualityMonitorModel(WeakReference(this));
|
qualityMonitorModel = QualityMonitorModel(WeakReference(this));
|
||||||
|
recordingModel = RecordingModel(WeakReference(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send a mouse tap event(down and up).
|
/// Send a mouse tap event(down and up).
|
||||||
@ -1136,7 +1176,7 @@ class FFI {
|
|||||||
Map<String, dynamic> event = json.decode(message.field0);
|
Map<String, dynamic> event = json.decode(message.field0);
|
||||||
await cb(event);
|
await cb(event);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('json.decode fail(): $e');
|
debugPrint('json.decode fail1(): $e, ${message.field0}');
|
||||||
}
|
}
|
||||||
} else if (message is Rgba) {
|
} else if (message is Rgba) {
|
||||||
imageModel.onRgba(message.field0, tabBarHeight);
|
imageModel.onRgba(message.field0, tabBarHeight);
|
||||||
|
@ -544,6 +544,7 @@ class Client {
|
|||||||
bool audio = false;
|
bool audio = false;
|
||||||
bool file = false;
|
bool file = false;
|
||||||
bool restart = false;
|
bool restart = false;
|
||||||
|
bool recording = false;
|
||||||
|
|
||||||
Client(this.id, this.authorized, this.isFileTransfer, this.name, this.peerId,
|
Client(this.id, this.authorized, this.isFileTransfer, this.name, this.peerId,
|
||||||
this.keyboard, this.clipboard, this.audio);
|
this.keyboard, this.clipboard, this.audio);
|
||||||
@ -559,6 +560,7 @@ class Client {
|
|||||||
audio = json['audio'];
|
audio = json['audio'];
|
||||||
file = json['file'];
|
file = json['file'];
|
||||||
restart = json['restart'];
|
restart = json['restart'];
|
||||||
|
recording = json['recording'];
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
|
@ -325,6 +325,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.4"
|
version: "6.1.4"
|
||||||
|
file_picker:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: file_picker
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "5.1.0"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -80,6 +80,7 @@ dependencies:
|
|||||||
desktop_drop: ^0.3.3
|
desktop_drop: ^0.3.3
|
||||||
scroll_pos: ^0.3.0
|
scroll_pos: ^0.3.0
|
||||||
rxdart: ^0.27.5
|
rxdart: ^0.27.5
|
||||||
|
file_picker: ^5.1.0
|
||||||
flutter_improved_scrolling: ^0.0.3
|
flutter_improved_scrolling: ^0.0.3
|
||||||
# currently, we use flutter 3.0.5 for windows build, latest for other builds.
|
# currently, we use flutter 3.0.5 for windows build, latest for other builds.
|
||||||
#
|
#
|
||||||
|
@ -30,6 +30,7 @@ filetime = "0.2"
|
|||||||
sodiumoxide = "0.2"
|
sodiumoxide = "0.2"
|
||||||
regex = "1.4"
|
regex = "1.4"
|
||||||
tokio-socks = { git = "https://github.com/open-trade/tokio-socks" }
|
tokio-socks = { git = "https://github.com/open-trade/tokio-socks" }
|
||||||
|
chrono = "0.4"
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
mac_address = "1.1"
|
mac_address = "1.1"
|
||||||
|
@ -436,6 +436,7 @@ message PermissionInfo {
|
|||||||
Audio = 3;
|
Audio = 3;
|
||||||
File = 4;
|
File = 4;
|
||||||
Restart = 5;
|
Restart = 5;
|
||||||
|
Recording = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
Permission permission = 1;
|
Permission permission = 1;
|
||||||
|
@ -38,6 +38,8 @@ pub use tokio_socks;
|
|||||||
pub use tokio_socks::IntoTargetAddr;
|
pub use tokio_socks::IntoTargetAddr;
|
||||||
pub use tokio_socks::TargetAddr;
|
pub use tokio_socks::TargetAddr;
|
||||||
pub mod password_security;
|
pub mod password_security;
|
||||||
|
pub use chrono;
|
||||||
|
pub use directories_next;
|
||||||
|
|
||||||
#[cfg(feature = "quic")]
|
#[cfg(feature = "quic")]
|
||||||
pub type Stream = quic::Connection;
|
pub type Stream = quic::Connection;
|
||||||
|
@ -20,6 +20,7 @@ libc = "0.2"
|
|||||||
num_cpus = "1.13"
|
num_cpus = "1.13"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
hbb_common = { path = "../hbb_common" }
|
hbb_common = { path = "../hbb_common" }
|
||||||
|
webm = "1.0"
|
||||||
|
|
||||||
[dependencies.winapi]
|
[dependencies.winapi]
|
||||||
version = "0.3"
|
version = "0.3"
|
||||||
@ -37,7 +38,6 @@ ndk = { version = "0.7", features = ["media"], optional = true}
|
|||||||
[target.'cfg(not(target_os = "android"))'.dev-dependencies]
|
[target.'cfg(not(target_os = "android"))'.dev-dependencies]
|
||||||
repng = "0.2"
|
repng = "0.2"
|
||||||
docopt = "1.1"
|
docopt = "1.1"
|
||||||
webm = "1.0"
|
|
||||||
serde = {version="1.0", features=["derive"]}
|
serde = {version="1.0", features=["derive"]}
|
||||||
quest = "0.3"
|
quest = "0.3"
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ const CFG_KEY_ENCODER: &str = "bestHwEncoders";
|
|||||||
const CFG_KEY_DECODER: &str = "bestHwDecoders";
|
const CFG_KEY_DECODER: &str = "bestHwDecoders";
|
||||||
|
|
||||||
const DEFAULT_PIXFMT: AVPixelFormat = AVPixelFormat::AV_PIX_FMT_YUV420P;
|
const DEFAULT_PIXFMT: AVPixelFormat = AVPixelFormat::AV_PIX_FMT_YUV420P;
|
||||||
const DEFAULT_TIME_BASE: [i32; 2] = [1, 30];
|
pub const DEFAULT_TIME_BASE: [i32; 2] = [1, 30];
|
||||||
const DEFAULT_GOP: i32 = 60;
|
const DEFAULT_GOP: i32 = 60;
|
||||||
const DEFAULT_HW_QUALITY: Quality = Quality_Default;
|
const DEFAULT_HW_QUALITY: Quality = Quality_Default;
|
||||||
const DEFAULT_RC: RateContorl = RC_DEFAULT;
|
const DEFAULT_RC: RateContorl = RC_DEFAULT;
|
||||||
@ -94,6 +94,7 @@ impl EncoderApi for HwEncoder {
|
|||||||
frames.push(EncodedVideoFrame {
|
frames.push(EncodedVideoFrame {
|
||||||
data: Bytes::from(frame.data),
|
data: Bytes::from(frame.data),
|
||||||
pts: frame.pts as _,
|
pts: frame.pts as _,
|
||||||
|
key:frame.key == 1,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@ pub use self::convert::*;
|
|||||||
pub const STRIDE_ALIGN: usize = 64; // commonly used in libvpx vpx_img_alloc caller
|
pub const STRIDE_ALIGN: usize = 64; // commonly used in libvpx vpx_img_alloc caller
|
||||||
pub const HW_STRIDE_ALIGN: usize = 0; // recommended by av_frame_get_buffer
|
pub const HW_STRIDE_ALIGN: usize = 0; // recommended by av_frame_get_buffer
|
||||||
|
|
||||||
|
pub mod record;
|
||||||
mod vpx;
|
mod vpx;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
306
libs/scrap/src/common/record.rs
Normal file
306
libs/scrap/src/common/record.rs
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
use hbb_common::anyhow::anyhow;
|
||||||
|
use hbb_common::{
|
||||||
|
bail, chrono,
|
||||||
|
config::Config,
|
||||||
|
directories_next,
|
||||||
|
message_proto::{message, video_frame, EncodedVideoFrame, Message},
|
||||||
|
ResultType,
|
||||||
|
};
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
use hwcodec::mux::{MuxContext, Muxer};
|
||||||
|
use std::{
|
||||||
|
fs::{File, OpenOptions},
|
||||||
|
io,
|
||||||
|
time::Instant,
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
use webm::mux::{self, Segment, Track, VideoTrack, Writer};
|
||||||
|
|
||||||
|
const MIN_SECS: u64 = 1;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum RecodeCodecID {
|
||||||
|
VP9,
|
||||||
|
H264,
|
||||||
|
H265,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct RecorderContext {
|
||||||
|
pub id: String,
|
||||||
|
pub filename: String,
|
||||||
|
pub width: usize,
|
||||||
|
pub height: usize,
|
||||||
|
pub codec_id: RecodeCodecID,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RecorderContext {
|
||||||
|
pub fn set_filename(&mut self) -> ResultType<()> {
|
||||||
|
let mut dir = Config::get_option("video-save-directory");
|
||||||
|
if !dir.is_empty() {
|
||||||
|
if !PathBuf::from(&dir).exists() {
|
||||||
|
std::fs::create_dir_all(&dir)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dir = Self::default_save_directory();
|
||||||
|
if !dir.is_empty() && !PathBuf::from(&dir).exists() {
|
||||||
|
std::fs::create_dir_all(&dir)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let file = self.id.clone()
|
||||||
|
+ &chrono::Local::now().format("_%Y%m%d%H%M%S").to_string()
|
||||||
|
+ if self.codec_id == RecodeCodecID::VP9 {
|
||||||
|
".webm"
|
||||||
|
} else {
|
||||||
|
".mp4"
|
||||||
|
};
|
||||||
|
self.filename = PathBuf::from(&dir).join(file).to_string_lossy().to_string();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_save_directory() -> String {
|
||||||
|
if let Some(user) = directories_next::UserDirs::new() {
|
||||||
|
if let Some(video_dir) = user.video_dir() {
|
||||||
|
return video_dir.join("RustDesk").to_string_lossy().to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"".to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for Recorder {}
|
||||||
|
unsafe impl Sync for Recorder {}
|
||||||
|
|
||||||
|
pub trait RecorderApi {
|
||||||
|
fn new(ctx: RecorderContext) -> ResultType<Self>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
fn write_video(&mut self, frame: &EncodedVideoFrame) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Recorder {
|
||||||
|
pub inner: Box<dyn RecorderApi>,
|
||||||
|
ctx: RecorderContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Recorder {
|
||||||
|
type Target = Box<dyn RecorderApi>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for Recorder {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Recorder {
|
||||||
|
pub fn new(mut ctx: RecorderContext) -> ResultType<Self> {
|
||||||
|
ctx.set_filename()?;
|
||||||
|
let recorder = match ctx.codec_id {
|
||||||
|
RecodeCodecID::VP9 => Recorder {
|
||||||
|
inner: Box::new(WebmRecorder::new(ctx.clone())?),
|
||||||
|
ctx,
|
||||||
|
},
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
_ => Recorder {
|
||||||
|
inner: Box::new(HwRecorder::new(ctx.clone())?),
|
||||||
|
ctx,
|
||||||
|
},
|
||||||
|
#[cfg(not(feature = "hwcodec"))]
|
||||||
|
_ => bail!("unsupported codec type"),
|
||||||
|
};
|
||||||
|
Ok(recorder)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn change(&mut self, mut ctx: RecorderContext) -> ResultType<()> {
|
||||||
|
ctx.set_filename()?;
|
||||||
|
self.inner = match ctx.codec_id {
|
||||||
|
RecodeCodecID::VP9 => Box::new(WebmRecorder::new(ctx.clone())?),
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
_ => Box::new(HwRecorder::new(ctx.clone())?),
|
||||||
|
#[cfg(not(feature = "hwcodec"))]
|
||||||
|
_ => bail!("unsupported codec type"),
|
||||||
|
};
|
||||||
|
self.ctx = ctx;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_message(&mut self, msg: &Message) {
|
||||||
|
if let Some(message::Union::VideoFrame(vf)) = &msg.union {
|
||||||
|
if let Some(frame) = &vf.union {
|
||||||
|
self.write_frame(frame).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_frame(&mut self, frame: &video_frame::Union) -> ResultType<()> {
|
||||||
|
match frame {
|
||||||
|
video_frame::Union::Vp9s(vp9s) => {
|
||||||
|
if self.ctx.codec_id != RecodeCodecID::VP9 {
|
||||||
|
self.change(RecorderContext {
|
||||||
|
codec_id: RecodeCodecID::VP9,
|
||||||
|
..self.ctx.clone()
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
vp9s.frames.iter().map(|f| self.write_video(f)).count();
|
||||||
|
}
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
video_frame::Union::H264s(h264s) => {
|
||||||
|
if self.ctx.codec_id != RecodeCodecID::H264 {
|
||||||
|
self.change(RecorderContext {
|
||||||
|
codec_id: RecodeCodecID::H264,
|
||||||
|
..self.ctx.clone()
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
if self.ctx.codec_id == RecodeCodecID::H264 {
|
||||||
|
h264s.frames.iter().map(|f| self.write_video(f)).count();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
video_frame::Union::H265s(h265s) => {
|
||||||
|
if self.ctx.codec_id != RecodeCodecID::H265 {
|
||||||
|
self.change(RecorderContext {
|
||||||
|
codec_id: RecodeCodecID::H265,
|
||||||
|
..self.ctx.clone()
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
if self.ctx.codec_id == RecodeCodecID::H265 {
|
||||||
|
h265s.frames.iter().map(|f| self.write_video(f)).count();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => bail!("unsupported frame type"),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WebmRecorder {
|
||||||
|
vt: VideoTrack,
|
||||||
|
webm: Option<Segment<Writer<File>>>,
|
||||||
|
ctx: RecorderContext,
|
||||||
|
key: bool,
|
||||||
|
written: bool,
|
||||||
|
start: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RecorderApi for WebmRecorder {
|
||||||
|
fn new(ctx: RecorderContext) -> ResultType<Self> {
|
||||||
|
let out = match {
|
||||||
|
OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create_new(true)
|
||||||
|
.open(&ctx.filename)
|
||||||
|
} {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => File::create(&ctx.filename)?,
|
||||||
|
Err(e) => return Err(e.into()),
|
||||||
|
};
|
||||||
|
let mut webm = match mux::Segment::new(mux::Writer::new(out)) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => bail!("Failed to create webm mux"),
|
||||||
|
};
|
||||||
|
let vt = webm.add_video_track(
|
||||||
|
ctx.width as _,
|
||||||
|
ctx.height as _,
|
||||||
|
None,
|
||||||
|
mux::VideoCodecId::VP9,
|
||||||
|
);
|
||||||
|
Ok(WebmRecorder {
|
||||||
|
vt,
|
||||||
|
webm: Some(webm),
|
||||||
|
ctx,
|
||||||
|
key: false,
|
||||||
|
written: false,
|
||||||
|
start: Instant::now(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_video(&mut self, frame: &EncodedVideoFrame) -> bool {
|
||||||
|
if frame.key {
|
||||||
|
self.key = true;
|
||||||
|
}
|
||||||
|
if self.key {
|
||||||
|
let ok = self
|
||||||
|
.vt
|
||||||
|
.add_frame(&frame.data, frame.pts as u64 * 1_000_000, frame.key);
|
||||||
|
if ok {
|
||||||
|
self.written = true;
|
||||||
|
}
|
||||||
|
ok
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for WebmRecorder {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
std::mem::replace(&mut self.webm, None).map_or(false, |webm| webm.finalize(None));
|
||||||
|
if !self.written || self.start.elapsed().as_secs() < MIN_SECS {
|
||||||
|
std::fs::remove_file(&self.ctx.filename).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
struct HwRecorder {
|
||||||
|
muxer: Muxer,
|
||||||
|
ctx: RecorderContext,
|
||||||
|
written: bool,
|
||||||
|
key: bool,
|
||||||
|
start: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
impl RecorderApi for HwRecorder {
|
||||||
|
fn new(ctx: RecorderContext) -> ResultType<Self> {
|
||||||
|
let muxer = Muxer::new(MuxContext {
|
||||||
|
filename: ctx.filename.clone(),
|
||||||
|
width: ctx.width,
|
||||||
|
height: ctx.height,
|
||||||
|
is265: ctx.codec_id == RecodeCodecID::H265,
|
||||||
|
framerate: crate::hwcodec::DEFAULT_TIME_BASE[1] as _,
|
||||||
|
})
|
||||||
|
.map_err(|_| anyhow!("Failed to create hardware muxer"))?;
|
||||||
|
Ok(HwRecorder {
|
||||||
|
muxer,
|
||||||
|
ctx,
|
||||||
|
written: false,
|
||||||
|
key: false,
|
||||||
|
start: Instant::now(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_video(&mut self, frame: &EncodedVideoFrame) -> bool {
|
||||||
|
if frame.key {
|
||||||
|
self.key = true;
|
||||||
|
}
|
||||||
|
if self.key {
|
||||||
|
let ok = self.muxer.write_video(&frame.data, frame.key).is_ok();
|
||||||
|
if ok {
|
||||||
|
self.written = true;
|
||||||
|
}
|
||||||
|
ok
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
impl Drop for HwRecorder {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.muxer.write_tail().ok();
|
||||||
|
if !self.written || self.start.elapsed().as_secs() < MIN_SECS {
|
||||||
|
std::fs::remove_file(&self.ctx.filename).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,3 @@
|
|||||||
use std::{
|
|
||||||
collections::HashMap,
|
|
||||||
net::SocketAddr,
|
|
||||||
ops::{Deref, Not},
|
|
||||||
sync::{atomic::AtomicBool, mpsc, Arc, Mutex, RwLock},
|
|
||||||
};
|
|
||||||
use std::sync::atomic::Ordering;
|
|
||||||
pub use async_trait::async_trait;
|
pub use async_trait::async_trait;
|
||||||
#[cfg(not(any(target_os = "android", target_os = "linux")))]
|
#[cfg(not(any(target_os = "android", target_os = "linux")))]
|
||||||
use cpal::{
|
use cpal::{
|
||||||
@ -13,6 +6,13 @@ use cpal::{
|
|||||||
};
|
};
|
||||||
use magnum_opus::{Channels::*, Decoder as AudioDecoder};
|
use magnum_opus::{Channels::*, Decoder as AudioDecoder};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
net::SocketAddr,
|
||||||
|
ops::{Deref, Not},
|
||||||
|
sync::{atomic::AtomicBool, mpsc, Arc, Mutex, RwLock},
|
||||||
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub use file_trait::FileManager;
|
pub use file_trait::FileManager;
|
||||||
@ -39,6 +39,7 @@ pub use helper::LatencyController;
|
|||||||
pub use helper::*;
|
pub use helper::*;
|
||||||
use scrap::{
|
use scrap::{
|
||||||
codec::{Decoder, DecoderCfg},
|
codec::{Decoder, DecoderCfg},
|
||||||
|
record::{Recorder, RecorderContext},
|
||||||
VpxDecoderConfig, VpxVideoCodecId,
|
VpxDecoderConfig, VpxVideoCodecId,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -154,8 +155,7 @@ impl Client {
|
|||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(x) => {
|
Ok(x) => Ok(x),
|
||||||
Ok(x)},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -798,6 +798,8 @@ pub struct VideoHandler {
|
|||||||
decoder: Decoder,
|
decoder: Decoder,
|
||||||
latency_controller: Arc<Mutex<LatencyController>>,
|
latency_controller: Arc<Mutex<LatencyController>>,
|
||||||
pub rgb: Vec<u8>,
|
pub rgb: Vec<u8>,
|
||||||
|
recorder: Arc<Mutex<Option<Recorder>>>,
|
||||||
|
record: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VideoHandler {
|
impl VideoHandler {
|
||||||
@ -812,6 +814,8 @@ impl VideoHandler {
|
|||||||
}),
|
}),
|
||||||
latency_controller,
|
latency_controller,
|
||||||
rgb: Default::default(),
|
rgb: Default::default(),
|
||||||
|
recorder: Default::default(),
|
||||||
|
record: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -825,32 +829,21 @@ impl VideoHandler {
|
|||||||
.update_video(vf.timestamp);
|
.update_video(vf.timestamp);
|
||||||
}
|
}
|
||||||
match &vf.union {
|
match &vf.union {
|
||||||
Some(frame) => self.decoder.handle_video_frame(frame, &mut self.rgb),
|
Some(frame) => {
|
||||||
|
let res = self.decoder.handle_video_frame(frame, &mut self.rgb);
|
||||||
|
if self.record {
|
||||||
|
self.recorder
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.as_mut()
|
||||||
|
.map(|r| r.write_frame(frame));
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
_ => Ok(false),
|
_ => Ok(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle a VP9S frame.
|
|
||||||
// pub fn handle_vp9s(&mut self, vp9s: &VP9s) -> ResultType<bool> {
|
|
||||||
// let mut last_frame = Image::new();
|
|
||||||
// for vp9 in vp9s.frames.iter() {
|
|
||||||
// for frame in self.decoder.decode(&vp9.data)? {
|
|
||||||
// drop(last_frame);
|
|
||||||
// last_frame = frame;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// for frame in self.decoder.flush()? {
|
|
||||||
// drop(last_frame);
|
|
||||||
// last_frame = frame;
|
|
||||||
// }
|
|
||||||
// if last_frame.is_null() {
|
|
||||||
// Ok(false)
|
|
||||||
// } else {
|
|
||||||
// last_frame.rgb(1, true, &mut self.rgb);
|
|
||||||
// Ok(true)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// Reset the decoder.
|
/// Reset the decoder.
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.decoder = Decoder::new(DecoderCfg {
|
self.decoder = Decoder::new(DecoderCfg {
|
||||||
@ -860,6 +853,24 @@ impl VideoHandler {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Start or stop screen record.
|
||||||
|
pub fn record_screen(&mut self, start: bool, w: i32, h: i32, id: String) {
|
||||||
|
self.record = false;
|
||||||
|
if start {
|
||||||
|
self.recorder = Recorder::new(RecorderContext {
|
||||||
|
id,
|
||||||
|
filename: "".to_owned(),
|
||||||
|
width: w as _,
|
||||||
|
height: h as _,
|
||||||
|
codec_id: scrap::record::RecodeCodecID::VP9,
|
||||||
|
})
|
||||||
|
.map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r))));
|
||||||
|
} else {
|
||||||
|
self.recorder = Default::default();
|
||||||
|
}
|
||||||
|
self.record = start;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Login config handler for [`Client`].
|
/// Login config handler for [`Client`].
|
||||||
@ -1395,6 +1406,7 @@ pub enum MediaData {
|
|||||||
AudioFrame(AudioFrame),
|
AudioFrame(AudioFrame),
|
||||||
AudioFormat(AudioFormat),
|
AudioFormat(AudioFormat),
|
||||||
Reset,
|
Reset,
|
||||||
|
RecordScreen(bool, i32, i32, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type MediaSender = mpsc::Sender<MediaData>;
|
pub type MediaSender = mpsc::Sender<MediaData>;
|
||||||
@ -1429,6 +1441,9 @@ where
|
|||||||
MediaData::Reset => {
|
MediaData::Reset => {
|
||||||
video_handler.reset();
|
video_handler.reset();
|
||||||
}
|
}
|
||||||
|
MediaData::RecordScreen(start, w, h, id) => {
|
||||||
|
video_handler.record_screen(start, w, h, id)
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1703,6 +1718,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, i32, i32, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Keycode for key events.
|
/// Keycode for key events.
|
||||||
|
@ -601,6 +601,11 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Data::RecordScreen(start, w, h, id) => {
|
||||||
|
let _ = self
|
||||||
|
.video_sender
|
||||||
|
.send(MediaData::RecordScreen(start, w, h, id));
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
@ -794,13 +799,8 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
fs::transform_windows_path(&mut entries);
|
fs::transform_windows_path(&mut entries);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.handler.update_folder_files(
|
self.handler
|
||||||
fd.id,
|
.update_folder_files(fd.id, &entries, fd.path, false, false);
|
||||||
&entries,
|
|
||||||
fd.path,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
if let Some(job) = fs::get_job(fd.id, &mut self.write_jobs) {
|
if let Some(job) = fs::get_job(fd.id, &mut self.write_jobs) {
|
||||||
log::info!("job set_files: {:?}", entries);
|
log::info!("job set_files: {:?}", entries);
|
||||||
job.set_files(entries);
|
job.set_files(entries);
|
||||||
@ -958,6 +958,9 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
Permission::Restart => {
|
Permission::Restart => {
|
||||||
self.handler.set_permission("restart", p.enabled);
|
self.handler.set_permission("restart", p.enabled);
|
||||||
}
|
}
|
||||||
|
Permission::Recording => {
|
||||||
|
self.handler.set_permission("recording", p.enabled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(misc::Union::SwitchDisplay(s)) => {
|
Some(misc::Union::SwitchDisplay(s)) => {
|
||||||
|
@ -257,7 +257,7 @@ impl InvokeUiSession for FlutterHandler {
|
|||||||
self.push_event(
|
self.push_event(
|
||||||
"switch_display",
|
"switch_display",
|
||||||
vec![
|
vec![
|
||||||
("display", &display.to_string()),
|
("display", &display.display.to_string()),
|
||||||
("x", &display.x.to_string()),
|
("x", &display.x.to_string()),
|
||||||
("y", &display.y.to_string()),
|
("y", &display.y.to_string()),
|
||||||
("width", &display.width.to_string()),
|
("width", &display.width.to_string()),
|
||||||
|
@ -15,19 +15,7 @@ use hbb_common::{message_proto::Hash, ResultType};
|
|||||||
|
|
||||||
use crate::flutter::{self, SESSIONS};
|
use crate::flutter::{self, SESSIONS};
|
||||||
use crate::start_server;
|
use crate::start_server;
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
use crate::ui_interface::{self, *};
|
||||||
use crate::ui_interface::get_sound_inputs;
|
|
||||||
use crate::ui_interface::{
|
|
||||||
self, change_id, check_mouse_time, check_super_user_permission, discover, forget_password,
|
|
||||||
get_api_server, get_app_name, get_async_job_status, get_connect_status, get_fav, get_id,
|
|
||||||
get_lan_peers, get_langs, get_license, get_local_option, get_mouse_time, get_new_version,
|
|
||||||
get_option, get_options, get_peer, get_peer_option, get_socks, get_uuid, get_version,
|
|
||||||
goto_install, has_hwcodec, has_rendezvous_service, is_can_screen_recording, is_installed,
|
|
||||||
is_installed_daemon, is_installed_lower_version, is_process_trusted, is_rdp_service_open,
|
|
||||||
is_share_rdp, peer_has_password, post_request, send_to_cm, set_local_option, set_option,
|
|
||||||
set_options, set_peer_option, set_permanent_password, set_socks, store_fav,
|
|
||||||
test_if_valid_server, update_me, update_temporary_password, using_public_server,
|
|
||||||
};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
client::file_trait::FileManager,
|
client::file_trait::FileManager,
|
||||||
flutter::{make_fd_to_json, session_add, session_start_},
|
flutter::{make_fd_to_json, session_add, session_start_},
|
||||||
@ -163,6 +151,12 @@ pub fn session_refresh(id: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn session_record_screen(id: String, start: bool, width: usize, height: usize) {
|
||||||
|
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||||
|
session.record_screen(start, width as _, height as _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn session_reconnect(id: String) {
|
pub fn session_reconnect(id: String) {
|
||||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||||
session.reconnect();
|
session.reconnect();
|
||||||
@ -719,6 +713,10 @@ pub fn main_change_language(lang: String) {
|
|||||||
send_to_cm(&crate::ipc::Data::Language(lang));
|
send_to_cm(&crate::ipc::Data::Language(lang));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn main_default_video_save_directory() -> String {
|
||||||
|
default_video_save_directory()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn session_add_port_forward(
|
pub fn session_add_port_forward(
|
||||||
id: String,
|
id: String,
|
||||||
local_port: i32,
|
local_port: i32,
|
||||||
|
@ -146,6 +146,7 @@ pub enum Data {
|
|||||||
file: bool,
|
file: bool,
|
||||||
file_transfer_enabled: bool,
|
file_transfer_enabled: bool,
|
||||||
restart: bool,
|
restart: bool,
|
||||||
|
recording: bool,
|
||||||
},
|
},
|
||||||
ChatMessage {
|
ChatMessage {
|
||||||
text: String,
|
text: String,
|
||||||
|
@ -349,6 +349,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Enable RDP", "允许RDP访问"),
|
("Enable RDP", "允许RDP访问"),
|
||||||
("Pin menubar", "固定菜单栏"),
|
("Pin menubar", "固定菜单栏"),
|
||||||
("Unpin menubar", "取消固定菜单栏"),
|
("Unpin menubar", "取消固定菜单栏"),
|
||||||
|
("Recording", "录屏"),
|
||||||
|
("Directory", "目录"),
|
||||||
|
("Automatically record incoming sessions", "自动录制来访会话"),
|
||||||
|
("Change", "更改"),
|
||||||
|
("Start session recording", "开始录屏"),
|
||||||
|
("Stop session recording", "结束录屏"),
|
||||||
|
("Enable Recording Session", "允许录制会话"),
|
||||||
|
("Allow recording session", "允许录制会话"),
|
||||||
("Enable LAN Discovery", "允许局域网发现"),
|
("Enable LAN Discovery", "允许局域网发现"),
|
||||||
("Deny LAN Discovery", "拒绝局域网发现"),
|
("Deny LAN Discovery", "拒绝局域网发现"),
|
||||||
("Write a message", "输入聊天消息"),
|
("Write a message", "输入聊天消息"),
|
||||||
|
@ -349,6 +349,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Enable RDP", ""),
|
("Enable RDP", ""),
|
||||||
("Pin menubar", "Připnout panel nabídek"),
|
("Pin menubar", "Připnout panel nabídek"),
|
||||||
("Unpin menubar", "Odepnout panel nabídek"),
|
("Unpin menubar", "Odepnout panel nabídek"),
|
||||||
|
("Recording", ""),
|
||||||
|
("Directory", ""),
|
||||||
|
("Automatically record incoming sessions", ""),
|
||||||
|
("Change", ""),
|
||||||
|
("Start session recording", ""),
|
||||||
|
("Stop session recording", ""),
|
||||||
|
("Enable Recording Session", ""),
|
||||||
|
("Allow recording session", ""),
|
||||||
("Enable LAN Discovery", ""),
|
("Enable LAN Discovery", ""),
|
||||||
("Deny LAN Discovery", ""),
|
("Deny LAN Discovery", ""),
|
||||||
("Write a message", ""),
|
("Write a message", ""),
|
||||||
|
@ -349,6 +349,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Enable RDP", ""),
|
("Enable RDP", ""),
|
||||||
("Pin menubar", "Fastgør menulinjen"),
|
("Pin menubar", "Fastgør menulinjen"),
|
||||||
("Unpin menubar", "Frigør menulinjen"),
|
("Unpin menubar", "Frigør menulinjen"),
|
||||||
|
("Recording", ""),
|
||||||
|
("Directory", ""),
|
||||||
|
("Automatically record incoming sessions", ""),
|
||||||
|
("Change", ""),
|
||||||
|
("Start session recording", ""),
|
||||||
|
("Stop session recording", ""),
|
||||||
|
("Enable Recording Session", ""),
|
||||||
|
("Allow recording session", ""),
|
||||||
("Enable LAN Discovery", ""),
|
("Enable LAN Discovery", ""),
|
||||||
("Deny LAN Discovery", ""),
|
("Deny LAN Discovery", ""),
|
||||||
("Write a message", ""),
|
("Write a message", ""),
|
||||||
|
@ -349,6 +349,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Enable RDP", ""),
|
("Enable RDP", ""),
|
||||||
("Pin menubar", "Pin-Menüleiste"),
|
("Pin menubar", "Pin-Menüleiste"),
|
||||||
("Unpin menubar", "Menüleiste lösen"),
|
("Unpin menubar", "Menüleiste lösen"),
|
||||||
|
("Recording", ""),
|
||||||
|
("Directory", ""),
|
||||||
|
("Automatically record incoming sessions", ""),
|
||||||
|
("Change", ""),
|
||||||
|
("Start session recording", ""),
|
||||||
|
("Stop session recording", ""),
|
||||||
|
("Enable Recording Session", ""),
|
||||||
|
("Allow recording session", ""),
|
||||||
("Enable LAN Discovery", ""),
|
("Enable LAN Discovery", ""),
|
||||||
("Deny LAN Discovery", ""),
|
("Deny LAN Discovery", ""),
|
||||||
("Write a message", ""),
|
("Write a message", ""),
|
||||||
|
@ -349,6 +349,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Enable RDP", ""),
|
("Enable RDP", ""),
|
||||||
("Pin menubar", "Alpingla menubreto"),
|
("Pin menubar", "Alpingla menubreto"),
|
||||||
("Unpin menubar", "Malfiksi menubreton"),
|
("Unpin menubar", "Malfiksi menubreton"),
|
||||||
|
("Recording", ""),
|
||||||
|
("Directory", ""),
|
||||||
|
("Automatically record incoming sessions", ""),
|
||||||
|
("Change", ""),
|
||||||
|
("Start session recording", ""),
|
||||||
|
("Stop session recording", ""),
|
||||||
|
("Enable Recording Session", ""),
|
||||||
|
("Allow recording session", ""),
|
||||||
("Enable LAN Discovery", ""),
|
("Enable LAN Discovery", ""),
|
||||||
("Deny LAN Discovery", ""),
|
("Deny LAN Discovery", ""),
|
||||||
("Write a message", ""),
|
("Write a message", ""),
|
||||||
|
@ -349,6 +349,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Enable RDP", ""),
|
("Enable RDP", ""),
|
||||||
("Pin menubar", "Pin barra de menú"),
|
("Pin menubar", "Pin barra de menú"),
|
||||||
("Unpin menubar", "Desbloquear barra de menú"),
|
("Unpin menubar", "Desbloquear barra de menú"),
|
||||||
|
("Recording", ""),
|
||||||
|
("Directory", ""),
|
||||||
|
("Automatically record incoming sessions", ""),
|
||||||
|
("Change", ""),
|
||||||
|
("Start session recording", ""),
|
||||||
|
("Stop session recording", ""),
|
||||||
|
("Enable Recording Session", ""),
|
||||||
|
("Allow recording session", ""),
|
||||||
("Enable LAN Discovery", ""),
|
("Enable LAN Discovery", ""),
|
||||||
("Deny LAN Discovery", ""),
|
("Deny LAN Discovery", ""),
|
||||||
("Write a message", ""),
|
("Write a message", ""),
|
||||||
|
@ -349,6 +349,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Enable RDP", ""),
|
("Enable RDP", ""),
|
||||||
("Pin menubar", "Épingler la barre de menus"),
|
("Pin menubar", "Épingler la barre de menus"),
|
||||||
("Unpin menubar", "Détacher la barre de menu"),
|
("Unpin menubar", "Détacher la barre de menu"),
|
||||||
|
("Recording", ""),
|
||||||
|
("Directory", ""),
|
||||||
|
("Automatically record incoming sessions", ""),
|
||||||
|
("Change", ""),
|
||||||
|
("Start session recording", ""),
|
||||||
|
("Stop session recording", ""),
|
||||||
|
("Enable Recording Session", ""),
|
||||||
|
("Allow recording session", ""),
|
||||||
("Enable LAN Discovery", ""),
|
("Enable LAN Discovery", ""),
|
||||||
("Deny LAN Discovery", ""),
|
("Deny LAN Discovery", ""),
|
||||||
("Write a message", ""),
|
("Write a message", ""),
|
||||||
|
@ -349,6 +349,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Enable RDP", ""),
|
("Enable RDP", ""),
|
||||||
("Pin menubar", "Menüsor rögzítése"),
|
("Pin menubar", "Menüsor rögzítése"),
|
||||||
("Unpin menubar", "Menüsor rögzítésének feloldása"),
|
("Unpin menubar", "Menüsor rögzítésének feloldása"),
|
||||||
|
("Recording", ""),
|
||||||
|
("Directory", ""),
|
||||||
|
("Automatically record incoming sessions", ""),
|
||||||
|
("Change", ""),
|
||||||
|
("Start session recording", ""),
|
||||||
|
("Stop session recording", ""),
|
||||||
|
("Enable Recording Session", ""),
|
||||||
|
("Allow recording session", ""),
|
||||||
("Enable LAN Discovery", ""),
|
("Enable LAN Discovery", ""),
|
||||||
("Deny LAN Discovery", ""),
|
("Deny LAN Discovery", ""),
|
||||||
("Write a message", ""),
|
("Write a message", ""),
|
||||||
|
@ -349,6 +349,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Enable RDP", ""),
|
("Enable RDP", ""),
|
||||||
("Pin menubar", "Pin menubar"),
|
("Pin menubar", "Pin menubar"),
|
||||||
("Unpin menubar", "Unpin menubar"),
|
("Unpin menubar", "Unpin menubar"),
|
||||||
|
("Recording", ""),
|
||||||
|
("Directory", ""),
|
||||||
|
("Automatically record incoming sessions", ""),
|
||||||
|
("Change", ""),
|
||||||
|
("Start session recording", ""),
|
||||||
|
("Stop session recording", ""),
|
||||||
|
("Enable Recording Session", ""),
|
||||||
|
("Allow recording session", ""),
|
||||||
("Enable LAN Discovery", ""),
|
("Enable LAN Discovery", ""),
|
||||||
("Deny LAN Discovery", ""),
|
("Deny LAN Discovery", ""),
|
||||||
("Write a message", ""),
|
("Write a message", ""),
|
||||||
|
@ -349,6 +349,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Enable RDP", ""),
|
("Enable RDP", ""),
|
||||||
("Pin menubar", "Blocca la barra dei menu"),
|
("Pin menubar", "Blocca la barra dei menu"),
|
||||||
("Unpin menubar", "Sblocca la barra dei menu"),
|
("Unpin menubar", "Sblocca la barra dei menu"),
|
||||||
|
("Recording", ""),
|
||||||
|
("Directory", ""),
|
||||||
|
("Automatically record incoming sessions", ""),
|
||||||
|
("Change", ""),
|
||||||
|
("Start session recording", ""),
|
||||||
|
("Stop session recording", ""),
|
||||||
|
("Enable Recording Session", ""),
|
||||||
|
("Allow recording session", ""),
|
||||||
("Enable LAN Discovery", ""),
|
("Enable LAN Discovery", ""),
|
||||||
("Deny LAN Discovery", ""),
|
("Deny LAN Discovery", ""),
|
||||||
("Write a message", ""),
|
("Write a message", ""),
|
||||||
|
@ -349,6 +349,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Enable RDP", ""),
|
("Enable RDP", ""),
|
||||||
("Pin menubar", "メニューバーを固定する"),
|
("Pin menubar", "メニューバーを固定する"),
|
||||||
("Unpin menubar", "メニューバーのピン留めを外す"),
|
("Unpin menubar", "メニューバーのピン留めを外す"),
|
||||||
|
("Recording", ""),
|
||||||
|
("Directory", ""),
|
||||||
|
("Automatically record incoming sessions", ""),
|
||||||
|
("Change", ""),
|
||||||
|
("Start session recording", ""),
|
||||||
|
("Stop session recording", ""),
|
||||||
|
("Enable Recording Session", ""),
|
||||||
|
("Allow recording session", ""),
|
||||||
("Enable LAN Discovery", ""),
|
("Enable LAN Discovery", ""),
|
||||||
("Deny LAN Discovery", ""),
|
("Deny LAN Discovery", ""),
|
||||||
("Write a message", ""),
|
("Write a message", ""),
|
||||||
|
@ -349,6 +349,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Enable RDP", ""),
|
("Enable RDP", ""),
|
||||||
("Pin menubar", "핀 메뉴 바"),
|
("Pin menubar", "핀 메뉴 바"),
|
||||||
("Unpin menubar", "메뉴 모음 고정 해제"),
|
("Unpin menubar", "메뉴 모음 고정 해제"),
|
||||||
|
("Recording", ""),
|
||||||
|
("Directory", ""),
|
||||||
|
("Automatically record incoming sessions", ""),
|
||||||
|
("Change", ""),
|
||||||
|
("Start session recording", ""),
|
||||||
|
("Stop session recording", ""),
|
||||||
|
("Enable Recording Session", ""),
|
||||||
|
("Allow recording session", ""),
|
||||||
("Enable LAN Discovery", ""),
|
("Enable LAN Discovery", ""),
|
||||||
("Deny LAN Discovery", ""),
|
("Deny LAN Discovery", ""),
|
||||||
("Write a message", ""),
|
("Write a message", ""),
|
||||||
|
@ -290,9 +290,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Ignore Battery Optimizations", "Бәтері Оңтайландыруларын Елемеу"),
|
("Ignore Battery Optimizations", "Бәтері Оңтайландыруларын Елемеу"),
|
||||||
("android_open_battery_optimizations_tip", "Егер де бұл ерекшелікті өшіруді қаласаңыз, келесі RustDesk апылқат орнатпалары бетіне барып, [Бәтері]'ні тауып кіріңіз де [Шектеусіз]'ден құсбелгіні алып тастауды өтінеміз"),
|
("android_open_battery_optimizations_tip", "Егер де бұл ерекшелікті өшіруді қаласаңыз, келесі RustDesk апылқат орнатпалары бетіне барып, [Бәтері]'ні тауып кіріңіз де [Шектеусіз]'ден құсбелгіні алып тастауды өтінеміз"),
|
||||||
("Connection not allowed", "Қосылу рұқсат етілмеген"),
|
("Connection not allowed", "Қосылу рұқсат етілмеген"),
|
||||||
("Legacy mode", ""),
|
|
||||||
("Map mode", ""),
|
|
||||||
("Translate mode", ""),
|
|
||||||
("Use temporary password", "Уақытша құпия сөзді қолдану"),
|
("Use temporary password", "Уақытша құпия сөзді қолдану"),
|
||||||
("Use permanent password", "Тұрақты құпия сөзді қолдану"),
|
("Use permanent password", "Тұрақты құпия сөзді қолдану"),
|
||||||
("Use both passwords", "Қос құпия сөзді қолдану"),
|
("Use both passwords", "Қос құпия сөзді қолдану"),
|
||||||
@ -322,6 +319,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Insecure Connection", "Қатерлі Қосылым"),
|
("Insecure Connection", "Қатерлі Қосылым"),
|
||||||
("Scale original", "Scale original"),
|
("Scale original", "Scale original"),
|
||||||
("Scale adaptive", "Scale adaptive"),
|
("Scale adaptive", "Scale adaptive"),
|
||||||
|
("Pin menubar", "Мәзір жолағын бекіту"),
|
||||||
|
("Unpin menubar", "Мәзір жолағын босату"),
|
||||||
|
("Recording", ""),
|
||||||
|
("Directory", ""),
|
||||||
|
("Automatically record incoming sessions", ""),
|
||||||
|
("Change", ""),
|
||||||
|
("Start session recording", ""),
|
||||||
|
("Stop session recording", ""),
|
||||||
|
("Enable Recording Session", ""),
|
||||||
|
("Allow recording session", ""),
|
||||||
("General", ""),
|
("General", ""),
|
||||||
("Security", ""),
|
("Security", ""),
|
||||||
("Account", "Есепкі"),
|
("Account", "Есепкі"),
|
||||||
|
@ -349,6 +349,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Enable RDP", ""),
|
("Enable RDP", ""),
|
||||||
("Pin menubar", "Przypnij pasek menu"),
|
("Pin menubar", "Przypnij pasek menu"),
|
||||||
("Unpin menubar", "Odepnij pasek menu"),
|
("Unpin menubar", "Odepnij pasek menu"),
|
||||||
|
("Recording", ""),
|
||||||
|
("Directory", ""),
|
||||||
|
("Automatically record incoming sessions", ""),
|
||||||
|
("Change", ""),
|
||||||
|
("Start session recording", ""),
|
||||||
|
("Stop session recording", ""),
|
||||||
|
("Enable Recording Session", ""),
|
||||||
|
("Allow recording session", ""),
|
||||||
("Enable LAN Discovery", ""),
|
("Enable LAN Discovery", ""),
|
||||||
("Deny LAN Discovery", ""),
|
("Deny LAN Discovery", ""),
|
||||||
("Write a message", ""),
|
("Write a message", ""),
|
||||||
|
@ -349,6 +349,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Enable RDP", ""),
|
("Enable RDP", ""),
|
||||||
("Pin menubar", "Fixar barra de menu"),
|
("Pin menubar", "Fixar barra de menu"),
|
||||||
("Unpin menubar", "Desenganxa la barra de menús"),
|
("Unpin menubar", "Desenganxa la barra de menús"),
|
||||||
|
("Recording", ""),
|
||||||
|
("Directory", ""),
|
||||||
|
("Automatically record incoming sessions", ""),
|
||||||
|
("Change", ""),
|
||||||
|
("Start session recording", ""),
|
||||||
|
("Stop session recording", ""),
|
||||||
|
("Enable Recording Session", ""),
|
||||||
|
("Allow recording session", ""),
|
||||||
("Enable LAN Discovery", ""),
|
("Enable LAN Discovery", ""),
|
||||||
("Deny LAN Discovery", ""),
|
("Deny LAN Discovery", ""),
|
||||||
("Write a message", ""),
|
("Write a message", ""),
|
||||||
|
@ -349,6 +349,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Enable RDP", ""),
|
("Enable RDP", ""),
|
||||||
("Pin menubar", ""),
|
("Pin menubar", ""),
|
||||||
("Unpin menubar", ""),
|
("Unpin menubar", ""),
|
||||||
|
("Recording", ""),
|
||||||
|
("Directory", ""),
|
||||||
|
("Automatically record incoming sessions", ""),
|
||||||
|
("Change", ""),
|
||||||
|
("Start session recording", ""),
|
||||||
|
("Stop session recording", ""),
|
||||||
|
("Enable Recording Session", ""),
|
||||||
|
("Allow recording session", ""),
|
||||||
("Enable LAN Discovery", ""),
|
("Enable LAN Discovery", ""),
|
||||||
("Deny LAN Discovery", ""),
|
("Deny LAN Discovery", ""),
|
||||||
("Write a message", ""),
|
("Write a message", ""),
|
||||||
|
@ -349,6 +349,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Enable RDP", ""),
|
("Enable RDP", ""),
|
||||||
("Pin menubar", "Закрепить строку меню"),
|
("Pin menubar", "Закрепить строку меню"),
|
||||||
("Unpin menubar", "Открепить строку меню"),
|
("Unpin menubar", "Открепить строку меню"),
|
||||||
|
("Recording", ""),
|
||||||
|
("Directory", ""),
|
||||||
|
("Automatically record incoming sessions", ""),
|
||||||
|
("Change", ""),
|
||||||
|
("Start session recording", ""),
|
||||||
|
("Stop session recording", ""),
|
||||||
|
("Enable Recording Session", ""),
|
||||||
|
("Allow recording session", ""),
|
||||||
("Enable LAN Discovery", ""),
|
("Enable LAN Discovery", ""),
|
||||||
("Deny LAN Discovery", ""),
|
("Deny LAN Discovery", ""),
|
||||||
("Write a message", ""),
|
("Write a message", ""),
|
||||||
|
@ -349,6 +349,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Enable RDP", ""),
|
("Enable RDP", ""),
|
||||||
("Pin menubar", "Pripnúť panel s ponukami"),
|
("Pin menubar", "Pripnúť panel s ponukami"),
|
||||||
("Unpin menubar", "Uvoľniť panel s ponukami"),
|
("Unpin menubar", "Uvoľniť panel s ponukami"),
|
||||||
|
("Recording", ""),
|
||||||
|
("Directory", ""),
|
||||||
|
("Automatically record incoming sessions", ""),
|
||||||
|
("Change", ""),
|
||||||
|
("Start session recording", ""),
|
||||||
|
("Stop session recording", ""),
|
||||||
|
("Enable Recording Session", ""),
|
||||||
|
("Allow recording session", ""),
|
||||||
("Enable LAN Discovery", ""),
|
("Enable LAN Discovery", ""),
|
||||||
("Deny LAN Discovery", ""),
|
("Deny LAN Discovery", ""),
|
||||||
("Write a message", ""),
|
("Write a message", ""),
|
||||||
|
@ -349,6 +349,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Enable RDP", ""),
|
("Enable RDP", ""),
|
||||||
("Pin menubar", ""),
|
("Pin menubar", ""),
|
||||||
("Unpin menubar", ""),
|
("Unpin menubar", ""),
|
||||||
|
("Recording", ""),
|
||||||
|
("Directory", ""),
|
||||||
|
("Automatically record incoming sessions", ""),
|
||||||
|
("Change", ""),
|
||||||
|
("Start session recording", ""),
|
||||||
|
("Stop session recording", ""),
|
||||||
|
("Enable Recording Session", ""),
|
||||||
|
("Allow recording session", ""),
|
||||||
("Enable LAN Discovery", ""),
|
("Enable LAN Discovery", ""),
|
||||||
("Deny LAN Discovery", ""),
|
("Deny LAN Discovery", ""),
|
||||||
("Write a message", ""),
|
("Write a message", ""),
|
||||||
|
@ -349,6 +349,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Enable RDP", ""),
|
("Enable RDP", ""),
|
||||||
("Pin menubar", "Menü çubuğunu sabitle"),
|
("Pin menubar", "Menü çubuğunu sabitle"),
|
||||||
("Unpin menubar", "Menü çubuğunun sabitlemesini kaldır"),
|
("Unpin menubar", "Menü çubuğunun sabitlemesini kaldır"),
|
||||||
|
("Recording", ""),
|
||||||
|
("Directory", ""),
|
||||||
|
("Automatically record incoming sessions", ""),
|
||||||
|
("Change", ""),
|
||||||
|
("Start session recording", ""),
|
||||||
|
("Stop session recording", ""),
|
||||||
|
("Enable Recording Session", ""),
|
||||||
|
("Allow recording session", ""),
|
||||||
("Enable LAN Discovery", ""),
|
("Enable LAN Discovery", ""),
|
||||||
("Deny LAN Discovery", ""),
|
("Deny LAN Discovery", ""),
|
||||||
("Write a message", ""),
|
("Write a message", ""),
|
||||||
|
@ -349,6 +349,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Enable RDP", "允許RDP訪問"),
|
("Enable RDP", "允許RDP訪問"),
|
||||||
("Pin menubar", "固定菜單欄"),
|
("Pin menubar", "固定菜單欄"),
|
||||||
("Unpin menubar", "取消固定菜單欄"),
|
("Unpin menubar", "取消固定菜單欄"),
|
||||||
|
("Recording", "錄屏"),
|
||||||
|
("Directory", "目錄"),
|
||||||
|
("Automatically record incoming sessions", "自動錄製來訪會話"),
|
||||||
|
("Change", "變更"),
|
||||||
|
("Start session recording", "開始錄屏"),
|
||||||
|
("Stop session recording", "結束錄屏"),
|
||||||
|
("Enable Recording Session", "允許錄製會話"),
|
||||||
|
("Allow recording session", "允許錄製會話"),
|
||||||
("Enable LAN Discovery", "允許局域網發現"),
|
("Enable LAN Discovery", "允許局域網發現"),
|
||||||
("Deny LAN Discovery", "拒絕局域網發現"),
|
("Deny LAN Discovery", "拒絕局域網發現"),
|
||||||
("Write a message", "輸入聊天消息"),
|
("Write a message", "輸入聊天消息"),
|
||||||
|
@ -349,6 +349,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Enable RDP", ""),
|
("Enable RDP", ""),
|
||||||
("Pin menubar", "Ghim thanh menu"),
|
("Pin menubar", "Ghim thanh menu"),
|
||||||
("Unpin menubar", "Bỏ ghim thanh menu"),
|
("Unpin menubar", "Bỏ ghim thanh menu"),
|
||||||
|
("Recording", ""),
|
||||||
|
("Directory", ""),
|
||||||
|
("Automatically record incoming sessions", ""),
|
||||||
|
("Change", ""),
|
||||||
|
("Start session recording", ""),
|
||||||
|
("Stop session recording", ""),
|
||||||
|
("Enable Recording Session", ""),
|
||||||
|
("Allow recording session", ""),
|
||||||
("Enable LAN Discovery", ""),
|
("Enable LAN Discovery", ""),
|
||||||
("Deny LAN Discovery", ""),
|
("Deny LAN Discovery", ""),
|
||||||
("Write a message", ""),
|
("Write a message", ""),
|
||||||
|
@ -81,6 +81,7 @@ pub struct Connection {
|
|||||||
audio: bool,
|
audio: bool,
|
||||||
file: bool,
|
file: bool,
|
||||||
restart: bool,
|
restart: bool,
|
||||||
|
recording: bool,
|
||||||
last_test_delay: i64,
|
last_test_delay: i64,
|
||||||
lock_after_session_end: bool,
|
lock_after_session_end: bool,
|
||||||
show_remote_cursor: bool, // by peer
|
show_remote_cursor: bool, // by peer
|
||||||
@ -169,6 +170,7 @@ impl Connection {
|
|||||||
audio: Config::get_option("enable-audio").is_empty(),
|
audio: Config::get_option("enable-audio").is_empty(),
|
||||||
file: Config::get_option("enable-file-transfer").is_empty(),
|
file: Config::get_option("enable-file-transfer").is_empty(),
|
||||||
restart: Config::get_option("enable-remote-restart").is_empty(),
|
restart: Config::get_option("enable-remote-restart").is_empty(),
|
||||||
|
recording: Config::get_option("enable-record-session").is_empty(),
|
||||||
last_test_delay: 0,
|
last_test_delay: 0,
|
||||||
lock_after_session_end: false,
|
lock_after_session_end: false,
|
||||||
show_remote_cursor: false,
|
show_remote_cursor: false,
|
||||||
@ -210,6 +212,9 @@ impl Connection {
|
|||||||
if !conn.restart {
|
if !conn.restart {
|
||||||
conn.send_permission(Permission::Restart, false).await;
|
conn.send_permission(Permission::Restart, false).await;
|
||||||
}
|
}
|
||||||
|
if !conn.recording {
|
||||||
|
conn.send_permission(Permission::Recording, false).await;
|
||||||
|
}
|
||||||
let mut test_delay_timer =
|
let mut test_delay_timer =
|
||||||
time::interval_at(Instant::now() + TEST_DELAY_TIMEOUT, TEST_DELAY_TIMEOUT);
|
time::interval_at(Instant::now() + TEST_DELAY_TIMEOUT, TEST_DELAY_TIMEOUT);
|
||||||
let mut last_recv_time = Instant::now();
|
let mut last_recv_time = Instant::now();
|
||||||
@ -290,6 +295,9 @@ impl Connection {
|
|||||||
} else if &name == "restart" {
|
} else if &name == "restart" {
|
||||||
conn.restart = enabled;
|
conn.restart = enabled;
|
||||||
conn.send_permission(Permission::Restart, enabled).await;
|
conn.send_permission(Permission::Restart, enabled).await;
|
||||||
|
} else if &name == "recording" {
|
||||||
|
conn.recording = enabled;
|
||||||
|
conn.send_permission(Permission::Recording, enabled).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ipc::Data::RawMessage(bytes) => {
|
ipc::Data::RawMessage(bytes) => {
|
||||||
@ -777,6 +785,7 @@ impl Connection {
|
|||||||
file: self.file,
|
file: self.file,
|
||||||
file_transfer_enabled: self.file_transfer_enabled(),
|
file_transfer_enabled: self.file_transfer_enabled(),
|
||||||
restart: self.restart,
|
restart: self.restart,
|
||||||
|
recording: self.recording,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ use hbb_common::tokio::sync::{
|
|||||||
};
|
};
|
||||||
use scrap::{
|
use scrap::{
|
||||||
codec::{Encoder, EncoderCfg, HwEncoderConfig},
|
codec::{Encoder, EncoderCfg, HwEncoderConfig},
|
||||||
|
record::{Recorder, RecorderContext},
|
||||||
vpxcodec::{VpxEncoderConfig, VpxVideoCodecId},
|
vpxcodec::{VpxEncoderConfig, VpxVideoCodecId},
|
||||||
Capturer, Display, TraitCapturer,
|
Capturer, Display, TraitCapturer,
|
||||||
};
|
};
|
||||||
@ -435,6 +436,21 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
log::info!("gdi: {}", c.is_gdi());
|
log::info!("gdi: {}", c.is_gdi());
|
||||||
let codec_name = Encoder::current_hw_encoder_name();
|
let codec_name = Encoder::current_hw_encoder_name();
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
let recorder = if !Config::get_option("allow-auto-record-incoming").is_empty() {
|
||||||
|
Recorder::new(RecorderContext {
|
||||||
|
id: "local".to_owned(),
|
||||||
|
filename: "".to_owned(),
|
||||||
|
width: c.width,
|
||||||
|
height: c.height,
|
||||||
|
codec_id: scrap::record::RecodeCodecID::VP9,
|
||||||
|
})
|
||||||
|
.map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r))))
|
||||||
|
} else {
|
||||||
|
Default::default()
|
||||||
|
};
|
||||||
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
|
let recorder: Arc<Mutex<Option<Recorder>>> = Default::default();
|
||||||
|
|
||||||
while sp.ok() {
|
while sp.ok() {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
@ -495,7 +511,8 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
}
|
}
|
||||||
scrap::Frame::RAW(data) => {
|
scrap::Frame::RAW(data) => {
|
||||||
if (data.len() != 0) {
|
if (data.len() != 0) {
|
||||||
let send_conn_ids = handle_one_frame(&sp, data, ms, &mut encoder)?;
|
let send_conn_ids =
|
||||||
|
handle_one_frame(&sp, data, ms, &mut encoder, recorder.clone())?;
|
||||||
frame_controller.set_send(now, send_conn_ids);
|
frame_controller.set_send(now, send_conn_ids);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -511,7 +528,8 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
Ok(frame) => {
|
Ok(frame) => {
|
||||||
let time = now - start;
|
let time = now - start;
|
||||||
let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64;
|
let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64;
|
||||||
let send_conn_ids = handle_one_frame(&sp, &frame, ms, &mut encoder)?;
|
let send_conn_ids =
|
||||||
|
handle_one_frame(&sp, &frame, ms, &mut encoder, recorder.clone())?;
|
||||||
frame_controller.set_send(now, send_conn_ids);
|
frame_controller.set_send(now, send_conn_ids);
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
@ -612,6 +630,7 @@ fn handle_one_frame(
|
|||||||
frame: &[u8],
|
frame: &[u8],
|
||||||
ms: i64,
|
ms: i64,
|
||||||
encoder: &mut Encoder,
|
encoder: &mut Encoder,
|
||||||
|
recorder: Arc<Mutex<Option<Recorder>>>,
|
||||||
) -> 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
|
||||||
@ -623,6 +642,12 @@ fn handle_one_frame(
|
|||||||
|
|
||||||
let mut send_conn_ids: HashSet<i32> = Default::default();
|
let mut send_conn_ids: HashSet<i32> = Default::default();
|
||||||
if let Ok(msg) = encoder.encode_to_message(frame, ms) {
|
if let Ok(msg) = encoder.encode_to_message(frame, ms) {
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
recorder
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.as_mut()
|
||||||
|
.map(|r| r.write_message(&msg));
|
||||||
send_conn_ids = sp.send_video_frame(msg);
|
send_conn_ids = sp.send_video_frame(msg);
|
||||||
}
|
}
|
||||||
Ok(send_conn_ids)
|
Ok(send_conn_ids)
|
||||||
|
33
src/ui.rs
33
src/ui.rs
@ -21,20 +21,20 @@ use hbb_common::{
|
|||||||
use crate::common::get_app_name;
|
use crate::common::get_app_name;
|
||||||
use crate::ipc;
|
use crate::ipc;
|
||||||
use crate::ui_interface::{
|
use crate::ui_interface::{
|
||||||
check_mouse_time, closing, create_shortcut, current_is_wayland, fix_login_wayland,
|
check_mouse_time, closing, create_shortcut, current_is_wayland, default_video_save_directory,
|
||||||
forget_password, get_api_server, get_async_job_status, get_connect_status, get_error, get_fav,
|
fix_login_wayland, forget_password, get_api_server, get_async_job_status, get_connect_status,
|
||||||
get_icon, get_lan_peers, get_langs, get_license, get_local_option, get_mouse_time,
|
get_error, get_fav, get_icon, get_lan_peers, get_langs, get_license, get_local_option,
|
||||||
get_new_version, get_option, get_options, get_peer, get_peer_option, get_recent_sessions,
|
get_mouse_time, get_new_version, get_option, get_options, get_peer, get_peer_option,
|
||||||
get_remote_id, get_size, get_socks, get_software_ext, get_software_store_path,
|
get_recent_sessions, get_remote_id, get_size, get_socks, get_software_ext,
|
||||||
get_software_update_url, get_uuid, get_version, goto_install, has_hwcodec,
|
get_software_store_path, get_software_update_url, get_uuid, get_version, goto_install,
|
||||||
has_rendezvous_service, install_me, install_path, is_can_screen_recording, is_installed,
|
has_hwcodec, has_rendezvous_service, install_me, install_path, is_can_screen_recording,
|
||||||
is_installed_daemon, is_installed_lower_version, is_login_wayland, is_ok_change_id,
|
is_installed, is_installed_daemon, is_installed_lower_version, is_login_wayland,
|
||||||
is_process_trusted, is_rdp_service_open, is_share_rdp, is_xfce, modify_default_login,
|
is_ok_change_id, is_process_trusted, is_rdp_service_open, is_share_rdp, is_xfce,
|
||||||
new_remote, open_url, peer_has_password, permanent_password, post_request,
|
modify_default_login, new_remote, open_url, peer_has_password, permanent_password,
|
||||||
recent_sessions_updated, remove_peer, run_without_install, set_local_option, set_option,
|
post_request, recent_sessions_updated, remove_peer, run_without_install, set_local_option,
|
||||||
set_options, set_peer_option, set_permanent_password, set_remote_id, set_share_rdp, set_socks,
|
set_option, set_options, set_peer_option, set_permanent_password, set_remote_id, set_share_rdp,
|
||||||
show_run_without_install, store_fav, t, temporary_password, test_if_valid_server, update_me,
|
set_socks, show_run_without_install, store_fav, t, temporary_password, test_if_valid_server,
|
||||||
update_temporary_password, using_public_server,
|
update_me, update_temporary_password, using_public_server,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod cm;
|
mod cm;
|
||||||
@ -579,6 +579,10 @@ impl UI {
|
|||||||
fn get_langs(&self) -> String {
|
fn get_langs(&self) -> String {
|
||||||
get_langs()
|
get_langs()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_video_save_directory(&self) -> String {
|
||||||
|
default_video_save_directory()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl sciter::EventHandler for UI {
|
impl sciter::EventHandler for UI {
|
||||||
@ -661,6 +665,7 @@ impl sciter::EventHandler for UI {
|
|||||||
fn get_uuid();
|
fn get_uuid();
|
||||||
fn has_hwcodec();
|
fn has_hwcodec();
|
||||||
fn get_langs();
|
fn get_langs();
|
||||||
|
fn default_video_save_directory();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,6 +108,10 @@ icon.restart {
|
|||||||
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAB7BAAAewQHDaVRTAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAbhJREFUWIXVlrFqFGEUhb+7UYxaWCQKlrKKxaZSQVGDJih2tj6MD2DnMwiWvoAIRnENIpZiYxEro6IooiS7SPwsMgNLkk3mjmYmnmb45/73nMNwz/x/qH3gMu2gH6rAU+Blw+Lngau4jpmGxVF7qp1iPWjaQKnZ2WnXbuP/NqAeUPc3ZkA9XDwvqc+BVWCgPlJ7tRwUKThZce819b46VH+pfXVRXVO/q2cSul3VOgZUl0ejq86r39TXI8mqZKDuDEwCw3IREQvAbWAGmMsQZQ0sAl3gHPB1Q+0e8BuYzRDuy2yOiFVgaUxtRf0ETGc4syk4rc6PqU0Cx9j8Zf6dAeAK8Fi9sUXtFjABvEgxJlNwRP2svlNPjbw/q35U36oTFbnyMSwabxb/gB/qA3VBHagrauV7RW0DRfP1IvMlXqkXkhz1DYyQTKtHa/Z2VVMx3IiI+PI3/bCHjuOpFrSnAMpL6QfgTcMGesDx0kBr2BMzsNyi/vtQu8CJlgwsRbZDnWP90NkKaxHxJMOXMqAeAn5u0ydwMCKGY+qbkB3C2W3EKWoXk5zVoHbUZ+6Mh7tl4G4F8RJ3qvL+AfV3r5Vdpj70AAAAAElFTkSuQmCC');
|
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAB7BAAAewQHDaVRTAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAbhJREFUWIXVlrFqFGEUhb+7UYxaWCQKlrKKxaZSQVGDJih2tj6MD2DnMwiWvoAIRnENIpZiYxEro6IooiS7SPwsMgNLkk3mjmYmnmb45/73nMNwz/x/qH3gMu2gH6rAU+Blw+Lngau4jpmGxVF7qp1iPWjaQKnZ2WnXbuP/NqAeUPc3ZkA9XDwvqc+BVWCgPlJ7tRwUKThZce819b46VH+pfXVRXVO/q2cSul3VOgZUl0ejq86r39TXI8mqZKDuDEwCw3IREQvAbWAGmMsQZQ0sAl3gHPB1Q+0e8BuYzRDuy2yOiFVgaUxtRf0ETGc4syk4rc6PqU0Cx9j8Zf6dAeAK8Fi9sUXtFjABvEgxJlNwRP2svlNPjbw/q35U36oTFbnyMSwabxb/gB/qA3VBHagrauV7RW0DRfP1IvMlXqkXkhz1DYyQTKtHa/Z2VVMx3IiI+PI3/bCHjuOpFrSnAMpL6QfgTcMGesDx0kBr2BMzsNyi/vtQu8CJlgwsRbZDnWP90NkKaxHxJMOXMqAeAn5u0ydwMCKGY+qbkB3C2W3EKWoXk5zVoHbUZ+6Mh7tl4G4F8RJ3qvL+AfV3r5Vdpj70AAAAAElFTkSuQmCC');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
icon.recording {
|
||||||
|
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAANpJREFUWEftltENAiEMhtsJ1NcynG6gI+gGugEOR591gppeQoIYSDBILxEeydH/57u2FMF4obE+TAOTwLoIhBDOAHBExG2n6rgR0akW640AM0sn4SWMiDycc7s8JjN7Ijro/k8NqAAR5RoeAPZxv2ggP9hCJiWZxtGbq3hqbJiBVHy4gVx8qAER8Yi4JFy6huVAKXemgb8icI+1b5KEitq0DOO/Nm1EEX1TK27p/bVvv36MOhl4EtHHbFF7jq8AoG1z08OAiFycczrkFNe6RrIet26NMQlMAuYEXiayryF/QQktAAAAAElFTkSuQmCC');
|
||||||
|
}
|
||||||
|
|
||||||
div.buttons {
|
div.buttons {
|
||||||
width: *;
|
width: *;
|
||||||
border-spacing: 0.5em;
|
border-spacing: 0.5em;
|
||||||
|
@ -32,7 +32,8 @@ impl InvokeUiCM for SciterHandler {
|
|||||||
client.clipboard,
|
client.clipboard,
|
||||||
client.audio,
|
client.audio,
|
||||||
client.file,
|
client.file,
|
||||||
client.restart
|
client.restart,
|
||||||
|
client.recording
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -41,13 +41,16 @@ class Body: Reactor.Component
|
|||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
{c.is_file_transfer || c.port_forward ? "" : <div>{translate('Permissions')}</div>}
|
{c.is_file_transfer || c.port_forward ? "" : <div>{translate('Permissions')}</div>}
|
||||||
{c.is_file_transfer || c.port_forward ? "" : <div .permissions>
|
{c.is_file_transfer || c.port_forward ? "" : <div> <div .permissions>
|
||||||
<div class={!c.keyboard ? "disabled" : ""} title={translate('Allow using keyboard and mouse')}><icon .keyboard /></div>
|
<div class={!c.keyboard ? "disabled" : ""} title={translate('Allow using keyboard and mouse')}><icon .keyboard /></div>
|
||||||
<div class={!c.clipboard ? "disabled" : ""} title={translate('Allow using clipboard')}><icon .clipboard /></div>
|
<div class={!c.clipboard ? "disabled" : ""} title={translate('Allow using clipboard')}><icon .clipboard /></div>
|
||||||
<div class={!c.audio ? "disabled" : ""} title={translate('Allow hearing sound')}><icon .audio /></div>
|
<div class={!c.audio ? "disabled" : ""} title={translate('Allow hearing sound')}><icon .audio /></div>
|
||||||
<div class={!c.file ? "disabled" : ""} title={translate('Allow file copy and paste')}><icon .file /></div>
|
<div class={!c.file ? "disabled" : ""} title={translate('Allow file copy and paste')}><icon .file /></div>
|
||||||
<div class={!c.restart ? "disabled" : ""} title={translate('Allow remote restart')}><icon .restart /></div>
|
<div class={!c.restart ? "disabled" : ""} title={translate('Allow remote restart')}><icon .restart /></div>
|
||||||
</div>}
|
</div> <div .permissions style="margin-top:8px;" >
|
||||||
|
<div class={!c.recording ? "disabled" : ""} title={translate('Allow recording session')}><icon .recording /></div>
|
||||||
|
</div></div>
|
||||||
|
}
|
||||||
{c.port_forward ? <div>Port Forwarding: {c.port_forward}</div> : ""}
|
{c.port_forward ? <div>Port Forwarding: {c.port_forward}</div> : ""}
|
||||||
<div style="size:*"/>
|
<div style="size:*"/>
|
||||||
<div .buttons>
|
<div .buttons>
|
||||||
@ -118,6 +121,15 @@ class Body: Reactor.Component
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
event click $(icon.recording) {
|
||||||
|
var { cid, connection } = this;
|
||||||
|
checkClickTime(function() {
|
||||||
|
connection.recording = !connection.recording;
|
||||||
|
body.update();
|
||||||
|
handler.switch_permission(cid, "recording", connection.recording);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
event click $(button#accept) {
|
event click $(button#accept) {
|
||||||
var { cid, connection } = this;
|
var { cid, connection } = this;
|
||||||
checkClickTime(function() {
|
checkClickTime(function() {
|
||||||
@ -276,7 +288,7 @@ function bring_to_top(idx=-1) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.addConnection = function(id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, restart) {
|
handler.addConnection = function(id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, restart, recording) {
|
||||||
stdout.println("new connection #" + id + ": " + peer_id);
|
stdout.println("new connection #" + id + ": " + peer_id);
|
||||||
var conn;
|
var conn;
|
||||||
connections.map(function(c) {
|
connections.map(function(c) {
|
||||||
@ -293,7 +305,7 @@ handler.addConnection = function(id, is_file_transfer, port_forward, peer_id, na
|
|||||||
port_forward: port_forward,
|
port_forward: port_forward,
|
||||||
name: name, authorized: authorized, time: new Date(),
|
name: name, authorized: authorized, time: new Date(),
|
||||||
keyboard: keyboard, clipboard: clipboard, msgs: [], unreaded: 0,
|
keyboard: keyboard, clipboard: clipboard, msgs: [], unreaded: 0,
|
||||||
audio: audio, file: file, restart: restart
|
audio: audio, file: file, restart: restart, recording: recording
|
||||||
});
|
});
|
||||||
body.cur = connections.length - 1;
|
body.cur = connections.length - 1;
|
||||||
bring_to_top();
|
bring_to_top();
|
||||||
|
@ -70,6 +70,15 @@ button.button:hover, button.outline:hover {
|
|||||||
border-color: color(hover-border);
|
border-color: color(hover-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.link {
|
||||||
|
background: none !important;
|
||||||
|
border: none;
|
||||||
|
padding: 0 !important;
|
||||||
|
color: color(button);
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
input[type=text], input[type=password], input[type=number] {
|
input[type=text], input[type=password], input[type=number] {
|
||||||
width: *;
|
width: *;
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
|
@ -14,6 +14,8 @@ var svg_secure = <svg viewBox="0 0 347.97 347.97">
|
|||||||
var svg_insecure = <svg viewBox="0 0 347.97 347.97"><path d="M317.469 61.615c-59.442 0-104.976-16.082-143.489-51.539-38.504 35.457-84.04 51.539-143.479 51.539 0 92.337-20.177 224.612 143.479 278.324 163.661-53.717 143.489-185.992 143.489-278.324z" fill="none" stroke="red" stroke-width="14.827"/><g fill="red"><path d="M238.802 115.023l-111.573 114.68-8.6-8.367L230.2 106.656z"/><path d="M125.559 108.093l114.68 111.572-8.368 8.601-114.68-111.572z"/></g></svg>;
|
var svg_insecure = <svg viewBox="0 0 347.97 347.97"><path d="M317.469 61.615c-59.442 0-104.976-16.082-143.489-51.539-38.504 35.457-84.04 51.539-143.479 51.539 0 92.337-20.177 224.612 143.479 278.324 163.661-53.717 143.489-185.992 143.489-278.324z" fill="none" stroke="red" stroke-width="14.827"/><g fill="red"><path d="M238.802 115.023l-111.573 114.68-8.6-8.367L230.2 106.656z"/><path d="M125.559 108.093l114.68 111.572-8.368 8.601-114.68-111.572z"/></g></svg>;
|
||||||
var svg_insecure_relay = <svg viewBox="0 0 347.97 347.97"><path d="M317.469 61.615c-59.442 0-104.976-16.082-143.489-51.539-38.504 35.457-84.04 51.539-143.479 51.539 0 92.337-20.177 224.612 143.479 278.324 163.661-53.717 143.489-185.992 143.489-278.324z" fill="none" stroke="red" stroke-width="14.827"/><g fill="red"><path d="M231.442 247.498l-7.754-10.205c-17.268 12.441-38.391 17.705-59.478 14.822-21.087-2.883-39.613-13.569-52.166-30.088-25.916-34.101-17.997-82.738 17.65-108.42 32.871-23.685 78.02-19.704 105.172 7.802l-32.052 7.987 3.082 12.369 48.722-12.142-11.712-46.998-12.822 3.196 4.496 18.039c-31.933-24.008-78.103-25.342-112.642-.458-31.361 22.596-44.3 60.436-35.754 94.723 2.77 11.115 7.801 21.862 15.192 31.588 30.19 39.727 88.538 47.705 130.066 17.785z"/></g></svg>;
|
var svg_insecure_relay = <svg viewBox="0 0 347.97 347.97"><path d="M317.469 61.615c-59.442 0-104.976-16.082-143.489-51.539-38.504 35.457-84.04 51.539-143.479 51.539 0 92.337-20.177 224.612 143.479 278.324 163.661-53.717 143.489-185.992 143.489-278.324z" fill="none" stroke="red" stroke-width="14.827"/><g fill="red"><path d="M231.442 247.498l-7.754-10.205c-17.268 12.441-38.391 17.705-59.478 14.822-21.087-2.883-39.613-13.569-52.166-30.088-25.916-34.101-17.997-82.738 17.65-108.42 32.871-23.685 78.02-19.704 105.172 7.802l-32.052 7.987 3.082 12.369 48.722-12.142-11.712-46.998-12.822 3.196 4.496 18.039c-31.933-24.008-78.103-25.342-112.642-.458-31.361 22.596-44.3 60.436-35.754 94.723 2.77 11.115 7.801 21.862 15.192 31.588 30.19 39.727 88.538 47.705 130.066 17.785z"/></g></svg>;
|
||||||
var svg_secure_relay = <svg viewBox="0 0 347.97 347.97"><path d="M317.469 61.615c-59.442 0-104.976-16.082-143.489-51.539-38.504 35.457-84.04 51.539-143.479 51.539 0 92.337-20.177 224.612 143.479 278.324 163.661-53.717 143.489-185.992 143.489-278.324z" fill="#3f7d46" stroke="#3f7d46" stroke-width="14.827"/><g fill="red"><path d="M231.442 247.498l-7.754-10.205c-17.268 12.441-38.391 17.705-59.478 14.822-21.087-2.883-39.613-13.569-52.166-30.088-25.916-34.101-17.997-82.738 17.65-108.42 32.871-23.685 78.02-19.704 105.172 7.802l-32.052 7.987 3.082 12.369 48.722-12.142-11.712-46.998-12.822 3.196 4.496 18.039c-31.933-24.008-78.103-25.342-112.642-.458-31.361 22.596-44.3 60.436-35.754 94.723 2.77 11.115 7.801 21.862 15.192 31.588 30.19 39.727 88.538 47.705 130.066 17.785z" fill="#fff"/></g></svg>;
|
var svg_secure_relay = <svg viewBox="0 0 347.97 347.97"><path d="M317.469 61.615c-59.442 0-104.976-16.082-143.489-51.539-38.504 35.457-84.04 51.539-143.479 51.539 0 92.337-20.177 224.612 143.479 278.324 163.661-53.717 143.489-185.992 143.489-278.324z" fill="#3f7d46" stroke="#3f7d46" stroke-width="14.827"/><g fill="red"><path d="M231.442 247.498l-7.754-10.205c-17.268 12.441-38.391 17.705-59.478 14.822-21.087-2.883-39.613-13.569-52.166-30.088-25.916-34.101-17.997-82.738 17.65-108.42 32.871-23.685 78.02-19.704 105.172 7.802l-32.052 7.987 3.082 12.369 48.722-12.142-11.712-46.998-12.822 3.196 4.496 18.039c-31.933-24.008-78.103-25.342-112.642-.458-31.361 22.596-44.3 60.436-35.754 94.723 2.77 11.115 7.801 21.862 15.192 31.588 30.19 39.727 88.538 47.705 130.066 17.785z" fill="#fff"/></g></svg>;
|
||||||
|
var svg_recording_off = <svg t="1663505560063" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5393" width="32" height="32"><path d="M1002.666667 260.266667c-12.8-8.533333-29.866667-4.266667-42.666667 4.266666L725.333333 430.933333V298.666667c0-72.533333-55.466667-128-128-128H128C55.466667 170.666667 0 226.133333 0 298.666667v426.666666c0 72.533333 55.466667 128 128 128h469.333333c72.533333 0 128-55.466667 128-128v-132.266666l230.4 166.4c17.066667 12.8 46.933333 8.533333 59.733334-8.533334 4.266667-8.533333 8.533333-17.066667 8.533333-25.6V298.666667c0-17.066667-8.533333-29.866667-21.333333-38.4zM640 725.333333c0 25.6-17.066667 42.666667-42.666667 42.666667H128c-25.6 0-42.666667-17.066667-42.666667-42.666667V298.666667c0-25.6 17.066667-42.666667 42.666667-42.666667h469.333333c25.6 0 42.666667 17.066667 42.666667 42.666667v426.666666z m298.666667-81.066666L755.2 512 938.666667 379.733333v264.533334z" p-id="5394" fill="#8a8a8a"></path></svg>;
|
||||||
|
var svg_recording_on = <svg t="1663505598640" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5644" width="32" height="32"><path d="M1002.666667 260.266667c-12.8-8.533333-29.866667-4.266667-42.666667 4.266666L725.333333 430.933333V298.666667c0-72.533333-55.466667-128-128-128H128C55.466667 170.666667 0 226.133333 0 298.666667v426.666666c0 72.533333 55.466667 128 128 128h469.333333c72.533333 0 128-55.466667 128-128v-132.266666l230.4 166.4c17.066667 12.8 46.933333 8.533333 59.733334-8.533334 4.266667-8.533333 8.533333-17.066667 8.533333-25.6V298.666667c0-17.066667-8.533333-29.866667-21.333333-38.4z" p-id="5645" fill="#2C8CFF"></path></svg>;
|
||||||
|
|
||||||
var cur_window_state = view.windowState;
|
var cur_window_state = view.windowState;
|
||||||
function check_state_change() {
|
function check_state_change() {
|
||||||
@ -90,6 +92,8 @@ function editOSPassword(login=false) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var recording = false;
|
||||||
|
|
||||||
class Header: Reactor.Component {
|
class Header: Reactor.Component {
|
||||||
this var conn_note = "";
|
this var conn_note = "";
|
||||||
|
|
||||||
@ -140,6 +144,7 @@ class Header: Reactor.Component {
|
|||||||
<span #action>{svg_action}</span>
|
<span #action>{svg_action}</span>
|
||||||
<span #display>{svg_display}</span>
|
<span #display>{svg_display}</span>
|
||||||
<span #keyboard>{svg_keyboard}</span>
|
<span #keyboard>{svg_keyboard}</span>
|
||||||
|
{recording_enabled ? <span #recording>{recording ? svg_recording_on : svg_recording_off}</span> : ""}
|
||||||
{this.renderKeyboardPop()}
|
{this.renderKeyboardPop()}
|
||||||
{this.renderDisplayPop()}
|
{this.renderDisplayPop()}
|
||||||
{this.renderActionPop()}
|
{this.renderActionPop()}
|
||||||
@ -279,6 +284,15 @@ class Header: Reactor.Component {
|
|||||||
me.popup(menu);
|
me.popup(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
event click $(span#recording) (_, me) {
|
||||||
|
recording = !recording;
|
||||||
|
header.update();
|
||||||
|
if (recording)
|
||||||
|
handler.refresh_video();
|
||||||
|
else
|
||||||
|
handler.record_screen(false, 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;
|
||||||
handler.switch_display(me.index);
|
handler.switch_display(me.index);
|
||||||
|
@ -214,6 +214,7 @@ class Enhancements: Reactor.Component {
|
|||||||
<menu #enhancements-menu>
|
<menu #enhancements-menu>
|
||||||
{has_hwcodec ? <li #enable-hwcodec><span>{svg_checkmark}</span>{translate("Hardware Codec")} (beta)</li> : ""}
|
{has_hwcodec ? <li #enable-hwcodec><span>{svg_checkmark}</span>{translate("Hardware Codec")} (beta)</li> : ""}
|
||||||
<li #enable-abr><span>{svg_checkmark}</span>{translate("Adaptive Bitrate")} (beta)</li>
|
<li #enable-abr><span>{svg_checkmark}</span>{translate("Adaptive Bitrate")} (beta)</li>
|
||||||
|
<li #screen-recording>{translate("Recording")}</li>
|
||||||
</menu>
|
</menu>
|
||||||
</li>;
|
</li>;
|
||||||
}
|
}
|
||||||
@ -232,6 +233,26 @@ class Enhancements: Reactor.Component {
|
|||||||
var v = me.id;
|
var v = me.id;
|
||||||
if (v.indexOf("enable-") == 0) {
|
if (v.indexOf("enable-") == 0) {
|
||||||
handler.set_option(v, handler.get_option(v) != 'N' ? 'N' : '');
|
handler.set_option(v, handler.get_option(v) != 'N' ? 'N' : '');
|
||||||
|
} else if (v == 'screen-recording') {
|
||||||
|
var dir = handler.get_option("video-save-directory");
|
||||||
|
if (!dir) dir = handler.default_video_save_directory();
|
||||||
|
var ts0 = handler.get_option("enable-record-session") == '' ? { checked: true } : {};
|
||||||
|
var ts1 = handler.get_option("allow-auto-record-incoming") == 'Y' ? { checked: true } : {};
|
||||||
|
msgbox("custom-recording", translate('Recording'),
|
||||||
|
<div .form>
|
||||||
|
<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>
|
||||||
|
<div style="word-wrap:break-word"><span>{translate("Directory")}: </span><span #folderPath>{dir}</span></div>
|
||||||
|
<div> <button #select_directory .link>{translate('Change')}</button> </div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
, function(res=null) {
|
||||||
|
if (!res) return;
|
||||||
|
handler.set_option("enable-record-session", res.enable_record_session ? '' : 'N');
|
||||||
|
handler.set_option("allow-auto-record-incoming", res.auto_record_incoming ? 'Y' : '');
|
||||||
|
handler.set_option("video-save-directory", $(#folderPath).text);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this.toggleMenuState();
|
this.toggleMenuState();
|
||||||
}
|
}
|
||||||
|
@ -193,6 +193,14 @@ class MsgboxComponent: Reactor.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
event click $(button#select_directory) {
|
||||||
|
var folder = view.selectFolder(translate("Change"), $(#folderPath).text);
|
||||||
|
if (folder) {
|
||||||
|
if (folder.indexOf("file://") == 0) folder = folder.substring(7);
|
||||||
|
$(#folderPath).text = folder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function show_progress(show=1, err="") {
|
function show_progress(show=1, err="") {
|
||||||
if (show == -1) {
|
if (show == -1) {
|
||||||
this.close()
|
this.close()
|
||||||
|
@ -394,6 +394,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();
|
fn refresh_video();
|
||||||
|
fn record_screen(bool, i32, i32);
|
||||||
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);
|
||||||
|
@ -12,6 +12,7 @@ var clipboard_enabled = true; // server side
|
|||||||
var audio_enabled = true; // server side
|
var audio_enabled = true; // server side
|
||||||
var file_enabled = true; // server side
|
var file_enabled = true; // server side
|
||||||
var restart_enabled = true; // server side
|
var restart_enabled = true; // server side
|
||||||
|
var recording_enabled = true; // server side
|
||||||
var scroll_body = $(body);
|
var scroll_body = $(body);
|
||||||
|
|
||||||
handler.setDisplay = function(x, y, w, h) {
|
handler.setDisplay = function(x, y, w, h) {
|
||||||
@ -20,6 +21,7 @@ handler.setDisplay = function(x, y, w, h) {
|
|||||||
display_origin_x = x;
|
display_origin_x = x;
|
||||||
display_origin_y = y;
|
display_origin_y = y;
|
||||||
adaptDisplay();
|
adaptDisplay();
|
||||||
|
if (recording) handler.record_screen(true, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
// in case toolbar not shown correclty
|
// in case toolbar not shown correclty
|
||||||
@ -467,6 +469,7 @@ function self.closing() {
|
|||||||
var (x, y, w, h) = view.box(#rectw, #border, #screen);
|
var (x, y, w, h) = view.box(#rectw, #border, #screen);
|
||||||
if (is_file_transfer) save_file_transfer_close_state();
|
if (is_file_transfer) save_file_transfer_close_state();
|
||||||
if (is_file_transfer || is_port_forward || size_adapted) handler.save_size(x, y, w, h);
|
if (is_file_transfer || is_port_forward || size_adapted) handler.save_size(x, y, w, h);
|
||||||
|
if (recording) handler.record_screen(false, display_width, display_height);
|
||||||
}
|
}
|
||||||
|
|
||||||
var qualityMonitor;
|
var qualityMonitor;
|
||||||
@ -519,6 +522,7 @@ handler.setPermission = function(name, enabled) {
|
|||||||
if (name == "file") file_enabled = enabled;
|
if (name == "file") file_enabled = enabled;
|
||||||
if (name == "clipboard") clipboard_enabled = enabled;
|
if (name == "clipboard") clipboard_enabled = enabled;
|
||||||
if (name == "restart") restart_enabled = enabled;
|
if (name == "restart") restart_enabled = enabled;
|
||||||
|
if (name == "recording") recording_enabled = enabled;
|
||||||
input_blocked = false;
|
input_blocked = false;
|
||||||
header.update();
|
header.update();
|
||||||
});
|
});
|
||||||
|
@ -40,6 +40,7 @@ pub struct Client {
|
|||||||
pub audio: bool,
|
pub audio: bool,
|
||||||
pub file: bool,
|
pub file: bool,
|
||||||
pub restart: bool,
|
pub restart: bool,
|
||||||
|
pub recording: bool,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
tx: UnboundedSender<Data>,
|
tx: UnboundedSender<Data>,
|
||||||
}
|
}
|
||||||
@ -94,6 +95,7 @@ impl<T: InvokeUiCM> ConnectionManager<T> {
|
|||||||
audio: bool,
|
audio: bool,
|
||||||
file: bool,
|
file: bool,
|
||||||
restart: bool,
|
restart: bool,
|
||||||
|
recording: bool,
|
||||||
tx: mpsc::UnboundedSender<Data>,
|
tx: mpsc::UnboundedSender<Data>,
|
||||||
) {
|
) {
|
||||||
let client = Client {
|
let client = Client {
|
||||||
@ -108,6 +110,7 @@ impl<T: InvokeUiCM> ConnectionManager<T> {
|
|||||||
audio,
|
audio,
|
||||||
file,
|
file,
|
||||||
restart,
|
restart,
|
||||||
|
recording,
|
||||||
tx,
|
tx,
|
||||||
};
|
};
|
||||||
self.ui_handler.add_connection(&client);
|
self.ui_handler.add_connection(&client);
|
||||||
@ -250,11 +253,11 @@ pub async fn start_ipc<T: InvokeUiCM>(cm: ConnectionManager<T>) {
|
|||||||
}
|
}
|
||||||
Ok(Some(data)) => {
|
Ok(Some(data)) => {
|
||||||
match data {
|
match data {
|
||||||
Data::Login{id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, file_transfer_enabled, restart} => {
|
Data::Login{id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, file_transfer_enabled, restart, recording} => {
|
||||||
log::debug!("conn_id: {}", id);
|
log::debug!("conn_id: {}", id);
|
||||||
conn_id = id;
|
conn_id = id;
|
||||||
tx_file.send(ClipboardFileData::Enable((id, file_transfer_enabled))).ok();
|
tx_file.send(ClipboardFileData::Enable((id, file_transfer_enabled))).ok();
|
||||||
cm.add_connection(id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, restart, tx.clone());
|
cm.add_connection(id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, restart, recording, tx.clone());
|
||||||
}
|
}
|
||||||
Data::Close => {
|
Data::Close => {
|
||||||
tx_file.send(ClipboardFileData::Enable((conn_id, false))).ok();
|
tx_file.send(ClipboardFileData::Enable((conn_id, false))).ok();
|
||||||
@ -349,6 +352,7 @@ pub async fn start_listen<T: InvokeUiCM>(
|
|||||||
audio,
|
audio,
|
||||||
file,
|
file,
|
||||||
restart,
|
restart,
|
||||||
|
recording,
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
current_id = id;
|
current_id = id;
|
||||||
@ -364,6 +368,7 @@ pub async fn start_listen<T: InvokeUiCM>(
|
|||||||
audio,
|
audio,
|
||||||
file,
|
file,
|
||||||
restart,
|
restart,
|
||||||
|
recording,
|
||||||
tx.clone(),
|
tx.clone(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -729,6 +729,11 @@ pub fn get_langs() -> String {
|
|||||||
crate::lang::LANGS.to_string()
|
crate::lang::LANGS.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn default_video_save_directory() -> String {
|
||||||
|
scrap::record::RecorderContext::default_save_directory()
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_xfce() -> bool {
|
pub fn is_xfce() -> bool {
|
||||||
crate::platform::is_xfce()
|
crate::platform::is_xfce()
|
||||||
|
@ -98,6 +98,10 @@ impl<T: InvokeUiSession> Session<T> {
|
|||||||
self.send(Data::Message(LoginConfigHandler::refresh()));
|
self.send(Data::Message(LoginConfigHandler::refresh()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn record_screen(&self, start: bool, w: i32, h: i32) {
|
||||||
|
self.send(Data::RecordScreen(start, w, h, self.id.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn save_custom_image_quality(&mut self, custom_image_quality: i32) {
|
pub fn save_custom_image_quality(&mut self, custom_image_quality: i32) {
|
||||||
let msg = self
|
let msg = self
|
||||||
.lc
|
.lc
|
||||||
|
Loading…
x
Reference in New Issue
Block a user