video record

Signed-off-by: 21pages <pages21@163.com>
This commit is contained in:
21pages 2022-09-15 17:31:28 +08:00
parent f5b7c34c81
commit 9489877c78
48 changed files with 1186 additions and 398 deletions

125
Cargo.lock generated
View File

@ -58,6 +58,21 @@ dependencies = [
"atomic",
]
[[package]]
name = "alloc-no-stdlib"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
[[package]]
name = "alloc-stdlib"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "alsa"
version = "0.6.0"
@ -420,6 +435,27 @@ dependencies = [
"once_cell",
]
[[package]]
name = "brotli"
version = "3.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
"brotli-decompressor",
]
[[package]]
name = "brotli-decompressor"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
]
[[package]]
name = "bumpalo"
version = "3.11.0"
@ -589,8 +625,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
dependencies = [
"iana-time-zone",
"js-sys",
"num-integer",
"num-traits 0.2.15",
"time 0.1.44",
"wasm-bindgen",
"winapi 0.3.9",
]
@ -1359,6 +1398,19 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
name = "embed-resource"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc24ff8d764818e9ab17963b0593c535f077a513f565e75e4352d758bc4d8c0"
dependencies = [
"cc",
"rustc_version 0.4.0",
"toml",
"vswhom",
"winreg 0.10.1",
]
[[package]]
name = "encoding_rs"
version = "0.8.31"
@ -1548,7 +1600,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92"
dependencies = [
"memoffset",
"rustc_version",
"rustc_version 0.3.3",
]
[[package]]
@ -1590,7 +1642,7 @@ dependencies = [
"regex",
"rustversion",
"thiserror",
"time",
"time 0.3.9",
]
[[package]]
@ -1895,7 +1947,7 @@ checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
dependencies = [
"cfg-if 1.0.0",
"libc",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
@ -2283,6 +2335,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"bytes",
"chrono",
"confy",
"directories-next",
"dirs-next",
@ -2391,7 +2444,7 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hwcodec"
version = "0.1.0"
source = "git+https://github.com/21pages/hwcodec#890204e0703a3d361fc7a45f035fe75c0575bb1d"
source = "git+https://github.com/21pages/hwcodec#097a476a0ee249e28d99573899ed4c9c0c01f884"
dependencies = [
"bindgen",
"cc",
@ -2822,6 +2875,12 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "md5"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
[[package]]
name = "memalloc"
version = "0.1.0"
@ -2919,7 +2978,7 @@ checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
dependencies = [
"libc",
"log",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.36.1",
]
@ -4230,6 +4289,15 @@ dependencies = [
"semver 0.11.0",
]
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver 1.0.13",
]
[[package]]
name = "rustdesk"
version = "1.2.0"
@ -4305,6 +4373,16 @@ dependencies = [
"wol-rs",
]
[[package]]
name = "rustdesk-portable-packer"
version = "0.1.0"
dependencies = [
"brotli",
"dirs",
"embed-resource",
"md5",
]
[[package]]
name = "rustfft"
version = "6.0.1"
@ -5050,6 +5128,17 @@ dependencies = [
"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]]
name = "time"
version = "0.3.9"
@ -5374,6 +5463,26 @@ dependencies = [
"thiserror",
]
[[package]]
name = "vswhom"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b"
dependencies = [
"libc",
"vswhom-sys",
]
[[package]]
name = "vswhom-sys"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22025f6d8eb903ebf920ea6933b70b1e495be37e2cb4099e62c80454aaf57c39"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "waker-fn"
version = "1.1.0"
@ -5401,6 +5510,12 @@ dependencies = [
"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]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"

View File

@ -1,6 +1,7 @@
import 'dart:convert';
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.dart';
@ -9,6 +10,7 @@ import 'package:flutter_hbb/models/platform_model.dart';
import 'package:flutter_hbb/models/server_model.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
@ -199,6 +201,7 @@ class _GeneralState extends State<_General> {
abr(),
hwcodec(),
audio(context),
record(context),
_Card(title: 'Language', children: [language()]),
],
).marginOnly(bottom: _kListViewBottomMargin));
@ -290,6 +293,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() {
return _futureBuilder(future: () async {
String langs = await bind.mainGetLangs();

View File

@ -166,6 +166,7 @@ class _RemotePageState extends State<RemotePage>
ChangeNotifierProvider.value(value: _ffi.imageModel),
ChangeNotifierProvider.value(value: _ffi.cursorModel),
ChangeNotifierProvider.value(value: _ffi.canvasModel),
ChangeNotifierProvider.value(value: _ffi.recordingModel),
], child: buildBody(context)));
}

View File

@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/models/chat_model.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:rxdart/rxdart.dart' as rxdart;
import '../../common.dart';
@ -134,6 +135,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
if (!isWeb) {
menubarItems.add(_buildChat(context));
}
menubarItems.add(_buildRecording(context));
menubarItems.add(_buildClose(context));
return PopupMenuTheme(
data: const PopupMenuThemeData(
@ -351,6 +353,24 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
);
}
Widget _buildRecording(BuildContext context) {
return Consumer<RecordingModel>(
builder: (context, value, child) => IconButton(
tooltip: value.start
? translate('Stop session recording')
: translate('Start session recording'),
onPressed: () async {
await value.toggle();
},
icon: Icon(
value.start
? Icons.pause_circle_filled
: Icons.videocam_outlined,
color: _MenubarTheme.commonColor,
),
));
}
Widget _buildClose(BuildContext context) {
return IconButton(
tooltip: translate('Close'),

View File

@ -197,6 +197,7 @@ class FfiModel with ChangeNotifier {
_display.height = int.parse(evt['height']);
if (old != _pi.currentDisplay) {
parent.target?.cursorModel.updateDisplayOrigin(_display.x, _display.y);
parent.target?.recordingModel.switchDisplay();
}
// remote is mobile, and orientation changed
@ -972,6 +973,41 @@ class QualityMonitorModel with ChangeNotifier {
}
}
class RecordingModel with ChangeNotifier {
WeakReference<FFI> parent;
RecordingModel(this.parent);
bool _start = false;
get start => _start;
switchDisplay() {
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: _start, width: width, height: height);
}
Future<void> toggle() async {
if (!isDesktop) 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;
await bind.sessionRecordScreen(
id: id, start: !_start, width: width, height: height);
_start = !_start;
notifyListeners();
if (_start) {
Future.delayed(const Duration(milliseconds: 100), () {
bind.sessionRefresh(id: id);
});
}
}
}
/// Mouse button enum.
enum MouseButtons { left, right, wheel }
@ -1013,6 +1049,7 @@ class FFI {
late final AbModel abModel; // global
late final UserModel userModel; // global
late final QualityMonitorModel qualityMonitorModel; // session
late final RecordingModel recordingModel; // recording
FFI() {
imageModel = ImageModel(WeakReference(this));
@ -1025,6 +1062,7 @@ class FFI {
abModel = AbModel(WeakReference(this));
userModel = UserModel(WeakReference(this));
qualityMonitorModel = QualityMonitorModel(WeakReference(this));
recordingModel = RecordingModel(WeakReference(this));
}
/// Send a mouse tap event(down and up).

View File

@ -140,7 +140,7 @@ packages:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.2.1"
charcode:
dependency: transitive
description:
@ -161,7 +161,7 @@ packages:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
version: "1.1.1"
code_builder:
dependency: transitive
description:
@ -325,6 +325,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
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:
dependency: transitive
description:
@ -588,7 +595,7 @@ packages:
name: material_color_utilities
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.4"
version: "0.1.5"
menu_base:
dependency: transitive
description:
@ -602,7 +609,7 @@ packages:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
version: "1.8.0"
mime:
dependency: transitive
description:
@ -679,7 +686,7 @@ packages:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.1"
version: "1.8.2"
path_provider:
dependency: "direct main"
description:

View File

@ -80,6 +80,7 @@ dependencies:
desktop_drop: ^0.3.3
scroll_pos: ^0.3.0
rxdart: ^0.27.5
file_picker: ^5.1.0
flutter_improved_scrolling: ^0.0.3
# currently, we use flutter 3.0.5 for windows build, latest for other builds.
#

View File

@ -30,6 +30,7 @@ filetime = "0.2"
sodiumoxide = "0.2"
regex = "1.4"
tokio-socks = { git = "https://github.com/open-trade/tokio-socks" }
chrono = "0.4"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
mac_address = "1.1"

View File

@ -38,6 +38,8 @@ pub use tokio_socks;
pub use tokio_socks::IntoTargetAddr;
pub use tokio_socks::TargetAddr;
pub mod password_security;
pub use chrono;
pub use directories_next;
#[cfg(feature = "quic")]
pub type Stream = quic::Connection;

View File

@ -20,6 +20,7 @@ libc = "0.2"
num_cpus = "1.13"
lazy_static = "1.4"
hbb_common = { path = "../hbb_common" }
webm = "1.0"
[dependencies.winapi]
version = "0.3"
@ -37,7 +38,6 @@ ndk = { version = "0.7", features = ["media"], optional = true}
[target.'cfg(not(target_os = "android"))'.dev-dependencies]
repng = "0.2"
docopt = "1.1"
webm = "1.0"
serde = {version="1.0", features=["derive"]}
quest = "0.3"

View File

@ -28,7 +28,7 @@ const CFG_KEY_ENCODER: &str = "bestHwEncoders";
const CFG_KEY_DECODER: &str = "bestHwDecoders";
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_HW_QUALITY: Quality = Quality_Default;
const DEFAULT_RC: RateContorl = RC_DEFAULT;

View File

@ -39,6 +39,7 @@ pub use self::convert::*;
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 mod record;
mod vpx;
#[inline]

View File

@ -0,0 +1,297 @@
#[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.last().map(|f| self.write_video(f));
}
}
#[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.last().map(|f| self.write_video(f));
}
}
_ => 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,
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,
start: Instant::now(),
})
}
fn write_video(&mut self, frame: &EncodedVideoFrame) -> bool {
let ok = self.muxer.write_video(&frame.data, frame.pts).is_ok();
if ok {
self.written = true;
}
ok
}
}
#[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();
}
}
}

