Merge branch 'rustdesk:master' into master

This commit is contained in:
Onyx47 2022-12-09 09:20:41 +01:00 committed by GitHub
commit 5af407d322
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 353 additions and 72 deletions

26
Cargo.lock generated
View File

@ -1533,7 +1533,7 @@ dependencies = [
"log",
"objc",
"pkg-config",
"rdev",
"rdev 0.5.0-2 (git+https://github.com/asur4s/rdev)",
"serde 1.0.147",
"serde_derive",
"tfc",
@ -4248,6 +4248,28 @@ dependencies = [
"x11 2.20.0",
]
[[package]]
name = "rdev"
version = "0.5.0-2"
source = "git+https://github.com/rustdesk/rdev#25c29f61bfdf5d8ec50f0a8a7743bc1d85eb2c04"
dependencies = [
"cocoa",
"core-foundation 0.9.3",
"core-foundation-sys 0.8.3",
"core-graphics 0.22.3",
"enum-map",
"epoll",
"inotify",
"lazy_static",
"libc",
"mio 0.8.5",
"strum 0.24.1",
"strum_macros 0.24.3",
"widestring 1.0.2",
"winapi 0.3.9",
"x11 2.20.0",
]
[[package]]
name = "rdrand"
version = "0.4.0"
@ -4518,7 +4540,7 @@ dependencies = [
"num_cpus",
"objc",
"parity-tokio-ipc",
"rdev",
"rdev 0.5.0-2 (git+https://github.com/rustdesk/rdev)",
"repng",
"reqwest",
"rpassword 7.1.0",

View File

@ -63,7 +63,7 @@ default-net = "0.11.0"
wol-rs = "0.9.1"
flutter_rust_bridge = { git = "https://github.com/SoLongAndThanksForAllThePizza/flutter_rust_bridge", optional = true }
errno = "0.2.8"
rdev = { git = "https://github.com/asur4s/rdev" }
rdev = { git = "https://github.com/rustdesk/rdev" }
url = { version = "2.1", features = ["serde"] }
reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features=false }
@ -162,4 +162,4 @@ codegen-units = 1
panic = 'abort'
strip = true
#opt-level = 'z' # only have smaller size after strip
rpath = true
rpath = true

View File

@ -1013,7 +1013,7 @@ class LastWindowPosition {
return LastWindowPosition(m["width"], m["height"], m["offsetWidth"],
m["offsetHeight"], m["isMaximized"]);
} catch (e) {
debugPrint(e.toString());
debugPrintStack(label: e.toString());
return null;
}
}
@ -1147,7 +1147,7 @@ Future<bool> restoreWindowPosition(WindowType type, {int? windowId}) async {
final pos = bind.getLocalFlutterConfig(k: kWindowPrefix + type.name);
var lpos = LastWindowPosition.loadFromString(pos);
if (lpos == null) {
debugPrint("window position saved, but cannot be parsed");
debugPrint("no window position saved, ignoring position restoration");
return false;
}
@ -1212,7 +1212,7 @@ Future<void> initUniLinks() async {
}
parseRustdeskUri(initialLink);
} catch (err) {
debugPrint("$err");
debugPrintStack(label: "$err");
}
}
@ -1422,7 +1422,7 @@ void onActiveWindowChanged() async {
rustDeskWinManager.closeAllSubWindows()
]);
} catch (err) {
debugPrint("$err");
debugPrintStack(label: "$err");
} finally {
await windowManager.setPreventClose(false);
await windowManager.close();

View File

@ -79,16 +79,19 @@ class _PeerTabPageState extends State<PeerTabPage>
.toList()
.obs;
try {
final json = jsonDecode(bind.getLocalFlutterConfig(k: 'peer-tab-order'));
if (json is List) {
final List<String> list = json.map((e) => e.toString()).toList();
if (list.length == visibleOrderedTabs.length &&
visibleOrderedTabs.every((e) => list.contains(e))) {
visibleOrderedTabs.value = list;
final conf = bind.getLocalFlutterConfig(k: 'peer-tab-order');
if (conf.isNotEmpty) {
final json = jsonDecode(conf);
if (json is List) {
final List<String> list = json.map((e) => e.toString()).toList();
if (list.length == visibleOrderedTabs.length &&
visibleOrderedTabs.every((e) => list.contains(e))) {
visibleOrderedTabs.value = list;
}
}
}
} catch (e) {
debugPrint('$e');
debugPrintStack(label: '$e');
}
adjustTab();

View File

@ -63,7 +63,7 @@ class DesktopSettingPage extends StatefulWidget {
DesktopTabPage.onAddSetting(initialPage: page);
}
} catch (e) {
debugPrint('$e');
debugPrintStack(label: '$e');
}
}
}

