mirror of
https://github.com/rustdesk/rustdesk.git
synced 2025-01-08 15:27:56 +08:00
f7e9057a39
Signed-off-by: dignow <linlong1265@gmail.com>
2068 lines
66 KiB
Rust
2068 lines
66 KiB
Rust
use crate::{
|
|
client::*,
|
|
flutter_ffi::{EventToUI, SessionID},
|
|
ui_session_interface::{io_loop, InvokeUiSession, Session},
|
|
};
|
|
use flutter_rust_bridge::StreamSink;
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
use hbb_common::dlopen::{
|
|
symbor::{Library, Symbol},
|
|
Error as LibError,
|
|
};
|
|
use hbb_common::{
|
|
anyhow::anyhow, bail, config::LocalConfig, get_version_number, log, message_proto::*,
|
|
rendezvous_proto::ConnType, ResultType,
|
|
};
|
|
use serde::Serialize;
|
|
use serde_json::json;
|
|
|
|
use std::{
|
|
collections::HashMap,
|
|
ffi::CString,
|
|
os::raw::{c_char, c_int, c_void},
|
|
str::FromStr,
|
|
sync::{
|
|
atomic::{AtomicBool, Ordering},
|
|
Arc, RwLock,
|
|
},
|
|
};
|
|
|
|
/// tag "main" for [Desktop Main Page] and [Mobile (Client and Server)] (the mobile don't need multiple windows, only one global event stream is needed)
|
|
/// tag "cm" only for [Desktop CM Page]
|
|
pub(crate) const APP_TYPE_MAIN: &str = "main";
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
pub(crate) const APP_TYPE_CM: &str = "cm";
|
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
|
pub(crate) const APP_TYPE_CM: &str = "main";
|
|
|
|
// Do not remove the following constants.
|
|
// Uncomment them when they are used.
|
|
// pub(crate) const APP_TYPE_DESKTOP_REMOTE: &str = "remote";
|
|
// pub(crate) const APP_TYPE_DESKTOP_FILE_TRANSFER: &str = "file transfer";
|
|
// pub(crate) const APP_TYPE_DESKTOP_PORT_FORWARD: &str = "port forward";
|
|
|
|
pub type FlutterSession = Arc<Session<FlutterHandler>>;
|
|
|
|
lazy_static::lazy_static! {
|
|
pub(crate) static ref CUR_SESSION_ID: RwLock<SessionID> = Default::default(); // For desktop only
|
|
static ref GLOBAL_EVENT_STREAM: RwLock<HashMap<String, StreamSink<String>>> = Default::default(); // rust to dart event channel
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
lazy_static::lazy_static! {
|
|
pub static ref TEXTURE_RGBA_RENDERER_PLUGIN: Result<Library, LibError> = Library::open("texture_rgba_renderer_plugin.dll");
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
lazy_static::lazy_static! {
|
|
pub static ref TEXTURE_RGBA_RENDERER_PLUGIN: Result<Library, LibError> = Library::open("libtexture_rgba_renderer_plugin.so");
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
lazy_static::lazy_static! {
|
|
pub static ref TEXTURE_RGBA_RENDERER_PLUGIN: Result<Library, LibError> = Library::open_self();
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
lazy_static::lazy_static! {
|
|
pub static ref TEXTURE_GPU_RENDERER_PLUGIN: Result<Library, LibError> = Library::open("flutter_gpu_texture_renderer_plugin.dll");
|
|
}
|
|
|
|
/// FFI for rustdesk core's main entry.
|
|
/// Return true if the app should continue running with UI(possibly Flutter), false if the app should exit.
|
|
#[cfg(not(windows))]
|
|
#[no_mangle]
|
|
pub extern "C" fn rustdesk_core_main() -> bool {
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
if crate::core_main::core_main().is_some() {
|
|
return true;
|
|
} else {
|
|
#[cfg(target_os = "macos")]
|
|
std::process::exit(0);
|
|
}
|
|
false
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
#[no_mangle]
|
|
pub extern "C" fn handle_applicationShouldOpenUntitledFile() {
|
|
crate::platform::macos::handle_application_should_open_untitled_file();
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
#[no_mangle]
|
|
pub extern "C" fn rustdesk_core_main_args(args_len: *mut c_int) -> *mut *mut c_char {
|
|
unsafe { std::ptr::write(args_len, 0) };
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
{
|
|
if let Some(args) = crate::core_main::core_main() {
|
|
return rust_args_to_c_args(args, args_len);
|
|
}
|
|
return std::ptr::null_mut() as _;
|
|
}
|
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
|
return std::ptr::null_mut() as _;
|
|
}
|
|
|
|
// https://gist.github.com/iskakaushik/1c5b8aa75c77479c33c4320913eebef6
|
|
#[cfg(windows)]
|
|
fn rust_args_to_c_args(args: Vec<String>, outlen: *mut c_int) -> *mut *mut c_char {
|
|
let mut v = vec![];
|
|
|
|
// Let's fill a vector with null-terminated strings
|
|
for s in args {
|
|
match CString::new(s) {
|
|
Ok(s) => v.push(s),
|
|
Err(_) => return std::ptr::null_mut() as _,
|
|
}
|
|
}
|
|
|
|
// Turning each null-terminated string into a pointer.
|
|
// `into_raw` takes ownershop, gives us the pointer and does NOT drop the data.
|
|
let mut out = v.into_iter().map(|s| s.into_raw()).collect::<Vec<_>>();
|
|
|
|
// Make sure we're not wasting space.
|
|
out.shrink_to_fit();
|
|
debug_assert!(out.len() == out.capacity());
|
|
|
|
// Get the pointer to our vector.
|
|
let len = out.len();
|
|
let ptr = out.as_mut_ptr();
|
|
std::mem::forget(out);
|
|
|
|
// Let's write back the length the caller can expect
|
|
unsafe { std::ptr::write(outlen, len as c_int) };
|
|
|
|
// Finally return the data
|
|
ptr
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn free_c_args(ptr: *mut *mut c_char, len: c_int) {
|
|
let len = len as usize;
|
|
|
|
// Get back our vector.
|
|
// Previously we shrank to fit, so capacity == length.
|
|
let v = Vec::from_raw_parts(ptr, len, len);
|
|
|
|
// Now drop one string at a time.
|
|
for elem in v {
|
|
let s = CString::from_raw(elem);
|
|
std::mem::drop(s);
|
|
}
|
|
|
|
// Afterwards the vector will be dropped and thus freed.
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn get_rustdesk_app_name(buffer: *mut u16, length: i32) -> i32 {
|
|
let name = crate::platform::wide_string(&crate::get_app_name());
|
|
if length > name.len() as i32 {
|
|
std::ptr::copy_nonoverlapping(name.as_ptr(), buffer, name.len());
|
|
return 0;
|
|
}
|
|
-1
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct SessionHandler {
|
|
event_stream: Option<StreamSink<EventToUI>>,
|
|
// displays of current session.
|
|
// We need this variable to check if the display is in use before pushing rgba to flutter.
|
|
displays: Vec<usize>,
|
|
renderer: VideoRenderer,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
|
enum RenderType {
|
|
PixelBuffer,
|
|
#[cfg(feature = "vram")]
|
|
Texture,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct FlutterHandler {
|
|
// ui session id -> display handler data
|
|
session_handlers: Arc<RwLock<HashMap<SessionID, SessionHandler>>>,
|
|
display_rgbas: Arc<RwLock<HashMap<usize, RgbaData>>>,
|
|
peer_info: Arc<RwLock<PeerInfo>>,
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
hooks: Arc<RwLock<HashMap<String, SessionHook>>>,
|
|
use_texture_render: Arc<AtomicBool>,
|
|
}
|
|
|
|
impl Default for FlutterHandler {
|
|
fn default() -> Self {
|
|
Self {
|
|
session_handlers: Default::default(),
|
|
display_rgbas: Default::default(),
|
|
peer_info: Default::default(),
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
hooks: Default::default(),
|
|
use_texture_render: Arc::new(
|
|
AtomicBool::new(crate::ui_interface::use_texture_render()),
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Clone)]
|
|
struct RgbaData {
|
|
// SAFETY: [rgba] is guarded by [rgba_valid], and it's safe to reach [rgba] with `rgba_valid == true`.
|
|
// We must check the `rgba_valid` before reading [rgba].
|
|
data: Vec<u8>,
|
|
valid: bool,
|
|
}
|
|
|
|
pub type FlutterRgbaRendererPluginOnRgba = unsafe extern "C" fn(
|
|
texture_rgba: *mut c_void,
|
|
buffer: *const u8,
|
|
len: c_int,
|
|
width: c_int,
|
|
height: c_int,
|
|
dst_rgba_stride: c_int,
|
|
);
|
|
|
|
#[cfg(feature = "vram")]
|
|
pub type FlutterGpuTextureRendererPluginCApiSetTexture =
|
|
unsafe extern "C" fn(output: *mut c_void, texture: *mut c_void);
|
|
|
|
#[cfg(feature = "vram")]
|
|
pub type FlutterGpuTextureRendererPluginCApiGetAdapterLuid = unsafe extern "C" fn() -> i64;
|
|
|
|
pub(super) type TextureRgbaPtr = usize;
|
|
|
|
struct DisplaySessionInfo {
|
|
// TextureRgba pointer in flutter native.
|
|
texture_rgba_ptr: TextureRgbaPtr,
|
|
size: (usize, usize),
|
|
#[cfg(feature = "vram")]
|
|
gpu_output_ptr: usize,
|
|
notify_render_type: Option<RenderType>,
|
|
}
|
|
|
|
// Video Texture Renderer in Flutter
|
|
#[derive(Clone)]
|
|
struct VideoRenderer {
|
|
is_support_multi_ui_session: bool,
|
|
map_display_sessions: Arc<RwLock<HashMap<usize, DisplaySessionInfo>>>,
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
on_rgba_func: Option<Symbol<'static, FlutterRgbaRendererPluginOnRgba>>,
|
|
#[cfg(feature = "vram")]
|
|
on_texture_func: Option<Symbol<'static, FlutterGpuTextureRendererPluginCApiSetTexture>>,
|
|
}
|
|
|
|
impl Default for VideoRenderer {
|
|
fn default() -> Self {
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
let on_rgba_func = match &*TEXTURE_RGBA_RENDERER_PLUGIN {
|
|
Ok(lib) => {
|
|
let find_sym_res = unsafe {
|
|
lib.symbol::<FlutterRgbaRendererPluginOnRgba>("FlutterRgbaRendererPluginOnRgba")
|
|
};
|
|
match find_sym_res {
|
|
Ok(sym) => Some(sym),
|
|
Err(e) => {
|
|
log::error!("Failed to find symbol FlutterRgbaRendererPluginOnRgba, {e}");
|
|
None
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
log::error!("Failed to load texture rgba renderer plugin, {e}");
|
|
None
|
|
}
|
|
};
|
|
#[cfg(feature = "vram")]
|
|
let on_texture_func = match &*TEXTURE_GPU_RENDERER_PLUGIN {
|
|
Ok(lib) => {
|
|
let find_sym_res = unsafe {
|
|
lib.symbol::<FlutterGpuTextureRendererPluginCApiSetTexture>(
|
|
"FlutterGpuTextureRendererPluginCApiSetTexture",
|
|
)
|
|
};
|
|
match find_sym_res {
|
|
Ok(sym) => Some(sym),
|
|
Err(e) => {
|
|
log::error!("Failed to find symbol FlutterGpuTextureRendererPluginCApiSetTexture, {e}");
|
|
None
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
log::error!("Failed to load texture gpu renderer plugin, {e}");
|
|
None
|
|
}
|
|
};
|
|
|
|
Self {
|
|
map_display_sessions: Default::default(),
|
|
is_support_multi_ui_session: false,
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
on_rgba_func,
|
|
#[cfg(feature = "vram")]
|
|
on_texture_func,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl VideoRenderer {
|
|
#[inline]
|
|
fn set_size(&mut self, display: usize, width: usize, height: usize) {
|
|
let mut sessions_lock = self.map_display_sessions.write().unwrap();
|
|
if let Some(info) = sessions_lock.get_mut(&display) {
|
|
info.size = (width, height);
|
|
info.notify_render_type = None;
|
|
} else {
|
|
sessions_lock.insert(
|
|
display,
|
|
DisplaySessionInfo {
|
|
texture_rgba_ptr: usize::default(),
|
|
size: (width, height),
|
|
#[cfg(feature = "vram")]
|
|
gpu_output_ptr: usize::default(),
|
|
notify_render_type: None,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
fn register_pixelbuffer_texture(&self, display: usize, ptr: usize) {
|
|
let mut sessions_lock = self.map_display_sessions.write().unwrap();
|
|
if ptr == 0 {
|
|
if let Some(info) = sessions_lock.get_mut(&display) {
|
|
if info.texture_rgba_ptr != usize::default() {
|
|
info.texture_rgba_ptr = usize::default();
|
|
}
|
|
#[cfg(feature = "vram")]
|
|
if info.gpu_output_ptr != usize::default() {
|
|
return;
|
|
}
|
|
}
|
|
sessions_lock.remove(&display);
|
|
} else {
|
|
if let Some(info) = sessions_lock.get_mut(&display) {
|
|
if info.texture_rgba_ptr != usize::default()
|
|
&& info.texture_rgba_ptr != ptr as TextureRgbaPtr
|
|
{
|
|
log::warn!(
|
|
"texture_rgba_ptr is not null and not equal to ptr, replace {} to {}",
|
|
info.texture_rgba_ptr,
|
|
ptr
|
|
);
|
|
}
|
|
info.texture_rgba_ptr = ptr as _;
|
|
info.notify_render_type = None;
|
|
} else {
|
|
if ptr != 0 {
|
|
sessions_lock.insert(
|
|
display,
|
|
DisplaySessionInfo {
|
|
texture_rgba_ptr: ptr as _,
|
|
size: (0, 0),
|
|
#[cfg(feature = "vram")]
|
|
gpu_output_ptr: usize::default(),
|
|
notify_render_type: None,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "vram")]
|
|
pub fn register_gpu_output(&self, display: usize, ptr: usize) {
|
|
let mut sessions_lock = self.map_display_sessions.write().unwrap();
|
|
if ptr == 0 {
|
|
if let Some(info) = sessions_lock.get_mut(&display) {
|
|
if info.gpu_output_ptr != usize::default() {
|
|
info.gpu_output_ptr = usize::default();
|
|
}
|
|
if info.texture_rgba_ptr != usize::default() {
|
|
return;
|
|
}
|
|
}
|
|
sessions_lock.remove(&display);
|
|
} else {
|
|
if let Some(info) = sessions_lock.get_mut(&display) {
|
|
if info.gpu_output_ptr != usize::default() && info.gpu_output_ptr != ptr {
|
|
log::error!(
|
|
"gpu_output_ptr is not null and not equal to ptr, relace {} to {}",
|
|
info.gpu_output_ptr,
|
|
ptr
|
|
);
|
|
}
|
|
info.gpu_output_ptr = ptr as _;
|
|
info.notify_render_type = None;
|
|
} else {
|
|
if ptr != usize::default() {
|
|
sessions_lock.insert(
|
|
display,
|
|
DisplaySessionInfo {
|
|
texture_rgba_ptr: usize::default(),
|
|
size: (0, 0),
|
|
gpu_output_ptr: ptr,
|
|
notify_render_type: None,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
pub fn on_rgba(&self, display: usize, rgba: &scrap::ImageRgb) -> bool {
|
|
let mut write_lock = self.map_display_sessions.write().unwrap();
|
|
let opt_info = if !self.is_support_multi_ui_session {
|
|
write_lock.values_mut().next()
|
|
} else {
|
|
write_lock.get_mut(&display)
|
|
};
|
|
let Some(info) = opt_info else {
|
|
return false;
|
|
};
|
|
if info.texture_rgba_ptr == usize::default() {
|
|
return false;
|
|
}
|
|
|
|
if info.size.0 != rgba.w || info.size.1 != rgba.h {
|
|
log::error!(
|
|
"width/height mismatch: ({},{}) != ({},{})",
|
|
info.size.0,
|
|
info.size.1,
|
|
rgba.w,
|
|
rgba.h
|
|
);
|
|
// Peer info's handling is async and may be late than video frame's handling
|
|
// Allow peer info not set, but not allow wrong width/height for correct local cursor position
|
|
if info.size != (0, 0) {
|
|
return false;
|
|
}
|
|
}
|
|
if let Some(func) = &self.on_rgba_func {
|
|
unsafe {
|
|
func(
|
|
info.texture_rgba_ptr as _,
|
|
rgba.raw.as_ptr() as _,
|
|
rgba.raw.len() as _,
|
|
rgba.w as _,
|
|
rgba.h as _,
|
|
rgba.align() as _,
|
|
)
|
|
};
|
|
}
|
|
if info.notify_render_type != Some(RenderType::PixelBuffer) {
|
|
info.notify_render_type = Some(RenderType::PixelBuffer);
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "vram")]
|
|
pub fn on_texture(&self, display: usize, texture: *mut c_void) -> bool {
|
|
let mut write_lock = self.map_display_sessions.write().unwrap();
|
|
let opt_info = if !self.is_support_multi_ui_session {
|
|
write_lock.values_mut().next()
|
|
} else {
|
|
write_lock.get_mut(&display)
|
|
};
|
|
let Some(info) = opt_info else {
|
|
return false;
|
|
};
|
|
if info.gpu_output_ptr == usize::default() {
|
|
return false;
|
|
}
|
|
if let Some(func) = &self.on_texture_func {
|
|
unsafe { func(info.gpu_output_ptr as _, texture) };
|
|
}
|
|
if info.notify_render_type != Some(RenderType::Texture) {
|
|
info.notify_render_type = Some(RenderType::Texture);
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
pub fn reset_all_display_render_type(&self) {
|
|
let mut write_lock = self.map_display_sessions.write().unwrap();
|
|
write_lock
|
|
.values_mut()
|
|
.map(|v| v.notify_render_type = None)
|
|
.count();
|
|
}
|
|
}
|
|
|
|
impl SessionHandler {
|
|
pub fn on_waiting_for_image_dialog_show(&self) {
|
|
self.renderer.reset_all_display_render_type();
|
|
// rgba array render will notify every frame
|
|
}
|
|
}
|
|
|
|
impl FlutterHandler {
|
|
/// Push an event to all the event queues.
|
|
/// An event is stored as json in the event queues.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `name` - The name of the event.
|
|
/// * `event` - Fields of the event content.
|
|
pub fn push_event<V>(&self, name: &str, event: &[(&str, V)], excludes: &[&SessionID])
|
|
where
|
|
V: Sized + Serialize + Clone,
|
|
{
|
|
let mut h: HashMap<&str, serde_json::Value> =
|
|
event.iter().map(|(k, v)| (*k, json!(*v))).collect();
|
|
debug_assert!(h.get("name").is_none());
|
|
h.insert("name", json!(name));
|
|
let out = serde_json::ser::to_string(&h).unwrap_or("".to_owned());
|
|
for (sid, session) in self.session_handlers.read().unwrap().iter() {
|
|
if excludes.contains(&sid) {
|
|
continue;
|
|
}
|
|
if let Some(stream) = &session.event_stream {
|
|
stream.add(EventToUI::Event(out.clone()));
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn close_event_stream(&self, session_id: SessionID) {
|
|
// to-do: Make sure the following logic is correct.
|
|
// No need to remove the display handler, because it will be removed when the connection is closed.
|
|
if let Some(session) = self.session_handlers.write().unwrap().get_mut(&session_id) {
|
|
try_send_close_event(&session.event_stream);
|
|
}
|
|
}
|
|
|
|
fn make_displays_msg(displays: &Vec<DisplayInfo>) -> String {
|
|
let mut msg_vec = Vec::new();
|
|
for ref d in displays.iter() {
|
|
let mut h: HashMap<&str, i32> = Default::default();
|
|
h.insert("x", d.x);
|
|
h.insert("y", d.y);
|
|
h.insert("width", d.width);
|
|
h.insert("height", d.height);
|
|
h.insert("cursor_embedded", if d.cursor_embedded { 1 } else { 0 });
|
|
if let Some(original_resolution) = d.original_resolution.as_ref() {
|
|
h.insert("original_width", original_resolution.width);
|
|
h.insert("original_height", original_resolution.height);
|
|
}
|
|
h.insert("scale", (d.scale * 100.0f64) as i32);
|
|
msg_vec.push(h);
|
|
}
|
|
serde_json::ser::to_string(&msg_vec).unwrap_or("".to_owned())
|
|
}
|
|
|
|
#[cfg(feature = "plugin_framework")]
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
pub(crate) fn add_session_hook(&self, key: String, hook: SessionHook) -> bool {
|
|
let mut hooks = self.hooks.write().unwrap();
|
|
if hooks.contains_key(&key) {
|
|
// Already has the hook with this key.
|
|
return false;
|
|
}
|
|
let _ = hooks.insert(key, hook);
|
|
true
|
|
}
|
|
|
|
#[cfg(feature = "plugin_framework")]
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
pub(crate) fn remove_session_hook(&self, key: &String) -> bool {
|
|
let mut hooks = self.hooks.write().unwrap();
|
|
if !hooks.contains_key(key) {
|
|
// The hook with this key does not found.
|
|
return false;
|
|
}
|
|
let _ = hooks.remove(key);
|
|
true
|
|
}
|
|
|
|
pub fn update_use_texture_render(&self) {
|
|
self.use_texture_render
|
|
.store(crate::ui_interface::use_texture_render(), Ordering::Relaxed);
|
|
self.display_rgbas.write().unwrap().clear();
|
|
}
|
|
}
|
|
|
|
impl InvokeUiSession for FlutterHandler {
|
|
fn set_cursor_data(&self, cd: CursorData) {
|
|
let colors = hbb_common::compress::decompress(&cd.colors);
|
|
self.push_event(
|
|
"cursor_data",
|
|
&[
|
|
("id", &cd.id.to_string()),
|
|
("hotx", &cd.hotx.to_string()),
|
|
("hoty", &cd.hoty.to_string()),
|
|
("width", &cd.width.to_string()),
|
|
("height", &cd.height.to_string()),
|
|
(
|
|
"colors",
|
|
&serde_json::ser::to_string(&colors).unwrap_or("".to_owned()),
|
|
),
|
|
],
|
|
&[],
|
|
);
|
|
}
|
|
|
|
fn set_cursor_id(&self, id: String) {
|
|
self.push_event("cursor_id", &[("id", &id.to_string())], &[]);
|
|
}
|
|
|
|
fn set_cursor_position(&self, cp: CursorPosition) {
|
|
self.push_event(
|
|
"cursor_position",
|
|
&[("x", &cp.x.to_string()), ("y", &cp.y.to_string())],
|
|
&[],
|
|
);
|
|
}
|
|
|
|
/// unused in flutter, use switch_display or set_peer_info
|
|
fn set_display(&self, _x: i32, _y: i32, _w: i32, _h: i32, _cursor_embedded: bool) {}
|
|
|
|
fn update_privacy_mode(&self) {
|
|
self.push_event::<&str>("update_privacy_mode", &[], &[]);
|
|
}
|
|
|
|
fn set_permission(&self, name: &str, value: bool) {
|
|
self.push_event("permission", &[(name, &value.to_string())], &[]);
|
|
}
|
|
|
|
// unused in flutter
|
|
fn close_success(&self) {}
|
|
|
|
fn update_quality_status(&self, status: QualityStatus) {
|
|
const NULL: String = String::new();
|
|
self.push_event(
|
|
"update_quality_status",
|
|
&[
|
|
("speed", &status.speed.map_or(NULL, |it| it)),
|
|
(
|
|
"fps",
|
|
&serde_json::ser::to_string(&status.fps).unwrap_or(NULL.to_owned()),
|
|
),
|
|
("delay", &status.delay.map_or(NULL, |it| it.to_string())),
|
|
(
|
|
"target_bitrate",
|
|
&status.target_bitrate.map_or(NULL, |it| it.to_string()),
|
|
),
|
|
(
|
|
"codec_format",
|
|
&status.codec_format.map_or(NULL, |it| it.to_string()),
|
|
),
|
|
("chroma", &status.chroma.map_or(NULL, |it| it.to_string())),
|
|
],
|
|
&[],
|
|
);
|
|
}
|
|
|
|
fn set_connection_type(&self, is_secured: bool, direct: bool) {
|
|
self.push_event(
|
|
"connection_ready",
|
|
&[
|
|
("secure", &is_secured.to_string()),
|
|
("direct", &direct.to_string()),
|
|
],
|
|
&[],
|
|
);
|
|
}
|
|
|
|
fn set_fingerprint(&self, fingerprint: String) {
|
|
self.push_event("fingerprint", &[("fingerprint", &fingerprint)], &[]);
|
|
}
|
|
|
|
fn job_error(&self, id: i32, err: String, file_num: i32) {
|
|
self.push_event(
|
|
"job_error",
|
|
&[
|
|
("id", &id.to_string()),
|
|
("err", &err),
|
|
("file_num", &file_num.to_string()),
|
|
],
|
|
&[],
|
|
);
|
|
}
|
|
|
|
fn job_done(&self, id: i32, file_num: i32) {
|
|
self.push_event(
|
|
"job_done",
|
|
&[("id", &id.to_string()), ("file_num", &file_num.to_string())],
|
|
&[],
|
|
);
|
|
}
|
|
|
|
// unused in flutter
|
|
fn clear_all_jobs(&self) {}
|
|
|
|
fn load_last_job(&self, _cnt: i32, job_json: &str) {
|
|
self.push_event("load_last_job", &[("value", job_json)], &[]);
|
|
}
|
|
|
|
fn update_folder_files(
|
|
&self,
|
|
id: i32,
|
|
entries: &Vec<FileEntry>,
|
|
path: String,
|
|
#[allow(unused_variables)] is_local: bool,
|
|
only_count: bool,
|
|
) {
|
|
// TODO opt
|
|
if only_count {
|
|
self.push_event(
|
|
"update_folder_files",
|
|
&[("info", &make_fd_flutter(id, entries, only_count))],
|
|
&[],
|
|
);
|
|
} else {
|
|
self.push_event(
|
|
"file_dir",
|
|
&[
|
|
("is_local", "false"),
|
|
("value", &crate::common::make_fd_to_json(id, path, entries)),
|
|
],
|
|
&[],
|
|
);
|
|
}
|
|
}
|
|
|
|
// unused in flutter
|
|
fn update_transfer_list(&self) {}
|
|
|
|
// unused in flutter // TEST flutter
|
|
fn confirm_delete_files(&self, _id: i32, _i: i32, _name: String) {}
|
|
|
|
fn override_file_confirm(
|
|
&self,
|
|
id: i32,
|
|
file_num: i32,
|
|
to: String,
|
|
is_upload: bool,
|
|
is_identical: bool,
|
|
) {
|
|
self.push_event(
|
|
"override_file_confirm",
|
|
&[
|
|
("id", &id.to_string()),
|
|
("file_num", &file_num.to_string()),
|
|
("read_path", &to),
|
|
("is_upload", &is_upload.to_string()),
|
|
("is_identical", &is_identical.to_string()),
|
|
],
|
|
&[],
|
|
);
|
|
}
|
|
|
|
fn job_progress(&self, id: i32, file_num: i32, speed: f64, finished_size: f64) {
|
|
self.push_event(
|
|
"job_progress",
|
|
&[
|
|
("id", &id.to_string()),
|
|
("file_num", &file_num.to_string()),
|
|
("speed", &speed.to_string()),
|
|
("finished_size", &finished_size.to_string()),
|
|
],
|
|
&[],
|
|
);
|
|
}
|
|
|
|
// unused in flutter
|
|
fn adapt_size(&self) {}
|
|
|
|
#[inline]
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
fn on_rgba(&self, display: usize, rgba: &mut scrap::ImageRgb) {
|
|
let use_texture_render = self.use_texture_render.load(Ordering::Relaxed);
|
|
self.on_rgba_flutter_texture_render(use_texture_render, display, rgba);
|
|
if !use_texture_render {
|
|
self.on_rgba_soft_render(display, rgba);
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
|
fn on_rgba(&self, display: usize, rgba: &mut scrap::ImageRgb) {
|
|
self.on_rgba_soft_render(display, rgba);
|
|
}
|
|
|
|
#[inline]
|
|
#[cfg(feature = "vram")]
|
|
fn on_texture(&self, display: usize, texture: *mut c_void) {
|
|
if !self.use_texture_render.load(Ordering::Relaxed) {
|
|
return;
|
|
}
|
|
for (_, session) in self.session_handlers.read().unwrap().iter() {
|
|
if session.renderer.on_texture(display, texture) {
|
|
if let Some(stream) = &session.event_stream {
|
|
stream.add(EventToUI::Texture(display, true));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn set_peer_info(&self, pi: &PeerInfo) {
|
|
let displays = Self::make_displays_msg(&pi.displays);
|
|
let mut features: HashMap<&str, i32> = Default::default();
|
|
for ref f in pi.features.iter() {
|
|
features.insert("privacy_mode", if f.privacy_mode { 1 } else { 0 });
|
|
}
|
|
// compatible with 1.1.9
|
|
if get_version_number(&pi.version) < get_version_number("1.2.0") {
|
|
features.insert("privacy_mode", 0);
|
|
}
|
|
let features = serde_json::ser::to_string(&features).unwrap_or("".to_owned());
|
|
let resolutions = serialize_resolutions(&pi.resolutions.resolutions);
|
|
*self.peer_info.write().unwrap() = pi.clone();
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
let is_support_multi_ui_session = crate::common::is_support_multi_ui_session(&pi.version);
|
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
|
let is_support_multi_ui_session = false;
|
|
self.session_handlers
|
|
.write()
|
|
.unwrap()
|
|
.values_mut()
|
|
.for_each(|h| {
|
|
h.renderer.is_support_multi_ui_session = is_support_multi_ui_session;
|
|
});
|
|
self.push_event(
|
|
"peer_info",
|
|
&[
|
|
("username", &pi.username),
|
|
("hostname", &pi.hostname),
|
|
("platform", &pi.platform),
|
|
("sas_enabled", &pi.sas_enabled.to_string()),
|
|
("displays", &displays),
|
|
("version", &pi.version),
|
|
("features", &features),
|
|
("current_display", &pi.current_display.to_string()),
|
|
("resolutions", &resolutions),
|
|
("platform_additions", &pi.platform_additions),
|
|
],
|
|
&[],
|
|
);
|
|
}
|
|
|
|
fn set_displays(&self, displays: &Vec<DisplayInfo>) {
|
|
self.peer_info.write().unwrap().displays = displays.clone();
|
|
self.push_event(
|
|
"sync_peer_info",
|
|
&[("displays", &Self::make_displays_msg(displays))],
|
|
&[],
|
|
);
|
|
}
|
|
|
|
fn set_platform_additions(&self, data: &str) {
|
|
self.push_event(
|
|
"sync_platform_additions",
|
|
&[("platform_additions", &data)],
|
|
&[],
|
|
)
|
|
}
|
|
|
|
fn set_multiple_windows_session(&self, sessions: Vec<WindowsSession>) {
|
|
let mut msg_vec = Vec::new();
|
|
let mut sessions = sessions;
|
|
for d in sessions.drain(..) {
|
|
let mut h: HashMap<&str, String> = Default::default();
|
|
h.insert("sid", d.sid.to_string());
|
|
h.insert("name", d.name);
|
|
msg_vec.push(h);
|
|
}
|
|
self.push_event(
|
|
"set_multiple_windows_session",
|
|
&[(
|
|
"windows_sessions",
|
|
&serde_json::ser::to_string(&msg_vec).unwrap_or("".to_owned()),
|
|
)],
|
|
&[],
|
|
);
|
|
}
|
|
|
|
fn is_multi_ui_session(&self) -> bool {
|
|
self.session_handlers.read().unwrap().len() > 1
|
|
}
|
|
|
|
fn set_current_display(&self, disp_idx: i32) {
|
|
if self.is_multi_ui_session() {
|
|
return;
|
|
}
|
|
self.push_event(
|
|
"follow_current_display",
|
|
&[("display_idx", &disp_idx.to_string())],
|
|
&[],
|
|
);
|
|
}
|
|
|
|
fn on_connected(&self, _conn_type: ConnType) {}
|
|
|
|
fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str, retry: bool) {
|
|
let has_retry = if retry { "true" } else { "" };
|
|
self.push_event(
|
|
"msgbox",
|
|
&[
|
|
("type", msgtype),
|
|
("title", title),
|
|
("text", text),
|
|
("link", link),
|
|
("hasRetry", has_retry),
|
|
],
|
|
&[],
|
|
);
|
|
}
|
|
|
|
fn cancel_msgbox(&self, tag: &str) {
|
|
self.push_event("cancel_msgbox", &[("tag", tag)], &[]);
|
|
}
|
|
|
|
fn new_message(&self, msg: String) {
|
|
self.push_event("chat_client_mode", &[("text", &msg)], &[]);
|
|
}
|
|
|
|
fn switch_display(&self, display: &SwitchDisplay) {
|
|
let resolutions = serialize_resolutions(&display.resolutions.resolutions);
|
|
self.push_event(
|
|
"switch_display",
|
|
&[
|
|
("display", &display.display.to_string()),
|
|
("x", &display.x.to_string()),
|
|
("y", &display.y.to_string()),
|
|
("width", &display.width.to_string()),
|
|
("height", &display.height.to_string()),
|
|
(
|
|
"cursor_embedded",
|
|
&{
|
|
if display.cursor_embedded {
|
|
1
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
.to_string(),
|
|
),
|
|
("resolutions", &resolutions),
|
|
(
|
|
"original_width",
|
|
&display.original_resolution.width.to_string(),
|
|
),
|
|
(
|
|
"original_height",
|
|
&display.original_resolution.height.to_string(),
|
|
),
|
|
],
|
|
&[],
|
|
);
|
|
}
|
|
|
|
fn update_block_input_state(&self, on: bool) {
|
|
self.push_event(
|
|
"update_block_input_state",
|
|
&[("input_state", if on { "on" } else { "off" })],
|
|
&[],
|
|
);
|
|
}
|
|
|
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
|
fn clipboard(&self, content: String) {
|
|
self.push_event("clipboard", &[("content", &content)], &[]);
|
|
}
|
|
|
|
fn switch_back(&self, peer_id: &str) {
|
|
self.push_event("switch_back", &[("peer_id", peer_id)], &[]);
|
|
}
|
|
|
|
fn portable_service_running(&self, running: bool) {
|
|
self.push_event(
|
|
"portable_service_running",
|
|
&[("running", running.to_string().as_str())],
|
|
&[],
|
|
);
|
|
}
|
|
|
|
fn on_voice_call_started(&self) {
|
|
self.push_event::<&str>("on_voice_call_started", &[], &[]);
|
|
}
|
|
|
|
fn on_voice_call_closed(&self, reason: &str) {
|
|
let _res = self.push_event("on_voice_call_closed", &[("reason", reason)], &[]);
|
|
}
|
|
|
|
fn on_voice_call_waiting(&self) {
|
|
self.push_event::<&str>("on_voice_call_waiting", &[], &[]);
|
|
}
|
|
|
|
fn on_voice_call_incoming(&self) {
|
|
self.push_event::<&str>("on_voice_call_incoming", &[], &[]);
|
|
}
|
|
|
|
#[inline]
|
|
fn get_rgba(&self, _display: usize) -> *const u8 {
|
|
if let Some(rgba_data) = self.display_rgbas.read().unwrap().get(&_display) {
|
|
if rgba_data.valid {
|
|
return rgba_data.data.as_ptr();
|
|
}
|
|
}
|
|
std::ptr::null_mut()
|
|
}
|
|
|
|
#[inline]
|
|
fn next_rgba(&self, _display: usize) {
|
|
if let Some(rgba_data) = self.display_rgbas.write().unwrap().get_mut(&_display) {
|
|
rgba_data.valid = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
impl FlutterHandler {
|
|
#[inline]
|
|
fn on_rgba_soft_render(&self, display: usize, rgba: &mut scrap::ImageRgb) {
|
|
// Give a chance for plugins or etc to hook a rgba data.
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
for (key, hook) in self.hooks.read().unwrap().iter() {
|
|
match hook {
|
|
SessionHook::OnSessionRgba(cb) => {
|
|
cb(key.to_owned(), rgba);
|
|
}
|
|
}
|
|
}
|
|
// If the current rgba is not fetched by flutter, i.e., is valid.
|
|
// We give up sending a new event to flutter.
|
|
let mut rgba_write_lock = self.display_rgbas.write().unwrap();
|
|
if let Some(rgba_data) = rgba_write_lock.get_mut(&display) {
|
|
if rgba_data.valid {
|
|
return;
|
|
} else {
|
|
rgba_data.valid = true;
|
|
}
|
|
// Return the rgba buffer to the video handler for reusing allocated rgba buffer.
|
|
std::mem::swap::<Vec<u8>>(&mut rgba.raw, &mut rgba_data.data);
|
|
} else {
|
|
let mut rgba_data = RgbaData::default();
|
|
std::mem::swap::<Vec<u8>>(&mut rgba.raw, &mut rgba_data.data);
|
|
rgba_data.valid = true;
|
|
rgba_write_lock.insert(display, rgba_data);
|
|
}
|
|
drop(rgba_write_lock);
|
|
|
|
let mut is_sent = false;
|
|
let is_multi_sessions = self.is_multi_ui_session();
|
|
for h in self.session_handlers.read().unwrap().values() {
|
|
// The soft renderer does not support multi-displays session for now.
|
|
if h.displays.len() > 1 {
|
|
continue;
|
|
}
|
|
// If there're multiple ui sessions, we only notify the ui session that has the display.
|
|
if is_multi_sessions {
|
|
if !h.displays.contains(&display) {
|
|
continue;
|
|
}
|
|
}
|
|
if let Some(stream) = &h.event_stream {
|
|
stream.add(EventToUI::Rgba(display));
|
|
is_sent = true;
|
|
}
|
|
}
|
|
// We need `is_sent` here. Because we use texture render for multi-displays session.
|
|
//
|
|
// Eg. We have two windows, one is display 1, the other is displays 0&1.
|
|
// When image of display 0 is received, we will not send the event.
|
|
//
|
|
// 1. "display 1" will not send the event.
|
|
// 2. "displays 0&1" will not send the event. Because it uses texutre render for now.
|
|
if !is_sent {
|
|
if let Some(rgba_data) = self.display_rgbas.write().unwrap().get_mut(&display) {
|
|
rgba_data.valid = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
fn on_rgba_flutter_texture_render(
|
|
&self,
|
|
use_texture_render: bool,
|
|
display: usize,
|
|
rgba: &mut scrap::ImageRgb,
|
|
) {
|
|
for (_, session) in self.session_handlers.read().unwrap().iter() {
|
|
if use_texture_render || session.displays.len() > 1 {
|
|
if session.renderer.on_rgba(display, rgba) {
|
|
if let Some(stream) = &session.event_stream {
|
|
stream.add(EventToUI::Texture(display, false));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// This function is only used for the default connection session.
|
|
pub fn session_add_existed(
|
|
peer_id: String,
|
|
session_id: SessionID,
|
|
displays: Vec<i32>,
|
|
) -> ResultType<()> {
|
|
sessions::insert_peer_session_id(peer_id, ConnType::DEFAULT_CONN, session_id, displays);
|
|
Ok(())
|
|
}
|
|
|
|
/// Create a new remote session with the given id.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `id` - The identifier of the remote session with prefix. Regex: [\w]*[\_]*[\d]+
|
|
/// * `is_file_transfer` - If the session is used for file transfer.
|
|
/// * `is_port_forward` - If the session is used for port forward.
|
|
pub fn session_add(
|
|
session_id: &SessionID,
|
|
id: &str,
|
|
is_file_transfer: bool,
|
|
is_port_forward: bool,
|
|
is_rdp: bool,
|
|
switch_uuid: &str,
|
|
force_relay: bool,
|
|
password: String,
|
|
is_shared_password: bool,
|
|
) -> ResultType<FlutterSession> {
|
|
let conn_type = if is_file_transfer {
|
|
ConnType::FILE_TRANSFER
|
|
} else if is_port_forward {
|
|
if is_rdp {
|
|
ConnType::RDP
|
|
} else {
|
|
ConnType::PORT_FORWARD
|
|
}
|
|
} else {
|
|
ConnType::DEFAULT_CONN
|
|
};
|
|
|
|
// to-do: check the same id session.
|
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
|
if session.lc.read().unwrap().conn_type != conn_type {
|
|
bail!("same session id is found with different conn type?");
|
|
}
|
|
// The same session is added before?
|
|
bail!("same session id is found");
|
|
}
|
|
|
|
LocalConfig::set_remote_id(&id);
|
|
|
|
let mut preset_password = password.clone();
|
|
let shared_password = if is_shared_password {
|
|
// To achieve a flexible password application order, we don't treat shared password as a preset password.
|
|
preset_password = Default::default();
|
|
Some(password)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let session: Session<FlutterHandler> = Session {
|
|
password: preset_password,
|
|
server_keyboard_enabled: Arc::new(RwLock::new(true)),
|
|
server_file_transfer_enabled: Arc::new(RwLock::new(true)),
|
|
server_clipboard_enabled: Arc::new(RwLock::new(true)),
|
|
..Default::default()
|
|
};
|
|
|
|
let switch_uuid = if switch_uuid.is_empty() {
|
|
None
|
|
} else {
|
|
Some(switch_uuid.to_string())
|
|
};
|
|
|
|
session.lc.write().unwrap().initialize(
|
|
id.to_owned(),
|
|
conn_type,
|
|
switch_uuid,
|
|
force_relay,
|
|
get_adapter_luid(),
|
|
shared_password,
|
|
);
|
|
|
|
let session = Arc::new(session.clone());
|
|
sessions::insert_session(session_id.to_owned(), conn_type, session.clone());
|
|
|
|
Ok(session)
|
|
}
|
|
|
|
/// start a session with the given id.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `id` - The identifier of the remote session with prefix. Regex: [\w]*[\_]*[\d]+
|
|
/// * `events2ui` - The events channel to ui.
|
|
pub fn session_start_(
|
|
session_id: &SessionID,
|
|
id: &str,
|
|
event_stream: StreamSink<EventToUI>,
|
|
) -> ResultType<()> {
|
|
// is_connected is used to indicate whether to start a peer connection. For two cases:
|
|
// 1. "Move tab to new window"
|
|
// 2. multi ui session within the same peer connection.
|
|
let mut is_connected = false;
|
|
let mut is_found = false;
|
|
for s in sessions::get_sessions() {
|
|
if let Some(h) = s.session_handlers.write().unwrap().get_mut(session_id) {
|
|
is_connected = h.event_stream.is_some();
|
|
try_send_close_event(&h.event_stream);
|
|
h.event_stream = Some(event_stream);
|
|
is_found = true;
|
|
break;
|
|
}
|
|
}
|
|
if !is_found {
|
|
bail!(
|
|
"No session with peer id {}, session id: {}",
|
|
id,
|
|
session_id.to_string()
|
|
);
|
|
}
|
|
|
|
if let Some(session) = sessions::get_session_by_session_id(session_id) {
|
|
let is_first_ui_session = session.session_handlers.read().unwrap().len() == 1;
|
|
if !is_connected && is_first_ui_session {
|
|
log::info!(
|
|
"Session {} start, use texture render: {}",
|
|
id,
|
|
session.use_texture_render.load(Ordering::Relaxed)
|
|
);
|
|
let session = (*session).clone();
|
|
std::thread::spawn(move || {
|
|
let round = session.connection_round_state.lock().unwrap().new_round();
|
|
io_loop(session, round);
|
|
});
|
|
}
|
|
Ok(())
|
|
} else {
|
|
bail!("No session with peer id {}", id)
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn try_send_close_event(event_stream: &Option<StreamSink<EventToUI>>) {
|
|
if let Some(stream) = &event_stream {
|
|
stream.add(EventToUI::Event("close".to_owned()));
|
|
}
|
|
}
|
|
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
pub fn update_text_clipboard_required() {
|
|
let is_required = sessions::get_sessions()
|
|
.iter()
|
|
.any(|s| s.is_text_clipboard_required());
|
|
Client::set_is_text_clipboard_required(is_required);
|
|
}
|
|
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
pub fn send_text_clipboard_msg(msg: Message) {
|
|
for s in sessions::get_sessions() {
|
|
if s.is_text_clipboard_required() {
|
|
s.send(Data::Message(msg.clone()));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Server Side
|
|
#[cfg(not(any(target_os = "ios")))]
|
|
pub mod connection_manager {
|
|
use std::collections::HashMap;
|
|
|
|
#[cfg(any(target_os = "android"))]
|
|
use hbb_common::log;
|
|
#[cfg(any(target_os = "android"))]
|
|
use scrap::android::call_main_service_set_by_name;
|
|
use serde_json::json;
|
|
|
|
use crate::ui_cm_interface::InvokeUiCM;
|
|
|
|
use super::GLOBAL_EVENT_STREAM;
|
|
|
|
#[derive(Clone)]
|
|
struct FlutterHandler {}
|
|
|
|
impl InvokeUiCM for FlutterHandler {
|
|
//TODO port_forward
|
|
fn add_connection(&self, client: &crate::ui_cm_interface::Client) {
|
|
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(target_os = "android")]
|
|
if let Err(e) =
|
|
call_main_service_set_by_name("add_connection", Some(&client_json), None)
|
|
{
|
|
log::debug!("call_main_service_set_by_name fail,{}", e);
|
|
}
|
|
// send to UI, refresh widget
|
|
self.push_event("add_connection", &[("client", &client_json)]);
|
|
}
|
|
|
|
fn remove_connection(&self, id: i32, close: bool) {
|
|
self.push_event(
|
|
"on_client_remove",
|
|
&[("id", &id.to_string()), ("close", &close.to_string())],
|
|
);
|
|
}
|
|
|
|
fn new_message(&self, id: i32, text: String) {
|
|
self.push_event(
|
|
"chat_server_mode",
|
|
&[("id", &id.to_string()), ("text", &text)],
|
|
);
|
|
}
|
|
|
|
fn change_theme(&self, dark: String) {
|
|
self.push_event("theme", &[("dark", &dark)]);
|
|
}
|
|
|
|
fn change_language(&self) {
|
|
self.push_event::<&str>("language", &[]);
|
|
}
|
|
|
|
fn show_elevation(&self, show: bool) {
|
|
self.push_event("show_elevation", &[("show", &show.to_string())]);
|
|
}
|
|
|
|
fn update_voice_call_state(&self, client: &crate::ui_cm_interface::Client) {
|
|
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(target_os = "android")]
|
|
if let Err(e) =
|
|
call_main_service_set_by_name("update_voice_call_state", Some(&client_json), None)
|
|
{
|
|
log::debug!("call_main_service_set_by_name fail,{}", e);
|
|
}
|
|
self.push_event("update_voice_call_state", &[("client", &client_json)]);
|
|
}
|
|
|
|
fn file_transfer_log(&self, action: &str, log: &str) {
|
|
self.push_event("cm_file_transfer_log", &[(action, log)]);
|
|
}
|
|
}
|
|
|
|
impl FlutterHandler {
|
|
fn push_event<V>(&self, name: &str, event: &[(&str, V)])
|
|
where
|
|
V: Sized + serde::Serialize + Clone,
|
|
{
|
|
let mut h: HashMap<&str, serde_json::Value> =
|
|
event.iter().map(|(k, v)| (*k, json!(*v))).collect();
|
|
debug_assert!(h.get("name").is_none());
|
|
h.insert("name", json!(name));
|
|
|
|
if let Some(s) = GLOBAL_EVENT_STREAM.read().unwrap().get(super::APP_TYPE_CM) {
|
|
s.add(serde_json::ser::to_string(&h).unwrap_or("".to_owned()));
|
|
} else {
|
|
println!(
|
|
"Push event {} failed. No {} event stream found.",
|
|
name,
|
|
super::APP_TYPE_CM
|
|
);
|
|
};
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
pub fn start_cm_no_ui() {
|
|
start_listen_ipc(false);
|
|
}
|
|
|
|
#[inline]
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
fn start_listen_ipc_thread() {
|
|
start_listen_ipc(true);
|
|
}
|
|
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
fn start_listen_ipc(new_thread: bool) {
|
|
use crate::ui_cm_interface::{start_ipc, ConnectionManager};
|
|
|
|
#[cfg(target_os = "linux")]
|
|
std::thread::spawn(crate::ipc::start_pa);
|
|
|
|
let cm = ConnectionManager {
|
|
ui_handler: FlutterHandler {},
|
|
};
|
|
if new_thread {
|
|
std::thread::spawn(move || start_ipc(cm));
|
|
} else {
|
|
start_ipc(cm);
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn cm_init() {
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
start_listen_ipc_thread();
|
|
}
|
|
|
|
#[cfg(target_os = "android")]
|
|
use hbb_common::tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
|
|
|
|
#[cfg(target_os = "android")]
|
|
pub fn start_channel(
|
|
rx: UnboundedReceiver<crate::ipc::Data>,
|
|
tx: UnboundedSender<crate::ipc::Data>,
|
|
) {
|
|
use crate::ui_cm_interface::start_listen;
|
|
let cm = crate::ui_cm_interface::ConnectionManager {
|
|
ui_handler: FlutterHandler {},
|
|
};
|
|
std::thread::spawn(move || start_listen(cm, rx, tx));
|
|
}
|
|
}
|
|
|
|
pub fn make_fd_flutter(id: i32, entries: &Vec<FileEntry>, only_count: bool) -> String {
|
|
let mut m = serde_json::Map::new();
|
|
m.insert("id".into(), json!(id));
|
|
let mut a = vec![];
|
|
let mut n: u64 = 0;
|
|
for entry in entries {
|
|
n += entry.size;
|
|
if only_count {
|
|
continue;
|
|
}
|
|
let mut e = serde_json::Map::new();
|
|
e.insert("name".into(), json!(entry.name.to_owned()));
|
|
let tmp = entry.entry_type.value();
|
|
e.insert("type".into(), json!(if tmp == 0 { 1 } else { tmp }));
|
|
e.insert("time".into(), json!(entry.modified_time as f64));
|
|
e.insert("size".into(), json!(entry.size as f64));
|
|
a.push(e);
|
|
}
|
|
if only_count {
|
|
m.insert("num_entries".into(), json!(entries.len() as i32));
|
|
} else {
|
|
m.insert("entries".into(), json!(a));
|
|
}
|
|
m.insert("total_size".into(), json!(n as f64));
|
|
serde_json::to_string(&m).unwrap_or("".into())
|
|
}
|
|
|
|
pub fn get_cur_session_id() -> SessionID {
|
|
CUR_SESSION_ID.read().unwrap().clone()
|
|
}
|
|
|
|
pub fn get_cur_peer_id() -> String {
|
|
sessions::get_peer_id_by_session_id(&get_cur_session_id(), ConnType::DEFAULT_CONN)
|
|
.unwrap_or("".to_string())
|
|
}
|
|
|
|
pub fn set_cur_session_id(session_id: SessionID) {
|
|
if get_cur_session_id() != session_id {
|
|
*CUR_SESSION_ID.write().unwrap() = session_id;
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn serialize_resolutions(resolutions: &Vec<Resolution>) -> String {
|
|
#[derive(Debug, serde::Serialize)]
|
|
struct ResolutionSerde {
|
|
width: i32,
|
|
height: i32,
|
|
}
|
|
|
|
let mut v = vec![];
|
|
resolutions
|
|
.iter()
|
|
.map(|r| {
|
|
v.push(ResolutionSerde {
|
|
width: r.width,
|
|
height: r.height,
|
|
})
|
|
})
|
|
.count();
|
|
serde_json::ser::to_string(&v).unwrap_or("".to_string())
|
|
}
|
|
|
|
fn char_to_session_id(c: *const char) -> ResultType<SessionID> {
|
|
if c.is_null() {
|
|
bail!("Session id ptr is null");
|
|
}
|
|
let cstr = unsafe { std::ffi::CStr::from_ptr(c as _) };
|
|
let str = cstr.to_str()?;
|
|
SessionID::from_str(str).map_err(|e| anyhow!("{:?}", e))
|
|
}
|
|
|
|
pub fn session_get_rgba_size(session_id: SessionID, display: usize) -> usize {
|
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
|
return session
|
|
.display_rgbas
|
|
.read()
|
|
.unwrap()
|
|
.get(&display)
|
|
.map_or(0, |rgba| rgba.data.len());
|
|
}
|
|
0
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn session_get_rgba(session_uuid_str: *const char, display: usize) -> *const u8 {
|
|
if let Ok(session_id) = char_to_session_id(session_uuid_str) {
|
|
if let Some(s) = sessions::get_session_by_session_id(&session_id) {
|
|
return s.ui_handler.get_rgba(display);
|
|
}
|
|
}
|
|
|
|
std::ptr::null()
|
|
}
|
|
|
|
pub fn session_next_rgba(session_id: SessionID, display: usize) {
|
|
if let Some(s) = sessions::get_session_by_session_id(&session_id) {
|
|
return s.ui_handler.next_rgba(display);
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn session_set_size(session_id: SessionID, display: usize, width: usize, height: usize) {
|
|
for s in sessions::get_sessions() {
|
|
if let Some(h) = s
|
|
.ui_handler
|
|
.session_handlers
|
|
.write()
|
|
.unwrap()
|
|
.get_mut(&session_id)
|
|
{
|
|
// If the session is the first connection, displays is not set yet.
|
|
// `displays`` is set while switching displays or adding a new session.
|
|
if !h.displays.contains(&display) {
|
|
h.displays.push(display);
|
|
}
|
|
h.renderer.set_size(display, width, height);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn session_register_pixelbuffer_texture(session_id: SessionID, display: usize, ptr: usize) {
|
|
for s in sessions::get_sessions() {
|
|
if let Some(h) = s
|
|
.ui_handler
|
|
.session_handlers
|
|
.read()
|
|
.unwrap()
|
|
.get(&session_id)
|
|
{
|
|
h.renderer.register_pixelbuffer_texture(display, ptr);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn session_register_gpu_texture(_session_id: SessionID, _display: usize, _output_ptr: usize) {
|
|
#[cfg(feature = "vram")]
|
|
for s in sessions::get_sessions() {
|
|
if let Some(h) = s
|
|
.ui_handler
|
|
.session_handlers
|
|
.read()
|
|
.unwrap()
|
|
.get(&_session_id)
|
|
{
|
|
h.renderer.register_gpu_output(_display, _output_ptr);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
#[cfg(not(feature = "vram"))]
|
|
pub fn get_adapter_luid() -> Option<i64> {
|
|
None
|
|
}
|
|
|
|
#[cfg(feature = "vram")]
|
|
pub fn get_adapter_luid() -> Option<i64> {
|
|
if !crate::ui_interface::use_texture_render() {
|
|
return None;
|
|
}
|
|
let get_adapter_luid_func = match &*TEXTURE_GPU_RENDERER_PLUGIN {
|
|
Ok(lib) => {
|
|
let find_sym_res = unsafe {
|
|
lib.symbol::<FlutterGpuTextureRendererPluginCApiGetAdapterLuid>(
|
|
"FlutterGpuTextureRendererPluginCApiGetAdapterLuid",
|
|
)
|
|
};
|
|
match find_sym_res {
|
|
Ok(sym) => Some(sym),
|
|
Err(e) => {
|
|
log::error!("Failed to find symbol FlutterGpuTextureRendererPluginCApiGetAdapterLuid, {e}");
|
|
None
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
log::error!("Failed to load texture gpu renderer plugin, {e}");
|
|
None
|
|
}
|
|
};
|
|
let adapter_luid = match get_adapter_luid_func {
|
|
Some(get_adapter_luid_func) => unsafe { Some(get_adapter_luid_func()) },
|
|
None => Default::default(),
|
|
};
|
|
return adapter_luid;
|
|
}
|
|
|
|
#[inline]
|
|
pub fn push_session_event(session_id: &SessionID, name: &str, event: Vec<(&str, &str)>) {
|
|
if let Some(s) = sessions::get_session_by_session_id(session_id) {
|
|
s.push_event(name, &event, &[]);
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn push_global_event(channel: &str, event: String) -> Option<bool> {
|
|
Some(GLOBAL_EVENT_STREAM.read().unwrap().get(channel)?.add(event))
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get_global_event_channels() -> Vec<String> {
|
|
GLOBAL_EVENT_STREAM
|
|
.read()
|
|
.unwrap()
|
|
.keys()
|
|
.cloned()
|
|
.collect()
|
|
}
|
|
|
|
pub fn start_global_event_stream(s: StreamSink<String>, app_type: String) -> ResultType<()> {
|
|
let app_type_values = app_type.split(",").collect::<Vec<&str>>();
|
|
let mut lock = GLOBAL_EVENT_STREAM.write().unwrap();
|
|
if !lock.contains_key(app_type_values[0]) {
|
|
lock.insert(app_type_values[0].to_string(), s);
|
|
} else {
|
|
if let Some(_) = lock.insert(app_type.clone(), s) {
|
|
log::warn!(
|
|
"Global event stream of type {} is started before, but now removed",
|
|
app_type
|
|
);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn stop_global_event_stream(app_type: String) {
|
|
let _ = GLOBAL_EVENT_STREAM.write().unwrap().remove(&app_type);
|
|
}
|
|
|
|
#[inline]
|
|
fn session_send_touch_scale(
|
|
session_id: SessionID,
|
|
v: &serde_json::Value,
|
|
alt: bool,
|
|
ctrl: bool,
|
|
shift: bool,
|
|
command: bool,
|
|
) {
|
|
match v.get("v").and_then(|s| s.as_i64()) {
|
|
Some(scale) => {
|
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
|
session.send_touch_scale(scale as _, alt, ctrl, shift, command);
|
|
}
|
|
}
|
|
None => {}
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn session_send_touch_pan(
|
|
session_id: SessionID,
|
|
v: &serde_json::Value,
|
|
pan_event: &str,
|
|
alt: bool,
|
|
ctrl: bool,
|
|
shift: bool,
|
|
command: bool,
|
|
) {
|
|
match v.get("v") {
|
|
Some(v) => match (
|
|
v.get("x").and_then(|x| x.as_i64()),
|
|
v.get("y").and_then(|y| y.as_i64()),
|
|
) {
|
|
(Some(x), Some(y)) => {
|
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
|
session
|
|
.send_touch_pan_event(pan_event, x as _, y as _, alt, ctrl, shift, command);
|
|
}
|
|
}
|
|
_ => {}
|
|
},
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
fn session_send_touch_event(
|
|
session_id: SessionID,
|
|
v: &serde_json::Value,
|
|
alt: bool,
|
|
ctrl: bool,
|
|
shift: bool,
|
|
command: bool,
|
|
) {
|
|
match v.get("t").and_then(|t| t.as_str()) {
|
|
Some("scale") => session_send_touch_scale(session_id, v, alt, ctrl, shift, command),
|
|
Some(pan_event) => {
|
|
session_send_touch_pan(session_id, v, pan_event, alt, ctrl, shift, command)
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
pub fn session_send_pointer(session_id: SessionID, msg: String) {
|
|
if let Ok(m) = serde_json::from_str::<HashMap<String, serde_json::Value>>(&msg) {
|
|
let alt = m.get("alt").is_some();
|
|
let ctrl = m.get("ctrl").is_some();
|
|
let shift = m.get("shift").is_some();
|
|
let command = m.get("command").is_some();
|
|
match (m.get("k"), m.get("v")) {
|
|
(Some(k), Some(v)) => match k.as_str() {
|
|
Some("touch") => session_send_touch_event(session_id, v, alt, ctrl, shift, command),
|
|
_ => {}
|
|
},
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn session_on_waiting_for_image_dialog_show(session_id: SessionID) {
|
|
for s in sessions::get_sessions() {
|
|
if let Some(h) = s.session_handlers.write().unwrap().get_mut(&session_id) {
|
|
h.on_waiting_for_image_dialog_show();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Hooks for session.
|
|
#[derive(Clone)]
|
|
pub enum SessionHook {
|
|
OnSessionRgba(fn(String, &mut scrap::ImageRgb)),
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get_cur_session() -> Option<FlutterSession> {
|
|
sessions::get_session_by_session_id(&*CUR_SESSION_ID.read().unwrap())
|
|
}
|
|
|
|
#[inline]
|
|
pub fn try_sync_peer_option(
|
|
session: &FlutterSession,
|
|
cur_id: &SessionID,
|
|
key: &str,
|
|
_value: Option<serde_json::Value>,
|
|
) {
|
|
let mut event = Vec::new();
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
if key == "view-only" {
|
|
event = vec![
|
|
("k", json!(key.to_string())),
|
|
("v", json!(session.lc.read().unwrap().view_only.v)),
|
|
];
|
|
}
|
|
if ["keyboard_mode", "input_source"].contains(&key) {
|
|
event = vec![("k", json!(key.to_string())), ("v", json!(""))];
|
|
}
|
|
if !event.is_empty() {
|
|
session.push_event("sync_peer_option", &event, &[cur_id]);
|
|
}
|
|
}
|
|
|
|
// sessions mod is used to avoid the big lock of sessions' map.
|
|
pub mod sessions {
|
|
use std::collections::HashSet;
|
|
|
|
use super::*;
|
|
|
|
lazy_static::lazy_static! {
|
|
// peer -> peer session, peer session -> ui sessions
|
|
static ref SESSIONS: RwLock<HashMap<(String, ConnType), FlutterSession>> = Default::default();
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get_session_count(peer_id: String, conn_type: ConnType) -> usize {
|
|
SESSIONS
|
|
.read()
|
|
.unwrap()
|
|
.get(&(peer_id, conn_type))
|
|
.map(|s| s.ui_handler.session_handlers.read().unwrap().len())
|
|
.unwrap_or(0)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get_peer_id_by_session_id(id: &SessionID, conn_type: ConnType) -> Option<String> {
|
|
SESSIONS
|
|
.read()
|
|
.unwrap()
|
|
.iter()
|
|
.find_map(|((peer_id, t), s)| {
|
|
if *t == conn_type
|
|
&& s.ui_handler
|
|
.session_handlers
|
|
.read()
|
|
.unwrap()
|
|
.contains_key(id)
|
|
{
|
|
Some(peer_id.clone())
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get_session_by_session_id(id: &SessionID) -> Option<FlutterSession> {
|
|
SESSIONS
|
|
.read()
|
|
.unwrap()
|
|
.values()
|
|
.find(|s| {
|
|
s.ui_handler
|
|
.session_handlers
|
|
.read()
|
|
.unwrap()
|
|
.contains_key(id)
|
|
})
|
|
.cloned()
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get_session_by_peer_id(peer_id: String, conn_type: ConnType) -> Option<FlutterSession> {
|
|
SESSIONS.read().unwrap().get(&(peer_id, conn_type)).cloned()
|
|
}
|
|
|
|
#[inline]
|
|
pub fn remove_session_by_session_id(id: &SessionID) -> Option<FlutterSession> {
|
|
let mut remove_peer_key = None;
|
|
for (peer_key, s) in SESSIONS.write().unwrap().iter_mut() {
|
|
let mut write_lock = s.ui_handler.session_handlers.write().unwrap();
|
|
let remove_ret = write_lock.remove(id);
|
|
match remove_ret {
|
|
Some(_) => {
|
|
if write_lock.is_empty() {
|
|
remove_peer_key = Some(peer_key.clone());
|
|
} else {
|
|
check_remove_unused_displays(None, id, s, &write_lock);
|
|
}
|
|
break;
|
|
}
|
|
None => {}
|
|
}
|
|
}
|
|
SESSIONS.write().unwrap().remove(&remove_peer_key?)
|
|
}
|
|
|
|
fn check_remove_unused_displays(
|
|
current: Option<usize>,
|
|
session_id: &SessionID,
|
|
session: &FlutterSession,
|
|
handlers: &HashMap<SessionID, SessionHandler>,
|
|
) {
|
|
// Set capture displays if some are not used any more.
|
|
let mut remains_displays = HashSet::new();
|
|
if let Some(current) = current {
|
|
remains_displays.insert(current);
|
|
}
|
|
for (k, h) in handlers.iter() {
|
|
if k == session_id {
|
|
continue;
|
|
}
|
|
remains_displays.extend(
|
|
h.renderer
|
|
.map_display_sessions
|
|
.read()
|
|
.unwrap()
|
|
.keys()
|
|
.cloned(),
|
|
);
|
|
}
|
|
if !remains_displays.is_empty() {
|
|
session.capture_displays(
|
|
vec![],
|
|
vec![],
|
|
remains_displays.iter().map(|d| *d as i32).collect(),
|
|
);
|
|
}
|
|
}
|
|
|
|
pub fn session_switch_display(is_desktop: bool, session_id: SessionID, value: Vec<i32>) {
|
|
for s in SESSIONS.read().unwrap().values() {
|
|
let mut write_lock = s.ui_handler.session_handlers.write().unwrap();
|
|
if let Some(h) = write_lock.get_mut(&session_id) {
|
|
h.displays = value.iter().map(|x| *x as usize).collect::<_>();
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
let displays_refresh = value.clone();
|
|
if value.len() == 1 {
|
|
// Switch display.
|
|
// This operation will also cause the peer to send a switch display message.
|
|
// The switch display message will contain `SupportedResolutions`, which is useful when changing resolutions.
|
|
s.switch_display(value[0]);
|
|
|
|
if !is_desktop {
|
|
s.capture_displays(vec![], vec![], value);
|
|
} else {
|
|
// Check if other displays are needed.
|
|
if value.len() == 1 {
|
|
check_remove_unused_displays(
|
|
Some(value[0] as _),
|
|
&session_id,
|
|
&s,
|
|
&write_lock,
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
// Try capture all displays.
|
|
s.capture_displays(vec![], vec![], value);
|
|
}
|
|
// When switching display, we also need to send "Refresh display" message.
|
|
// On the controlled side:
|
|
// 1. If this display is not currently captured -> Refresh -> Message "Refresh display" is not required.
|
|
// One more key frame (first frame) will be sent because the refresh message.
|
|
// 2. If this display is currently captured -> Not refresh -> Message "Refresh display" is required.
|
|
// Without the message, the control side cannot see the latest display image.
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
{
|
|
let is_support_multi_ui_session = crate::common::is_support_multi_ui_session(
|
|
&s.ui_handler.peer_info.read().unwrap().version,
|
|
);
|
|
if is_support_multi_ui_session {
|
|
for display in displays_refresh.iter() {
|
|
s.refresh_video(*display);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn insert_session(session_id: SessionID, conn_type: ConnType, session: FlutterSession) {
|
|
SESSIONS
|
|
.write()
|
|
.unwrap()
|
|
.entry((session.get_id(), conn_type))
|
|
.or_insert(session)
|
|
.ui_handler
|
|
.session_handlers
|
|
.write()
|
|
.unwrap()
|
|
.insert(session_id, Default::default());
|
|
}
|
|
|
|
#[inline]
|
|
pub fn insert_peer_session_id(
|
|
peer_id: String,
|
|
conn_type: ConnType,
|
|
session_id: SessionID,
|
|
displays: Vec<i32>,
|
|
) -> bool {
|
|
if let Some(s) = SESSIONS.read().unwrap().get(&(peer_id, conn_type)) {
|
|
let mut h = SessionHandler::default();
|
|
h.displays = displays.iter().map(|x| *x as usize).collect::<_>();
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
let is_support_multi_ui_session = crate::common::is_support_multi_ui_session(
|
|
&s.ui_handler.peer_info.read().unwrap().version,
|
|
);
|
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
|
let is_support_multi_ui_session = false;
|
|
h.renderer.is_support_multi_ui_session = is_support_multi_ui_session;
|
|
let _ = s
|
|
.ui_handler
|
|
.session_handlers
|
|
.write()
|
|
.unwrap()
|
|
.insert(session_id, h);
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get_sessions() -> Vec<FlutterSession> {
|
|
SESSIONS.read().unwrap().values().cloned().collect()
|
|
}
|
|
|
|
#[inline]
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
pub fn has_sessions_running(conn_type: ConnType) -> bool {
|
|
SESSIONS.read().unwrap().iter().any(|((_, r#type), s)| {
|
|
*r#type == conn_type && s.session_handlers.read().unwrap().len() != 0
|
|
})
|
|
}
|
|
}
|
|
|
|
pub(super) mod async_tasks {
|
|
use hbb_common::{
|
|
bail,
|
|
tokio::{
|
|
self, select,
|
|
sync::mpsc::{unbounded_channel, UnboundedSender},
|
|
},
|
|
ResultType,
|
|
};
|
|
use std::{
|
|
collections::HashMap,
|
|
sync::{Arc, Mutex},
|
|
};
|
|
|
|
type TxQueryOnlines = UnboundedSender<Vec<String>>;
|
|
lazy_static::lazy_static! {
|
|
static ref TX_QUERY_ONLINES: Arc<Mutex<Option<TxQueryOnlines>>> = Default::default();
|
|
}
|
|
|
|
#[inline]
|
|
pub fn start_flutter_async_runner() {
|
|
std::thread::spawn(start_flutter_async_runner_);
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub fn stop_flutter_async_runner() {
|
|
let _ = TX_QUERY_ONLINES.lock().unwrap().take();
|
|
}
|
|
|
|
#[tokio::main(flavor = "current_thread")]
|
|
async fn start_flutter_async_runner_() {
|
|
let (tx_onlines, mut rx_onlines) = unbounded_channel::<Vec<String>>();
|
|
TX_QUERY_ONLINES.lock().unwrap().replace(tx_onlines);
|
|
|
|
loop {
|
|
select! {
|
|
ids = rx_onlines.recv() => {
|
|
match ids {
|
|
Some(_ids) => {
|
|
#[cfg(not(any(target_os = "ios")))]
|
|
crate::rendezvous_mediator::query_online_states(_ids, handle_query_onlines).await
|
|
}
|
|
None => {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn query_onlines(ids: Vec<String>) -> ResultType<()> {
|
|
if let Some(tx) = TX_QUERY_ONLINES.lock().unwrap().as_ref() {
|
|
let _ = tx.send(ids)?;
|
|
} else {
|
|
bail!("No tx_query_onlines");
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn handle_query_onlines(onlines: Vec<String>, offlines: Vec<String>) {
|
|
let data = HashMap::from([
|
|
("name", "callback_query_onlines".to_owned()),
|
|
("onlines", onlines.join(",")),
|
|
("offlines", offlines.join(",")),
|
|
]);
|
|
let _res = super::push_global_event(
|
|
super::APP_TYPE_MAIN,
|
|
serde_json::ser::to_string(&data).unwrap_or("".to_owned()),
|
|
);
|
|
}
|
|
}
|