add desktop cm backend
This commit is contained in:
parent
fc061d2b49
commit
3063adc2fd
flutter/lib
src
@ -106,7 +106,6 @@ class DesktopServerPage extends StatefulWidget implements PageShape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _DesktopServerPageState extends State<DesktopServerPage> {
|
class _DesktopServerPageState extends State<DesktopServerPage> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ChangeNotifierProvider.value(
|
return ChangeNotifierProvider.value(
|
||||||
@ -182,7 +181,7 @@ class ConnectionManager extends StatelessWidget {
|
|||||||
MaterialStateProperty.all(Colors.red)),
|
MaterialStateProperty.all(Colors.red)),
|
||||||
icon: Icon(Icons.close),
|
icon: Icon(Icons.close),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
bind.serverCloseConnection(connId: entry.key);
|
bind.cmCloseConnection(connId: entry.key);
|
||||||
gFFI.invokeMethod(
|
gFFI.invokeMethod(
|
||||||
"cancel_notification", entry.key);
|
"cancel_notification", entry.key);
|
||||||
},
|
},
|
||||||
|
@ -409,7 +409,7 @@ class ConnectionManager extends StatelessWidget {
|
|||||||
MaterialStateProperty.all(Colors.red)),
|
MaterialStateProperty.all(Colors.red)),
|
||||||
icon: Icon(Icons.close),
|
icon: Icon(Icons.close),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
bind.serverCloseConnection(connId: entry.key);
|
bind.cmCloseConnection(connId: entry.key);
|
||||||
gFFI.invokeMethod(
|
gFFI.invokeMethod(
|
||||||
"cancel_notification", entry.key);
|
"cancel_notification", entry.key);
|
||||||
},
|
},
|
||||||
|
@ -206,7 +206,7 @@ class ChatModel with ChangeNotifier {
|
|||||||
bind.sessionSendChat(id: _ffi.target!.id, text: message.text);
|
bind.sessionSendChat(id: _ffi.target!.id, text: message.text);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bind.serverSendChat(connId: _currentID, msg: message.text);
|
bind.cmSendChat(connId: _currentID, msg: message.text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
@ -423,7 +423,7 @@ class ServerModel with ChangeNotifier {
|
|||||||
|
|
||||||
void sendLoginResponse(Client client, bool res) async {
|
void sendLoginResponse(Client client, bool res) async {
|
||||||
if (res) {
|
if (res) {
|
||||||
bind.serverLoginRes(connId: client.id, res: res);
|
bind.cmLoginRes(connId: client.id, res: res);
|
||||||
if (!client.isFileTransfer) {
|
if (!client.isFileTransfer) {
|
||||||
parent.target?.invokeMethod("start_capture");
|
parent.target?.invokeMethod("start_capture");
|
||||||
}
|
}
|
||||||
@ -431,7 +431,7 @@ class ServerModel with ChangeNotifier {
|
|||||||
_clients[client.id]?.authorized = true;
|
_clients[client.id]?.authorized = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
} else {
|
} else {
|
||||||
bind.serverLoginRes(connId: client.id, res: res);
|
bind.cmLoginRes(connId: client.id, res: res);
|
||||||
parent.target?.invokeMethod("cancel_notification", client.id);
|
parent.target?.invokeMethod("cancel_notification", client.id);
|
||||||
_clients.remove(client.id);
|
_clients.remove(client.id);
|
||||||
}
|
}
|
||||||
@ -463,7 +463,7 @@ class ServerModel with ChangeNotifier {
|
|||||||
|
|
||||||
closeAll() {
|
closeAll() {
|
||||||
_clients.forEach((id, client) {
|
_clients.forEach((id, client) {
|
||||||
bind.serverCloseConnection(connId: id);
|
bind.cmCloseConnection(connId: id);
|
||||||
});
|
});
|
||||||
_clients.clear();
|
_clients.clear();
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use hbb_common::log;
|
use hbb_common::log;
|
||||||
|
|
||||||
use crate::start_os_service;
|
use crate::{start_os_service, flutter::connection_manager};
|
||||||
|
|
||||||
/// Main entry of the RustDesk Core.
|
/// Main entry of the RustDesk Core.
|
||||||
/// Return true if the app should continue running with UI(possibly Flutter), false if the app should exit.
|
/// Return true if the app should continue running with UI(possibly Flutter), false if the app should exit.
|
||||||
@ -11,6 +11,7 @@ pub fn core_main() -> bool {
|
|||||||
if args[1] == "--cm" {
|
if args[1] == "--cm" {
|
||||||
// call connection manager to establish connections
|
// call connection manager to establish connections
|
||||||
// meanwhile, return true to call flutter window to show control panel
|
// meanwhile, return true to call flutter window to show control panel
|
||||||
|
connection_manager::start_listen_ipc_thread();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if args[1] == "--service" {
|
if args[1] == "--service" {
|
||||||
|
293
src/flutter.rs
293
src/flutter.rs
@ -1,5 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, VecDeque},
|
collections::HashMap,
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, AtomicUsize, Ordering},
|
atomic::{AtomicBool, AtomicUsize, Ordering},
|
||||||
Arc, Mutex, RwLock,
|
Arc, Mutex, RwLock,
|
||||||
@ -9,7 +9,7 @@ use std::{
|
|||||||
use flutter_rust_bridge::{StreamSink, ZeroCopyBuffer};
|
use flutter_rust_bridge::{StreamSink, ZeroCopyBuffer};
|
||||||
|
|
||||||
use hbb_common::config::{PeerConfig, TransferSerde};
|
use hbb_common::config::{PeerConfig, TransferSerde};
|
||||||
use hbb_common::fs::{get_job, TransferJobMeta};
|
use hbb_common::fs::get_job;
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
allow_err,
|
allow_err,
|
||||||
compress::decompress,
|
compress::decompress,
|
||||||
@ -451,7 +451,6 @@ impl Session {
|
|||||||
key_event.set_chr(raw);
|
key_event.set_chr(raw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
if alt {
|
if alt {
|
||||||
key_event.modifiers.push(ControlKey::Alt.into());
|
key_event.modifiers.push(ControlKey::Alt.into());
|
||||||
@ -794,7 +793,7 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
if !conn.read_jobs.is_empty() {
|
if !conn.read_jobs.is_empty() {
|
||||||
if let Err(err) = fs::handle_read_jobs(&mut conn.read_jobs, &mut peer).await {
|
if let Err(err) = fs::handle_read_jobs(&mut conn.read_jobs, &mut peer).await {
|
||||||
log::debug!("Connection Error");
|
log::debug!("Connection Error: {}", err);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
conn.update_jobs_status();
|
conn.update_jobs_status();
|
||||||
@ -915,7 +914,7 @@ impl Connection {
|
|||||||
Some(file_response::Union::Dir(fd)) => {
|
Some(file_response::Union::Dir(fd)) => {
|
||||||
let mut entries = fd.entries.to_vec();
|
let mut entries = fd.entries.to_vec();
|
||||||
if self.session.peer_platform() == "Windows" {
|
if self.session.peer_platform() == "Windows" {
|
||||||
fs::transform_windows_path(&mut entries);
|
transform_windows_path(&mut entries);
|
||||||
}
|
}
|
||||||
let id = fd.id;
|
let id = fd.id;
|
||||||
self.session.push_event(
|
self.session.push_event(
|
||||||
@ -1636,8 +1635,10 @@ pub mod connection_manager {
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
iter::FromIterator,
|
iter::FromIterator,
|
||||||
rc::{Rc, Weak},
|
sync::{
|
||||||
sync::{Mutex, RwLock},
|
atomic::{AtomicI64, Ordering},
|
||||||
|
RwLock,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use serde_derive::Serialize;
|
use serde_derive::Serialize;
|
||||||
@ -1652,16 +1653,18 @@ pub mod connection_manager {
|
|||||||
protobuf::Message as _,
|
protobuf::Message as _,
|
||||||
tokio::{
|
tokio::{
|
||||||
self,
|
self,
|
||||||
sync::mpsc::{UnboundedReceiver, UnboundedSender},
|
sync::mpsc::{self, UnboundedReceiver, UnboundedSender},
|
||||||
task::spawn_blocking,
|
task::spawn_blocking,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
#[cfg(any(target_os = "android"))]
|
#[cfg(any(target_os = "android"))]
|
||||||
use scrap::android::call_main_service_set_by_name;
|
use scrap::android::call_main_service_set_by_name;
|
||||||
|
|
||||||
use crate::ipc;
|
#[cfg(windows)]
|
||||||
|
use crate::ipc::start_clipboard_file;
|
||||||
|
|
||||||
use crate::ipc::Data;
|
use crate::ipc::Data;
|
||||||
use crate::server::Connection as Conn;
|
use crate::ipc::{self, new_listener, Connection};
|
||||||
|
|
||||||
use super::GLOBAL_EVENT_STREAM;
|
use super::GLOBAL_EVENT_STREAM;
|
||||||
|
|
||||||
@ -1681,76 +1684,184 @@ pub mod connection_manager {
|
|||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref CLIENTS: RwLock<HashMap<i32,Client>> = Default::default();
|
static ref CLIENTS: RwLock<HashMap<i32,Client>> = Default::default();
|
||||||
static ref WRITE_JOBS: Mutex<Vec<fs::TransferJob>> = Mutex::new(Vec::new());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static CLICK_TIME: AtomicI64 = AtomicI64::new(0);
|
||||||
|
|
||||||
|
enum ClipboardFileData {
|
||||||
|
#[cfg(windows)]
|
||||||
|
Clip((i32, ipc::ClipbaordFile)),
|
||||||
|
Enable((i32, bool)),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
pub fn start_listen_ipc_thread() {
|
||||||
|
std::thread::spawn(move || start_ipc());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
#[tokio::main(flavor = "current_thread")]
|
||||||
|
async fn start_ipc() {
|
||||||
|
let (tx_file, _rx_file) = mpsc::unbounded_channel::<ClipboardFileData>();
|
||||||
|
#[cfg(windows)]
|
||||||
|
let cm_clip = cm.clone();
|
||||||
|
#[cfg(windows)]
|
||||||
|
std::thread::spawn(move || start_clipboard_file(cm_clip, _rx_file));
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
log::info!("try create privacy mode window");
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
if let Err(e) = crate::platform::windows::check_update_broker_process() {
|
||||||
|
log::warn!(
|
||||||
|
"Failed to check update broker process. Privacy mode may not work properly. {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allow_err!(crate::ui::win_privacy::start());
|
||||||
|
});
|
||||||
|
|
||||||
|
match new_listener("_cm").await {
|
||||||
|
Ok(mut incoming) => {
|
||||||
|
while let Some(result) = incoming.next().await {
|
||||||
|
match result {
|
||||||
|
Ok(stream) => {
|
||||||
|
log::debug!("Got new connection");
|
||||||
|
let mut stream = Connection::new(stream);
|
||||||
|
let tx_file = tx_file.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
// for tmp use, without real conn id
|
||||||
|
let conn_id_tmp = -1;
|
||||||
|
let mut conn_id: i32 = 0;
|
||||||
|
let (tx, mut rx) = mpsc::unbounded_channel::<Data>();
|
||||||
|
let mut write_jobs: Vec<fs::TransferJob> = Vec::new();
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
res = stream.next() => {
|
||||||
|
match res {
|
||||||
|
Err(err) => {
|
||||||
|
log::info!("cm ipc connection closed: {}", err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Ok(Some(data)) => {
|
||||||
|
match data {
|
||||||
|
Data::Login{id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, file_transfer_enabled, restart} => {
|
||||||
|
log::debug!("conn_id: {}", id);
|
||||||
|
conn_id = id;
|
||||||
|
tx_file.send(ClipboardFileData::Enable((id, file_transfer_enabled))).ok();
|
||||||
|
on_login(id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, restart, tx.clone());
|
||||||
|
}
|
||||||
|
Data::Close => {
|
||||||
|
tx_file.send(ClipboardFileData::Enable((conn_id, false))).ok();
|
||||||
|
log::info!("cm ipc connection closed from connection request");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Data::PrivacyModeState((_, _)) => {
|
||||||
|
conn_id = conn_id_tmp;
|
||||||
|
allow_err!(tx.send(data));
|
||||||
|
}
|
||||||
|
Data::ClickTime(ms) => {
|
||||||
|
CLICK_TIME.store(ms, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
Data::ChatMessage { text } => {
|
||||||
|
handle_chat(conn_id, text);
|
||||||
|
}
|
||||||
|
Data::FS(fs) => {
|
||||||
|
handle_fs(fs, &mut write_jobs, &tx).await;
|
||||||
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
Data::ClipbaordFile(_clip) => {
|
||||||
|
tx_file
|
||||||
|
.send(ClipboardFileData::Clip((id, _clip)))
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
Data::ClipboardFileEnabled(enabled) => {
|
||||||
|
tx_file
|
||||||
|
.send(ClipboardFileData::Enable((id, enabled)))
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(data) = rx.recv() => {
|
||||||
|
if stream.send(&data).await.is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if conn_id != conn_id_tmp {
|
||||||
|
remove_connection(conn_id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("Couldn't get cm client: {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("Failed to start cm ipc server: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// crate::platform::quit_gui();
|
||||||
|
// TODO flutter quit_gui
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
pub fn start_channel(rx: UnboundedReceiver<Data>, tx: UnboundedSender<Data>) {
|
pub fn start_channel(rx: UnboundedReceiver<Data>, tx: UnboundedSender<Data>) {
|
||||||
std::thread::spawn(move || start_listen(rx, tx));
|
std::thread::spawn(move || start_listen(rx, tx));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
async fn start_listen(mut rx: UnboundedReceiver<Data>, tx: UnboundedSender<Data>) {
|
async fn start_listen(mut rx: UnboundedReceiver<Data>, tx: UnboundedSender<Data>) {
|
||||||
let mut current_id = 0;
|
let mut current_id = 0;
|
||||||
|
let mut write_jobs: Vec<fs::TransferJob> = Vec::new();
|
||||||
loop {
|
loop {
|
||||||
match rx.recv().await {
|
match rx.recv().await {
|
||||||
Some(Data::Login {
|
Some(Data::Login {
|
||||||
id,
|
id,
|
||||||
is_file_transfer,
|
is_file_transfer,
|
||||||
|
port_forward,
|
||||||
peer_id,
|
peer_id,
|
||||||
name,
|
name,
|
||||||
authorized,
|
authorized,
|
||||||
keyboard,
|
keyboard,
|
||||||
clipboard,
|
clipboard,
|
||||||
audio,
|
audio,
|
||||||
|
file,
|
||||||
|
restart,
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
current_id = id;
|
current_id = id;
|
||||||
let mut client = Client {
|
on_login(
|
||||||
id,
|
id,
|
||||||
authorized,
|
|
||||||
is_file_transfer,
|
is_file_transfer,
|
||||||
name: name.clone(),
|
port_forward,
|
||||||
peer_id: peer_id.clone(),
|
peer_id,
|
||||||
|
name,
|
||||||
|
authorized,
|
||||||
keyboard,
|
keyboard,
|
||||||
clipboard,
|
clipboard,
|
||||||
audio,
|
audio,
|
||||||
tx: tx.clone(),
|
file,
|
||||||
};
|
restart,
|
||||||
if authorized {
|
tx.clone(),
|
||||||
client.authorized = true;
|
);
|
||||||
let client_json = serde_json::to_string(&client).unwrap_or("".into());
|
|
||||||
// send to Android service,active notification no matter UI is shown or not.
|
|
||||||
#[cfg(any(target_os = "android"))]
|
|
||||||
if let Err(e) = call_main_service_set_by_name(
|
|
||||||
"on_client_authorized",
|
|
||||||
Some(&client_json),
|
|
||||||
None,
|
|
||||||
) {
|
|
||||||
log::debug!("call_service_set_by_name fail,{}", e);
|
|
||||||
}
|
|
||||||
// send to UI,refresh widget
|
|
||||||
push_event("on_client_authorized", vec![("client", &client_json)]);
|
|
||||||
} else {
|
|
||||||
let client_json = serde_json::to_string(&client).unwrap_or("".into());
|
|
||||||
// send to Android service,active notification no matter UI is shown or not.
|
|
||||||
#[cfg(any(target_os = "android"))]
|
|
||||||
if let Err(e) = call_main_service_set_by_name(
|
|
||||||
"try_start_without_auth",
|
|
||||||
Some(&client_json),
|
|
||||||
None,
|
|
||||||
) {
|
|
||||||
log::debug!("call_service_set_by_name fail,{}", e);
|
|
||||||
}
|
|
||||||
// send to UI,refresh widget
|
|
||||||
push_event("try_start_without_auth", vec![("client", &client_json)]);
|
|
||||||
}
|
|
||||||
CLIENTS.write().unwrap().insert(id, client);
|
|
||||||
}
|
}
|
||||||
Some(Data::ChatMessage { text }) => {
|
Some(Data::ChatMessage { text }) => {
|
||||||
handle_chat(current_id, text);
|
handle_chat(current_id, text);
|
||||||
}
|
}
|
||||||
Some(Data::FS(fs)) => {
|
Some(Data::FS(fs)) => {
|
||||||
handle_fs(fs, &tx).await;
|
handle_fs(fs, &mut write_jobs, &tx).await;
|
||||||
}
|
}
|
||||||
Some(Data::Close) => {
|
Some(Data::Close) => {
|
||||||
break;
|
break;
|
||||||
@ -1764,6 +1875,58 @@ pub mod connection_manager {
|
|||||||
remove_connection(current_id);
|
remove_connection(current_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_login(
|
||||||
|
id: i32,
|
||||||
|
is_file_transfer: bool,
|
||||||
|
_port_forward: String,
|
||||||
|
peer_id: String,
|
||||||
|
name: String,
|
||||||
|
authorized: bool,
|
||||||
|
keyboard: bool,
|
||||||
|
clipboard: bool,
|
||||||
|
audio: bool,
|
||||||
|
_file: bool,
|
||||||
|
_restart: bool,
|
||||||
|
tx: mpsc::UnboundedSender<Data>,
|
||||||
|
) {
|
||||||
|
let mut client = Client {
|
||||||
|
id,
|
||||||
|
authorized,
|
||||||
|
is_file_transfer,
|
||||||
|
name: name.clone(),
|
||||||
|
peer_id: peer_id.clone(),
|
||||||
|
keyboard,
|
||||||
|
clipboard,
|
||||||
|
audio,
|
||||||
|
tx,
|
||||||
|
};
|
||||||
|
if authorized {
|
||||||
|
client.authorized = true;
|
||||||
|
let client_json = serde_json::to_string(&client).unwrap_or("".into());
|
||||||
|
// send to Android service, active notification no matter UI is shown or not.
|
||||||
|
#[cfg(any(target_os = "android"))]
|
||||||
|
if let Err(e) =
|
||||||
|
call_main_service_set_by_name("on_client_authorized", Some(&client_json), None)
|
||||||
|
{
|
||||||
|
log::debug!("call_service_set_by_name fail,{}", e);
|
||||||
|
}
|
||||||
|
// send to UI, refresh widget
|
||||||
|
push_event("on_client_authorized", vec![("client", &client_json)]);
|
||||||
|
} else {
|
||||||
|
let client_json = serde_json::to_string(&client).unwrap_or("".into());
|
||||||
|
// send to Android service, active notification no matter UI is shown or not.
|
||||||
|
#[cfg(any(target_os = "android"))]
|
||||||
|
if let Err(e) =
|
||||||
|
call_main_service_set_by_name("try_start_without_auth", Some(&client_json), None)
|
||||||
|
{
|
||||||
|
log::debug!("call_service_set_by_name fail,{}", e);
|
||||||
|
}
|
||||||
|
// send to UI, refresh widget
|
||||||
|
push_event("try_start_without_auth", vec![("client", &client_json)]);
|
||||||
|
}
|
||||||
|
CLIENTS.write().unwrap().insert(id, client);
|
||||||
|
}
|
||||||
|
|
||||||
fn push_event(name: &str, event: Vec<(&str, &str)>) {
|
fn push_event(name: &str, event: Vec<(&str, &str)>) {
|
||||||
let mut h: HashMap<&str, &str> = event.iter().cloned().collect();
|
let mut h: HashMap<&str, &str> = event.iter().cloned().collect();
|
||||||
assert!(h.get("name").is_none());
|
assert!(h.get("name").is_none());
|
||||||
@ -1778,6 +1941,22 @@ pub mod connection_manager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_click_time() -> i64 {
|
||||||
|
CLICK_TIME.load(Ordering::SeqCst)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_click_time(id: i32) {
|
||||||
|
if let Some(client) = CLIENTS.read().unwrap().get(&id) {
|
||||||
|
allow_err!(client.tx.send(Data::ClickTime(0)));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn switch_permission(id: i32, name: String, enabled: bool) {
|
||||||
|
if let Some(client) = CLIENTS.read().unwrap().get(&id) {
|
||||||
|
allow_err!(client.tx.send(Data::SwitchPermission { name, enabled }));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_clients_state() -> String {
|
pub fn get_clients_state() -> String {
|
||||||
let clients = CLIENTS.read().unwrap();
|
let clients = CLIENTS.read().unwrap();
|
||||||
let res = Vec::from_iter(clients.values().cloned());
|
let res = Vec::from_iter(clients.values().cloned());
|
||||||
@ -1790,7 +1969,7 @@ pub mod connection_manager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn close_conn(id: i32) {
|
pub fn close_conn(id: i32) {
|
||||||
if let Some(client) = CLIENTS.write().unwrap().get(&id) {
|
if let Some(client) = CLIENTS.read().unwrap().get(&id) {
|
||||||
allow_err!(client.tx.send(Data::Close));
|
allow_err!(client.tx.send(Data::Close));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -1812,7 +1991,7 @@ pub mod connection_manager {
|
|||||||
|
|
||||||
if clients
|
if clients
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(k, v)| !v.is_file_transfer)
|
.filter(|(_k, v)| !v.is_file_transfer)
|
||||||
.next()
|
.next()
|
||||||
.is_none()
|
.is_none()
|
||||||
{
|
{
|
||||||
@ -1835,14 +2014,18 @@ pub mod connection_manager {
|
|||||||
|
|
||||||
// server mode send chat to peer
|
// server mode send chat to peer
|
||||||
pub fn send_chat(id: i32, text: String) {
|
pub fn send_chat(id: i32, text: String) {
|
||||||
let mut clients = CLIENTS.read().unwrap();
|
let clients = CLIENTS.read().unwrap();
|
||||||
if let Some(client) = clients.get(&id) {
|
if let Some(client) = clients.get(&id) {
|
||||||
allow_err!(client.tx.send(Data::ChatMessage { text }));
|
allow_err!(client.tx.send(Data::ChatMessage { text }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle FS server
|
// handle FS server
|
||||||
async fn handle_fs(fs: ipc::FS, tx: &UnboundedSender<Data>) {
|
async fn handle_fs(
|
||||||
|
fs: ipc::FS,
|
||||||
|
write_jobs: &mut Vec<fs::TransferJob>,
|
||||||
|
tx: &UnboundedSender<Data>,
|
||||||
|
) {
|
||||||
match fs {
|
match fs {
|
||||||
ipc::FS::ReadDir {
|
ipc::FS::ReadDir {
|
||||||
dir,
|
dir,
|
||||||
@ -1870,7 +2053,7 @@ pub mod connection_manager {
|
|||||||
mut files,
|
mut files,
|
||||||
overwrite_detection,
|
overwrite_detection,
|
||||||
} => {
|
} => {
|
||||||
WRITE_JOBS.lock().unwrap().push(fs::TransferJob::new_write(
|
write_jobs.push(fs::TransferJob::new_write(
|
||||||
id,
|
id,
|
||||||
"".to_string(),
|
"".to_string(),
|
||||||
path,
|
path,
|
||||||
@ -1889,14 +2072,12 @@ pub mod connection_manager {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
ipc::FS::CancelWrite { id } => {
|
ipc::FS::CancelWrite { id } => {
|
||||||
let write_jobs = &mut *WRITE_JOBS.lock().unwrap();
|
|
||||||
if let Some(job) = fs::get_job(id, write_jobs) {
|
if let Some(job) = fs::get_job(id, write_jobs) {
|
||||||
job.remove_download_file();
|
job.remove_download_file();
|
||||||
fs::remove_job(id, write_jobs);
|
fs::remove_job(id, write_jobs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ipc::FS::WriteDone { id, file_num } => {
|
ipc::FS::WriteDone { id, file_num } => {
|
||||||
let write_jobs = &mut *WRITE_JOBS.lock().unwrap();
|
|
||||||
if let Some(job) = fs::get_job(id, write_jobs) {
|
if let Some(job) = fs::get_job(id, write_jobs) {
|
||||||
job.modify_time();
|
job.modify_time();
|
||||||
send_raw(fs::new_done(id, file_num), tx);
|
send_raw(fs::new_done(id, file_num), tx);
|
||||||
@ -1909,7 +2090,7 @@ pub mod connection_manager {
|
|||||||
data,
|
data,
|
||||||
compressed,
|
compressed,
|
||||||
} => {
|
} => {
|
||||||
if let Some(job) = fs::get_job(id, &mut *WRITE_JOBS.lock().unwrap()) {
|
if let Some(job) = fs::get_job(id, write_jobs) {
|
||||||
if let Err(err) = job
|
if let Err(err) = job
|
||||||
.write(
|
.write(
|
||||||
FileTransferBlock {
|
FileTransferBlock {
|
||||||
@ -1934,7 +2115,7 @@ pub mod connection_manager {
|
|||||||
last_modified,
|
last_modified,
|
||||||
is_upload,
|
is_upload,
|
||||||
} => {
|
} => {
|
||||||
if let Some(job) = fs::get_job(id, &mut *WRITE_JOBS.lock().unwrap()) {
|
if let Some(job) = fs::get_job(id, write_jobs) {
|
||||||
let mut req = FileTransferSendConfirmRequest {
|
let mut req = FileTransferSendConfirmRequest {
|
||||||
id,
|
id,
|
||||||
file_num,
|
file_num,
|
||||||
|
@ -735,18 +735,37 @@ pub fn main_set_permanent_password(password: String) {
|
|||||||
set_permanent_password(password);
|
set_permanent_password(password);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn server_send_chat(conn_id: i32, msg: String) {
|
pub fn cm_send_chat(conn_id: i32, msg: String) {
|
||||||
connection_manager::send_chat(conn_id, msg);
|
connection_manager::send_chat(conn_id, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn server_login_res(conn_id: i32, res: bool) {
|
pub fn cm_login_res(conn_id: i32, res: bool) {
|
||||||
connection_manager::on_login_res(conn_id, res);
|
connection_manager::on_login_res(conn_id, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn server_close_connection(conn_id: i32) {
|
pub fn cm_close_connection(conn_id: i32) {
|
||||||
connection_manager::close_conn(conn_id);
|
connection_manager::close_conn(conn_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cm_check_click_time(conn_id: i32) {
|
||||||
|
connection_manager::check_click_time(conn_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cm_get_click_time() -> f64 {
|
||||||
|
connection_manager::get_click_time() as _
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cm_switch_permission(conn_id: i32, name: String, enabled: bool) {
|
||||||
|
connection_manager::switch_permission(conn_id, name, enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main_get_icon() -> String {
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))]
|
||||||
|
return ui_interface::get_icon();
|
||||||
|
#[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
unsafe extern "C" fn translate(name: *const c_char, locale: *const c_char) -> *const c_char {
|
unsafe extern "C" fn translate(name: *const c_char, locale: *const c_char) -> *const c_char {
|
||||||
let name = CStr::from_ptr(name);
|
let name = CStr::from_ptr(name);
|
||||||
|
155
src/ipc.rs
155
src/ipc.rs
@ -1,3 +1,7 @@
|
|||||||
|
#[cfg(windows)]
|
||||||
|
use clipboard::{
|
||||||
|
create_cliprdr_context, empty_clipboard, get_rx_clip_client, server_clip_file, set_conn_enabled,
|
||||||
|
};
|
||||||
use std::{collections::HashMap, sync::atomic::Ordering};
|
use std::{collections::HashMap, sync::atomic::Ordering};
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
use std::{fs::File, io::prelude::*};
|
use std::{fs::File, io::prelude::*};
|
||||||
@ -413,6 +417,157 @@ pub async fn connect(ms_timeout: u64, postfix: &str) -> ResultType<ConnectionTmp
|
|||||||
Ok(ConnectionTmpl::new(client))
|
Ok(ConnectionTmpl::new(client))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[tokio::main(flavor = "current_thread")]
|
||||||
|
pub async fn start_pa() {
|
||||||
|
use crate::audio_service::AUDIO_DATA_SIZE_U8;
|
||||||
|
|
||||||
|
match new_listener("_pa").await {
|
||||||
|
Ok(mut incoming) => {
|
||||||
|
loop {
|
||||||
|
if let Some(result) = incoming.next().await {
|
||||||
|
match result {
|
||||||
|
Ok(stream) => {
|
||||||
|
let mut stream = Connection::new(stream);
|
||||||
|
let mut device: String = "".to_owned();
|
||||||
|
if let Some(Ok(Some(Data::Config((_, Some(x)))))) =
|
||||||
|
stream.next_timeout2(1000).await
|
||||||
|
{
|
||||||
|
device = x;
|
||||||
|
}
|
||||||
|
if !device.is_empty() {
|
||||||
|
device = crate::platform::linux::get_pa_source_name(&device);
|
||||||
|
}
|
||||||
|
if device.is_empty() {
|
||||||
|
device = crate::platform::linux::get_pa_monitor();
|
||||||
|
}
|
||||||
|
if device.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let spec = pulse::sample::Spec {
|
||||||
|
format: pulse::sample::Format::F32le,
|
||||||
|
channels: 2,
|
||||||
|
rate: crate::platform::PA_SAMPLE_RATE,
|
||||||
|
};
|
||||||
|
log::info!("pa monitor: {:?}", device);
|
||||||
|
// systemctl --user status pulseaudio.service
|
||||||
|
let mut buf: Vec<u8> = vec![0; AUDIO_DATA_SIZE_U8];
|
||||||
|
match psimple::Simple::new(
|
||||||
|
None, // Use the default server
|
||||||
|
&crate::get_app_name(), // Our application’s name
|
||||||
|
pulse::stream::Direction::Record, // We want a record stream
|
||||||
|
Some(&device), // Use the default device
|
||||||
|
"record", // Description of our stream
|
||||||
|
&spec, // Our sample format
|
||||||
|
None, // Use default channel map
|
||||||
|
None, // Use default buffering attributes
|
||||||
|
) {
|
||||||
|
Ok(s) => loop {
|
||||||
|
if let Ok(_) = s.read(&mut buf) {
|
||||||
|
let out =
|
||||||
|
if buf.iter().filter(|x| **x != 0).next().is_none() {
|
||||||
|
vec![]
|
||||||
|
} else {
|
||||||
|
buf.clone()
|
||||||
|
};
|
||||||
|
if let Err(err) = stream.send_raw(out.into()).await {
|
||||||
|
log::error!("Failed to send audio data:{}", err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("Could not create simple pulse: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("Couldn't get pa client: {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("Failed to start pa ipc server: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
#[tokio::main(flavor = "current_thread")]
|
||||||
|
pub async fn start_clipboard_file(
|
||||||
|
cm: ConnectionManager,
|
||||||
|
mut rx: mpsc::UnboundedReceiver<ClipboardFileData>,
|
||||||
|
) {
|
||||||
|
let mut cliprdr_context = None;
|
||||||
|
let mut rx_clip_client = get_rx_clip_client().lock().await;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
clip_file = rx_clip_client.recv() => match clip_file {
|
||||||
|
Some((conn_id, clip)) => {
|
||||||
|
cmd_inner_send(
|
||||||
|
&cm,
|
||||||
|
conn_id,
|
||||||
|
Data::ClipbaordFile(clip)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
},
|
||||||
|
server_msg = rx.recv() => match server_msg {
|
||||||
|
Some(ClipboardFileData::Clip((conn_id, clip))) => {
|
||||||
|
if let Some(ctx) = cliprdr_context.as_mut() {
|
||||||
|
server_clip_file(ctx, conn_id, clip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(ClipboardFileData::Enable((id, enabled))) => {
|
||||||
|
if enabled && cliprdr_context.is_none() {
|
||||||
|
cliprdr_context = Some(match create_cliprdr_context(true, false) {
|
||||||
|
Ok(context) => {
|
||||||
|
log::info!("clipboard context for file transfer created.");
|
||||||
|
context
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::error!(
|
||||||
|
"Create clipboard context for file transfer: {}",
|
||||||
|
err.to_string()
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
set_conn_enabled(id, enabled);
|
||||||
|
if !enabled {
|
||||||
|
if let Some(ctx) = cliprdr_context.as_mut() {
|
||||||
|
empty_clipboard(ctx, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn cmd_inner_send(cm: &ConnectionManager, id: i32, data: Data) {
|
||||||
|
let lock = cm.read().unwrap();
|
||||||
|
if id != 0 {
|
||||||
|
if let Some(s) = lock.senders.get(&id) {
|
||||||
|
allow_err!(s.send(data));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for s in lock.senders.values() {
|
||||||
|
allow_err!(s.send(data.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
fn get_pid_file(postfix: &str) -> String {
|
fn get_pid_file(postfix: &str) -> String {
|
||||||
|
158
src/ui/cm.rs
158
src/ui/cm.rs
@ -1,9 +1,7 @@
|
|||||||
use crate::ipc::{self, new_listener, Connection, Data};
|
use crate::ipc::{self, new_listener, Connection, Data, start_pa};
|
||||||
use crate::VERSION;
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use clipboard::{
|
use crate::ipc::start_clipboard_file;
|
||||||
create_cliprdr_context, empty_clipboard, get_rx_clip_client, server_clip_file, set_conn_enabled,
|
use crate::VERSION;
|
||||||
};
|
|
||||||
use hbb_common::fs::{
|
use hbb_common::fs::{
|
||||||
can_enable_overwrite_detection, get_string, is_write_need_confirmation, new_send_confirm,
|
can_enable_overwrite_detection, get_string, is_write_need_confirmation, new_send_confirm,
|
||||||
DigestCheckResult,
|
DigestCheckResult,
|
||||||
@ -539,153 +537,3 @@ async fn start_ipc(cm: ConnectionManager) {
|
|||||||
crate::platform::quit_gui();
|
crate::platform::quit_gui();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
|
||||||
async fn start_pa() {
|
|
||||||
use crate::audio_service::AUDIO_DATA_SIZE_U8;
|
|
||||||
|
|
||||||
match new_listener("_pa").await {
|
|
||||||
Ok(mut incoming) => {
|
|
||||||
loop {
|
|
||||||
if let Some(result) = incoming.next().await {
|
|
||||||
match result {
|
|
||||||
Ok(stream) => {
|
|
||||||
let mut stream = Connection::new(stream);
|
|
||||||
let mut device: String = "".to_owned();
|
|
||||||
if let Some(Ok(Some(Data::Config((_, Some(x)))))) =
|
|
||||||
stream.next_timeout2(1000).await
|
|
||||||
{
|
|
||||||
device = x;
|
|
||||||
}
|
|
||||||
if !device.is_empty() {
|
|
||||||
device = crate::platform::linux::get_pa_source_name(&device);
|
|
||||||
}
|
|
||||||
if device.is_empty() {
|
|
||||||
device = crate::platform::linux::get_pa_monitor();
|
|
||||||
}
|
|
||||||
if device.is_empty() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let spec = pulse::sample::Spec {
|
|
||||||
format: pulse::sample::Format::F32le,
|
|
||||||
channels: 2,
|
|
||||||
rate: crate::platform::PA_SAMPLE_RATE,
|
|
||||||
};
|
|
||||||
log::info!("pa monitor: {:?}", device);
|
|
||||||
// systemctl --user status pulseaudio.service
|
|
||||||
let mut buf: Vec<u8> = vec![0; AUDIO_DATA_SIZE_U8];
|
|
||||||
match psimple::Simple::new(
|
|
||||||
None, // Use the default server
|
|
||||||
&crate::get_app_name(), // Our application’s name
|
|
||||||
pulse::stream::Direction::Record, // We want a record stream
|
|
||||||
Some(&device), // Use the default device
|
|
||||||
"record", // Description of our stream
|
|
||||||
&spec, // Our sample format
|
|
||||||
None, // Use default channel map
|
|
||||||
None, // Use default buffering attributes
|
|
||||||
) {
|
|
||||||
Ok(s) => loop {
|
|
||||||
if let Ok(_) = s.read(&mut buf) {
|
|
||||||
let out =
|
|
||||||
if buf.iter().filter(|x| **x != 0).next().is_none() {
|
|
||||||
vec![]
|
|
||||||
} else {
|
|
||||||
buf.clone()
|
|
||||||
};
|
|
||||||
if let Err(err) = stream.send_raw(out.into()).await {
|
|
||||||
log::error!("Failed to send audio data:{}", err);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(err) => {
|
|
||||||
log::error!("Could not create simple pulse: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
log::error!("Couldn't get pa client: {:?}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
log::error!("Failed to start pa ipc server: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
|
||||||
async fn start_clipboard_file(
|
|
||||||
cm: ConnectionManager,
|
|
||||||
mut rx: mpsc::UnboundedReceiver<ClipboardFileData>,
|
|
||||||
) {
|
|
||||||
let mut cliprdr_context = None;
|
|
||||||
let mut rx_clip_client = get_rx_clip_client().lock().await;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
tokio::select! {
|
|
||||||
clip_file = rx_clip_client.recv() => match clip_file {
|
|
||||||
Some((conn_id, clip)) => {
|
|
||||||
cmd_inner_send(
|
|
||||||
&cm,
|
|
||||||
conn_id,
|
|
||||||
Data::ClipbaordFile(clip)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
},
|
|
||||||
server_msg = rx.recv() => match server_msg {
|
|
||||||
Some(ClipboardFileData::Clip((conn_id, clip))) => {
|
|
||||||
if let Some(ctx) = cliprdr_context.as_mut() {
|
|
||||||
server_clip_file(ctx, conn_id, clip);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(ClipboardFileData::Enable((id, enabled))) => {
|
|
||||||
if enabled && cliprdr_context.is_none() {
|
|
||||||
cliprdr_context = Some(match create_cliprdr_context(true, false) {
|
|
||||||
Ok(context) => {
|
|
||||||
log::info!("clipboard context for file transfer created.");
|
|
||||||
context
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
log::error!(
|
|
||||||
"Create clipboard context for file transfer: {}",
|
|
||||||
err.to_string()
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
set_conn_enabled(id, enabled);
|
|
||||||
if !enabled {
|
|
||||||
if let Some(ctx) = cliprdr_context.as_mut() {
|
|
||||||
empty_clipboard(ctx, id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
fn cmd_inner_send(cm: &ConnectionManager, id: i32, data: Data) {
|
|
||||||
let lock = cm.read().unwrap();
|
|
||||||
if id != 0 {
|
|
||||||
if let Some(s) = lock.senders.get(&id) {
|
|
||||||
allow_err!(s.send(data));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for s in lock.senders.values() {
|
|
||||||
allow_err!(s.send(data.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user