View File

@ -31,7 +31,7 @@ class DesktopTabPage extends StatefulWidget {
initialPage: initialPage,
)));
} catch (e) {
debugPrint('$e');
debugPrintStack(label: '$e');
}
}
}

View File

@ -460,7 +460,7 @@ Future<bool> loginDialog() async {
debugPrint('$resp');
completer.complete(true);
} catch (err) {
debugPrint(err.toString());
debugPrintStack(label: err.toString());
cancel();
return;
}

View File

@ -564,7 +564,7 @@ void androidChannelInit() {
}
}
} catch (e) {
debugPrint("MethodCallHandler err:$e");
debugPrintStack(label: "MethodCallHandler err:$e");
}
return "";
});

View File

@ -363,8 +363,6 @@ class FileModel extends ChangeNotifier {
if (_currentRemoteDir.path.isEmpty) {
openDirectory(_remoteOption.home, isLocal: false);
}
// load last transfer jobs
await bind.sessionLoadLastTransferJobs(id: '${parent.target?.id}');
}
Future<void> onClose() async {

View File

@ -126,7 +126,7 @@ class PlatformFFI {
// no need to set home dir
}
} catch (e) {
debugPrint('initialize failed: $e');
debugPrintStack(label: 'initialize failed: $e');
}
String id = 'NA';
String name = 'Flutter';
@ -151,9 +151,8 @@ class PlatformFFI {
WindowsDeviceInfo winInfo = await deviceInfo.windowsInfo;
name = winInfo.computerName;
id = winInfo.computerName;
} catch (e, stacktrace) {
debugPrint("get windows device info failed: $e");
debugPrintStack(stackTrace: stacktrace);
} catch (e) {
debugPrintStack(label: "get windows device info failed: $e");
name = "unknown";
id = "unknown";
}
@ -174,7 +173,7 @@ class PlatformFFI {
await _ffiBind.mainSetHomeDir(home: _homeDir);
await _ffiBind.mainInit(appDir: _dir);
} catch (e) {
debugPrint('initialize failed: $e');
debugPrintStack(label: 'initialize failed: $e');
}
version = await getVersion();
}

View File

