This commit is contained in:
Asura 2022-07-23 06:33:15 -07:00
commit a46df491e5
43 changed files with 637 additions and 464 deletions

10
Cargo.lock generated
View File

@ -378,9 +378,12 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.1.0" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" checksum = "f0b3de4a0c5e67e16066a0715723abd91edc2f9001d09c46e1dca929351e130e"
dependencies = [
"serde 1.0.139",
]
[[package]] [[package]]
name = "cache-padded" name = "cache-padded"
@ -2230,7 +2233,7 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]] [[package]]
name = "hwcodec" name = "hwcodec"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/21pages/hwcodec#91d1cd327c88490f917457072aeef0676ddb2be7" source = "git+https://github.com/21pages/hwcodec#890204e0703a3d361fc7a45f035fe75c0575bb1d"
dependencies = [ dependencies = [
"bindgen", "bindgen",
"cc", "cc",
@ -3986,6 +3989,7 @@ dependencies = [
"async-process", "async-process",
"async-trait", "async-trait",
"base64", "base64",
"bytes",
"cc", "cc",
"cfg-if 1.0.0", "cfg-if 1.0.0",
"clap 3.2.12", "clap 3.2.12",

View File

@ -55,6 +55,7 @@ rpassword = "7.0"
base64 = "0.13" base64 = "0.13"
sysinfo = "0.24" sysinfo = "0.24"
num_cpus = "1.13" num_cpus = "1.13"
bytes = { version = "1.2", features = ["serde"] }
default-net = "0.11.0" default-net = "0.11.0"
wol-rs = "0.9.1" wol-rs = "0.9.1"

32
build.py Normal file → Executable file
View File

@ -22,7 +22,7 @@ def get_version():
return '' return ''
def get_features(feature): def parse_rc_features(feature):
available_features = { available_features = {
'IddDriver': { 'IddDriver': {
'zip_url': 'https://github.com/fufesou/RustDeskIddDriver/releases/download/v0.1/RustDeskIddDriver_x64.zip', 'zip_url': 'https://github.com/fufesou/RustDeskIddDriver/releases/download/v0.1/RustDeskIddDriver_x64.zip',
@ -94,8 +94,8 @@ def download_extract_features(features, res_dir):
print(f'{feat} extract end') print(f'{feat} extract end')
def build_windows(args): def get_rc_features(args):
features = get_features(args.feature) features = parse_rc_features(args.feature)
if features: if features:
print(f'Build with features {list(features.keys())}') print(f'Build with features {list(features.keys())}')
res_dir = 'resources' res_dir = 'resources'
@ -105,12 +105,17 @@ def build_windows(args):
raise Exception(f'Find file {res_dir}, not a directory') raise Exception(f'Find file {res_dir}, not a directory')
os.makedirs(res_dir, exist_ok=True) os.makedirs(res_dir, exist_ok=True)
download_extract_features(features, res_dir) download_extract_features(features, res_dir)
return ['with_rc'] if features else []
with_rc = ',with_rc' if features else '' def get_features(args):
hwcodec = ',hwcodec' if args.hwcodec else '' features = ['inline']
cmd = 'cargo build --release --features inline' + with_rc + hwcodec if windows:
print(cmd) features.extend(get_rc_features(args))
os.system(cmd) if args.hwcodec:
features.append('hwcodec')
print("features:", features)
return features
def main(): def main():
parser = make_parser() parser = make_parser()
@ -129,8 +134,9 @@ def main():
if os.path.isfile('/usr/bin/pacman'): if os.path.isfile('/usr/bin/pacman'):
os.system('git checkout src/ui/common.tis') os.system('git checkout src/ui/common.tis')
version = get_version() version = get_version()
features = ",".join(get_features(args))
if windows: if windows:
build_windows(args) os.system('cargo build --release --features ' + features)
# os.system('upx.exe target/release/rustdesk.exe') # os.system('upx.exe target/release/rustdesk.exe')
os.system('mv target/release/rustdesk.exe target/release/RustDesk.exe') os.system('mv target/release/rustdesk.exe target/release/RustDesk.exe')
pa = os.environ.get('P') pa = os.environ.get('P')
@ -141,7 +147,7 @@ def main():
print('Not signed') print('Not signed')
os.system(f'cp -rf target/release/RustDesk.exe rustdesk-{version}-setdown.exe') os.system(f'cp -rf target/release/RustDesk.exe rustdesk-{version}-setdown.exe')
elif os.path.isfile('/usr/bin/pacman'): elif os.path.isfile('/usr/bin/pacman'):
os.system('cargo build --release --features inline') os.system('cargo build --release --features ' + features)
os.system('git checkout src/ui/common.tis') os.system('git checkout src/ui/common.tis')
os.system('strip target/release/rustdesk') os.system('strip target/release/rustdesk')
os.system("sed -i 's/pkgver=.*/pkgver=%s/g' PKGBUILD" % version) os.system("sed -i 's/pkgver=.*/pkgver=%s/g' PKGBUILD" % version)
@ -150,7 +156,7 @@ def main():
os.system('mv rustdesk-%s-0-x86_64.pkg.tar.zst rustdesk-%s-manjaro-arch.pkg.tar.zst' % (version, version)) os.system('mv rustdesk-%s-0-x86_64.pkg.tar.zst rustdesk-%s-manjaro-arch.pkg.tar.zst' % (version, version))
# pacman -U ./rustdesk.pkg.tar.zst # pacman -U ./rustdesk.pkg.tar.zst
elif os.path.isfile('/usr/bin/yum'): elif os.path.isfile('/usr/bin/yum'):
os.system('cargo build --release --features inline') os.system('cargo build --release --features ' + features)
os.system('strip target/release/rustdesk') os.system('strip target/release/rustdesk')
os.system("sed -i 's/Version: .*/Version: %s/g' rpm.spec" % version) os.system("sed -i 's/Version: .*/Version: %s/g' rpm.spec" % version)
os.system('HBB=`pwd` rpmbuild -ba rpm.spec') os.system('HBB=`pwd` rpmbuild -ba rpm.spec')
@ -158,14 +164,14 @@ def main():
version, version)) version, version))
# yum localinstall rustdesk.rpm # yum localinstall rustdesk.rpm
elif os.path.isfile('/usr/bin/zypper'): elif os.path.isfile('/usr/bin/zypper'):
os.system('cargo build --release --features inline') os.system('cargo build --release --features ' + features)
os.system('strip target/release/rustdesk') os.system('strip target/release/rustdesk')
os.system("sed -i 's/Version: .*/Version: %s/g' rpm-suse.spec" % version) os.system("sed -i 's/Version: .*/Version: %s/g' rpm-suse.spec" % version)
os.system('HBB=`pwd` rpmbuild -ba rpm-suse.spec') os.system('HBB=`pwd` rpmbuild -ba rpm-suse.spec')
os.system('mv $HOME/rpmbuild/RPMS/x86_64/rustdesk-%s-0.x86_64.rpm ./rustdesk-%s-suse.rpm' % (version, version)) os.system('mv $HOME/rpmbuild/RPMS/x86_64/rustdesk-%s-0.x86_64.rpm ./rustdesk-%s-suse.rpm' % (version, version))
# yum localinstall rustdesk.rpm # yum localinstall rustdesk.rpm
else: else:
os.system('cargo bundle --release --features inline') os.system('cargo bundle --release --features ' + features)
if osx: if osx:
os.system( os.system(
'strip target/release/bundle/osx/RustDesk.app/Contents/MacOS/rustdesk') 'strip target/release/bundle/osx/RustDesk.app/Contents/MacOS/rustdesk')

View File

@ -11,7 +11,7 @@ protobuf = { version = "3.1", features = ["with-bytes"] }
tokio = { version = "1.20", features = ["full"] } tokio = { version = "1.20", features = ["full"] }
tokio-util = { version = "0.7", features = ["full"] } tokio-util = { version = "0.7", features = ["full"] }
futures = "0.3" futures = "0.3"
bytes = "1.1" bytes = { version = "1.2", features = ["serde"] }
log = "0.4" log = "0.4"
env_logger = "0.9" env_logger = "0.9"
socket2 = { version = "0.3", features = ["reuseport"] } socket2 = { version = "0.3", features = ["reuseport"] }

View File

@ -5,6 +5,10 @@ fn main() {
.out_dir("src/protos") .out_dir("src/protos")
.inputs(&["protos/rendezvous.proto", "protos/message.proto"]) .inputs(&["protos/rendezvous.proto", "protos/message.proto"])
.include("protos") .include("protos")
.customize(
protobuf_codegen::Customize::default()
.tokio_bytes(true)
)
.run() .run()
.expect("Codegen failed."); .expect("Codegen failed.");
} }

View File

@ -72,6 +72,11 @@ message Features {
bool privacy_mode = 1; bool privacy_mode = 1;
} }
message SupportedEncoding {
bool h264 = 1;
bool h265 = 2;
}
message PeerInfo { message PeerInfo {
string username = 1; string username = 1;
string hostname = 2; string hostname = 2;
@ -82,6 +87,7 @@ message PeerInfo {
string version = 7; string version = 7;
int32 conn_id = 8; int32 conn_id = 8;
Features features = 9; Features features = 9;
SupportedEncoding encoding = 10;
} }
message LoginResponse { message LoginResponse {
@ -442,9 +448,17 @@ enum ImageQuality {
} }
message VideoCodecState { message VideoCodecState {
int32 ScoreVpx = 1; enum PerferCodec {
int32 ScoreH264 = 2; Auto = 0;
int32 ScoreH265 = 3; VPX = 1;
H264 = 2;
H265 = 3;
}
int32 score_vpx = 1;
int32 score_h264 = 2;
int32 score_h265 = 3;
PerferCodec perfer = 4;
} }
message OptionMessage { message OptionMessage {

View File

@ -978,6 +978,10 @@ impl HwCodecConfig {
pub fn store(&self) { pub fn store(&self) {
Config::store_(self, "_hwcodec"); Config::store_(self, "_hwcodec");
} }
pub fn remove() {
std::fs::remove_file(Config::file_("_hwcodec")).ok();
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -50,5 +50,5 @@ gstreamer = { version = "0.16", optional = true }
gstreamer-app = { version = "0.16", features = ["v1_10"], optional = true } gstreamer-app = { version = "0.16", features = ["v1_10"], optional = true }
gstreamer-video = { version = "0.16", optional = true } gstreamer-video = { version = "0.16", optional = true }
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(any(target_os = "windows", target_os = "linux"))'.dependencies]
hwcodec = { git = "https://github.com/21pages/hwcodec", optional = true } hwcodec = { git = "https://github.com/21pages/hwcodec", optional = true }

View File

@ -4,7 +4,7 @@ extern crate scrap;
use std::fs::File; use std::fs::File;
#[cfg(windows)] #[cfg(windows)]
use scrap::CapturerMag; use scrap::{CapturerMag, TraitCapturer};
use scrap::{i420_to_rgb, Display}; use scrap::{i420_to_rgb, Display};
fn main() { fn main() {

View File

@ -3,7 +3,7 @@ use std::time::Duration;
extern crate scrap; extern crate scrap;
fn main() { fn main() {
use scrap::{Capturer, Display}; use scrap::{Capturer, Display, TraitCapturer};
use std::io::ErrorKind::WouldBlock; use std::io::ErrorKind::WouldBlock;
use std::io::Write; use std::io::Write;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};

View File

@ -18,7 +18,7 @@ use webm::mux;
use webm::mux::Track; use webm::mux::Track;
use scrap::vpxcodec as vpx_encode; use scrap::vpxcodec as vpx_encode;
use scrap::{Capturer, Display, STRIDE_ALIGN}; use scrap::{TraitCapturer, Capturer, Display, STRIDE_ALIGN};
const USAGE: &'static str = " const USAGE: &'static str = "
Simple WebM screen capture. Simple WebM screen capture.

View File

@ -6,7 +6,7 @@ use std::io::ErrorKind::WouldBlock;
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
use scrap::{i420_to_rgb, Capturer, Display}; use scrap::{i420_to_rgb, Capturer, Display, TraitCapturer};
fn main() { fn main() {
let n = Display::all().unwrap().len(); let n = Display::all().unwrap().len();

View File

@ -32,8 +32,12 @@ impl Capturer {
pub fn height(&self) -> usize { pub fn height(&self) -> usize {
self.display.height() as usize self.display.height() as usize
} }
}
pub fn frame<'a>(&'a mut self, _timeout: Duration) -> io::Result<Frame<'a>> { impl crate::TraitCapturer for Capturer {
fn set_use_yuv(&mut self, _use_yuv: bool) {}
fn frame<'a>(&'a mut self, _timeout: Duration) -> io::Result<Frame<'a>> {
if let Some(buf) = get_video_raw() { if let Some(buf) = get_video_raw() {
crate::would_block_if_equal(&mut self.saved_raw_data, buf)?; crate::would_block_if_equal(&mut self.saved_raw_data, buf)?;
rgba_to_i420(self.width(), self.height(), buf, &mut self.bgra); rgba_to_i420(self.width(), self.height(), buf, &mut self.bgra);

View File

@ -16,12 +16,15 @@ use hbb_common::{
ResultType, ResultType,
}; };
#[cfg(feature = "hwcodec")] #[cfg(feature = "hwcodec")]
use hbb_common::{config::Config2, lazy_static}; use hbb_common::{
config::{Config2, PeerConfig},
lazy_static,
message_proto::video_codec_state::PerferCodec,
};
#[cfg(feature = "hwcodec")] #[cfg(feature = "hwcodec")]
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref PEER_DECODER_STATES: Arc<Mutex<HashMap<i32, VideoCodecState>>> = Default::default(); static ref PEER_DECODER_STATES: Arc<Mutex<HashMap<i32, VideoCodecState>>> = Default::default();
static ref MY_DECODER_STATE: Arc<Mutex<VideoCodecState>> = Default::default();
} }
const SCORE_VPX: i32 = 90; const SCORE_VPX: i32 = 90;
@ -102,7 +105,7 @@ impl Encoder {
codec: Box::new(hw), codec: Box::new(hw),
}), }),
Err(e) => { Err(e) => {
HwEncoder::best(true, true); check_config_process(true);
Err(e) Err(e)
} }
}, },
@ -113,7 +116,6 @@ impl Encoder {
// TODO // TODO
pub fn update_video_encoder(id: i32, update: EncoderUpdate) { pub fn update_video_encoder(id: i32, update: EncoderUpdate) {
log::info!("encoder update: {:?}", update);
#[cfg(feature = "hwcodec")] #[cfg(feature = "hwcodec")]
{ {
let mut states = PEER_DECODER_STATES.lock().unwrap(); let mut states = PEER_DECODER_STATES.lock().unwrap();
@ -130,49 +132,75 @@ impl Encoder {
} }
} }
} }
let current_encoder_name = HwEncoder::current_name(); let name = HwEncoder::current_name();
if states.len() > 0 { if states.len() > 0 {
let (best, _) = HwEncoder::best(false, true); let best = HwEncoder::best();
let enabled_h264 = best.h264.is_some() let enabled_h264 = best.h264.is_some()
&& states.len() > 0 && states.len() > 0
&& states.iter().all(|(_, s)| s.ScoreH264 > 0); && states.iter().all(|(_, s)| s.score_h264 > 0);
let enabled_h265 = best.h265.is_some() let enabled_h265 = best.h265.is_some()
&& states.len() > 0 && states.len() > 0
&& states.iter().all(|(_, s)| s.ScoreH265 > 0); && states.iter().all(|(_, s)| s.score_h265 > 0);
// score encoder // Preference first
let mut score_vpx = SCORE_VPX; let mut preference = PerferCodec::Auto;
let mut score_h264 = best.h264.as_ref().map_or(0, |c| c.score); let preferences: Vec<_> = states
let mut score_h265 = best.h265.as_ref().map_or(0, |c| c.score); .iter()
.filter(|(_, s)| {
// score decoder s.perfer == PerferCodec::VPX.into()
score_vpx += states.iter().map(|s| s.1.ScoreVpx).sum::<i32>(); || s.perfer == PerferCodec::H264.into() && enabled_h264
if enabled_h264 { || s.perfer == PerferCodec::H265.into() && enabled_h265
score_h264 += states.iter().map(|s| s.1.ScoreH264).sum::<i32>(); })
} .map(|(_, s)| s.perfer)
if enabled_h265 { .collect();
score_h265 += states.iter().map(|s| s.1.ScoreH265).sum::<i32>(); if preferences.len() > 0 && preferences.iter().all(|&p| p == preferences[0]) {
preference = preferences[0].enum_value_or(PerferCodec::Auto);
} }
if enabled_h265 && score_h265 >= score_vpx && score_h265 >= score_h264 { match preference {
*current_encoder_name.lock().unwrap() = Some(best.h265.unwrap().name); PerferCodec::VPX => *name.lock().unwrap() = None,
} else if enabled_h264 && score_h264 >= score_vpx && score_h264 >= score_h265 { PerferCodec::H264 => {
*current_encoder_name.lock().unwrap() = Some(best.h264.unwrap().name); *name.lock().unwrap() = best.h264.map_or(None, |c| Some(c.name))
} else { }
*current_encoder_name.lock().unwrap() = None; PerferCodec::H265 => {
*name.lock().unwrap() = best.h265.map_or(None, |c| Some(c.name))
}
PerferCodec::Auto => {
// score encoder
let mut score_vpx = SCORE_VPX;
let mut score_h264 = best.h264.as_ref().map_or(0, |c| c.score);
let mut score_h265 = best.h265.as_ref().map_or(0, |c| c.score);
// score decoder
score_vpx += states.iter().map(|s| s.1.score_vpx).sum::<i32>();
if enabled_h264 {
score_h264 += states.iter().map(|s| s.1.score_h264).sum::<i32>();
}
if enabled_h265 {
score_h265 += states.iter().map(|s| s.1.score_h265).sum::<i32>();
}
if enabled_h265 && score_h265 >= score_vpx && score_h265 >= score_h264 {
*name.lock().unwrap() = best.h265.map_or(None, |c| Some(c.name));
} else if enabled_h264
&& score_h264 >= score_vpx
&& score_h264 >= score_h265
{
*name.lock().unwrap() = best.h264.map_or(None, |c| Some(c.name));
} else {
*name.lock().unwrap() = None;
}
}
} }
log::info!( log::info!(
"connection count:{}, h264:{}, h265:{}, score: vpx({}), h264({}), h265({}), set current encoder name {:?}", "connection count:{}, used preference:{:?}, encoder:{:?}",
states.len(), states.len(),
enabled_h264, preference,
enabled_h265, name.lock().unwrap()
score_vpx, )
score_h264,
score_h265,
current_encoder_name.lock().unwrap()
)
} else { } else {
*current_encoder_name.lock().unwrap() = None; *name.lock().unwrap() = None;
} }
} }
#[cfg(not(feature = "hwcodec"))] #[cfg(not(feature = "hwcodec"))]
@ -192,57 +220,57 @@ impl Encoder {
#[cfg(not(feature = "hwcodec"))] #[cfg(not(feature = "hwcodec"))]
return None; return None;
} }
}
#[cfg(feature = "hwcodec")] pub fn supported_encoding() -> (bool, bool) {
impl Drop for Decoder { #[cfg(feature = "hwcodec")]
fn drop(&mut self) { if check_hwcodec_config() {
*MY_DECODER_STATE.lock().unwrap() = VideoCodecState { let best = HwEncoder::best();
ScoreVpx: SCORE_VPX, (
..Default::default() best.h264.as_ref().map_or(false, |c| c.score > 0),
}; best.h265.as_ref().map_or(false, |c| c.score > 0),
)
} else {
(false, false)
}
#[cfg(not(feature = "hwcodec"))]
(false, false)
} }
} }
impl Decoder { impl Decoder {
pub fn video_codec_state() -> VideoCodecState { pub fn video_codec_state(_id: &str) -> VideoCodecState {
// video_codec_state is mainted by creation and destruction of Decoder.
// It has been ensured to use after Decoder's creation.
#[cfg(feature = "hwcodec")] #[cfg(feature = "hwcodec")]
if check_hwcodec_config() { if check_hwcodec_config() {
return MY_DECODER_STATE.lock().unwrap().clone(); let best = HwDecoder::best();
VideoCodecState {
score_vpx: SCORE_VPX,
score_h264: best.h264.map_or(0, |c| c.score),
score_h265: best.h265.map_or(0, |c| c.score),
perfer: Self::codec_preference(_id).into(),
..Default::default()
}
} else { } else {
return VideoCodecState { return VideoCodecState {
ScoreVpx: SCORE_VPX, score_vpx: SCORE_VPX,
..Default::default() ..Default::default()
}; };
} }
#[cfg(not(feature = "hwcodec"))] #[cfg(not(feature = "hwcodec"))]
VideoCodecState { VideoCodecState {
ScoreVpx: SCORE_VPX, score_vpx: SCORE_VPX,
..Default::default() ..Default::default()
} }
} }
pub fn new(config: DecoderCfg) -> Decoder { pub fn new(config: DecoderCfg) -> Decoder {
let vpx = VpxDecoder::new(config.vpx).unwrap(); let vpx = VpxDecoder::new(config.vpx).unwrap();
let decoder = Decoder { Decoder {
vpx, vpx,
#[cfg(feature = "hwcodec")] #[cfg(feature = "hwcodec")]
hw: HwDecoder::new_decoders(), hw: HwDecoder::new_decoders(),
#[cfg(feature = "hwcodec")] #[cfg(feature = "hwcodec")]
i420: vec![], i420: vec![],
};
#[cfg(feature = "hwcodec")]
{
let mut state = MY_DECODER_STATE.lock().unwrap();
state.ScoreVpx = SCORE_VPX;
state.ScoreH264 = decoder.hw.h264.as_ref().map_or(0, |d| d.info.score);
state.ScoreH265 = decoder.hw.h265.as_ref().map_or(0, |d| d.info.score);
} }
decoder
} }
pub fn handle_video_frame( pub fn handle_video_frame(
@ -316,6 +344,23 @@ impl Decoder {
} }
return Ok(ret); return Ok(ret);
} }
#[cfg(feature = "hwcodec")]
fn codec_preference(id: &str) -> PerferCodec {
let codec = PeerConfig::load(id)
.options
.get("codec-preference")
.map_or("".to_owned(), |c| c.to_owned());
if codec == "vp9" {
PerferCodec::VPX
} else if codec == "h264" {
PerferCodec::H264
} else if codec == "h265" {
PerferCodec::H265
} else {
PerferCodec::Auto
}
}
} }
#[cfg(feature = "hwcodec")] #[cfg(feature = "hwcodec")]