View File

@ -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;
#[cfg(not(any(target_os = "android", target_os = "linux")))]
use cpal::{
@ -13,6 +6,13 @@ use cpal::{
};
use magnum_opus::{Channels::*, Decoder as AudioDecoder};
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;
pub use file_trait::FileManager;
@ -39,6 +39,7 @@ pub use helper::LatencyController;
pub use helper::*;
use scrap::{
codec::{Decoder, DecoderCfg},
record::{Recorder, RecorderContext},
VpxDecoderConfig, VpxVideoCodecId,
};
@ -154,8 +155,7 @@ impl Client {
return Err(err);
}
}
Ok(x) => {
Ok(x)},
Ok(x) => Ok(x),
}
}
@ -798,6 +798,8 @@ pub struct VideoHandler {
decoder: Decoder,
latency_controller: Arc<Mutex<LatencyController>>,
pub rgb: Vec<u8>,
recorder: Arc<Mutex<Option<Recorder>>>,
record: bool,
}
impl VideoHandler {
@ -812,6 +814,8 @@ impl VideoHandler {
}),
latency_controller,
rgb: Default::default(),
recorder: Default::default(),
record: false,
}
}
@ -825,32 +829,21 @@ impl VideoHandler {
.update_video(vf.timestamp);
}
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),
}
}
/// 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.
pub fn reset(&mut self) {
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`].
@ -1395,6 +1406,7 @@ pub enum MediaData {
AudioFrame(AudioFrame),
AudioFormat(AudioFormat),
Reset,
RecordScreen(bool, i32, i32, String),
}
pub type MediaSender = mpsc::Sender<MediaData>;
@ -1429,6 +1441,9 @@ where
MediaData::Reset => {
video_handler.reset();
}
MediaData::RecordScreen(start, w, h, id) => {
video_handler.record_screen(start, w, h, id)
}
_ => {}
}
} else {
@ -1703,6 +1718,7 @@ pub enum Data {
SetConfirmOverrideFile((i32, i32, bool, bool, bool)),
AddJob((i32, String, String, i32, bool, bool)),
ResumeJob((i32, bool)),
RecordScreen(bool, i32, i32, String),
}
/// Keycode for key events.
@ -1892,4 +1908,4 @@ fn decode_id_pk(signed: &[u8], key: &sign::PublicKey) -> ResultType<(String, [u8
pub fn disable_keyboard_listening() {
crate::ui_session_interface::KEYBOARD_HOOKED.store(false, Ordering::SeqCst);
}
}

View File

@ -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
@ -794,13 +799,8 @@ impl<T: InvokeUiSession> Remote<T> {
fs::transform_windows_path(&mut entries);
}
}
self.handler.update_folder_files(
fd.id,
&entries,
fd.path,
false,
false,
);
self.handler
.update_folder_files(fd.id, &entries, fd.path, false, false);
if let Some(job) = fs::get_job(fd.id, &mut self.write_jobs) {
log::info!("job set_files: {:?}", entries);
job.set_files(entries);

View File

@ -19,13 +19,13 @@ use crate::ui_interface;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::ui_interface::get_sound_inputs;
use crate::ui_interface::{
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_option,
get_options, get_peer, get_peer_option, get_socks, get_uuid, get_version, has_hwcodec,
has_rendezvous_service, 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_temporary_password, using_public_server,
change_id, check_mouse_time, check_super_user_permission, default_video_save_directory,
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_option, get_options, get_peer, get_peer_option, get_socks, get_uuid,
get_version, has_hwcodec, has_rendezvous_service, 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_temporary_password, using_public_server,
};
use crate::{
client::file_trait::FileManager,
@ -162,6 +162,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) {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
session.reconnect();
@ -705,6 +711,10 @@ pub fn main_change_language(lang: String) {
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(
id: String,
local_port: i32,

View File

@ -346,5 +346,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable RDP", "允许RDP访问"),
("Pin menubar", "固定菜单栏"),
("Unpin menubar", "取消固定菜单栏"),
("Recording", "录屏"),
("Directory", "目录"),
("Automatically record incoming sessions", "自动录制来访会话"),
("Change", "更改"),
("Start session recording", "开始录屏"),
("Stop session recording", "结束录屏"),
].iter().cloned().collect();
}

View File

@ -346,5 +346,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable RDP", ""),
("Pin menubar", "Připnout panel nabídek"),
("Unpin menubar", "Odepnout panel nabídek"),
("Recording", ""),
("Directory", ""),
("Automatically record incoming sessions", ""),
("Change", ""),
("Start session recording", ""),
("Stop session recording", ""),
].iter().cloned().collect();
}

View File

@ -346,5 +346,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable RDP", ""),
("Pin menubar", "Fastgør menulinjen"),
("Unpin menubar", "Frigør menulinjen"),
("Recording", ""),
("Directory", ""),
("Automatically record incoming sessions", ""),
("Change", ""),
("Start session recording", ""),
("Stop session recording", ""),
].iter().cloned().collect();
}

View File

@ -346,5 +346,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable RDP", ""),
("Pin menubar", "Pin-Menüleiste"),
("Unpin menubar", "Menüleiste lösen"),
("Recording", ""),
("Directory", ""),
("Automatically record incoming sessions", ""),
("Change", ""),
("Start session recording", ""),
("Stop session recording", ""),
].iter().cloned().collect();
}

View File

@ -346,5 +346,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable RDP", ""),
("Pin menubar", "Alpingla menubreto"),
("Unpin menubar", "Malfiksi menubreton"),
("Recording", ""),
("Directory", ""),
("Automatically record incoming sessions", ""),
("Change", ""),
("Start session recording", ""),
("Stop session recording", ""),
].iter().cloned().collect();
}

View File

@ -359,5 +359,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable RDP", ""),
("Pin menubar", "Pin barra de menú"),
("Unpin menubar", "Desbloquear barra de menú"),
("Recording", ""),
("Directory", ""),
("Automatically record incoming sessions", ""),
("Change", ""),
("Start session recording", ""),
("Stop session recording", ""),
].iter().cloned().collect();
}

View File

@ -346,5 +346,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable RDP", ""),
("Pin menubar", "Épingler la barre de menus"),
("Unpin menubar", "Détacher la barre de menu"),
("Recording", ""),
("Directory", ""),
("Automatically record incoming sessions", ""),
("Change", ""),
("Start session recording", ""),
("Stop session recording", ""),
].iter().cloned().collect();
}

View File

@ -346,5 +346,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable RDP", ""),
("Pin menubar", "Menüsor rögzítése"),
("Unpin menubar", "Menüsor rögzítésének feloldása"),
("Recording", ""),
("Directory", ""),
("Automatically record incoming sessions", ""),
("Change", ""),
("Start session recording", ""),
("Stop session recording", ""),
].iter().cloned().collect();
}

View File

@ -359,5 +359,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable RDP", ""),
("Pin menubar", "Pin menubar"),
("Unpin menubar", "Unpin menubar"),
("Recording", ""),
("Directory", ""),
("Automatically record incoming sessions", ""),
("Change", ""),
("Start session recording", ""),
("Stop session recording", ""),
].iter().cloned().collect();
}

View File

@ -345,5 +345,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable RDP", ""),
("Pin menubar", "Blocca la barra dei menu"),
("Unpin menubar", "Sblocca la barra dei menu"),
("Recording", ""),
("Directory", ""),
("Automatically record incoming sessions", ""),
("Change", ""),
("Start session recording", ""),
("Stop session recording", ""),
].iter().cloned().collect();
}

View File

@ -343,5 +343,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable RDP", ""),
("Pin menubar", "メニューバーを固定する"),
("Unpin menubar", "メニューバーのピン留めを外す"),
("Recording", ""),
("Directory", ""),
("Automatically record incoming sessions", ""),
("Change", ""),
("Start session recording", ""),
("Stop session recording", ""),
].iter().cloned().collect();
}

View File

@ -340,5 +340,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable RDP", ""),
("Pin menubar", "핀 메뉴 바"),
("Unpin menubar", "메뉴 모음 고정 해제"),
("Recording", ""),
("Directory", ""),
("Automatically record incoming sessions", ""),
("Change", ""),
("Start session recording", ""),
("Stop session recording", ""),
].iter().cloned().collect();
}

View File

@ -1,325 +1,331 @@
lazy_static::lazy_static! {
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
[
("Status", "Күй"),
("Your Desktop", "Сіздің Жұмыс үстеліңіз"),
("desk_tip", "Сіздің Жұмыс үстеліңіз осы ID мен құпия сөз арқылы қолжетімді"),
("Password", "Құпия сөз"),
("Ready", "Дайын"),
("Established", "Қосылды"),
("connecting_status", "RustDesk желісіне қосылуда..."),
("Enable Service", "Сербесті қосу"),
("Start Service", "Сербесті іске қосу"),
("Service is running", "Сербес істеуде"),
("Service is not running", "Сербес істемеуде"),
("not_ready_status", "Дайын емес. Қосылымды тексеруді өтінеміз"),
("Control Remote Desktop", "Қашықтағы Жұмыс үстелін Басқару"),
("Transfer File", "Файыл Тасымалдау"),
("Connect", "Қосылу"),
("Recent Sessions", "Соңғы Сештер"),
("Address Book", "Мекенжай Кітабы"),
("Confirmation", "Мақұлдау"),
("TCP Tunneling", "TCP тунелдеу"),
("Remove", "Жою"),
("Refresh random password", "Кездейсоқ құпия сөзді жаңарту"),
("Set your own password", "Өз құпия сөзіңізді орнатыңыз"),
("Enable Keyboard/Mouse", "Пернетақта/Тінтуірді қосу"),
("Enable Clipboard", "Көшіру-тақтасын қосу"),
("Enable File Transfer", "Файыл Тасымалдауды қосу"),
("Enable TCP Tunneling", "TCP тунелдеуді қосу"),
("IP Whitelisting", "IP Ақ-тізімі"),
("ID/Relay Server", "ID/Relay сербері"),
("Stop service", "Сербесті тоқтату"),
("Change ID", "ID ауыстыру"),
("Website", "Web-сайт"),
("About", "Туралы"),
("Mute", "Дыбыссыздандыру"),
("Audio Input", "Аудио Еңгізу"),
("Enhancements", "Жақсартулар"),
("Hardware Codec", "Hardware Codec"),
("Adaptive Bitrate", "Adaptive Bitrate"),
("ID Server", "ID Сербері"),
("Relay Server", "Relay Сербері"),
("API Server", "API Сербері"),
("invalid_http", "http:// немесе https://'пен басталуы қажет"),
("Invalid IP", "Бұрыс IP-Мекенжай"),
("id_change_tip", "Тек a-z, A-Z, 0-9 және _ (астынғы-сызық) таңбалары рұқсат етілген. Бірінші таңба a-z, A-Z болуы қажет. Ұзындығы 6 мен 16 арасы."),
("Invalid format", "Бұрыс формат"),
("server_not_support", "Сербер әзірше қолдамайды"),
("Not available", "Қолжетімсіз"),
("Too frequent", "Тым жиі"),
("Cancel", "Болдырмау"),
("Skip", "Өткізіп жіберу"),
("Close", "Жабу"),
("Retry", "Қайтадан көру"),
("OK", "OK"),
("Password Required", "Құпия сөз Қажет"),
("Please enter your password", "Құпия сөзіңізді еңгізуді өтінеміз"),
("Remember password", "Құпия сөзді есте сақтау"),
("Wrong Password", "Бұрыс Құпия сөз"),
("Do you want to enter again?", "Қайтадан кіргіңіз келеді ме?"),
("Connection Error", "Қосылым Қатесі"),
("Error", "Қате"),
("Reset by the peer", "Пир қалпына келтірді"),
("Connecting...", "Қосылуда..."),
("Connection in progress. Please wait.", "Қосылым барысында. Күтуді өтінеміз"),
("Please try 1 minute later", "1 минуттан соң қайта көріңіз"),
("Login Error", "Кіру Қатесі"),
("Successful", "Сәтті"),
("Connected, waiting for image...", "Қосылды, сурет күтілуде..."),
("Name", "Ат"),
("Type", "Түр"),
("Modified", "Өзгертілді"),
("Size", "Өлшем"),
("Show Hidden Files", "Жасырын Файылдарды Көрсету"),
("Receive", "Қабылдау"),
("Send", "Жіберу"),
("Refresh File", "Файылды жаңарту"),
("Local", "Лақал"),
("Remote", "Қашықтағы"),
("Remote Computer", "Қашықтағы Қампұтыр"),
("Local Computer", "Лақал Қампұтыр"),
("Confirm Delete", "Жоюды Растау"),
("Delete", "Жою"),
("Properties", "Қасиеттер"),
("Multi Select", "Көптік таңдау"),
("Empty Directory", "Бос Бума"),
("Not an empty directory", "Бос бума емес"),
("Are you sure you want to delete this file?", "Бұл файылды жоюға сенімдісіз бе?"),
("Are you sure you want to delete this empty directory?", "Бұл бос буманы жоюға сенімдісіз бе?"),
("Are you sure you want to delete the file of this directory?", "Бұл буманың файылын жоюға сенімдісіз бе?"),
("Do this for all conflicts", "Мұны барлық қанпілектер үшін жасау"),
("This is irreversible!", "Бұл қайтымсыз!"),
("Deleting", "Жойылу"),
("files", "файылдар"),
("Waiting", "Күту"),
("Finished", "Аяқталды"),
("Speed", "Жылдамдық"),
("Custom Image Quality", "Теңшеулі Сурет Сапасы"),
("Privacy mode", "Құпиялылық Модасы"),
("Block user input", "Қолданушы еңгізуін бұғаттау"),
("Unblock user input", "Қолданушы еңгізуін бұғаттан шығару"),
("Adjust Window", "Терезені Реттеу"),
("Original", "Түпнұсқа"),
("Shrink", "Қысу"),
("Stretch", "Созу"),
("Scrollbar", "Scrollbar"),
("ScrollAuto", "ScrollAuto"),
("Good image quality", "Жақсы сурет сапасы"),
("Balanced", "Теңдестірілген"),
("Optimize reaction time", "Реакция уақытын оңтайландыру"),
("Custom", "Теңшеулі"),
("Show remote cursor", "Қашықтағы курсорды көрсету"),
("Show quality monitor", "Сапа мониторын көрсету"),
("Disable clipboard", "Көшіру-тақтасын өшіру"),
("Lock after session end", "Сеш аяқталған соң құлыптау"),
("Insert", "Кірістіру"),
("Insert Lock", "Кірістіруді Құлыптау"),
("Refresh", "Жаңарту"),
("ID does not exist", "ID табылмады"),
("Failed to connect to rendezvous server", "Rendezvous серберіне қосылу сәтсіз"),
("Please try later", "Кейінірек қайта көруді өтінеміз"),
("Remote desktop is offline", "Қашықтағы жұмыс үстелі офлайн күйінде"),
("Key mismatch", "Кілт сәйкессіздігі"),
("Timeout", "Үзіліс"),
("Failed to connect to relay server", "Relay серберіне қосылу сәтсіз"),
("Failed to connect via rendezvous server", "Rendezvous сербері арқылы қосылу сәтсіз"),
("Failed to connect via relay server", "Relay сербері арқылы қосылу сәтсіз"),
("Failed to make direct connection to remote desktop", "Қашықтағы жұмыс үстеліне тікелей қосылым жасау сәтсіз"),
("Set Password", "Құпия сөзді Орнату"),
("OS Password", "OS Құпия сөзі"),
("install_tip", "UAC кесірінен, RustDesk кейбірде қашықтағы жақ ретінде дұрыс жұмыс істей алмайды. UAC'пен қиындықты болдырмау үшін, төмендегі батырманы басып RustDesk'ті жүйеге орнатыңыз."),
("Click to upgrade", "Жаңғырту үшін басыңыз"),
("Click to download", "Жүктеу үшін басыңыз"),
("Click to update", "Жаңарту үшін басыңыз"),
("Configure", "Қалыптау"),
("config_acc", "Сіздің Жұмыс үстеліңізді қашықтан басқару үшін, RustDesk'ке \"Қолжетімділік\" рұқсаттарын беруіңіз керек."),
("config_screen", "Сіздің Жұмыс үстеліңізге қашықтан қол жеткізу үшін, RustDesk'ке \"Екіренді Жазу\" рұқсаттарын беруіңіз керек."),
("Installing ...", "Орнатылу..."),
("Install", "Орнату"),
("Installation", "Орнатылу"),
("Installation Path", "Орнатылу Жолы"),
("Create start menu shortcuts", "Бастау мәзірі белгішесің жасау"),
("Create desktop icon", "Жұмыс үстелі белгішесің жасау"),
("agreement_tip", "Орнатуды бастасаңыз, сіз лисензе келісімін қабылдайсыз."),
("Accept and Install", "Қабылдау және Орнату"),
("End-user license agreement", "Түпкі қолданушының лисензе келісімі"),
("Generating ...", "Генератталуда..."),
("Your installation is lower version.", "Сіздің орнатуыныз төменгі нұсқа."),
("not_close_tcp_tip", "Тунел қолдану кезінде бұл терезені жаппаңыз"),
("Listening ...", "Тыңдау ..."),
("Remote Host", "Қашықтағы Хост"),
("Remote Port", "Қашықтағы Порт"),
("Action", "Әрекет"),
("Add", "Қосу"),
("Local Port", "Лақал Порт"),
("setup_server_tip", "Тез қосылым үшін өз серберіңізді орнатуды өтінеміз"),
("Too short, at least 6 characters.", "Тым қысқа, кемінде 6 таңба."),
("The confirmation is not identical.", "Растау сәйкес келмейді."),
("Permissions", "Рұқсаттар"),
("Accept", "Қабылдау"),
("Dismiss", "Босату"),
("Disconnect", "Ажырату"),
("Allow using keyboard and mouse", "Пернетақта мен тінтуірді қолдануды рұқсат ету"),
("Allow using clipboard", "Көшіру-тақтасын рұқсат ету"),
("Allow hearing sound", "Дыбыс естуді рұқсат ету"),
("Allow file copy and paste", "Файылды көшіру мен қоюды рұқсат ету"),
("Connected", "Қосылды"),
("Direct and encrypted connection", "Тікелей және кіриптелген қосылым"),
("Relayed and encrypted connection", "Релайданған және кіриптелген қосылым"),
("Direct and unencrypted connection", "Тікелей және кіриптелмеген қосылым"),
("Relayed and unencrypted connection", "Релайданған және кіриптелмеген қосылым"),
("Enter Remote ID", "Қашықтағы ID еңгізіңіз"),
("Enter your password", "Құпия сөзіңізді енгізіңіз"),
("Logging in...", "Кіруде..."),
("Enable RDP session sharing", "RDP сешті бөлісуді іске қосу"),
("Auto Login", "Ауты Кіру (\"Сеш аяқталған соң құлыптау\"'ды орнатқанда ғана жарамды)"),
("Enable Direct IP Access", "Тікелей IP Қолжетімді іске қосу"),
("Rename", "Атын өзгерту"),
("Space", "Орын"),
("Create Desktop Shortcut", "Жұмыс үстелі Таңбашасын Жасау"),
("Change Path", "Жолды өзгерту"),
("Create Folder", "Бума жасау"),
("Please enter the folder name", "Буманың атауын еңгізуді өтінеміз"),
("Fix it", "Түзету"),
("Warning", "Ескерту"),
("Login screen using Wayland is not supported", "Wayland қолданған Кіру екіреніне қолдау көрсетілмейді"),
("Reboot required", "Қайта-қосу қажет"),
("Unsupported display server ", "Қолдаусыз дисплей сербері"),
("x11 expected", "x11 күтілген"),
("Port", "Порт"),
("Settings", "Орнатпалар"),
("Username", "Қолданушы аты"),
("Invalid port", "Бұрыс порт"),
("Closed manually by the peer", "Пир қолымен жабылған"),
("Enable remote configuration modification", "Қашықтан қалыптарды өзгертуді іске қосу"),
("Run without install", "Орнатпай-ақ Іске қосу"),
("Always connected via relay", "Әрқашан да релай сербері арқылы қосулы"),
("Always connect via relay", "Әрқашан да релай сербері арқылы қосылу"),
("whitelist_tip", "Маған тек ақ-тізімделген IP қол жеткізе алады"),
("Login", "Кіру"),
("Logout", "Шығу"),
("Tags", "Тақтар"),
("Search ID", "ID Іздеу"),
("Current Wayland display server is not supported", "Ағымдағы Wayland дисплей серберіне қолдау көрсетілмейді"),
("whitelist_sep", "Үтір, нүктелі үтір, бос орын және жаңа жолал арқылы бөлінеді"),
("Add ID", "ID Қосу"),
("Add Tag", "Тақ Қосу"),
("Unselect all tags", "Барлық тақтардың таңдауын алып тастау"),
("Network error", "Желі қатесі"),
("Username missed", "Қолданушы аты бос"),
("Password missed", "Құпия сөз бос"),
("Wrong credentials", "Бұрыс тіркелгі деректер"),
("Edit Tag", "Тақты Өндеу"),
("Unremember Password", "Құпия сөзді Ұмыту"),
("Favorites", "Таңдаулылар"),
("Add to Favorites", "Таңдаулыларға Қосу"),
("Remove from Favorites", "Таңдаулылардан алып тастау"),
("Empty", "Бос"),
("Invalid folder name", "Бұрыс бума атауы"),
("Socks5 Proxy", "Socks5 Proxy"),
("Hostname", "Хост атауы"),
("Discovered", "Табылды"),
("install_daemon_tip", "Бут кезінде қосылу үшін жүйелік сербесті орнатуыныз керек."),
("Remote ID", "Қашықтағы ID"),
("Paste", "Қою"),
("Paste here?", "Осында қою керек пе?"),
("Are you sure to close the connection?", "Қосылымды жабуға сенімдісіз бе?"),
("Download new version", "Жаңа нұсқаны жүктеу"),
("Touch mode", "Жанасатын мода"),
("Mouse mode", "Тінтуірлі мода"),
("One-Finger Tap", "Бір-Саусақпен Түрту"),
("Left Mouse", "Солақ Тінтуір"),
("One-Long Tap", "Бір-Ұзақ Түрту"),
("Two-Finger Tap", "Екі-Саусақпен Түрту"),
("Right Mouse", "Оңақ Тінтуір"),
("One-Finger Move", "Бір-Саусақпен Жылжыту"),
("Double Tap & Move", "Екі-рет Түртіп Жылжыту"),
("Mouse Drag", "Тінтуір Тартуы"),
("Three-Finger vertically", "Үш-Саусақпен тік-бағытты"),
("Mouse Wheel", "Тінтуір Дөңгелегі"),
("Two-Finger Move", "Екі-Саусақпен Жылжыту"),
("Canvas Move", "Кенеп Жылжуы"),
("Pinch to Zoom", "Зумдау үшін Шымшыңыз"),
("Canvas Zoom", "Кенеп Зумы"),
("Reset canvas", "Кенепті қалпына келтіру"),
("No permission of file transfer", "Файыл алмасуға рұқсат берілмеген"),
("Note", "Нота"),
("Connection", "Қосылым"),
("Share Screen", "Екіренді Бөлісу"),
("CLOSE", "ЖАБУ"),
("OPEN", "АШУ"),
("Chat", "Чат"),
("Total", "Барлығы"),
("items", "зат"),
("Selected", "Таңдалған"),
("Screen Capture", "Екіренді Түсіру"),
("Input Control", "Еңгізуді Басқару/Қадағалау"),
("Audio Capture", "Аудио Түсіру"),
("File Connection", "Файыл Қосылымы"),
("Screen Connection", "Екірен Қосылымы"),
("Do you accept?", "Қабылдайсыз ба?"),
("Open System Setting", "Жүйе Орнатпаларын Ашу"),
("How to get Android input permission?", "Android еңгізу рұқсатын қалай алуға болады?"),
("android_input_permission_tip1", "Қашықтағы құрылғы сіздің Android құрылғыңызды тінтуір немесе түрту арқылы басқару үшін, RustDesk'ке \"Қолжетімділік\" сербесін қолдануға рұқсат беруініз керек."),
("android_input_permission_tip2", "Келесі Жүйе Орнатпалары бетіне барып, [Орнатылған Сербестер]'ді тауып кіріңіз, сосын [RustDesk Еңгізу] сербесін іске қосыңыз."),
("android_new_connection_tip", "Сіздің ағымдағы құрылғыңызды басқаруды қалайтын жаңа басқару сұранысы түсті."),
("android_service_will_start_tip", "\"Екіренді Тұсіру\" қосылған кезде сербес аутыматты іске қосылып, басқа құрылғыларға сіздің құрылғыға қосылым сұраныстауға мүмкіндің береді."),
("android_stop_service_tip", "Сербесті жабу аутыматты түрде барлық орнатылған қосылымдарды жабады."),
("android_version_audio_tip", "Ағымдағы Android нұсқасы аудионы түсіруді қолдамайды, Android 10 не жоғарғысына жаңғыртуды өтінеміз."),
("android_start_service_tip", "[Сербесті Іске қосу]'ды түртіңіз не [Екіренді Түсіру] рұқсатын АШУ арқылы екіренді бөлісу сербесін іске қосыңыз."),
("Account", "Есепкі"),
("Overwrite", "Үстінен қайта жазу"),
("This file exists, skip or overwrite this file?", "Бұл файыл бар, өткізіп жіберу әлде үстінен қайта жазу керек пе?"),
("Quit", "Шығу"),
("doc_mac_permission", ""),
("Help", "Көмек"),
("Failed", "Сәтсіз"),
("Succeeded", "Сәтті"),
("Someone turns on privacy mode, exit", "Біреу құпиялылық модасын қосты, шығу"),
("Unsupported", "Қолдаусыз"),
("Peer denied", "Пир қабылдамады"),
("Please install plugins", "Плагиндерді орнатуды өтінеміз"),
("Peer exit", "Пирдің шығуы"),
("Failed to turn off", "Сөндіру сәтсіз болды"),
("Turned off", "Өшірілген"),
("In privacy mode", "Құпиялылық модасында"),
("Out privacy mode", "Құпиялылық модасынан Шығу"),
("Language", "Тіл"),
("Keep RustDesk background service", "Артжақтағы RustDesk сербесін сақтап тұру"),
("Ignore Battery Optimizations", "Бәтері Оңтайландыруларын Елемеу"),
("android_open_battery_optimizations_tip", "Егер де бұл ерекшелікті өшіруді қаласаңыз, келесі RustDesk апылқат орнатпалары бетіне барып, [Бәтері]'ні тауып кіріңіз де [Шектеусіз]'ден құсбелгіні алып тастауды өтінеміз"),
("Connection not allowed", "Қосылу рұқсат етілмеген"),
("Use temporary password", "Уақытша құпия сөзді қолдану"),
("Use permanent password", "Тұрақты құпия сөзді қолдану"),
("Use both passwords", "Қос құпия сөзді қолдану"),
("Set permanent password", "Тұрақты құпия сөзді орнату"),
("Set temporary password length", "Уақытша құпия сөздің ұзындығын орнату"),
("Enable Remote Restart", "Қашықтан қайта-қосуды іске қосу"),
("Allow remote restart", "Қашықтан қайта-қосуды рұқсат ету"),
("Restart Remote Device", "Қашықтағы құрылғыны қайта-қосу"),
("Are you sure you want to restart", "Қайта-қосуға сенімдісіз бе?"),
("Restarting Remote Device", "Қашықтағы Құрылғыны қайта-қосуда"),
("remote_restarting_tip", "Қашықтағы құрылғы қайта-қосылуда, бұл хабар терезесін жабып, біраздан соң тұрақты құпия сөзбен қайта қосылуды өтінеміз"),
("Copied", "Көшірілді"),
("Exit Fullscreen", "Толық екіреннен Шығу"),
("Fullscreen", "Толық екірен"),
("Mobile Actions", "Мабыл Әрекеттері"),
("Select Monitor", "Мониторды Таңдау"),
("Control Actions", "Басқару Әрекеттері"),
("Display Settings", "Дисплей Орнатпалары"),
("Ratio", "Арақатынас"),
("Image Quality", "Сурет Сапасы"),
("Scroll Style", "Scroll Теңшетұрі"),
("Show Menubar", "Мәзір жолағын көрсету"),
("Hide Menubar", "Мәзір жолағын жасыру"),
("Direct Connection", "Тікелей Қосылым"),
("Relay Connection", "Релай Қосылым"),
("Secure Connection", "Қауіпсіз Қосылым"),
("Insecure Connection", "Қатерлі Қосылым"),
("Scale original", "Scale original"),
("Scale adaptive", "Scale adaptive"),
("Pin menubar", "Мәзір жолағын бекіту"),
("Unpin menubar", "Мәзір жолағын босату"),
].iter().cloned().collect();
}
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
[
("Status", "Күй"),
("Your Desktop", "Сіздің Жұмыс үстеліңіз"),
("desk_tip", "Сіздің Жұмыс үстеліңіз осы ID мен құпия сөз арқылы қолжетімді"),
("Password", "Құпия сөз"),
("Ready", "Дайын"),
("Established", "Қосылды"),
("connecting_status", "RustDesk желісіне қосылуда..."),
("Enable Service", "Сербесті қосу"),
("Start Service", "Сербесті іске қосу"),
("Service is running", "Сербес істеуде"),
("Service is not running", "Сербес істемеуде"),
("not_ready_status", "Дайын емес. Қосылымды тексеруді өтінеміз"),
("Control Remote Desktop", "Қашықтағы Жұмыс үстелін Басқару"),
("Transfer File", "Файыл Тасымалдау"),
("Connect", "Қосылу"),
("Recent Sessions", "Соңғы Сештер"),
("Address Book", "Мекенжай Кітабы"),
("Confirmation", "Мақұлдау"),
("TCP Tunneling", "TCP тунелдеу"),
("Remove", "Жою"),
("Refresh random password", "Кездейсоқ құпия сөзді жаңарту"),
("Set your own password", "Өз құпия сөзіңізді орнатыңыз"),
("Enable Keyboard/Mouse", "Пернетақта/Тінтуірді қосу"),
("Enable Clipboard", "Көшіру-тақтасын қосу"),
("Enable File Transfer", "Файыл Тасымалдауды қосу"),
("Enable TCP Tunneling", "TCP тунелдеуді қосу"),
("IP Whitelisting", "IP Ақ-тізімі"),
("ID/Relay Server", "ID/Relay сербері"),
("Stop service", "Сербесті тоқтату"),
("Change ID", "ID ауыстыру"),
("Website", "Web-сайт"),
("About", "Туралы"),
("Mute", "Дыбыссыздандыру"),
("Audio Input", "Аудио Еңгізу"),
("Enhancements", "Жақсартулар"),
("Hardware Codec", "Hardware Codec"),
("Adaptive Bitrate", "Adaptive Bitrate"),
("ID Server", "ID Сербері"),
("Relay Server", "Relay Сербері"),
("API Server", "API Сербері"),
("invalid_http", "http:// немесе https://'пен басталуы қажет"),
("Invalid IP", "Бұрыс IP-Мекенжай"),
("id_change_tip", "Тек a-z, A-Z, 0-9 және _ (астынғы-сызық) таңбалары рұқсат етілген. Бірінші таңба a-z, A-Z болуы қажет. Ұзындығы 6 мен 16 арасы."),
("Invalid format", "Бұрыс формат"),
("server_not_support", "Сербер әзірше қолдамайды"),
("Not available", "Қолжетімсіз"),
("Too frequent", "Тым жиі"),
("Cancel", "Болдырмау"),
("Skip", "Өткізіп жіберу"),
("Close", "Жабу"),
("Retry", "Қайтадан көру"),
("OK", "OK"),
("Password Required", "Құпия сөз Қажет"),
("Please enter your password", "Құпия сөзіңізді еңгізуді өтінеміз"),
("Remember password", "Құпия сөзді есте сақтау"),
("Wrong Password", "Бұрыс Құпия сөз"),
("Do you want to enter again?", "Қайтадан кіргіңіз келеді ме?"),
("Connection Error", "Қосылым Қатесі"),
("Error", "Қате"),
("Reset by the peer", "Пир қалпына келтірді"),
("Connecting...", "Қосылуда..."),
("Connection in progress. Please wait.", "Қосылым барысында. Күтуді өтінеміз"),
("Please try 1 minute later", "1 минуттан соң қайта көріңіз"),
("Login Error", "Кіру Қатесі"),
("Successful", "Сәтті"),
("Connected, waiting for image...", "Қосылды, сурет күтілуде..."),
("Name", "Ат"),
("Type", "Түр"),
("Modified", "Өзгертілді"),
("Size", "Өлшем"),
("Show Hidden Files", "Жасырын Файылдарды Көрсету"),
("Receive", "Қабылдау"),
("Send", "Жіберу"),
("Refresh File", "Файылды жаңарту"),
("Local", "Лақал"),
("Remote", "Қашықтағы"),
("Remote Computer", "Қашықтағы Қампұтыр"),
("Local Computer", "Лақал Қампұтыр"),
("Confirm Delete", "Жоюды Растау"),
("Delete", "Жою"),
("Properties", "Қасиеттер"),
("Multi Select", "Көптік таңдау"),
("Empty Directory", "Бос Бума"),
("Not an empty directory", "Бос бума емес"),
("Are you sure you want to delete this file?", "Бұл файылды жоюға сенімдісіз бе?"),
("Are you sure you want to delete this empty directory?", "Бұл бос буманы жоюға сенімдісіз бе?"),
("Are you sure you want to delete the file of this directory?", "Бұл буманың файылын жоюға сенімдісіз бе?"),
("Do this for all conflicts", "Мұны барлық қанпілектер үшін жасау"),
("This is irreversible!", "Бұл қайтымсыз!"),
("Deleting", "Жойылу"),
("files", "файылдар"),
("Waiting", "Күту"),
("Finished", "Аяқталды"),
("Speed", "Жылдамдық"),
("Custom Image Quality", "Теңшеулі Сурет Сапасы"),
("Privacy mode", "Құпиялылық Модасы"),
("Block user input", "Қолданушы еңгізуін бұғаттау"),
("Unblock user input", "Қолданушы еңгізуін бұғаттан шығару"),
("Adjust Window", "Терезені Реттеу"),
("Original", "Түпнұсқа"),
("Shrink", "Қысу"),
("Stretch", "Созу"),
("Scrollbar", "Scrollbar"),
("ScrollAuto", "ScrollAuto"),
("Good image quality", "Жақсы сурет сапасы"),
("Balanced", "Теңдестірілген"),
("Optimize reaction time", "Реакция уақытын оңтайландыру"),
("Custom", "Теңшеулі"),
("Show remote cursor", "Қашықтағы курсорды көрсету"),
("Show quality monitor", "Сапа мониторын көрсету"),
("Disable clipboard", "Көшіру-тақтасын өшіру"),
("Lock after session end", "Сеш аяқталған соң құлыптау"),
("Insert", "Кірістіру"),
("Insert Lock", "Кірістіруді Құлыптау"),
("Refresh", "Жаңарту"),
("ID does not exist", "ID табылмады"),
("Failed to connect to rendezvous server", "Rendezvous серберіне қосылу сәтсіз"),
("Please try later", "Кейінірек қайта көруді өтінеміз"),
("Remote desktop is offline", "Қашықтағы жұмыс үстелі офлайн күйінде"),
("Key mismatch", "Кілт сәйкессіздігі"),
("Timeout", "Үзіліс"),
("Failed to connect to relay server", "Relay серберіне қосылу сәтсіз"),
("Failed to connect via rendezvous server", "Rendezvous сербері арқылы қосылу сәтсіз"),
("Failed to connect via relay server", "Relay сербері арқылы қосылу сәтсіз"),
("Failed to make direct connection to remote desktop", "Қашықтағы жұмыс үстеліне тікелей қосылым жасау сәтсіз"),
("Set Password", "Құпия сөзді Орнату"),
("OS Password", "OS Құпия сөзі"),
("install_tip", "UAC кесірінен, RustDesk кейбірде қашықтағы жақ ретінде дұрыс жұмыс істей алмайды. UAC'пен қиындықты болдырмау үшін, төмендегі батырманы басып RustDesk'ті жүйеге орнатыңыз."),
("Click to upgrade", "Жаңғырту үшін басыңыз"),
("Click to download", "Жүктеу үшін басыңыз"),
("Click to update", "Жаңарту үшін басыңыз"),
("Configure", "Қалыптау"),
("config_acc", "Сіздің Жұмыс үстеліңізді қашықтан басқару үшін, RustDesk'ке \"Қолжетімділік\" рұқсаттарын беруіңіз керек."),
("config_screen", "Сіздің Жұмыс үстеліңізге қашықтан қол жеткізу үшін, RustDesk'ке \"Екіренді Жазу\" рұқсаттарын беруіңіз керек."),
("Installing ...", "Орнатылу..."),
("Install", "Орнату"),
("Installation", "Орнатылу"),
("Installation Path", "Орнатылу Жолы"),
("Create start menu shortcuts", "Бастау мәзірі белгішесің жасау"),
("Create desktop icon", "Жұмыс үстелі белгішесің жасау"),
("agreement_tip", "Орнатуды бастасаңыз, сіз лисензе келісімін қабылдайсыз."),
("Accept and Install", "Қабылдау және Орнату"),
("End-user license agreement", "Түпкі қолданушының лисензе келісімі"),
("Generating ...", "Генератталуда..."),
("Your installation is lower version.", "Сіздің орнатуыныз төменгі нұсқа."),
("not_close_tcp_tip", "Тунел қолдану кезінде бұл терезені жаппаңыз"),
("Listening ...", "Тыңдау ..."),
("Remote Host", "Қашықтағы Хост"),
("Remote Port", "Қашықтағы Порт"),
("Action", "Әрекет"),
("Add", "Қосу"),
("Local Port", "Лақал Порт"),
("setup_server_tip", "Тез қосылым үшін өз серберіңізді орнатуды өтінеміз"),
("Too short, at least 6 characters.", "Тым қысқа, кемінде 6 таңба."),
("The confirmation is not identical.", "Растау сәйкес келмейді."),
("Permissions", "Рұқсаттар"),
("Accept", "Қабылдау"),
("Dismiss", "Босату"),
("Disconnect", "Ажырату"),
("Allow using keyboard and mouse", "Пернетақта мен тінтуірді қолдануды рұқсат ету"),
("Allow using clipboard", "Көшіру-тақтасын рұқсат ету"),
("Allow hearing sound", "Дыбыс естуді рұқсат ету"),
("Allow file copy and paste", "Файылды көшіру мен қоюды рұқсат ету"),
("Connected", "Қосылды"),
("Direct and encrypted connection", "Тікелей және кіриптелген қосылым"),
("Relayed and encrypted connection", "Релайданған және кіриптелген қосылым"),
("Direct and unencrypted connection", "Тікелей және кіриптелмеген қосылым"),
("Relayed and unencrypted connection", "Релайданған және кіриптелмеген қосылым"),
("Enter Remote ID", "Қашықтағы ID еңгізіңіз"),
("Enter your password", "Құпия сөзіңізді енгізіңіз"),
("Logging in...", "Кіруде..."),
("Enable RDP session sharing", "RDP сешті бөлісуді іске қосу"),
("Auto Login", "Ауты Кіру (\"Сеш аяқталған соң құлыптау\"'ды орнатқанда ғана жарамды)"),
("Enable Direct IP Access", "Тікелей IP Қолжетімді іске қосу"),
("Rename", "Атын өзгерту"),
("Space", "Орын"),
("Create Desktop Shortcut", "Жұмыс үстелі Таңбашасын Жасау"),
("Change Path", "Жолды өзгерту"),
("Create Folder", "Бума жасау"),
("Please enter the folder name", "Буманың атауын еңгізуді өтінеміз"),
("Fix it", "Түзету"),
("Warning", "Ескерту"),
("Login screen using Wayland is not supported", "Wayland қолданған Кіру екіреніне қолдау көрсетілмейді"),
("Reboot required", "Қайта-қосу қажет"),
("Unsupported display server ", "Қолдаусыз дисплей сербері"),
("x11 expected", "x11 күтілген"),
("Port", "Порт"),
("Settings", "Орнатпалар"),
("Username", "Қолданушы аты"),
("Invalid port", "Бұрыс порт"),
("Closed manually by the peer", "Пир қолымен жабылған"),
("Enable remote configuration modification", "Қашықтан қалыптарды өзгертуді іске қосу"),
("Run without install", "Орнатпай-ақ Іске қосу"),
("Always connected via relay", "Әрқашан да релай сербері арқылы қосулы"),
("Always connect via relay", "Әрқашан да релай сербері арқылы қосылу"),
("whitelist_tip", "Маған тек ақ-тізімделген IP қол жеткізе алады"),
("Login", "Кіру"),
("Logout", "Шығу"),
("Tags", "Тақтар"),
("Search ID", "ID Іздеу"),
("Current Wayland display server is not supported", "Ағымдағы Wayland дисплей серберіне қолдау көрсетілмейді"),
("whitelist_sep", "Үтір, нүктелі үтір, бос орын және жаңа жолал арқылы бөлінеді"),
("Add ID", "ID Қосу"),
("Add Tag", "Тақ Қосу"),
("Unselect all tags", "Барлық тақтардың таңдауын алып тастау"),
("Network error", "Желі қатесі"),
("Username missed", "Қолданушы аты бос"),
("Password missed", "Құпия сөз бос"),
("Wrong credentials", "Бұрыс тіркелгі деректер"),
("Edit Tag", "Тақты Өндеу"),
("Unremember Password", "Құпия сөзді Ұмыту"),
("Favorites", "Таңдаулылар"),
("Add to Favorites", "Таңдаулыларға Қосу"),
("Remove from Favorites", "Таңдаулылардан алып тастау"),
("Empty", "Бос"),
("Invalid folder name", "Бұрыс бума атауы"),
("Socks5 Proxy", "Socks5 Proxy"),
("Hostname", "Хост атауы"),
("Discovered", "Табылды"),
("install_daemon_tip", "Бут кезінде қосылу үшін жүйелік сербесті орнатуыныз керек."),
("Remote ID", "Қашықтағы ID"),
("Paste", "Қою"),
("Paste here?", "Осында қою керек пе?"),
("Are you sure to close the connection?", "Қосылымды жабуға сенімдісіз бе?"),
("Download new version", "Жаңа нұсқаны жүктеу"),
("Touch mode", "Жанасатын мода"),
("Mouse mode", "Тінтуірлі мода"),
("One-Finger Tap", "Бір-Саусақпен Түрту"),
("Left Mouse", "Солақ Тінтуір"),
("One-Long Tap", "Бір-Ұзақ Түрту"),
("Two-Finger Tap", "Екі-Саусақпен Түрту"),
("Right Mouse", "Оңақ Тінтуір"),
("One-Finger Move", "Бір-Саусақпен Жылжыту"),
("Double Tap & Move", "Екі-рет Түртіп Жылжыту"),
("Mouse Drag", "Тінтуір Тартуы"),
("Three-Finger vertically", "Үш-Саусақпен тік-бағытты"),
("Mouse Wheel", "Тінтуір Дөңгелегі"),
("Two-Finger Move", "Екі-Саусақпен Жылжыту"),
("Canvas Move", "Кенеп Жылжуы"),
("Pinch to Zoom", "Зумдау үшін Шымшыңыз"),
("Canvas Zoom", "Кенеп Зумы"),
("Reset canvas", "Кенепті қалпына келтіру"),
("No permission of file transfer", "Файыл алмасуға рұқсат берілмеген"),
("Note", "Нота"),
("Connection", "Қосылым"),
("Share Screen", "Екіренді Бөлісу"),
("CLOSE", "ЖАБУ"),
("OPEN", "АШУ"),
("Chat", "Чат"),
("Total", "Барлығы"),
("items", "зат"),
("Selected", "Таңдалған"),
("Screen Capture", "Екіренді Түсіру"),
("Input Control", "Еңгізуді Басқару/Қадағалау"),
("Audio Capture", "Аудио Түсіру"),
("File Connection", "Файыл Қосылымы"),
("Screen Connection", "Екірен Қосылымы"),
("Do you accept?", "Қабылдайсыз ба?"),
("Open System Setting", "Жүйе Орнатпаларын Ашу"),
("How to get Android input permission?", "Android еңгізу рұқсатын қалай алуға болады?"),
("android_input_permission_tip1", "Қашықтағы құрылғы сіздің Android құрылғыңызды тінтуір немесе түрту арқылы басқару үшін, RustDesk'ке \"Қолжетімділік\" сербесін қолдануға рұқсат беруініз керек."),
("android_input_permission_tip2", "Келесі Жүйе Орнатпалары бетіне барып, [Орнатылған Сербестер]'ді тауып кіріңіз, сосын [RustDesk Еңгізу] сербесін іске қосыңыз."),
("android_new_connection_tip", "Сіздің ағымдағы құрылғыңызды басқаруды қалайтын жаңа басқару сұранысы түсті."),
("android_service_will_start_tip", "\"Екіренді Тұсіру\" қосылған кезде сербес аутыматты іске қосылып, басқа құрылғыларға сіздің құрылғыға қосылым сұраныстауға мүмкіндің береді."),
("android_stop_service_tip", "Сербесті жабу аутыматты түрде барлық орнатылған қосылымдарды жабады."),
("android_version_audio_tip", "Ағымдағы Android нұсқасы аудионы түсіруді қолдамайды, Android 10 не жоғарғысына жаңғыртуды өтінеміз."),
("android_start_service_tip", "[Сербесті Іске қосу]'ды түртіңіз не [Екіренді Түсіру] рұқсатын АШУ арқылы екіренді бөлісу сербесін іске қосыңыз."),
("Account", "Есепкі"),
("Overwrite", "Үстінен қайта жазу"),
("This file exists, skip or overwrite this file?", "Бұл файыл бар, өткізіп жіберу әлде үстінен қайта жазу керек пе?"),
("Quit", "Шығу"),
("doc_mac_permission", ""),
("Help", "Көмек"),
("Failed", "Сәтсіз"),
("Succeeded", "Сәтті"),
("Someone turns on privacy mode, exit", "Біреу құпиялылық модасын қосты, шығу"),
("Unsupported", "Қолдаусыз"),
("Peer denied", "Пир қабылдамады"),
("Please install plugins", "Плагиндерді орнатуды өтінеміз"),
("Peer exit", "Пирдің шығуы"),
("Failed to turn off", "Сөндіру сәтсіз болды"),
("Turned off", "Өшірілген"),
("In privacy mode", "Құпиялылық модасында"),
("Out privacy mode", "Құпиялылық модасынан Шығу"),
("Language", "Тіл"),
("Keep RustDesk background service", "Артжақтағы RustDesk сербесін сақтап тұру"),
("Ignore Battery Optimizations", "Бәтері Оңтайландыруларын Елемеу"),
("android_open_battery_optimizations_tip", "Егер де бұл ерекшелікті өшіруді қаласаңыз, келесі RustDesk апылқат орнатпалары бетіне барып, [Бәтері]'ні тауып кіріңіз де [Шектеусіз]'ден құсбелгіні алып тастауды өтінеміз"),
("Connection not allowed", "Қосылу рұқсат етілмеген"),
("Use temporary password", "Уақытша құпия сөзді қолдану"),
("Use permanent password", "Тұрақты құпия сөзді қолдану"),
("Use both passwords", "Қос құпия сөзді қолдану"),
("Set permanent password", "Тұрақты құпия сөзді орнату"),
("Set temporary password length", "Уақытша құпия сөздің ұзындығын орнату"),
("Enable Remote Restart", "Қашықтан қайта-қосуды іске қосу"),
("Allow remote restart", "Қашықтан қайта-қосуды рұқсат ету"),
("Restart Remote Device", "Қашықтағы құрылғыны қайта-қосу"),
("Are you sure you want to restart", "Қайта-қосуға сенімдісіз бе?"),
("Restarting Remote Device", "Қашықтағы Құрылғыны қайта-қосуда"),
("remote_restarting_tip", "Қашықтағы құрылғы қайта-қосылуда, бұл хабар терезесін жабып, біраздан соң тұрақты құпия сөзбен қайта қосылуды өтінеміз"),
("Copied", "Көшірілді"),
("Exit Fullscreen", "Толық екіреннен Шығу"),
("Fullscreen", "Толық екірен"),
("Mobile Actions", "Мабыл Әрекеттері"),
("Select Monitor", "Мониторды Таңдау"),
("Control Actions", "Басқару Әрекеттері"),
("Display Settings", "Дисплей Орнатпалары"),
("Ratio", "Арақатынас"),
("Image Quality", "Сурет Сапасы"),
("Scroll Style", "Scroll Теңшетұрі"),
("Show Menubar", "Мәзір жолағын көрсету"),
("Hide Menubar", "Мәзір жолағын жасыру"),
("Direct Connection", "Тікелей Қосылым"),
("Relay Connection", "Релай Қосылым"),
("Secure Connection", "Қауіпсіз Қосылым"),
("Insecure Connection", "Қатерлі Қосылым"),
("Scale original", "Scale original"),
("Scale adaptive", "Scale adaptive"),
("Pin menubar", "Мәзір жолағын бекіту"),
("Unpin menubar", "Мәзір жолағын босату"),
("Recording", ""),
("Directory", ""),
("Automatically record incoming sessions", ""),
("Change", ""),
("Start session recording", ""),
("Stop session recording", ""),
].iter().cloned().collect();
}

View File

@ -344,5 +344,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable RDP", ""),
("Pin menubar", "Przypnij pasek menu"),
("Unpin menubar", "Odepnij pasek menu"),
("Recording", ""),
("Directory", ""),
("Automatically record incoming sessions", ""),
("Change", ""),
("Start session recording", ""),
("Stop session recording", ""),
].iter().cloned().collect();
}

View File

@ -340,5 +340,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable RDP", ""),
("Pin menubar", "Fixar barra de menu"),
("Unpin menubar", "Desenganxa la barra de menús"),
("Recording", ""),
("Directory", ""),
("Automatically record incoming sessions", ""),
("Change", ""),
("Start session recording", ""),
("Stop session recording", ""),
].iter().cloned().collect();
}

View File

@ -346,5 +346,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable RDP", ""),
("Pin menubar", ""),
("Unpin menubar", ""),
("Recording", ""),
("Directory", ""),
("Automatically record incoming sessions", ""),
("Change", ""),
("Start session recording", ""),
("Stop session recording", ""),
].iter().cloned().collect();
}

View File

@ -346,5 +346,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable RDP", ""),
("Pin menubar", "Закрепить строку меню"),
("Unpin menubar", "Открепить строку меню"),
("Recording", ""),
("Directory", ""),
("Automatically record incoming sessions", ""),
("Change", ""),
("Start session recording", ""),
("Stop session recording", ""),
].iter().cloned().collect();
}

View File

@ -346,5 +346,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable RDP", ""),
("Pin menubar", "Pripnúť panel s ponukami"),
("Unpin menubar", "Uvoľniť panel s ponukami"),
("Recording", ""),
("Directory", ""),
("Automatically record incoming sessions", ""),
("Change", ""),
("Start session recording", ""),
("Stop session recording", ""),
].iter().cloned().collect();
}

View File

@ -346,5 +346,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable RDP", ""),
("Pin menubar", ""),
("Unpin menubar", ""),
("Recording", ""),
("Directory", ""),
("Automatically record incoming sessions", ""),
("Change", ""),
("Start session recording", ""),
("Stop session recording", ""),
].iter().cloned().collect();
}

View File

@ -359,5 +359,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable RDP", ""),
("Pin menubar", "Menü çubuğunu sabitle"),
("Unpin menubar", "Menü çubuğunun sabitlemesini kaldır"),
("Recording", ""),
("Directory", ""),
("Automatically record incoming sessions", ""),
("Change", ""),
("Start session recording", ""),
("Stop session recording", ""),
].iter().cloned().collect();
}

View File

@ -346,5 +346,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable RDP", "允許RDP訪問"),
("Pin menubar", "固定菜單欄"),
("Unpin menubar", "取消固定菜單欄"),
("Recording", "錄屏"),
("Directory", "目錄"),
("Automatically record incoming sessions", "自動錄製來訪會話"),
("Change", "變更"),
("Start session recording", "開始錄屏"),
("Stop session recording", "結束錄屏"),
].iter().cloned().collect();
}

View File

@ -346,5 +346,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable RDP", ""),
("Pin menubar", "Ghim thanh menu"),
("Unpin menubar", "Bỏ ghim thanh menu"),
("Recording", ""),
("Directory", ""),
("Automatically record incoming sessions", ""),
("Change", ""),
("Start session recording", ""),
("Stop session recording", ""),
].iter().cloned().collect();
}

View File

@ -25,6 +25,7 @@ use hbb_common::tokio::sync::{
};
use scrap::{
codec::{Encoder, EncoderCfg, HwEncoderConfig},
record::{Recorder, RecorderContext},
vpxcodec::{VpxEncoderConfig, VpxVideoCodecId},
Capturer, Display, TraitCapturer,
};
@ -435,6 +436,21 @@ fn run(sp: GenericService) -> ResultType<()> {
#[cfg(windows)]
log::info!("gdi: {}", c.is_gdi());
let codec_name = Encoder::current_hw_encoder_name();
#[cfg(not(any(target_os = "android", target_os = "ios")))]
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() {
#[cfg(windows)]
@ -495,7 +511,8 @@ fn run(sp: GenericService) -> ResultType<()> {
}
scrap::Frame::RAW(data) => {
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);
}
}
@ -511,7 +528,8 @@ fn run(sp: GenericService) -> ResultType<()> {
Ok(frame) => {
let time = now - start;
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);
#[cfg(windows)]
{
@ -612,6 +630,7 @@ fn handle_one_frame(
frame: &[u8],
ms: i64,
encoder: &mut Encoder,
recorder: Arc<Mutex<Option<Recorder>>>,
) -> ResultType<HashSet<i32>> {
sp.snapshot(|sps| {
// 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();
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);
}
Ok(send_conn_ids)

View File

@ -21,20 +21,20 @@ use hbb_common::{
use crate::common::get_app_name;
use crate::ipc;
use crate::ui_interface::{
check_mouse_time, closing, create_shortcut, current_is_wayland, fix_login_wayland,
forget_password, get_api_server, get_async_job_status, get_connect_status, get_error, get_fav,
get_icon, 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_recent_sessions,
get_remote_id, get_size, get_socks, get_software_ext, get_software_store_path,
get_software_update_url, get_uuid, get_version, goto_install, has_hwcodec,
has_rendezvous_service, install_me, install_path, is_can_screen_recording, is_installed,
is_installed_daemon, is_installed_lower_version, is_login_wayland, is_ok_change_id,
is_process_trusted, is_rdp_service_open, is_share_rdp, is_xfce, modify_default_login,
new_remote, open_url, peer_has_password, permanent_password, post_request,
recent_sessions_updated, remove_peer, run_without_install, set_local_option, set_option,
set_options, set_peer_option, set_permanent_password, set_remote_id, set_share_rdp, set_socks,
show_run_without_install, store_fav, t, temporary_password, test_if_valid_server, update_me,
update_temporary_password, using_public_server,
check_mouse_time, closing, create_shortcut, current_is_wayland, default_video_save_directory,
fix_login_wayland, forget_password, get_api_server, get_async_job_status, get_connect_status,
get_error, get_fav, get_icon, 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_recent_sessions, get_remote_id, get_size, get_socks, get_software_ext,
get_software_store_path, get_software_update_url, get_uuid, get_version, goto_install,
has_hwcodec, has_rendezvous_service, install_me, install_path, is_can_screen_recording,
is_installed, is_installed_daemon, is_installed_lower_version, is_login_wayland,
is_ok_change_id, is_process_trusted, is_rdp_service_open, is_share_rdp, is_xfce,
modify_default_login, new_remote, open_url, peer_has_password, permanent_password,
post_request, recent_sessions_updated, remove_peer, run_without_install, set_local_option,
set_option, set_options, set_peer_option, set_permanent_password, set_remote_id, set_share_rdp,
set_socks, show_run_without_install, store_fav, t, temporary_password, test_if_valid_server,
update_me, update_temporary_password, using_public_server,
};
mod cm;
@ -579,6 +579,10 @@ impl UI {
fn get_langs(&self) -> String {
get_langs()
}
fn default_video_save_directory(&self) -> String {
default_video_save_directory()
}
}
impl sciter::EventHandler for UI {
@ -661,6 +665,7 @@ impl sciter::EventHandler for UI {
fn get_uuid();
fn has_hwcodec();
fn get_langs();
fn default_video_save_directory();
}
}

View File

@ -70,6 +70,15 @@ button.button:hover, button.outline:hover {
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] {
width: *;
font-size: 1.5em;

View File

@ -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_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_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;
function check_state_change() {
@ -90,6 +92,9 @@ function editOSPassword(login=false) {
});
}
var recording = false;
var recording_refresh = false;
class Header: Reactor.Component {
this var conn_note = "";
@ -140,6 +145,7 @@ class Header: Reactor.Component {
<span #action>{svg_action}</span>
<span #display>{svg_display}</span>
<span #keyboard>{svg_keyboard}</span>
<span #recording>{recording ? svg_recording_on : svg_recording_off}</span>
{this.renderKeyboardPop()}
{this.renderDisplayPop()}
{this.renderActionPop()}
@ -279,6 +285,13 @@ class Header: Reactor.Component {
me.popup(menu);
}
event click $(span#recording) (_, me) {
handler.record_screen(!recording, display_width, display_height);
recording = !recording;
header.update();
if (recording) self.timer(100ms, function() { recording_refresh = true; handler.refresh_video(); });
}
event click $(#screen) (_, me) {
if (pi.current_display == me.index) return;
handler.switch_display(me.index);

View File

@ -214,6 +214,7 @@ class Enhancements: Reactor.Component {
<menu #enhancements-menu>
{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 #screen-recording>{translate("Recording")}</li>
</menu>
</li>;
}
@ -232,6 +233,23 @@ class Enhancements: Reactor.Component {
var v = me.id;
if (v.indexOf("enable-") == 0) {
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 ts1 = handler.get_option("allow-auto-record-incoming") == 'Y' ? { checked: true } : {};
msgbox("custom-recording", translate('Recording'),
<div .form>
<div><button|checkbox(auto_record_incoming) {ts1}>{translate('Automatically record incoming sessions')}</button></div>
<div>
<div style="word-wrap:break-word"><span>{translate("Directory")}:&nbsp;&nbsp;</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("allow-auto-record-incoming", res.auto_record_incoming ? 'Y' : '');
handler.set_option("video-save-directory", $(#folderPath).text);
});
}
this.toggleMenuState();
}

View File

@ -192,6 +192,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="") {
if (show == -1) {

View File

@ -394,6 +394,7 @@ impl sciter::EventHandler for SciterSession {
fn save_image_quality(String);
fn save_custom_image_quality(i32);
fn refresh_video();
fn record_screen(bool, i32, i32);
fn get_toggle_option(String);
fn is_privacy_mode_supported();
fn toggle_option(String);

View File

@ -20,6 +20,9 @@ handler.setDisplay = function(x, y, w, h) {
display_origin_x = x;
display_origin_y = y;
adaptDisplay();
if (recording && !recording_refresh) handler.record_screen(true, w, h);
recording_refresh = false;
}
// in case toolbar not shown correclty

View File

@ -726,6 +726,11 @@ pub fn get_langs() -> String {
crate::lang::LANGS.to_string()
}
#[inline]
pub fn default_video_save_directory() -> String {
scrap::record::RecorderContext::default_save_directory()
}
#[inline]
pub fn is_xfce() -> bool {
crate::platform::is_xfce()

View File

@ -98,6 +98,10 @@ impl<T: InvokeUiSession> Session<T> {
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) {
let msg = self
.lc