@ -43,7 +43,8 @@ pub fn get_display_server() -> String {
}
fn get_display_server_of_session(session: &str) -> String {
if let Ok(output) = run_loginctl(Some(vec!["show-session", "-p", "Type", session]))
let mut display_server = if let Ok(output) =
run_loginctl(Some(vec!["show-session", "-p", "Type", session]))
// Check session type of the session
{
let display_server = String::from_utf8_lossy(&output.stdout)
@ -64,28 +65,23 @@ fn get_display_server_of_session(session: &str) -> String {
{
if xorg_results.trim_end().to_string() != "" {
// If it is, manually return "x11", otherwise return tty
"x11".to_owned()
} else {
display_server
return "x11".to_owned();
}
} else {
// If any of these commands fail just fall back to the display server
display_server
}
} else {
display_server
}
} else {
// loginctl has not given the expected output. try something else.
if let Ok(sestype) = std::env::var("XDG_SESSION_TYPE") {
return sestype.to_owned();
}
// If the session is not a tty, then just return the type as usual
display_server
}
display_server
} else {
"".to_owned()
};
if display_server.is_empty() {
// loginctl has not given the expected output. try something else.
if let Ok(sestype) = std::env::var("XDG_SESSION_TYPE") {
display_server = sestype;
}
}
// If the session is not a tty, then just return the type as usual
display_server
}
pub fn get_values_of_seat0(indices: Vec<usize>) -> Vec<String> {
@ -126,8 +122,7 @@ pub fn get_values_of_seat0(indices: Vec<usize>) -> Vec<String> {
}
fn is_active(sid: &str) -> bool {
if let Ok(output) = run_loginctl(Some(vec!["show-session", "-p", "State", sid]))
{
if let Ok(output) = run_loginctl(Some(vec!["show-session", "-p", "State", sid])) {
String::from_utf8_lossy(&output.stdout).contains("active")
} else {
false

View File

@ -12,11 +12,10 @@ use hwcodec::mux::{MuxContext, Muxer};
use std::{
fs::{File, OpenOptions},
io,
time::Instant,
};
use std::{
ops::{Deref, DerefMut},
path::PathBuf,
sync::mpsc::Sender,
time::Instant,
};
use webm::mux::{self, Segment, Track, VideoTrack, Writer};
@ -31,12 +30,14 @@ pub enum RecordCodecID {
#[derive(Debug, Clone)]
pub struct RecorderContext {
pub server: bool,
pub id: String,
pub default_dir: String,
pub filename: String,
pub width: usize,
pub height: usize,
pub codec_id: RecordCodecID,
pub tx: Option<Sender<RecordState>>,
}
impl RecorderContext {
@ -52,7 +53,8 @@ impl RecorderContext {
std::fs::create_dir_all(&dir)?;
}
}
let file = self.id.clone()
let file = if self.server { "s" } else { "c" }.to_string()
+ &self.id.clone()
+ &chrono::Local::now().format("_%Y%m%d%H%M%S").to_string()
+ if self.codec_id == RecordCodecID::VP9 {
".webm"
@ -60,7 +62,7 @@ impl RecorderContext {
".mp4"
};
self.filename = PathBuf::from(&dir).join(file).to_string_lossy().to_string();
log::info!("video save to:{}", self.filename);
log::info!("video will save to:{}", self.filename);
Ok(())
}
}
@ -75,6 +77,14 @@ pub trait RecorderApi {
fn write_video(&mut self, frame: &EncodedVideoFrame) -> bool;
}
#[derive(Debug)]
pub enum RecordState {
NewFile(String),
NewFrame,
WriteTail,
RemoveFile,
}
pub struct Recorder {
pub inner: Box<dyn RecorderApi>,
ctx: RecorderContext,
@ -110,6 +120,7 @@ impl Recorder {
#[cfg(not(feature = "hwcodec"))]
_ => bail!("unsupported codec type"),
};
recorder.send_state(RecordState::NewFile(recorder.ctx.filename.clone()));
Ok(recorder)
}
@ -123,6 +134,7 @@ impl Recorder {
_ => bail!("unsupported codec type"),
};
self.ctx = ctx;
self.send_state(RecordState::NewFile(self.ctx.filename.clone()));
Ok(())
}
@ -171,8 +183,13 @@ impl Recorder {
}
_ => bail!("unsupported frame type"),
}
self.send_state(RecordState::NewFrame);
Ok(())
}
fn send_state(&self, state: RecordState) {
self.ctx.tx.as_ref().map(|tx| tx.send(state));
}
}
struct WebmRecorder {
@ -237,9 +254,12 @@ impl RecorderApi for WebmRecorder {
impl Drop for WebmRecorder {
fn drop(&mut self) {
std::mem::replace(&mut self.webm, None).map_or(false, |webm| webm.finalize(None));
let mut state = RecordState::WriteTail;
if !self.written || self.start.elapsed().as_secs() < MIN_SECS {
std::fs::remove_file(&self.ctx.filename).ok();
state = RecordState::RemoveFile;
}
self.ctx.tx.as_ref().map(|tx| tx.send(state));
}
}
@ -292,8 +312,11 @@ impl RecorderApi for HwRecorder {
impl Drop for HwRecorder {
fn drop(&mut self) {
self.muxer.write_tail().ok();
let mut state = RecordState::WriteTail;
if !self.written || self.start.elapsed().as_secs() < MIN_SECS {
std::fs::remove_file(&self.ctx.filename).ok();
state = RecordState::RemoveFile;
}
self.ctx.tx.as_ref().map(|tx| tx.send(state));
}
}

View File

@ -863,12 +863,14 @@ impl VideoHandler {
self.record = false;
if start {
self.recorder = Recorder::new(RecorderContext {
server: false,
id,
default_dir: crate::ui_interface::default_video_save_directory(),
filename: "".to_owned(),
width: w as _,
height: h as _,
codec_id: scrap::record::RecordCodecID::VP9,
tx: None,
})
.map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r))));
} else {

View File

@ -936,7 +936,7 @@ impl<T: InvokeUiSession> Remote<T> {
self.handle_job_status(d.id, d.file_num, err);
}
Some(file_response::Union::Error(e)) => {
if let Some(job) = fs::get_job(e.id, &mut self.write_jobs) {
if let Some(_job) = fs::get_job(e.id, &mut self.write_jobs) {
fs::remove_job(e.id, &mut self.write_jobs);
}
self.handle_job_status(e.id, e.file_num, Some(e.error));

View File

@ -217,7 +217,7 @@ pub fn core_main() -> Option<Vec<String>> {
if crate::platform::is_root() {
crate::ipc::set_permanent_password(args[1].to_owned()).unwrap();
} else {
log::info!("Permission denied!");
println!("Administrative privileges required!");
}
}
return None;

View File

@ -4,6 +4,7 @@ use serde_json::{Map, Value};
#[cfg(feature = "flutter")]
pub mod account;
pub mod record_upload;
#[derive(Debug)]
pub enum HbbHttpResponse<T> {

View File

@ -0,0 +1,204 @@
use bytes::Bytes;
use hbb_common::{bail, config::Config, lazy_static, log, ResultType};
use reqwest::blocking::{Body, Client};
use scrap::record::RecordState;
use serde::Serialize;
use serde_json::Map;
use std::{
fs::File,
io::{prelude::*, SeekFrom},
sync::{mpsc::Receiver, Arc, Mutex},
time::{Duration, Instant},
};
const MAX_HEADER_LEN: usize = 1024;
const SHOULD_SEND_TIME: Duration = Duration::from_secs(1);
const SHOULD_SEND_SIZE: u64 = 1024 * 1024;
lazy_static::lazy_static! {
static ref ENABLE: Arc<Mutex<bool>> = Default::default();
}
pub fn is_enable() -> bool {
ENABLE.lock().unwrap().clone()
}
pub fn run(rx: Receiver<RecordState>) {
let mut uploader = RecordUploader {
client: Client::new(),
api_server: crate::get_api_server(
Config::get_option("api-server"),
Config::get_option("custom-rendezvous-server"),
),
filepath: Default::default(),
filename: Default::default(),
upload_size: Default::default(),
running: Default::default(),
last_send: Instant::now(),
};
std::thread::spawn(move || loop {
if let Err(e) = match rx.recv() {
Ok(state) => match state {
RecordState::NewFile(filepath) => uploader.handle_new_file(filepath),
RecordState::NewFrame => {
if uploader.running {
uploader.handle_frame(false)
} else {
Ok(())
}
}
RecordState::WriteTail => {
if uploader.running {
uploader.handle_tail()
} else {
Ok(())
}
}
RecordState::RemoveFile => {
if uploader.running {
uploader.handle_remove()
} else {
Ok(())
}
}
},
Err(e) => {
log::trace!("upload thread stop:{}", e);
break;
}
} {
uploader.running = false;
log::error!("upload stop:{}", e);
}
});
}
struct RecordUploader {
client: Client,
api_server: String,
filepath: String,
filename: String,
upload_size: u64,
running: bool,
last_send: Instant,
}
impl RecordUploader {
fn send<Q, B>(&self, query: &Q, body: B) -> ResultType<()>
where
Q: Serialize + ?Sized,
B: Into<Body>,
{
match self
.client
.post(format!("{}/api/record", self.api_server))
.query(query)
.body(body)
.send()
{
Ok(resp) => {
if let Ok(m) = resp.json::<Map<String, serde_json::Value>>() {
if let Some(e) = m.get("error") {
bail!(e.to_string());
}
}
Ok(())
}
Err(e) => bail!(e.to_string()),
}
}
fn handle_new_file(&mut self, filepath: String) -> ResultType<()> {
match std::path::PathBuf::from(&filepath).file_name() {
Some(filename) => match filename.to_owned().into_string() {
Ok(filename) => {
self.filename = filename.clone();
self.filepath = filepath.clone();
self.upload_size = 0;
self.running = true;
self.last_send = Instant::now();
self.send(&[("type", "new"), ("file", &filename)], Bytes::new())?;
Ok(())
}
Err(_) => bail!("can't parse filename:{:?}", filename),
},
None => bail!("can't parse filepath:{}", filepath),
}
}
fn handle_frame(&mut self, flush: bool) -> ResultType<()> {
if !flush && self.last_send.elapsed() < SHOULD_SEND_TIME {
return Ok(());
}
match File::open(&self.filepath) {
Ok(mut file) => match file.metadata() {
Ok(m) => {
let len = m.len();
if len <= self.upload_size {
return Ok(());
}
if !flush && len - self.upload_size < SHOULD_SEND_SIZE {
return Ok(());
}
let mut buf = Vec::new();
match file.seek(SeekFrom::Start(self.upload_size)) {
Ok(_) => match file.read_to_end(&mut buf) {
Ok(length) => {
self.send(
&[
("type", "part"),
("file", &self.filename),
("offset", &self.upload_size.to_string()),
("length", &length.to_string()),
],
buf,
)?;
self.upload_size = len;
self.last_send = Instant::now();
Ok(())
}
Err(e) => bail!(e.to_string()),
},
Err(e) => bail!(e.to_string()),
}
}
Err(e) => bail!(e.to_string()),
},
Err(e) => bail!(e.to_string()),
}
}
fn handle_tail(&mut self) -> ResultType<()> {
self.handle_frame(true)?;
match File::open(&self.filepath) {
Ok(mut file) => {
let mut buf = vec![0u8; MAX_HEADER_LEN];
match file.read(&mut buf) {
Ok(length) => {
buf.truncate(length);
self.send(
&[
("type", "tail"),
("file", &self.filename),
("offset", "0"),
("length", &length.to_string()),
],
buf,
)?;
log::info!("upload success, file:{}", self.filename);
Ok(())
}
Err(e) => bail!(e.to_string()),
}
}
Err(e) => bail!(e.to_string()),
}
}
fn handle_remove(&mut self) -> ResultType<()> {
self.send(
&[("type", "remove"), ("file", &self.filename)],
Bytes::new(),
)?;
Ok(())
}
}

View File

@ -399,6 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", "Permitir ocultar solo si se aceptan sesiones a través de contraseña y usando contraseña permanente"),
("wayland_experiment_tip", "El soporte para Wayland está en fase experimental, por favor, use X11 si necesita acceso desatendido."),
("Right click to select tabs", "Clic derecho para seleccionar pestañas"),
("Add to Address Book", ""),
("Add to Address Book", "Añadir a la libreta de direcciones"),
].iter().cloned().collect();
}

View File

@ -44,7 +44,7 @@ const ADDR_CAPTURE_FRAME_COUNTER: usize = ADDR_CAPTURE_WOULDBLOCK + size_of::<i3
const ADDR_CAPTURE_FRAME: usize =
(ADDR_CAPTURE_FRAME_COUNTER + SIZE_COUNTER + FRAME_ALIGN - 1) / FRAME_ALIGN * FRAME_ALIGN;
const IPC_PROFIX: &str = "_portable_service";
const IPC_SUFFIX: &str = "_portable_service";
pub const SHMEM_NAME: &str = "_portable_service";
const MAX_NACK: usize = 3;
const MAX_DXGI_FAIL_TIME: usize = 5;
@ -376,7 +376,7 @@ pub mod server {
async fn run_ipc_client() {
use DataPortableService::*;
let postfix = IPC_PROFIX;
let postfix = IPC_SUFFIX;
match ipc::connect(1000, postfix).await {
Ok(mut stream) => {
@ -622,7 +622,7 @@ pub mod client {
async fn start_ipc_server_async(rx: mpsc::UnboundedReceiver<Data>) {
use DataPortableService::*;
let rx = Arc::new(tokio::sync::Mutex::new(rx));
let postfix = IPC_PROFIX;
let postfix = IPC_SUFFIX;
#[cfg(feature = "flutter")]
let quick_support = {
let args: Vec<_> = std::env::args().collect();

View File

@ -21,8 +21,9 @@
use super::{video_qos::VideoQoS, *};
#[cfg(windows)]
use crate::portable_service::client::PORTABLE_SERVICE_RUNNING;
#[cfg(windows)]
use hbb_common::get_version_number;
use hbb_common::{
get_version_number,
tokio::sync::{
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
Mutex as TokioMutex,
@ -481,22 +482,7 @@ fn run(sp: GenericService) -> ResultType<()> {
#[cfg(windows)]
log::info!("gdi: {}", c.is_gdi());
let codec_name = Encoder::current_hw_encoder_name();
#[cfg(not(target_os = "ios"))]
let recorder = if !Config::get_option("allow-auto-record-incoming").is_empty() {
Recorder::new(RecorderContext {
id: "local".to_owned(),
default_dir: crate::ui_interface::default_video_save_directory(),
filename: "".to_owned(),
width: c.width,
height: c.height,
codec_id: scrap::record::RecordCodecID::VP9,
})
.map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r))))
} else {
Default::default()
};
#[cfg(target_os = "ios")]
let recorder: Arc<Mutex<Option<Recorder>>> = Default::default();
let recorder = get_recorder(c.width, c.height, &codec_name);
#[cfg(windows)]
start_uac_elevation_check();
@ -673,6 +659,53 @@ fn run(sp: GenericService) -> ResultType<()> {
Ok(())
}
fn get_recorder(
width: usize,
height: usize,
codec_name: &Option<String>,
) -> Arc<Mutex<Option<Recorder>>> {
#[cfg(not(target_os = "ios"))]
let recorder = if !Config::get_option("allow-auto-record-incoming").is_empty() {
use crate::hbbs_http::record_upload;
use scrap::record::RecordCodecID::*;
let tx = if record_upload::is_enable() {
let (tx, rx) = std::sync::mpsc::channel();
record_upload::run(rx);
Some(tx)
} else {
None
};
let codec_id = match codec_name {
Some(name) => {
if name.contains("264") {
H264
} else {
H265
}
}
None => VP9,
};
Recorder::new(RecorderContext {
server: true,
id: Config::get_id(),
default_dir: crate::ui_interface::default_video_save_directory(),
filename: "".to_owned(),
width,
height,
codec_id,
tx,
})
.map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r))))
} else {
Default::default()
};
#[cfg(target_os = "ios")]
let recorder: Arc<Mutex<Option<Recorder>>> = Default::default();
recorder
}
fn check_privacy_mode_changed(sp: &GenericService, privacy_mode_id: i32) -> ResultType<()> {
let privacy_mode_id_2 = *PRIVACY_MODE_CONN_ID.lock().unwrap();
if privacy_mode_id != privacy_mode_id_2 {

View File

@ -685,6 +685,7 @@ pub fn discover() {
});
}
#[cfg(feature = "flutter")]
pub fn peer_to_map(id: String, p: PeerConfig) -> HashMap<&'static str, String> {
HashMap::<&str, String>::from_iter([
("id", id),