View File

@ -246,6 +246,7 @@ pub unsafe fn nv12_to_i420(
#[cfg(feature = "hwcodec")] #[cfg(feature = "hwcodec")]
pub mod hw { pub mod hw {
use hbb_common::{anyhow::anyhow, ResultType}; use hbb_common::{anyhow::anyhow, ResultType};
#[cfg(target_os = "windows")]
use hwcodec::{ffmpeg::ffmpeg_linesize_offset_length, AVPixelFormat}; use hwcodec::{ffmpeg::ffmpeg_linesize_offset_length, AVPixelFormat};
pub fn hw_bgra_to_i420( pub fn hw_bgra_to_i420(
@ -381,6 +382,8 @@ pub mod hw {
src_stride_y: usize, src_stride_y: usize,
src_stride_uv: usize, src_stride_uv: usize,
dst: &mut Vec<u8>, dst: &mut Vec<u8>,
_i420: &mut Vec<u8>,
_align: usize,
) -> ResultType<()> { ) -> ResultType<()> {
dst.resize(width * height * 4, 0); dst.resize(width * height * 4, 0);
unsafe { unsafe {

View File

@ -1,7 +1,12 @@
use crate::dxgi; use crate::{common::TraitCapturer, dxgi};
use std::io::ErrorKind::{NotFound, TimedOut, WouldBlock}; use std::{
use std::time::Duration; io::{
use std::{io, ops}; self,
ErrorKind::{NotFound, TimedOut, WouldBlock},
},
ops,
time::Duration,
};
pub struct Capturer { pub struct Capturer {
inner: dxgi::Capturer, inner: dxgi::Capturer,
@ -21,18 +26,6 @@ impl Capturer {
}) })
} }
pub fn set_use_yuv(&mut self, use_yuv: bool) {
self.inner.set_use_yuv(use_yuv);
}
pub fn is_gdi(&self) -> bool {
self.inner.is_gdi()
}
pub fn set_gdi(&mut self) -> bool {
self.inner.set_gdi()
}
pub fn cancel_gdi(&mut self) { pub fn cancel_gdi(&mut self) {
self.inner.cancel_gdi() self.inner.cancel_gdi()
} }
@ -44,14 +37,28 @@ impl Capturer {
pub fn height(&self) -> usize { pub fn height(&self) -> usize {
self.height self.height
} }
}
pub fn frame<'a>(&'a mut self, timeout: Duration) -> io::Result<Frame<'a>> { impl TraitCapturer for Capturer {
fn set_use_yuv(&mut self, use_yuv: bool) {
self.inner.set_use_yuv(use_yuv);
}
fn frame<'a>(&'a mut self, timeout: Duration) -> io::Result<Frame<'a>> {
match self.inner.frame(timeout.as_millis() as _) { match self.inner.frame(timeout.as_millis() as _) {
Ok(frame) => Ok(Frame(frame)), Ok(frame) => Ok(Frame(frame)),
Err(ref error) if error.kind() == TimedOut => Err(WouldBlock.into()), Err(ref error) if error.kind() == TimedOut => Err(WouldBlock.into()),
Err(error) => Err(error), Err(error) => Err(error),
} }
} }
fn is_gdi(&self) -> bool {
self.inner.is_gdi()
}
fn set_gdi(&mut self) -> bool {
self.inner.set_gdi()
}
} }
pub struct Frame<'a>(&'a [u8]); pub struct Frame<'a>(&'a [u8]);
@ -134,10 +141,6 @@ impl CapturerMag {
}) })
} }
pub fn set_use_yuv(&mut self, use_yuv: bool) {
self.inner.set_use_yuv(use_yuv)
}
pub fn exclude(&mut self, cls: &str, name: &str) -> io::Result<bool> { pub fn exclude(&mut self, cls: &str, name: &str) -> io::Result<bool> {
self.inner.exclude(cls, name) self.inner.exclude(cls, name)
} }
@ -145,8 +148,23 @@ impl CapturerMag {
pub fn get_rect(&self) -> ((i32, i32), usize, usize) { pub fn get_rect(&self) -> ((i32, i32), usize, usize) {
self.inner.get_rect() self.inner.get_rect()
} }
pub fn frame<'a>(&'a mut self, _timeout_ms: Duration) -> io::Result<Frame<'a>> { }
impl TraitCapturer for CapturerMag {
fn set_use_yuv(&mut self, use_yuv: bool) {
self.inner.set_use_yuv(use_yuv)
}
fn frame<'a>(&'a mut self, _timeout_ms: Duration) -> io::Result<Frame<'a>> {
self.inner.frame(&mut self.data)?; self.inner.frame(&mut self.data)?;
Ok(Frame(&self.data)) Ok(Frame(&self.data))
} }
fn is_gdi(&self) -> bool {
false
}
fn set_gdi(&mut self) -> bool {
false
}
} }

View File

@ -7,7 +7,7 @@ use hbb_common::{
config::HwCodecConfig, config::HwCodecConfig,
lazy_static, log, lazy_static, log,
message_proto::{EncodedVideoFrame, EncodedVideoFrames, Message, VideoFrame}, message_proto::{EncodedVideoFrame, EncodedVideoFrames, Message, VideoFrame},
ResultType, ResultType, bytes::Bytes,
}; };
use hwcodec::{ use hwcodec::{
decode::{DecodeContext, DecodeFrame, Decoder}, decode::{DecodeContext, DecodeFrame, Decoder},
@ -91,7 +91,7 @@ impl EncoderApi for HwEncoder {
let mut frames = Vec::new(); let mut frames = Vec::new();
for frame in self.encode(frame).with_context(|| "Failed to encode")? { for frame in self.encode(frame).with_context(|| "Failed to encode")? {
frames.push(EncodedVideoFrame { frames.push(EncodedVideoFrame {
data: frame.data, data: Bytes::from(frame.data),
pts: frame.pts as _, pts: frame.pts as _,
..Default::default() ..Default::default()
}); });
@ -123,40 +123,11 @@ impl EncoderApi for HwEncoder {
} }
impl HwEncoder { impl HwEncoder {
/// Get best encoders. pub fn best() -> CodecInfos {
/// get_config(CFG_KEY_ENCODER).unwrap_or(CodecInfos {
/// # Parameter h264: None,
/// `force_reset`: force to refresh config. h265: None,
/// `write`: write to config file. })
///
/// # Return
/// `CodecInfos`: infos.
/// `bool`: whether the config is refreshed.
pub fn best(force_reset: bool, write: bool) -> (CodecInfos, bool) {
let config = get_config(CFG_KEY_ENCODER);
if !force_reset && config.is_ok() {
(config.unwrap(), false)
} else {
let ctx = EncodeContext {
name: String::from(""),
width: 1920,
height: 1080,
pixfmt: DEFAULT_PIXFMT,
align: HW_STRIDE_ALIGN as _,
bitrate: 0,
timebase: DEFAULT_TIME_BASE,
gop: DEFAULT_GOP,
quality: DEFAULT_HW_QUALITY,
rc: DEFAULT_RC,
};
let encoders = CodecInfo::score(Encoder::avaliable_encoders(ctx));
if write {
set_config(CFG_KEY_ENCODER, &encoders)
.map_err(|e| log::error!("{:?}", e))
.ok();
}
(encoders, true)
}
} }
pub fn current_name() -> Arc<Mutex<Option<String>>> { pub fn current_name() -> Arc<Mutex<Option<String>>> {
@ -207,24 +178,15 @@ pub struct HwDecoders {
} }
impl HwDecoder { impl HwDecoder {
/// See HwEncoder::best pub fn best() -> CodecInfos {
fn best(force_reset: bool, write: bool) -> (CodecInfos, bool) { get_config(CFG_KEY_DECODER).unwrap_or(CodecInfos {
let config = get_config(CFG_KEY_DECODER); h264: None,
if !force_reset && config.is_ok() { h265: None,
(config.unwrap(), false) })
} else {
let decoders = CodecInfo::score(Decoder::avaliable_decoders());
if write {
set_config(CFG_KEY_DECODER, &decoders)
.map_err(|e| log::error!("{:?}", e))
.ok();
}
(decoders, true)
}
} }
pub fn new_decoders() -> HwDecoders { pub fn new_decoders() -> HwDecoders {
let (best, _) = HwDecoder::best(false, true); let best = HwDecoder::best();
let mut h264: Option<HwDecoder> = None; let mut h264: Option<HwDecoder> = None;
let mut h265: Option<HwDecoder> = None; let mut h265: Option<HwDecoder> = None;
let mut fail = false; let mut fail = false;
@ -242,7 +204,7 @@ impl HwDecoder {
} }
} }
if fail { if fail {
HwDecoder::best(true, true); check_config_process(true);
} }
HwDecoders { h264, h265 } HwDecoders { h264, h265 }
} }
@ -314,31 +276,52 @@ fn get_config(k: &str) -> ResultType<CodecInfos> {
} }
} }
fn set_config(k: &str, v: &CodecInfos) -> ResultType<()> {
match v.serialize() {
Ok(v) => {
let mut config = HwCodecConfig::load();
config.options.insert(k.to_owned(), v);
config.store();
Ok(())
}
Err(_) => Err(anyhow!("Failed to set config:{}", k)),
}
}
pub fn check_config() { pub fn check_config() {
let (encoders, update_encoders) = HwEncoder::best(false, false); let ctx = EncodeContext {
let (decoders, update_decoders) = HwDecoder::best(false, false); name: String::from(""),
if update_encoders || update_decoders { width: 1920,
if let Ok(encoders) = encoders.serialize() { height: 1080,
if let Ok(decoders) = decoders.serialize() { pixfmt: DEFAULT_PIXFMT,
let mut config = HwCodecConfig::load(); align: HW_STRIDE_ALIGN as _,
config.options.insert(CFG_KEY_ENCODER.to_owned(), encoders); bitrate: 0,
config.options.insert(CFG_KEY_DECODER.to_owned(), decoders); timebase: DEFAULT_TIME_BASE,
config.store(); gop: DEFAULT_GOP,
quality: DEFAULT_HW_QUALITY,
rc: DEFAULT_RC,
};
let encoders = CodecInfo::score(Encoder::avaliable_encoders(ctx));
let decoders = CodecInfo::score(Decoder::avaliable_decoders());
if let Ok(old_encoders) = get_config(CFG_KEY_ENCODER) {
if let Ok(old_decoders) = get_config(CFG_KEY_DECODER) {
if encoders == old_encoders && decoders == old_decoders {
return; return;
} }
} }
log::error!("Failed to serialize codec info");
} }
if let Ok(encoders) = encoders.serialize() {
if let Ok(decoders) = decoders.serialize() {
let mut config = HwCodecConfig::load();
config.options.insert(CFG_KEY_ENCODER.to_owned(), encoders);
config.options.insert(CFG_KEY_DECODER.to_owned(), decoders);
config.store();
return;
}
}
log::error!("Failed to serialize codec info");
}
pub fn check_config_process(force_reset: bool) {
if force_reset {
HwCodecConfig::remove();
}
if let Ok(exe) = std::env::current_exe() {
std::thread::spawn(move || {
std::process::Command::new(exe)
.arg("--check-hwcodec-config")
.status()
.ok()
});
};
} }

View File

@ -1,6 +1,7 @@
use crate::common::{ use crate::common::{
wayland, wayland,
x11::{self, Frame}, x11::{self, Frame},
TraitCapturer,
}; };
use std::{io, time::Duration}; use std::{io, time::Duration};
@ -17,13 +18,6 @@ impl Capturer {
}) })
} }
pub fn set_use_yuv(&mut self, use_yuv: bool) {
match self {
Capturer::X11(d) => d.set_use_yuv(use_yuv),
Capturer::WAYLAND(d) => d.set_use_yuv(use_yuv),
}
}
pub fn width(&self) -> usize { pub fn width(&self) -> usize {
match self { match self {
Capturer::X11(d) => d.width(), Capturer::X11(d) => d.width(),
@ -37,8 +31,17 @@ impl Capturer {
Capturer::WAYLAND(d) => d.height(), Capturer::WAYLAND(d) => d.height(),
} }
} }
}
pub fn frame<'a>(&'a mut self, timeout: Duration) -> io::Result<Frame<'a>> { impl TraitCapturer for Capturer {
fn set_use_yuv(&mut self, use_yuv: bool) {
match self {
Capturer::X11(d) => d.set_use_yuv(use_yuv),
Capturer::WAYLAND(d) => d.set_use_yuv(use_yuv),
}
}
fn frame<'a>(&'a mut self, timeout: Duration) -> io::Result<Frame<'a>> {
match self { match self {
Capturer::X11(d) => d.frame(timeout), Capturer::X11(d) => d.frame(timeout),
Capturer::WAYLAND(d) => d.frame(timeout), Capturer::WAYLAND(d) => d.frame(timeout),

View File

@ -49,3 +49,13 @@ pub fn would_block_if_equal(old: &mut Vec<u128>, b: &[u8]) -> std::io::Result<()
old.copy_from_slice(b); old.copy_from_slice(b);
Ok(()) Ok(())
} }
pub trait TraitCapturer {
fn set_use_yuv(&mut self, use_yuv: bool);
fn frame<'a>(&'a mut self, timeout: std::time::Duration) -> std::io::Result<Frame<'a>>;
#[cfg(windows)]
fn is_gdi(&self) -> bool;
#[cfg(windows)]
fn set_gdi(&mut self) -> bool;
}

View File

@ -50,8 +50,14 @@ impl Capturer {
pub fn height(&self) -> usize { pub fn height(&self) -> usize {
self.inner.height() self.inner.height()
} }
}
pub fn frame<'a>(&'a mut self, _timeout_ms: std::time::Duration) -> io::Result<Frame<'a>> { impl crate::TraitCapturer for Capturer {
fn set_use_yuv(&mut self, use_yuv: bool) {
self.use_yuv = use_yuv;
}
fn frame<'a>(&'a mut self, _timeout_ms: std::time::Duration) -> io::Result<Frame<'a>> {
match self.frame.try_lock() { match self.frame.try_lock() {
Ok(mut handle) => { Ok(mut handle) => {
let mut frame = None; let mut frame = None;

View File

@ -12,6 +12,7 @@ use crate::STRIDE_ALIGN;
use super::vpx::{vp8e_enc_control_id::*, vpx_codec_err_t::*, *}; use super::vpx::{vp8e_enc_control_id::*, vpx_codec_err_t::*, *};
use std::os::raw::{c_int, c_uint}; use std::os::raw::{c_int, c_uint};
use std::{ptr, slice}; use std::{ptr, slice};
use hbb_common::bytes::Bytes;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum VpxVideoCodecId { pub enum VpxVideoCodecId {
@ -291,7 +292,7 @@ impl VpxEncoder {
#[inline] #[inline]
fn create_frame(frame: &EncodeFrame) -> EncodedVideoFrame { fn create_frame(frame: &EncodeFrame) -> EncodedVideoFrame {
EncodedVideoFrame { EncodedVideoFrame {
data: frame.data.to_vec(), data: Bytes::from(frame.data.to_vec()),
key: frame.key, key: frame.key,
pts: frame.pts, pts: frame.pts,
..Default::default() ..Default::default()

View File

@ -1,4 +1,4 @@
use crate::common::x11::Frame; use crate::common::{x11::Frame, TraitCapturer};
use crate::wayland::{capturable::*, *}; use crate::wayland::{capturable::*, *};
use std::{io, time::Duration}; use std::{io, time::Duration};
@ -14,10 +14,6 @@ impl Capturer {
Ok(Capturer(display, r, yuv, Default::default())) Ok(Capturer(display, r, yuv, Default::default()))
} }
pub fn set_use_yuv(&mut self, use_yuv: bool) {
self.2 = use_yuv;
}
pub fn width(&self) -> usize { pub fn width(&self) -> usize {
self.0.width() self.0.width()
} }
@ -25,8 +21,14 @@ impl Capturer {
pub fn height(&self) -> usize { pub fn height(&self) -> usize {
self.0.height() self.0.height()
} }
}
pub fn frame<'a>(&'a mut self, timeout: Duration) -> io::Result<Frame<'a>> { impl TraitCapturer for Capturer {
fn set_use_yuv(&mut self, use_yuv: bool) {
self.2 = use_yuv;
}
fn frame<'a>(&'a mut self, timeout: Duration) -> io::Result<Frame<'a>> {
match self.1.capture(timeout.as_millis() as _).map_err(map_err)? { match self.1.capture(timeout.as_millis() as _).map_err(map_err)? {
PixelProvider::BGR0(w, h, x) => Ok(Frame(if self.2 { PixelProvider::BGR0(w, h, x) => Ok(Frame(if self.2 {
crate::common::bgra_to_i420(w as _, h as _, &x, &mut self.3); crate::common::bgra_to_i420(w as _, h as _, &x, &mut self.3);

View File

@ -1,4 +1,4 @@
use crate::x11; use crate::{x11, common::TraitCapturer};
use std::{io, ops, time::Duration}; use std::{io, ops, time::Duration};
pub struct Capturer(x11::Capturer); pub struct Capturer(x11::Capturer);
@ -8,10 +8,6 @@ impl Capturer {
x11::Capturer::new(display.0, yuv).map(Capturer) x11::Capturer::new(display.0, yuv).map(Capturer)
} }
pub fn set_use_yuv(&mut self, use_yuv: bool) {
self.0.set_use_yuv(use_yuv);
}
pub fn width(&self) -> usize { pub fn width(&self) -> usize {
self.0.display().rect().w as usize self.0.display().rect().w as usize
} }
@ -19,8 +15,14 @@ impl Capturer {
pub fn height(&self) -> usize { pub fn height(&self) -> usize {
self.0.display().rect().h as usize self.0.display().rect().h as usize
} }
}
pub fn frame<'a>(&'a mut self, _timeout: Duration) -> io::Result<Frame<'a>> { impl TraitCapturer for Capturer {
fn set_use_yuv(&mut self, use_yuv: bool) {
self.0.set_use_yuv(use_yuv);
}
fn frame<'a>(&'a mut self, _timeout: Duration) -> io::Result<Frame<'a>> {
Ok(Frame(self.0.frame()?)) Ok(Frame(self.0.frame()?))
} }
} }

View File

@ -216,7 +216,7 @@ impl Client {
} else { } else {
peer_nat_type = ph.nat_type(); peer_nat_type = ph.nat_type();
is_local = ph.is_local(); is_local = ph.is_local();
signed_id_pk = ph.pk; signed_id_pk = ph.pk.into();
relay_server = ph.relay_server; relay_server = ph.relay_server;
peer_addr = AddrMangle::decode(&ph.socket_addr); peer_addr = AddrMangle::decode(&ph.socket_addr);
log::info!("Hole Punched {} = {}", peer, peer_addr); log::info!("Hole Punched {} = {}", peer, peer_addr);
@ -408,8 +408,8 @@ impl Client {
let sealed_key = box_::seal(&key.0, &nonce, &their_pk_b, &out_sk_b); let sealed_key = box_::seal(&key.0, &nonce, &their_pk_b, &out_sk_b);
let mut msg_out = Message::new(); let mut msg_out = Message::new();
msg_out.set_public_key(PublicKey { msg_out.set_public_key(PublicKey {
asymmetric_value: our_pk_b.0.into(), asymmetric_value: Vec::from(our_pk_b.0).into(),
symmetric_value: sealed_key, symmetric_value: sealed_key.into(),
..Default::default() ..Default::default()
}); });
timeout(CONNECT_TIMEOUT, conn.send(&msg_out)).await??; timeout(CONNECT_TIMEOUT, conn.send(&msg_out)).await??;
@ -784,6 +784,7 @@ pub struct LoginConfigHandler {
pub conn_id: i32, pub conn_id: i32,
features: Option<Features>, features: Option<Features>,
session_id: u64, session_id: u64,
pub supported_encoding: Option<(bool, bool)>,
} }
impl Deref for LoginConfigHandler { impl Deref for LoginConfigHandler {
@ -808,6 +809,7 @@ impl LoginConfigHandler {
self.remember = !config.password.is_empty(); self.remember = !config.password.is_empty();
self.config = config; self.config = config;
self.session_id = rand::random(); self.session_id = rand::random();
self.supported_encoding = None;
} }
pub fn should_auto_login(&self) -> String { pub fn should_auto_login(&self) -> String {
@ -958,8 +960,7 @@ impl LoginConfigHandler {
msg.disable_clipboard = BoolOption::Yes.into(); msg.disable_clipboard = BoolOption::Yes.into();
n += 1; n += 1;
} }
// TODO: add option let state = Decoder::video_codec_state(&self.id);
let state = Decoder::video_codec_state();
msg.video_codec_state = hbb_common::protobuf::MessageField::some(state); msg.video_codec_state = hbb_common::protobuf::MessageField::some(state);
n += 1; n += 1;
@ -1111,6 +1112,10 @@ impl LoginConfigHandler {
self.conn_id = pi.conn_id; self.conn_id = pi.conn_id;
// no matter if change, for update file time // no matter if change, for update file time
self.save_config(config); self.save_config(config);
#[cfg(feature = "hwcodec")]
{
self.supported_encoding = Some((pi.encoding.h264, pi.encoding.h265));
}
} }
pub fn get_remote_dir(&self) -> String { pub fn get_remote_dir(&self) -> String {
@ -1139,7 +1144,7 @@ impl LoginConfigHandler {
let my_id = Config::get_id(); let my_id = Config::get_id();
let mut lr = LoginRequest { let mut lr = LoginRequest {
username: self.id.clone(), username: self.id.clone(),
password, password:password.into(),
my_id, my_id,
my_name: crate::username(), my_name: crate::username(),
option: self.get_option_message(true).into(), option: self.get_option_message(true).into(),
@ -1163,6 +1168,18 @@ impl LoginConfigHandler {
msg_out.set_login_request(lr); msg_out.set_login_request(lr);
msg_out msg_out
} }
pub fn change_prefer_codec(&self) -> Message {
let state = scrap::codec::Decoder::video_codec_state(&self.id);
let mut misc = Misc::new();
misc.set_option(OptionMessage {
video_codec_state: hbb_common::protobuf::MessageField::some(state),
..Default::default()
});
let mut msg_out = Message::new();
msg_out.set_misc(misc);
msg_out
}
} }
pub enum MediaData { pub enum MediaData {

View File

@ -67,7 +67,7 @@ pub fn clip_2_msg(clip: ClipbaordFile) -> Message {
CliprdrServerFormatDataResponse { CliprdrServerFormatDataResponse {
conn_id, conn_id,
msg_flags, msg_flags,
format_data, format_data: format_data.into(),
..Default::default() ..Default::default()
}, },
)), )),
@ -117,7 +117,7 @@ pub fn clip_2_msg(clip: ClipbaordFile) -> Message {
conn_id, conn_id,
msg_flags, msg_flags,
stream_id, stream_id,
requested_data, requested_data: requested_data.into(),
..Default::default() ..Default::default()
}, },
)), )),
@ -156,7 +156,7 @@ pub fn msg_2_clip(msg: Cliprdr) -> Option<ClipbaordFile> {
Some(ClipbaordFile::ServerFormatDataResponse { Some(ClipbaordFile::ServerFormatDataResponse {
conn_id: data.conn_id, conn_id: data.conn_id,
msg_flags: data.msg_flags, msg_flags: data.msg_flags,
format_data: data.format_data, format_data: data.format_data.into(),
}) })
} }
Some(cliprdr::Union::FileContentsRequest(data)) => { Some(cliprdr::Union::FileContentsRequest(data)) => {
@ -177,7 +177,7 @@ pub fn msg_2_clip(msg: Cliprdr) -> Option<ClipbaordFile> {
conn_id: data.conn_id, conn_id: data.conn_id,
msg_flags: data.msg_flags, msg_flags: data.msg_flags,
stream_id: data.stream_id, stream_id: data.stream_id,
requested_data: data.requested_data, requested_data: data.requested_data.into(),
}) })
} }
_ => None, _ => None,

View File

@ -48,7 +48,7 @@ pub fn create_clipboard_msg(content: String) -> Message {
let mut msg = Message::new(); let mut msg = Message::new();
msg.set_clipboard(Clipboard { msg.set_clipboard(Clipboard {
compress, compress,
content, content:content.into(),
..Default::default() ..Default::default()
}); });
msg msg
@ -79,7 +79,7 @@ pub fn update_clipboard(clipboard: Clipboard, old: Option<&Arc<Mutex<String>>>)
let content = if clipboard.compress { let content = if clipboard.compress {
decompress(&clipboard.content) decompress(&clipboard.content)
} else { } else {
clipboard.content clipboard.content.into()
}; };
if let Ok(content) = String::from_utf8(content) { if let Ok(content) = String::from_utf8(content) {
if content.is_empty() { if content.is_empty() {

View File

@ -19,6 +19,7 @@ use parity_tokio_ipc::{
}; };
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use std::{collections::HashMap, sync::atomic::Ordering}; use std::{collections::HashMap, sync::atomic::Ordering};
use bytes::Bytes;
#[cfg(not(windows))] #[cfg(not(windows))]
use std::{fs::File, io::prelude::*}; use std::{fs::File, io::prelude::*};
@ -75,7 +76,7 @@ pub enum FS {
WriteBlock { WriteBlock {
id: i32, id: i32,
file_num: i32, file_num: i32,
data: Vec<u8>, data: Bytes,
compressed: bool, compressed: bool,
}, },
WriteDone { WriteDone {
@ -562,8 +563,8 @@ where
} }
} }
pub async fn send_raw(&mut self, data: Vec<u8>) -> ResultType<()> { pub async fn send_raw(&mut self, data: Bytes) -> ResultType<()> {
self.inner.send(bytes::Bytes::from(data)).await?; self.inner.send(data).await?;
Ok(()) Ok(())
} }

View File

@ -6,14 +6,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("desk_tip", "Mit dieser ID und diesem Passwort können Sie auf Ihren Desktop zugreifen."), ("desk_tip", "Mit dieser ID und diesem Passwort können Sie auf Ihren Desktop zugreifen."),
("Password", "Passwort"), ("Password", "Passwort"),
("Ready", "Bereit"), ("Ready", "Bereit"),
("Established", "Etabliert"), ("Established", "Verbunden"),
("connecting_status", "Verbinden mit dem RustDesk-Netzwerk..."), ("connecting_status", "Verbinden mit dem RustDesk-Netzwerk..."),
("Enable Service", "Verbindungsserver einschalten"), ("Enable Service", "Verbindungsserver aktivieren"),
("Start Service", "Starte Verbindungsserver"), ("Start Service", "Starte Vermittlungsdienst"),
("Service is running", "Dienst läuft"), ("Service is running", "Vermittlungsdienst aktiv"),
("Service is not running", "Der Verbindungsserver läuft nicht"), ("Service is not running", "Vermittlungsdienst deaktiviert"),
("not_ready_status", "Nicht bereit. Bitte überprüfen Sie Ihre Verbindung"), ("not_ready_status", "Nicht bereit. Bitte überprüfen Sie Ihre Verbindung"),
("Control Remote Desktop", "Entfernten Desktop steuern"), ("Control Remote Desktop", "Entfernten PC steuern"),
("Transfer File", "Datei übertragen"), ("Transfer File", "Datei übertragen"),
("Connect", "Verbinden"), ("Connect", "Verbinden"),
("Recent Sessions", "Letzte Sitzungen"), ("Recent Sessions", "Letzte Sitzungen"),
@ -21,49 +21,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Confirmation", "Bestätigung"), ("Confirmation", "Bestätigung"),
("TCP Tunneling", "TCP Tunneln"), ("TCP Tunneling", "TCP Tunneln"),
("Remove", "Entfernen"), ("Remove", "Entfernen"),
("Refresh random password", "Zufälliges Passwort aktualisieren"), ("Refresh random password", "Neues zufälliges Passwort"),
("Set your own password", "Legen Sie Ihr eigenes Passwort fest"), ("Set your own password", "Eigenes Passwort setzen"),
("Enable Keyboard/Mouse", "Tastatur/Maus einschalten"), ("Enable Keyboard/Mouse", "Tastatur/Maus aktivieren"),
("Enable Clipboard", "Zwischenablage einschalten"), ("Enable Clipboard", "Zwischenablage aktivieren"),
("Enable File Transfer", "Dateiübertragung aktivieren"), ("Enable File Transfer", "Dateiübertragung aktivieren"),
("Enable TCP Tunneling", "TCP-Tunneling einschalten"), ("Enable TCP Tunneling", "TCP-Tunneln aktivieren"),
("IP Whitelisting", "IP Freigabeliste"), ("IP Whitelisting", "IP-Whitelist"),
("ID/Relay Server", "ID/Verbindungsserver"), ("ID/Relay Server", "ID/Vermittlungsserver"),
("Stop service", "Verbindungsserver ausschalten"), ("Stop service", "Vermittlungsdienst deaktivieren"),
("Change ID", "ID wechseln"), ("Change ID", "ID ändern"),
("Website", "Webseite"), ("Website", "Webseite"),
("About", "Über"), ("About", "Über"),
("Mute", "Stummschalten"), ("Mute", "Stummschalten"),
("Audio Input", "Audio-Eingang"), ("Audio Input", "Audio-Eingang"),
("Enhancements", ""), ("Enhancements", "Verbesserungen"),
("Hardware Codec", ""), ("Hardware Codec", "Hardware-Codec"),
("Adaptive Bitrate", ""), ("Adaptive Bitrate", "Adaptive Bitrate"),
("ID Server", "ID Server"), ("ID Server", "ID Server"),
("Relay Server", "Verbindungsserver Server"), ("Relay Server", "Vermittlungsserver"),
("API Server", "API Server"), ("API Server", "API-Server"),
("invalid_http", "Muss mit http:// oder https:// beginnen"), ("invalid_http", "Muss mit http:// oder https:// beginnen"),
("Invalid IP", "Ungültige IP-Adresse"), ("Invalid IP", "Ungültige IP-Adresse"),
("id_change_tip", "Nur die Zeichen a-z, A-Z, 0-9 und _ (Unterstrich) sind erlaubt. Der erste Buchstabe muss a-z, A-Z sein. Länge zwischen 6 und 16."), ("id_change_tip", "Nur die Zeichen a-z, A-Z, 0-9 und _ (Unterstrich) sind erlaubt. Der erste Buchstabe muss a-z, A-Z sein, Länge zwischen 6 und 16."),
("Invalid format", "Ungültiges Format"), ("Invalid format", "Ungültiges Format"),
("server_not_support", "Noch nicht vom Server unterstützt"), ("server_not_support", "Diese Funktion wird noch nicht vom Server unterstützt"),
("Not available", "Nicht verfügbar"), ("Not available", "Nicht verfügbar"),
("Too frequent", "Zu häufig"), ("Too frequent", "Zu häufig"),
("Cancel", "Abbrechen"), ("Cancel", "Abbrechen"),
("Skip", "Überspringen"), ("Skip", "Überspringen"),
("Close", "Schließen"), ("Close", "Schließen"),
("Retry", "Nochmal versuchen"), ("Retry", "Erneut versuchen"),
("OK", "OK"), ("OK", "OK"),
("Password Required", "Passwort erforderlich"), ("Password Required", "Passwort erforderlich"),
("Please enter your password", "Bitte geben Sie Ihr Passwort ein"), ("Please enter your password", "Bitte geben Sie das Passwort des entfernten PCs ein."),
("Remember password", "Passwort merken"), ("Remember password", "Passwort merken"),
("Wrong Password", "Falsches Passwort"), ("Wrong Password", "Falsches Passwort"),
("Do you want to enter again?", "Möchten Sie erneut teilnehmen?"), ("Do you want to enter again?", "Erneut verbinden?"),
("Connection Error", "Verbindungsfehler"), ("Connection Error", "Verbindungsfehler"),
("Error", "Fehler"), ("Error", "Fehler"),
("Reset by the peer", "Zurücksetzen durch die Gegenstelle"), ("Reset by the peer", "Verbindung wurde von der Gegenstelle zurückgesetzt"),
("Connecting...", "Verbinden..."), ("Connecting...", "Verbinden..."),
("Connection in progress. Please wait.", "Die Verbindung wird hergestellt. Bitte warten Sie."), ("Connection in progress. Please wait.", "Die Verbindung wird hergestellt. Bitte warten Sie."),
("Please try 1 minute later", "Bitte versuchen Sie es 1 Minute später"), ("Please try 1 minute later", "Bitte versuchen Sie es später erneut"),
("Login Error", "Anmeldefehler"), ("Login Error", "Anmeldefehler"),
("Successful", "Erfolgreich"), ("Successful", "Erfolgreich"),
("Connected, waiting for image...", "Verbunden, warten auf Bild..."), ("Connected, waiting for image...", "Verbunden, warten auf Bild..."),
@ -75,92 +75,92 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Receive", "Empfangen"), ("Receive", "Empfangen"),
("Send", "Senden"), ("Send", "Senden"),
("Refresh File", "Datei aktualisieren"), ("Refresh File", "Datei aktualisieren"),
("Local", "Lokaler"), ("Local", "Lokal"),
("Remote", "Entfernter"), ("Remote", "Entfernt"),
("Remote Computer", "Entfernter Computer"), ("Remote Computer", "Entfernter Computer"),
("Local Computer", "Lokaler Computer"), ("Local Computer", "Dieser Computer"),
("Confirm Delete", "Löschen bestätigen"), ("Confirm Delete", "Löschen bestätigen"),
("Delete", "Löschen"), ("Delete", "Löschen"),
("Properties", "Eigenschaften"), ("Properties", "Eigenschaften"),
("Multi Select", "Mehrfachauswahl"), ("Multi Select", "Mehrfachauswahl"),
("Empty Directory", "Leeres Verzeichnis"), ("Empty Directory", "Leerer Ordner"),
("Not an empty directory", "Kein leeres Verzeichnis"), ("Not an empty directory", "Ordner nicht leer"),
("Are you sure you want to delete this file?", "Sind Sie sicher, dass Sie diese Datei löschen wollen?"), ("Are you sure you want to delete this file?", "Sind Sie sicher, dass Sie diese Datei löschen wollen?"),
("Are you sure you want to delete this empty directory?", "Sind Sie sicher, dass Sie dieses leere Verzeichnis löschen möchten?"), ("Are you sure you want to delete this empty directory?", "Sind Sie sicher, dass Sie diesen leeren Ordner löschen möchten?"),
("Are you sure you want to delete the file of this directory?", "Sind Sie sicher, dass Sie die Datei dieses Verzeichnisses löschen möchten?"), ("Are you sure you want to delete the file of this directory?", "Sind Sie sicher, dass Sie die Datei dieses Ordners löschen möchten?"),
("Do this for all conflicts", "Dies gilt für alle Konflikte"), ("Do this for all conflicts", "Für alle Konflikte merken"),
("This is irreversible!", "Dies ist irreversibel!"), ("This is irreversible!", "Dies ist irreversibel!"),
("Deleting", "Löschen"), ("Deleting", "Löschen"),
("files", "Dateien"), ("files", "Dateien"),
("Waiting", "Warten"), ("Waiting", "Warten"),
("Finished", "Fertiggestellt"), ("Finished", "Fertiggestellt"),
("Speed", "Geschwindigkeit"), ("Speed", "Geschwindigkeit"),
("Custom Image Quality", "Individuelle Bildqualität"), ("Custom Image Quality", "Benutzerdefinierte Bildqualität"),
("Privacy mode", "Datenschutz-Modus"), ("Privacy mode", "Datenschutz-Modus"),
("Block user input", "Benutzereingaben blockieren"), ("Block user input", "Benutzereingaben blockieren"),
("Unblock user input", "Benutzereingaben freigeben"), ("Unblock user input", "Benutzereingaben freigeben"),
("Adjust Window", "Fenster anpassen"), ("Adjust Window", "Fenster anpassen"),
("Original", "Original"), ("Original", "Original"),
("Shrink", "Geschrumpft"), ("Shrink", "Verkleinern"),
("Stretch", "Gestreckt"), ("Stretch", "Strecken"),
("Good image quality", "Gute Bildqualität"), ("Good image quality", "Schöner"),
("Balanced", "Ausgeglichen"), ("Balanced", "Ausgeglichen"),
("Optimize reaction time", "Optimierte Reaktionszeit"), ("Optimize reaction time", "Schneller"),
("Custom", "Benutzerdefiniert"), ("Custom", "Benutzerdefiniert"),
("Show remote cursor", "Ferngesteuerten Cursor anzeigen"), ("Show remote cursor", "Entfernten Cursor anzeigen"),
("Show quality monitor", ""), ("Show quality monitor", "Qualitätsüberwachung anzeigen"),
("Disable clipboard", "Zwischenablage deaktivieren"), ("Disable clipboard", "Zwischenablage deaktivieren"),
("Lock after session end", "Sperren nach Sitzungsende"), ("Lock after session end", "Sperren nach Sitzungsende"),
("Insert", "Einfügen"), ("Insert", "Einfügen"),
("Insert Lock", "Sperre einfügen"), ("Insert Lock", "Win+L (Sperren) senden"),
("Refresh", "Aktualisieren"), ("Refresh", "Aktualisieren"),
("ID does not exist", "Die ID existiert nicht"), ("ID does not exist", "Diese ID existiert nicht"),
("Failed to connect to rendezvous server", "Verbindung zum Verbindungsserver fehlgeschlagen"), ("Failed to connect to rendezvous server", "Verbindung zum Vermittlungsserver fehlgeschlagen"),
("Please try later", "Bitte versuchen Sie es später"), ("Please try later", "Bitte versuchen Sie es später erneut"),
("Remote desktop is offline", "Entfernter Desktop ist offline"), ("Remote desktop is offline", "Entfernter PC ist offline"),
("Key mismatch", "Schlüssel nicht übereinstimmend"), ("Key mismatch", "Schlüssel stimmt nicht mit Serverschlüssel überein"),
("Timeout", "Zeitüberschreitung"), ("Timeout", "Zeitüberschreitung"),
("Failed to connect to relay server", "Verbindung zum Verbindungsserver fehlgeschlagen"), ("Failed to connect to relay server", "Verbindung zum Vermittlungsserver fehlgeschlagen"),
("Failed to connect via rendezvous server", "Verbindung über rendezvous server fehlgeschlagen"), ("Failed to connect via rendezvous server", "Verbindung über Vermittlungsserver ist fehlgeschlagen"),
("Failed to connect via relay server", "Verbindung über den Verbindungsserver ist fehlgeschlagen"), ("Failed to connect via relay server", "Verbindung über Relay-Server ist fehlgeschlagen"),
("Failed to make direct connection to remote desktop", "Direkte Verbindung zum Entfernten-Desktop konnte nicht hergestellt werden"), ("Failed to make direct connection to remote desktop", "Direkte Verbindung zum entfernten PC fehlgeschlagen"),
("Set Password", "Passwort festlegen"), ("Set Password", "Passwort festlegen"),
("OS Password", "Betriebssystem-Passwort"), ("OS Password", "Betriebssystem-Passwort"),
("install_tip", "Aufgrund der UAC kann RustDesk in manchen Fällen nicht ordnungsgemäß auf der Gegenseite funktionieren. Um UAC zu vermeiden, klicken Sie bitte auf die Schaltfläche unten, um RustDesk auf dem System zu installieren"), ("install_tip", "Aufgrund der UAC kann RustDesk in manchen Fällen nicht ordnungsgemäß auf der Gegenseite funktionieren. Um UAC zu vermeiden, klicken Sie bitte auf die Schaltfläche unten, um RustDesk auf dem System zu installieren"),
("Click to upgrade", "Zum Upgrade anklicken"), ("Click to upgrade", "Zum Aktualisieren anklicken"),
("Click to download", "Zum Herunterladen klicken"), ("Click to download", "Zum Herunterladen klicken"),
("Click to update", "Zum Aktualisieren klicken"), ("Click to update", "Zum Aktualisieren klicken"),
("Configure", "Konfigurieren"), ("Configure", "Konfigurieren"),
("config_acc", "Um Ihren Desktop aus der Ferne zu steuern, müssen Sie RustDesk \"Zugangs\" Rechte erteilen."), ("config_acc", "Um Ihren PC aus der Ferne zu steuern, müssen Sie RustDesk Zugriffsrechte erteilen."),
("config_screen", "Um aus der Ferne auf Ihren Desktop zugreifen zu können, müssen Sie RustDesk \"Bildschirm-Aufnahme\" Berechtigungen erteilen."), ("config_screen", "Um aus der Ferne auf Ihren PC zugreifen zu können, müssen Sie RustDesk \"Bildschirm-Aufnahme\"-Berechtigung erteilen."),
("Installing ...", "Installiere ..."), ("Installing ...", "Installiere ..."),
("Install", "Installieren"), ("Install", "Installieren"),
("Installation", "Einrichtung"), ("Installation", "Installation"),
("Installation Path", "Einrichtungs Pfad"), ("Installation Path", "Installationspfad"),
("Create start menu shortcuts", "Startmenü Verknüpfungen erstellen"), ("Create start menu shortcuts", "Verknüpfung im Startmenü erstellen"),
("Create desktop icon", "Desktop Symbol erstellen"), ("Create desktop icon", "Desktop-Verknüpfung erstellen"),
("agreement_tip", "Wenn Sie die Einrichtung starten, akzeptieren Sie die Lizenzvereinbarung"), ("agreement_tip", "Durch die Installation akzeptieren Sie die Lizenzvereinbarung"),
("Accept and Install", "Akzeptieren und installieren"), ("Accept and Install", "Akzeptieren und Installieren"),
("End-user license agreement", "Lizenzvereinbarung für Endbenutzer"), ("End-user license agreement", "Lizenzvereinbarung für Endbenutzer"),
("Generating ...", "Generierung ..."), ("Generating ...", "Generiere..."),
("Your installation is lower version.", "Ihre Installation ist eine niedrigere Version."), ("Your installation is lower version.", "Ihre Installation ist älter."),
("not_close_tcp_tip", "Schließen Sie dieses Fenster nicht, während Sie den Tunnel benutzen."), ("not_close_tcp_tip", "Schließen Sie dieses Fenster nicht, solange Sie den Tunnel benutzen."),
("Listening ...", "Hören ..."), ("Listening ...", "Höre..."),
("Remote Host", "Entfernter Rechner"), ("Remote Host", "Entfernter PC"),
("Remote Port", "Entfernter Port"), ("Remote Port", "Entfernter Port"),
("Action", "Aktion"), ("Action", "Aktion"),
("Add", "Hinzufügen"), ("Add", "Hinzufügen"),
("Local Port", "Lokaler Port"), ("Local Port", "Lokaler Port"),
("setup_server_tip", "Für eine schnellere Verbindung, richten Sie bitte Ihren eigenen Verbindungsserver ein"), ("setup_server_tip", "Für eine schnellere Verbindung richten Sie bitte Ihren eigenen Verbindungsserver ein"),
("Too short, at least 6 characters.", "Zu kurz, mindestens 6 Zeichen."), ("Too short, at least 6 characters.", "Zu kurz, mindestens 6 Zeichen."),
("The confirmation is not identical.", "Die Bestätigung ist nicht identisch."), ("The confirmation is not identical.", "Die Passwörter sind nicht identisch."),
("Permissions", "Berechtigungen"), ("Permissions", "Berechtigungen"),
("Accept", "Akzeptieren"), ("Accept", "Akzeptieren"),
("Dismiss", "Ablehnen"), ("Dismiss", "Ablehnen"),
("Disconnect", "Verbindung trennen"), ("Disconnect", "Verbindung trennen"),
("Allow using keyboard and mouse", "Erlaubt die Verwendung von Tastatur und Maus"), ("Allow using keyboard and mouse", "Verwendung von Maus und Tastatur zulassen"),
("Allow using clipboard", "Verwendung der Zwischenablage zulassen"), ("Allow using clipboard", "Verwendung der Zwischenablage zulassen"),
("Allow hearing sound", "Erlaubt das Hören von Sound"), ("Allow hearing sound", "System-Audio übertragen"),
("Allow file copy and paste", "Kopieren und Einfügen von Dateien zulassen"), ("Allow file copy and paste", "Kopieren und Einfügen von Dateien zulassen"),
("Connected", "Verbunden"), ("Connected", "Verbunden"),
("Direct and encrypted connection", "Direkte und verschlüsselte Verbindung"), ("Direct and encrypted connection", "Direkte und verschlüsselte Verbindung"),
@ -171,17 +171,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enter your password", "Geben Sie Ihr Passwort ein"), ("Enter your password", "Geben Sie Ihr Passwort ein"),
("Logging in...", "Anmeldung..."), ("Logging in...", "Anmeldung..."),
("Enable RDP session sharing", "RDP-Sitzungsfreigabe aktivieren"), ("Enable RDP session sharing", "RDP-Sitzungsfreigabe aktivieren"),
("Auto Login", "Automatisches Login (nur gültig, wenn Sie \"Sperren nach Sitzungsende\" eingestellt haben)"), ("Auto Login", "Automatisch anmelden (nur gültig, wenn Sie \"Sperren nach Sitzungsende\" aktiviert haben)"),
("Enable Direct IP Access", "Direkten IP-Zugang aktivieren"), ("Enable Direct IP Access", "Direkten IP-Zugang aktivieren"),
("Rename", "Umbenennen"), ("Rename", "Umbenennen"),
("Space", "Platz"), ("Space", "Speicherplatz"),
("Create Desktop Shortcut", "Desktop-Verknüpfung erstellen"), ("Create Desktop Shortcut", "Desktop-Verknüpfung erstellen"),
("Change Path", "Pfad ändern"), ("Change Path", "Pfad ändern"),
("Create Folder", "Ordner erstellen"), ("Create Folder", "Ordner erstellen"),
("Please enter the folder name", "Bitte geben Sie den Ordnernamen ein"), ("Please enter the folder name", "Bitte geben Sie den Ordnernamen ein"),
("Fix it", "Reparieren"), ("Fix it", "Reparieren"),
("Warning", "Warnung"), ("Warning", "Warnung"),
("Login screen using Wayland is not supported", "Anmeldebildschirm mit Wayland wird nicht unterstützt"), ("Login screen using Wayland is not supported", "Anmeldebildschirm wird mit Wayland nicht unterstützt"),
("Reboot required", "Neustart erforderlich"), ("Reboot required", "Neustart erforderlich"),
("Unsupported display server ", "Nicht unterstützter Display-Server"), ("Unsupported display server ", "Nicht unterstützter Display-Server"),
("x11 expected", "X11 erwartet"), ("x11 expected", "X11 erwartet"),
@ -189,14 +189,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Settings", "Einstellungen"), ("Settings", "Einstellungen"),
("Username", " Benutzername"), ("Username", " Benutzername"),
("Invalid port", "Ungültiger Port"), ("Invalid port", "Ungültiger Port"),
("Closed manually by the peer", "Vom Peer manuell geschlossen"), ("Closed manually by the peer", "Von der Gegenstelle manuell geschlossen"),
("Enable remote configuration modification", "Änderung der Fernkonfiguration zulassen"), ("Enable remote configuration modification", "Änderung der Konfiguration aus der Ferne zulassen"),
("Run without install", "Ohne Installation ausführen"), ("Run without install", "Ohne Installation ausführen"),
("Always connected via relay", "Immer über Verbindungsserver verbunden"), ("Always connected via relay", "Immer über Relay-Server verbunden"),
("Always connect via relay", "Verbindung immer über Verbindungsserver"), ("Always connect via relay", "Immer über Relay-Server verbinden"),
("whitelist_tip", "Nur IPs auf der Freigabeliste können auf mich zugreifen"), ("whitelist_tip", "Nur IPs auf der Whitelist können zugreifen"),
("Login", "Anmeldung"), ("Login", "Anmelden"),
("Logout", "Abmeldung"), ("Logout", "Abmelden"),
("Tags", "Stichworte"), ("Tags", "Stichworte"),
("Search ID", "Suche ID"), ("Search ID", "Suche ID"),
("Current Wayland display server is not supported", "Der aktuelle Wayland-Anzeigeserver wird nicht unterstützt"), ("Current Wayland display server is not supported", "Der aktuelle Wayland-Anzeigeserver wird nicht unterstützt"),
@ -205,100 +205,100 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add Tag", "Stichwort hinzufügen"), ("Add Tag", "Stichwort hinzufügen"),
("Unselect all tags", "Alle Stichworte abwählen"), ("Unselect all tags", "Alle Stichworte abwählen"),
("Network error", "Netzwerkfehler"), ("Network error", "Netzwerkfehler"),
("Username missed", "Benutzername fehlt"), ("Username missed", "Benutzername vergessen"),
("Password missed", "Passwort vergessen"), ("Password missed", "Passwort vergessen"),
("Wrong credentials", "Falsche Anmeldedaten"), ("Wrong credentials", "Falsche Anmeldedaten"),
("Edit Tag", "Stichwort bearbeiten"), ("Edit Tag", "Stichwort bearbeiten"),
("Unremember Password", "Passwort nicht merken"), ("Unremember Password", "Passwort vergessen"),
("Favorites", "Favoriten"), ("Favorites", "Favoriten"),
("Add to Favorites", "Zu Favoriten hinzufügen"), ("Add to Favorites", "Zu Favoriten hinzufügen"),
("Remove from Favorites", "Entferne von Favoriten"), ("Remove from Favorites", "Aus Favoriten entfernen"),
("Empty", "Leer"), ("Empty", "Leer"),
("Invalid folder name", "Ungültiger Ordnername"), ("Invalid folder name", "Ungültiger Ordnername"),
("Socks5 Proxy", "Socks5 Proxy"), ("Socks5 Proxy", "Socks5 Proxy"),
("Hostname", "Rechnername"), ("Hostname", "Rechnername"),
("Discovered", "Gefunden"), ("Discovered", "Gefunden"),
("install_daemon_tip", "Um beim Booten zu starten, müssen Sie den Systemdienst installieren"), ("install_daemon_tip", "Um mit System zu starten, muss der Systemdienst installiert sein"),
("Remote ID", "Entfernte ID"), ("Remote ID", "Entfernte ID"),
("Paste", "Einfügen"), ("Paste", "Einfügen"),
("Paste here?", "Hier einfügen?"), ("Paste here?", "Hier einfügen?"),
("Are you sure to close the connection?", "Sind Sie sicher, dass Sie die Verbindung schließen wollen?"), ("Are you sure to close the connection?", "Möchten Sie diese Verbindung wirklich trennen?"),
("Download new version", "Neue Version herunterladen"), ("Download new version", "Neue Version herunterladen"),
("Touch mode", "Touch-Modus"), ("Touch mode", "Touch-Modus"),
("Mouse mode", "Mouse-Modus"), ("Mouse mode", "Maus-Modus"),
("One-Finger Tap", "Ein Fingertipp"), ("One-Finger Tap", "1-Finger-Tipp"),
("Left Mouse", "Linke Maus"), ("Left Mouse", "Linksklick"),
("One-Long Tap", "Tippen Sie mit einem Finger lang"), ("One-Long Tap", "1-Finger-Halten"),
("Two-Finger Tap", "Zwei Finger tippen"), ("Two-Finger Tap", "2-Finger-Tipp"),
("Right Mouse", "Rechte Maus"), ("Right Mouse", "Rechtsklick"),
("One-Finger Move", "Eine Fingerbewegung"), ("One-Finger Move", "Einen Finger bewegen"),
("Double Tap & Move", "Doppeltippen und verschieben"), ("Double Tap & Move", "Doppeltippen und bewegen"),
("Mouse Drag", "Maus ziehen"), ("Mouse Drag", "Maus bewegen"),
("Three-Finger vertically", "Drei Finger vertikal"), ("Three-Finger vertically", "Drei Finger vertikal bewegen"),
("Mouse Wheel", "Mausrad"), ("Mouse Wheel", "Mausrad"),
("Two-Finger Move", "Zwei Finger Bewegung"), ("Two-Finger Move", "Zwei Finger bewegen"),
("Canvas Move", "Leinwand bewegen"), ("Canvas Move", "Sichtfeld bewegen"),
("Pinch to Zoom", "Zum Zoomen kneifen"), ("Pinch to Zoom", "2-Finger-Zoom"),
("Canvas Zoom", "Leinwand Zoom"), ("Canvas Zoom", "Sichtfeld-Zoom"),
("Reset canvas", "Anzeige zurücksetzen"), ("Reset canvas", "Sichtfeld zurücksetzen"),
("No permission of file transfer", "Keine Erlaubnis zur Dateiübertragung"), ("No permission of file transfer", "Keine Dateizugriff-Berechtigung"),
("Note", "Notiz"), ("Note", "Anmerkung"),
("Connection", "Verbindung"), ("Connection", "Verbindung"),
("Share Screen", "Bildschirm freigeben"), ("Share Screen", "Bildschirm freigeben"),
("CLOSE", "NAH DRAN"), ("CLOSE", "DEAKTIV."),
("OPEN", "OFFEN"), ("OPEN", "AKTIVIER."),
("Chat", "Plaudern"), ("Chat", "Chat"),
("Total", "Gesamt"), ("Total", "Gesamt"),
("items", "Artikel"), ("items", "Einträge"),
("Selected", "Ausgewählt"), ("Selected", "Ausgewählt"),
("Screen Capture", "Bildschirmaufnahme"), ("Screen Capture", "Bildschirmzugr."),
("Input Control", "Eingabesteuerung"), ("Input Control", "Eingabezugriff"),
("Audio Capture", "Audioaufnahme"), ("Audio Capture", "Audiozugriff"),
("File Connection", "Dateiverbindung"), ("File Connection", "Dateizugriff"),
("Screen Connection", "Bildschirmanschluss"), ("Screen Connection", "Bildschirmanschluss"),
("Do you accept?", "Akzeptieren Sie?"), ("Do you accept?", "Verbindung zulassen?"),
("Open System Setting", "Systemeinstellung öffnen"), ("Open System Setting", "Systemeinstellung öffnen"),
("How to get Android input permission?", "Wie erhalte ich eine Android-Eingabeberechtigung?"), ("How to get Android input permission?", "Wie erhalte ich eine Android-Eingabeberechtigung?"),
("android_input_permission_tip1", "Damit ein Remote-Gerät Ihr Android-Gerät per Maus oder Berührung steuern kann, müssen Sie RustDesk erlauben, den Dienst \"Barrierefreiheit\" zu verwenden."), ("android_input_permission_tip1", "Damit ein Remote-Gerät Ihr Android-Gerät steuern kann, müssen Sie RustDesk erlauben, den Dienst \"Barrierefreiheit\" zu verwenden."),
("android_input_permission_tip2", "Bitte gehen Sie zur nächsten Systemeinstellungsseite, suchen und geben Sie [Installierte Dienste] ein, schalten Sie den Dienst [RustDesk Input] ein."), ("android_input_permission_tip2", "Bitte gehen Sie zur nächsten Systemeinstellungsseite, suchen und geben Sie [Installierte Dienste] ein, schalten Sie den Dienst [RustDesk Input] ein."),
("android_new_connection_tip", "Es wurde eine neue Steuerungsanforderung empfangen, die Ihr aktuelles Gerät steuern möchte."), ("android_new_connection_tip", "möchte ihr Gerät steuern."),
("android_service_will_start_tip", "Durch das Einschalten der Bildschirmaufnahme wird der Dienst automatisch gestartet, sodass andere Geräte eine Verbindung von diesem Gerät anfordern können."), ("android_service_will_start_tip", "Durch das Aktivieren der Bildschirmfreigabe wird der Dienst automatisch gestartet, sodass andere Geräte dieses Android-Gerät steuern können."),
("android_stop_service_tip", "Durch das Schließen des Dienstes werden automatisch alle hergestellten Verbindungen geschlossen."), ("android_stop_service_tip", "Durch das Deaktivieren des Dienstes werden automatisch alle hergestellten Verbindungen getrennt."),
("android_version_audio_tip", "Die aktuelle Android-Version unterstützt keine Audioaufnahme, bitte aktualisieren Sie auf Android 10 oder höher."), ("android_version_audio_tip", "Ihre Android-Version unterstützt keine Audioaufnahme, bitte aktualisieren Sie auf Android 10 oder höher, falls möglich."),
("android_start_service_tip", "Tippen Sie auf [Dienst starten] oder ÖFFNEN Sie die Berechtigung [Bildschirmaufnahme], um den Bildschirmfreigabedienst zu starten."), ("android_start_service_tip", "Tippen Sie auf [Dienst aktivieren] oder aktivieren Sie die Berechtigung [Bildschirmzugr.], um den Bildschirmfreigabedienst zu starten."),
("Account", "Konto"), ("Account", "Konto"),
("Overwrite", "Überschreiben"), ("Overwrite", "Überschreiben"),
("This file exists, skip or overwrite this file?", "Diese Datei existiert, diese Datei überspringen oder überschreiben?"), ("This file exists, skip or overwrite this file?", "Diese Datei existiert; überspringen oder überschreiben?"),
("Quit", "Aufhören"), ("Quit", "Beenden"),
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("doc_mac_permission", "https://rustdesk.com/docs/de/manual/mac/#berechtigungen-aktivieren"),
("Help", "Hilfe"), ("Help", "Hilfe"),
("Failed", "Gescheitert"), ("Failed", "Fehlgeschlagen"),
("Succeeded", "Erfolgreich"), ("Succeeded", "Erfolgreich"),
("Someone turns on privacy mode, exit", "Jemand aktiviert den Datenschutzmodus, beenden"), ("Someone turns on privacy mode, exit", "Jemand hat den Datenschutzmodus aktiviert, beende..."),
("Unsupported", "Nicht unterstützt"), ("Unsupported", "Nicht unterstützt"),
("Peer denied", "Peer verweigert"), ("Peer denied", "Die Gegenstelle hat die Verbindung abgelehnt"),
("Please install plugins", "Bitte installieren Sie Plugins"), ("Please install plugins", "Bitte installieren Sie Plugins"),
("Peer exit", "Peer-Ausgang"), ("Peer exit", "Die Gegenstelle hat die Verbindung getrennt"),
("Failed to turn off", "Ausschalten fehlgeschlagen"), ("Failed to turn off", "Ausschalten fehlgeschlagen"),
("Turned off", "Ausgeschaltet"), ("Turned off", "Ausgeschaltet"),
("In privacy mode", "im Datenschutzmodus"), ("In privacy mode", "Datenschutzmodus aktivieren"),
("Out privacy mode", "Datenschutzmodus aus"), ("Out privacy mode", "Datenschutzmodus deaktivieren"),
("Language", "Sprache"), ("Language", "Sprache"),
("Keep RustDesk background service", ""), ("Keep RustDesk background service", "RustDesk im Hintergrund ausführen"),
("Ignore Battery Optimizations", ""), ("Ignore Battery Optimizations", "Batterieoptimierung ignorieren"),
("android_open_battery_optimizations_tip", ""), ("android_open_battery_optimizations_tip", "Möchten Sie die Batterieopimierungs-Einstellungen öffnen?"),
("Random Password After Session", ""), ("Random Password After Session", "Neues zufälliges Passwort nach jeder Sitzung"),
("Keep", ""), ("Keep", "Behalten"),
("Update", ""), ("Update", "Aktualisieren"),
("Disable", ""), ("Disable", "Deaktivieren"),
("Onetime Password", ""), ("Onetime Password", "Einmal-Passwort"),
("Verification Method", ""), ("Verification Method", "Überprüfungsmethode"),
("Enable security password", ""), ("Enable security password", "Sicheres Passwort aktivieren"),
("Enable random password", ""), ("Enable random password", "Zufälliges Passwort aktivieren"),
("Enable onetime password", ""), ("Enable onetime password", "Einmal-Passwort aktivieren"),
("Disable onetime password", ""), ("Disable onetime password", "Einmal-Passwort deaktivieren"),
("Activate onetime password", ""), ("Activate onetime password", "Einmal-Passwort aktivieren"),
("Set security password", ""), ("Set security password", "Sicheres Passwort setzen"),
("Connection not allowed", ""), ("Connection not allowed", "Verbindung abgelehnt"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -111,7 +111,8 @@ pub fn get_cursor_data(hcursor: u64) -> ResultType<CursorData> {
cd.id = (*img).cursor_serial as _; cd.id = (*img).cursor_serial as _;
let pixels = let pixels =
std::slice::from_raw_parts((*img).pixels, (cd.width * cd.height) as _); std::slice::from_raw_parts((*img).pixels, (cd.width * cd.height) as _);
cd.colors.resize(pixels.len() * 4, 0); // cd.colors.resize(pixels.len() * 4, 0);
let mut cd_colors = vec![0_u8; pixels.len() * 4];
for y in 0..cd.height { for y in 0..cd.height {
for x in 0..cd.width { for x in 0..cd.width {
let pos = (y * cd.width + x) as usize; let pos = (y * cd.width + x) as usize;
@ -124,12 +125,13 @@ pub fn get_cursor_data(hcursor: u64) -> ResultType<CursorData> {
continue; continue;
} }
let pos = pos * 4; let pos = pos * 4;
cd.colors[pos] = r as _; cd_colors[pos] = r as _;
cd.colors[pos + 1] = g as _; cd_colors[pos + 1] = g as _;
cd.colors[pos + 2] = b as _; cd_colors[pos + 2] = b as _;
cd.colors[pos + 3] = a as _; cd_colors[pos + 3] = a as _;
} }
} }
cd.colors = cd_colors.into();
res = Some(cd); res = Some(cd);
} }
if !img.is_null() { if !img.is_null() {

View File

@ -342,7 +342,7 @@ pub fn get_cursor_data(hcursor: u64) -> ResultType<CursorData> {
} }
Ok(CursorData { Ok(CursorData {
id: hcursor, id: hcursor,
colors, colors: colors.into(),
hotx: hotspot.x as _, hotx: hotspot.x as _,
hoty: hotspot.y as _, hoty: hotspot.y as _,
width: size.width as _, width: size.width as _,

View File

@ -164,7 +164,7 @@ pub fn get_cursor_data(hcursor: u64) -> ResultType<CursorData> {
Ok(CursorData { Ok(CursorData {
id: hcursor, id: hcursor,
colors: cbits, colors: cbits.into(),
hotx: ii.0.xHotspot as _, hotx: ii.0.xHotspot as _,
hoty: ii.0.yHotspot as _, hoty: ii.0.yHotspot as _,
width: width as _, width: width as _,

View File

@ -266,7 +266,7 @@ impl RendezvousMediator {
async fn handle_request_relay(&self, rr: RequestRelay, server: ServerPtr) -> ResultType<()> { async fn handle_request_relay(&self, rr: RequestRelay, server: ServerPtr) -> ResultType<()> {
self.create_relay( self.create_relay(
rr.socket_addr, rr.socket_addr.into(),
rr.relay_server, rr.relay_server,
rr.uuid, rr.uuid,
server, server,
@ -303,7 +303,7 @@ impl RendezvousMediator {
let mut msg_out = Message::new(); let mut msg_out = Message::new();
let mut rr = RelayResponse { let mut rr = RelayResponse {
socket_addr, socket_addr: socket_addr.into(),
version: crate::VERSION.to_owned(), version: crate::VERSION.to_owned(),
..Default::default() ..Default::default()
}; };
@ -334,8 +334,8 @@ impl RendezvousMediator {
let relay_server = self.get_relay_server(fla.relay_server); let relay_server = self.get_relay_server(fla.relay_server);
msg_out.set_local_addr(LocalAddr { msg_out.set_local_addr(LocalAddr {
id: Config::get_id(), id: Config::get_id(),
socket_addr: AddrMangle::encode(peer_addr), socket_addr: AddrMangle::encode(peer_addr).into(),
local_addr: AddrMangle::encode(local_addr), local_addr: AddrMangle::encode(local_addr).into(),
relay_server, relay_server,
version: crate::VERSION.to_owned(), version: crate::VERSION.to_owned(),
..Default::default() ..Default::default()
@ -353,7 +353,7 @@ impl RendezvousMediator {
{ {
let uuid = Uuid::new_v4().to_string(); let uuid = Uuid::new_v4().to_string();
return self return self
.create_relay(ph.socket_addr, relay_server, uuid, server, true, true) .create_relay(ph.socket_addr.into(), relay_server, uuid, server, true, true)
.await; .await;
} }
let peer_addr = AddrMangle::decode(&ph.socket_addr); let peer_addr = AddrMangle::decode(&ph.socket_addr);
@ -394,8 +394,8 @@ impl RendezvousMediator {
self.last_id_pk_registry = id.clone(); self.last_id_pk_registry = id.clone();
msg_out.set_register_pk(RegisterPk { msg_out.set_register_pk(RegisterPk {
id, id,
uuid, uuid: uuid.into(),
pk, pk: pk.into(),
..Default::default() ..Default::default()
}); });
socket.send(&msg_out, self.addr.to_owned()).await?; socket.send(&msg_out, self.addr.to_owned()).await?;

View File

@ -20,6 +20,8 @@ use std::{
sync::{Arc, Mutex, RwLock, Weak}, sync::{Arc, Mutex, RwLock, Weak},
time::Duration, time::Duration,
}; };
use bytes::Bytes;
pub mod audio_service; pub mod audio_service;
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(not(any(target_os = "android", target_os = "ios")))] { if #[cfg(not(any(target_os = "android", target_os = "ios")))] {
@ -130,13 +132,13 @@ pub async fn create_tcp_connection(
id: sign::sign( id: sign::sign(
&IdPk { &IdPk {
id: Config::get_id(), id: Config::get_id(),
pk: our_pk_b.0.to_vec(), pk: Bytes::from(our_pk_b.0.to_vec()),
..Default::default() ..Default::default()
} }
.write_to_bytes() .write_to_bytes()
.unwrap_or_default(), .unwrap_or_default(),
&sk, &sk,
), ).into(),
..Default::default() ..Default::default()
}); });
timeout(CONNECT_TIMEOUT, stream.send(&msg_out)).await??; timeout(CONNECT_TIMEOUT, stream.send(&msg_out)).await??;
@ -319,6 +321,14 @@ pub async fn start_server(is_server: bool) {
log::info!("DISPLAY={:?}", std::env::var("DISPLAY")); log::info!("DISPLAY={:?}", std::env::var("DISPLAY"));
log::info!("XAUTHORITY={:?}", std::env::var("XAUTHORITY")); log::info!("XAUTHORITY={:?}", std::env::var("XAUTHORITY"));
} }
#[cfg(feature = "hwcodec")]
{
use std::sync::Once;
static ONCE: Once = Once::new();
ONCE.call_once(|| {
scrap::hwcodec::check_config_process(false);
})
}
if is_server { if is_server {
std::thread::spawn(move || { std::thread::spawn(move || {
@ -327,15 +337,6 @@ pub async fn start_server(is_server: bool) {
std::process::exit(-1); std::process::exit(-1);
} }
}); });
#[cfg(feature = "hwcodec")]
if let Ok(exe) = std::env::current_exe() {
std::thread::spawn(move || {
std::process::Command::new(exe)
.arg("--check-hwcodec-config")
.status()
.ok()
});
}
#[cfg(windows)] #[cfg(windows)]
crate::platform::windows::bootstrap(); crate::platform::windows::bootstrap();
input_service::fix_key_down_timeout_loop(); input_service::fix_key_down_timeout_loop();

View File

@ -367,7 +367,7 @@ fn send_f32(data: &[f32], encoder: &mut Encoder, sp: &GenericService) {
Ok(data) => { Ok(data) => {
let mut msg_out = Message::new(); let mut msg_out = Message::new();
msg_out.set_audio_frame(AudioFrame { msg_out.set_audio_frame(AudioFrame {
data, data: data.into(),
timestamp: crate::common::get_time(), timestamp: crate::common::get_time(),
..Default::default() ..Default::default()
}); });

View File

@ -636,6 +636,16 @@ impl Connection {
pi.hostname = MOBILE_INFO2.lock().unwrap().clone(); pi.hostname = MOBILE_INFO2.lock().unwrap().clone();
pi.platform = "Android".into(); pi.platform = "Android".into();
} }
#[cfg(feature = "hwcodec")]
{
let (h264, h265) = scrap::codec::Encoder::supported_encoding();
pi.encoding = Some(SupportedEncoding {
h264,
h265,
..Default::default()
})
.into();
}
if self.port_forward_socket.is_some() { if self.port_forward_socket.is_some() {
let mut msg_out = Message::new(); let mut msg_out = Message::new();
@ -1352,6 +1362,12 @@ impl Connection {
} }
} }
} }
if let Some(q) = o.video_codec_state.clone().take() {
scrap::codec::Encoder::update_video_encoder(
self.inner.id(),
scrap::codec::EncoderUpdate::State(q),
);
}
} }
async fn on_close(&mut self, reason: &str, lock: bool) { async fn on_close(&mut self, reason: &str, lock: bool) {
@ -1460,7 +1476,7 @@ async fn start_ipc(
file_num, file_num,
data, data,
compressed}) = data { compressed}) = data {
stream.send(&Data::FS(ipc::FS::WriteBlock{id, file_num, data: Vec::new(), compressed})).await?; stream.send(&Data::FS(ipc::FS::WriteBlock{id, file_num, data: Bytes::new(), compressed})).await?;
stream.send_raw(data).await?; stream.send_raw(data).await?;
} else { } else {
stream.send(&data).await?; stream.send(&data).await?;

View File

@ -146,7 +146,7 @@ fn run_cursor(sp: MouseCursorService, state: &mut StateCursor) -> ResultType<()>
msg = cached.clone(); msg = cached.clone();
} else { } else {
let mut data = crate::get_cursor_data(hcursor)?; let mut data = crate::get_cursor_data(hcursor)?;
data.colors = hbb_common::compress::compress(&data.colors[..], COMPRESS_LEVEL); data.colors = hbb_common::compress::compress(&data.colors[..], COMPRESS_LEVEL).into();
let mut tmp = Message::new(); let mut tmp = Message::new();
tmp.set_cursor_data(data); tmp.set_cursor_data(data);
msg = Arc::new(tmp); msg = Arc::new(tmp);

View File

@ -26,11 +26,11 @@ use hbb_common::tokio::sync::{
use scrap::{ use scrap::{
codec::{Encoder, EncoderCfg, HwEncoderConfig}, codec::{Encoder, EncoderCfg, HwEncoderConfig},
vpxcodec::{VpxEncoderConfig, VpxVideoCodecId}, vpxcodec::{VpxEncoderConfig, VpxVideoCodecId},
Capturer, Display, Frame, Capturer, Display, TraitCapturer,
}; };
use std::{ use std::{
collections::HashSet, collections::HashSet,
io::{ErrorKind::WouldBlock, Result}, io::ErrorKind::WouldBlock,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
time::{self, Duration, Instant}, time::{self, Duration, Instant},
}; };
@ -128,56 +128,6 @@ impl VideoFrameController {
} }
} }
pub(super) trait TraitCapturer {
fn frame<'a>(&'a mut self, timeout: Duration) -> Result<Frame<'a>>;
fn set_use_yuv(&mut self, use_yuv: bool);
#[cfg(windows)]
fn is_gdi(&self) -> bool;
#[cfg(windows)]
fn set_gdi(&mut self) -> bool;
}
impl TraitCapturer for Capturer {
fn frame<'a>(&'a mut self, timeout: Duration) -> Result<Frame<'a>> {
self.frame(timeout)
}
fn set_use_yuv(&mut self, use_yuv: bool) {
self.set_use_yuv(use_yuv);
}
#[cfg(windows)]
fn is_gdi(&self) -> bool {
self.is_gdi()
}
#[cfg(windows)]
fn set_gdi(&mut self) -> bool {
self.set_gdi()
}
}
#[cfg(windows)]
impl TraitCapturer for scrap::CapturerMag {
fn frame<'a>(&'a mut self, _timeout_ms: Duration) -> Result<Frame<'a>> {
self.frame(_timeout_ms)
}
fn set_use_yuv(&mut self, use_yuv: bool) {
self.set_use_yuv(use_yuv);
}
fn is_gdi(&self) -> bool {
false
}
fn set_gdi(&mut self) -> bool {
false
}
}
pub fn new() -> GenericService { pub fn new() -> GenericService {
let sp = GenericService::new(NAME, true); let sp = GenericService::new(NAME, true);
sp.run(run); sp.run(run);
@ -393,8 +343,10 @@ fn get_capturer(use_yuv: bool) -> ResultType<CapturerInfo> {
#[cfg(windows)] #[cfg(windows)]
let mut captuerer_privacy_mode_id = privacy_mode_id; let mut captuerer_privacy_mode_id = privacy_mode_id;
#[cfg(windows)] #[cfg(windows)]
if crate::ui::win_privacy::is_process_consent_running()? { if captuerer_privacy_mode_id != 0 {
captuerer_privacy_mode_id = 0; if crate::ui::win_privacy::is_process_consent_running()? {
captuerer_privacy_mode_id = 0;
}
} }
log::debug!( log::debug!(
"Try create capturer with captuerer privacy mode id {}", "Try create capturer with captuerer privacy mode id {}",
@ -483,6 +435,7 @@ fn run(sp: GenericService) -> ResultType<()> {
let mut try_gdi = 1; let mut try_gdi = 1;
#[cfg(windows)] #[cfg(windows)]
log::info!("gdi: {}", c.is_gdi()); log::info!("gdi: {}", c.is_gdi());
let codec_name = Encoder::current_hw_encoder_name();
while sp.ok() { while sp.ok() {
#[cfg(windows)] #[cfg(windows)]
@ -508,6 +461,9 @@ fn run(sp: GenericService) -> ResultType<()> {
*SWITCH.lock().unwrap() = true; *SWITCH.lock().unwrap() = true;
bail!("SWITCH"); bail!("SWITCH");
} }
if codec_name != Encoder::current_hw_encoder_name() {
bail!("SWITCH");
}
check_privacy_mode_changed(&sp, c.privacy_mode_id)?; check_privacy_mode_changed(&sp, c.privacy_mode_id)?;
#[cfg(windows)] #[cfg(windows)]
{ {

View File

@ -1,30 +1,39 @@
use super::*; use super::*;
use hbb_common::allow_err; use hbb_common::allow_err;
use scrap::{Capturer, Display, Frame}; use scrap::{Capturer, Display, Frame, TraitCapturer};
use std::{io::Result, time::Duration}; use std::io::Result;
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref CAP_DISPLAY_INFO: RwLock<u64> = RwLock::new(0); static ref CAP_DISPLAY_INFO: RwLock<u64> = RwLock::new(0);
} }
struct CapturerPtr(*mut Capturer);
impl Clone for CapturerPtr {
fn clone(&self) -> Self {
Self(self.0)
}
}
impl TraitCapturer for CapturerPtr {
fn frame<'a>(&'a mut self, timeout: Duration) -> Result<Frame<'a>> {
unsafe { (*self.0).frame(timeout) }
}
fn set_use_yuv(&mut self, use_yuv: bool) {
unsafe {
(*self.0).set_use_yuv(use_yuv);
}
}
}
struct CapDisplayInfo { struct CapDisplayInfo {
rects: Vec<((i32, i32), usize, usize)>, rects: Vec<((i32, i32), usize, usize)>,
displays: Vec<DisplayInfo>, displays: Vec<DisplayInfo>,
num: usize, num: usize,
primary: usize, primary: usize,
current: usize, current: usize,
capturer: *mut Capturer, capturer: CapturerPtr,
}
impl super::video_service::TraitCapturer for *mut Capturer {
fn frame<'a>(&'a mut self, timeout: Duration) -> Result<Frame<'a>> {
unsafe { (**self).frame(timeout) }
}
fn set_use_yuv(&mut self, use_yuv: bool) {
unsafe {
(**self).set_use_yuv(use_yuv);
}
}
} }
async fn check_init() -> ResultType<()> { async fn check_init() -> ResultType<()> {
@ -68,6 +77,7 @@ async fn check_init() -> ResultType<()> {
let capturer = Box::into_raw(Box::new( let capturer = Box::into_raw(Box::new(
Capturer::new(display, true).with_context(|| "Failed to create capturer")?, Capturer::new(display, true).with_context(|| "Failed to create capturer")?,
)); ));
let capturer = CapturerPtr(capturer);
let cap_display_info = Box::into_raw(Box::new(CapDisplayInfo { let cap_display_info = Box::into_raw(Box::new(CapDisplayInfo {
rects, rects,
displays, displays,
@ -104,7 +114,7 @@ pub fn clear() {
if *lock != 0 { if *lock != 0 {
unsafe { unsafe {
let cap_display_info = Box::from_raw(*lock as *mut CapDisplayInfo); let cap_display_info = Box::from_raw(*lock as *mut CapDisplayInfo);
let _ = Box::from_raw(cap_display_info.capturer); let _ = Box::from_raw(cap_display_info.capturer.0);
} }
*lock = 0; *lock = 0;
} }
@ -170,7 +180,7 @@ pub(super) fn get_capturer() -> ResultType<super::video_service::CapturerInfo> {
current: cap_display_info.current, current: cap_display_info.current,
privacy_mode_id: 0, privacy_mode_id: 0,
_captuerer_privacy_mode_id: 0, _captuerer_privacy_mode_id: 0,
capturer: Box::new(cap_display_info.capturer), capturer: Box::new(cap_display_info.capturer.clone()),
}) })
} }
} else { } else {

View File

@ -590,7 +590,7 @@ async fn start_pa() {
} else { } else {
buf.clone() buf.clone()
}; };
if let Err(err) = stream.send_raw(out).await { if let Err(err) = stream.send_raw(out.into()).await {
log::error!("Failed to send audio data:{}", err); log::error!("Failed to send audio data:{}", err);
break; break;
} }

View File

@ -94,3 +94,4 @@ span#fullscreen.active {
button:disabled { button:disabled {
opacity: 0.3; opacity: 0.3;
} }

View File

@ -156,6 +156,9 @@ class Header: Reactor.Component {
} }
function renderDisplayPop() { function renderDisplayPop() {
var codecs = handler.supported_hwcodec();
var show_codec = handler.has_hwcodec() && (codecs[0] || codecs[1]);
return <popup> return <popup>
<menu.context #display-options> <menu.context #display-options>
<li #adjust-window style="display:none">{translate('Adjust Window')}</li> <li #adjust-window style="display:none">{translate('Adjust Window')}</li>
@ -168,6 +171,13 @@ class Header: Reactor.Component {
<li #balanced type="image-quality"><span>{svg_checkmark}</span>{translate('Balanced')}</li> <li #balanced type="image-quality"><span>{svg_checkmark}</span>{translate('Balanced')}</li>
<li #low type="image-quality"><span>{svg_checkmark}</span>{translate('Optimize reaction time')}</li> <li #low type="image-quality"><span>{svg_checkmark}</span>{translate('Optimize reaction time')}</li>
<li #custom type="image-quality"><span>{svg_checkmark}</span>{translate('Custom')}</li> <li #custom type="image-quality"><span>{svg_checkmark}</span>{translate('Custom')}</li>
{show_codec ? <div>
<div .separator />
<li #auto type="codec-preference"><span>{svg_checkmark}</span>Auto</li>
<li #vp9 type="codec-preference"><span>{svg_checkmark}</span>VP9</li>
{codecs[0] ? <li #h264 type="codec-preference"><span>{svg_checkmark}</span>H264</li> : ""}
{codecs[1] ? <li #h265 type="codec-preference"><span>{svg_checkmark}</span>H265</li> : ""}
</div> : ""}
<div .separator /> <div .separator />
<li #show-remote-cursor .toggle-option><span>{svg_checkmark}</span>{translate('Show remote cursor')}</li> <li #show-remote-cursor .toggle-option><span>{svg_checkmark}</span>{translate('Show remote cursor')}</li>
<li #show-quality-monitor .toggle-option><span>{svg_checkmark}</span>{translate('Show quality monitor')}</li> <li #show-quality-monitor .toggle-option><span>{svg_checkmark}</span>{translate('Show quality monitor')}</li>
@ -327,7 +337,7 @@ class Header: Reactor.Component {
} }
} }
event click $(menu#display-options>li) (_, me) { event click $(menu#display-options li) (_, me) {
if (me.id == "custom") { if (me.id == "custom") {
handle_custom_image_quality(); handle_custom_image_quality();
} else if (me.id == "privacy-mode") { } else if (me.id == "privacy-mode") {
@ -344,6 +354,9 @@ class Header: Reactor.Component {
} else if (type == "view-style") { } else if (type == "view-style") {
handler.save_view_style(me.id); handler.save_view_style(me.id);
adaptDisplay(); adaptDisplay();
} else if (type == "codec-preference") {
handler.set_option("codec-preference", me.id);
handler.change_prefer_codec();
} }
toggleMenuState(); toggleMenuState();
} }
@ -383,7 +396,10 @@ function toggleMenuState() {
values.push(s); values.push(s);
var k = handler.get_keyboard_mode(); var k = handler.get_keyboard_mode();
values.push(k); values.push(k);
for (var el in $$(menu#display-options>li)) { var c = handler.get_option("codec-preference");
if (!c) c = "auto";
values.push(c);
for (var el in $$(menu#display-options li)) {
el.attributes.toggleClass("selected", values.indexOf(el.id) >= 0); el.attributes.toggleClass("selected", values.indexOf(el.id) >= 0);
} }
for (var el in $$(menu#keyboard-options>li)) { for (var el in $$(menu#keyboard-options>li)) {

View File

@ -235,6 +235,9 @@ impl sciter::EventHandler for Handler {
fn set_write_override(i32, i32, bool, bool, bool); fn set_write_override(i32, i32, bool, bool, bool);
fn get_keyboard_mode(); fn get_keyboard_mode();
fn save_keyboard_mode(String); fn save_keyboard_mode(String);
fn has_hwcodec();
fn supported_hwcodec();
fn change_prefer_codec();
} }
} }
@ -441,6 +444,42 @@ impl Handler {
true true
} }
fn has_hwcodec(&self) -> bool {
#[cfg(not(feature = "hwcodec"))]
return false;
#[cfg(feature = "hwcodec")]
return true;
}
fn supported_hwcodec(&self) -> Value {
#[cfg(feature = "hwcodec")]
{
let mut v = Value::array(0);
let decoder = scrap::codec::Decoder::video_codec_state(&self.id);
let mut h264 = decoder.score_h264 > 0;
let mut h265 = decoder.score_h265 > 0;
if let Some((encoding_264, encoding_265)) = self.lc.read().unwrap().supported_encoding {
h264 = h264 && encoding_264;
h265 = h265 && encoding_265;
}
v.push(h264);
v.push(h265);
v
}
#[cfg(not(feature = "hwcodec"))]
{
let mut v = Value::array(0);
v.push(false);
v.push(false);
v
}
}
fn change_prefer_codec(&self) {
let msg = self.lc.write().unwrap().change_prefer_codec();
self.send(Data::Message(msg));
}
fn t(&self, name: String) -> String { fn t(&self, name: String) -> String {
crate::client::translate(name) crate::client::translate(name)
} }

View File

@ -5,6 +5,7 @@ use crate::{
use hbb_common::{allow_err, bail, lazy_static, log, tokio, ResultType}; use hbb_common::{allow_err, bail, lazy_static, log, tokio, ResultType};
use std::{ use std::{
ffi::CString, ffi::CString,
os::windows::process::CommandExt,
sync::Mutex, sync::Mutex,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
@ -24,7 +25,9 @@ use winapi::{
CreateProcessAsUserW, GetCurrentThreadId, QueueUserAPC, ResumeThread, CreateProcessAsUserW, GetCurrentThreadId, QueueUserAPC, ResumeThread,
PROCESS_INFORMATION, STARTUPINFOW, PROCESS_INFORMATION, STARTUPINFOW,
}, },
winbase::{WTSGetActiveConsoleSessionId, CREATE_SUSPENDED, DETACHED_PROCESS}, winbase::{
WTSGetActiveConsoleSessionId, CREATE_NO_WINDOW, CREATE_SUSPENDED, DETACHED_PROCESS,
},
winnt::{MEM_COMMIT, PAGE_READWRITE}, winnt::{MEM_COMMIT, PAGE_READWRITE},
winuser::*, winuser::*,
}, },
@ -317,6 +320,7 @@ fn wait_find_privacy_hwnd(msecs: u128) -> ResultType<HWND> {
pub fn is_process_consent_running() -> ResultType<bool> { pub fn is_process_consent_running() -> ResultType<bool> {
let output = std::process::Command::new("cmd") let output = std::process::Command::new("cmd")
.args(&["/C", "tasklist | findstr consent.exe"]) .args(&["/C", "tasklist | findstr consent.exe"])
.creation_flags(CREATE_NO_WINDOW)
.output()?; .output()?;
Ok(output.status.success() && !output.stdout.is_empty()) Ok(output.status.success() && !output.stdout.is_empty())
} }