rustdesk/src/flutter.rs

2003 lines
64 KiB
Rust
Raw Normal View History

2023-02-09 16:54:26 +08:00
use crate::{
client::*,
flutter_ffi::{EventToUI, SessionID},
2023-02-09 16:54:26 +08:00
ui_session_interface::{io_loop, InvokeUiSession, Session},
};
use flutter_rust_bridge::StreamSink;
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
use hbb_common::dlopen::{
symbor::{Library, Symbol},
Error as LibError,
};
2022-09-05 10:27:33 +08:00
use hbb_common::{
anyhow::anyhow, bail, config::LocalConfig, get_version_number, log, message_proto::*,
rendezvous_proto::ConnType, ResultType,
2022-09-05 10:27:33 +08:00
};
use serde::Serialize;
2022-09-05 10:27:33 +08:00
use serde_json::json;
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
use std::os::raw::c_void;
use std::{
collections::HashMap,
ffi::CString,
os::raw::{c_char, c_int},
str::FromStr,
sync::{Arc, RwLock},
};
2022-05-12 17:35:25 +08:00
2023-03-07 10:08:41 +08:00
/// 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";
2023-03-07 10:08:41 +08:00
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub(crate) const APP_TYPE_CM: &str = "cm";
2023-03-07 10:08:41 +08:00
#[cfg(any(target_os = "android", target_os = "ios"))]
pub(crate) const APP_TYPE_CM: &str = "main";
2023-03-07 10:08:41 +08:00
// 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>>;
2022-05-12 17:35:25 +08:00
lazy_static::lazy_static! {
pub(crate) static ref CUR_SESSION_ID: RwLock<SessionID> = Default::default();
static ref GLOBAL_EVENT_STREAM: RwLock<HashMap<String, StreamSink<String>>> = Default::default(); // rust to dart event channel
}
#[cfg(all(target_os = "windows", feature = "flutter_texture_render"))]
lazy_static::lazy_static! {
pub static ref TEXTURE_RGBA_RENDERER_PLUGIN: Result<Library, LibError> = Library::open("texture_rgba_renderer_plugin.dll");
}
#[cfg(all(target_os = "linux", feature = "flutter_texture_render"))]
lazy_static::lazy_static! {
pub static ref TEXTURE_RGBA_RENDERER_PLUGIN: Result<Library, LibError> = Library::open("libtexture_rgba_renderer_plugin.so");
}
#[cfg(all(target_os = "macos", feature = "flutter_texture_render"))]
lazy_static::lazy_static! {
pub static ref TEXTURE_RGBA_RENDERER_PLUGIN: Result<Library, LibError> = Library::open_self();
2022-05-12 17:35:25 +08:00
}
#[cfg(all(target_os = "windows", feature = "vram"))]
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
}
2023-01-20 01:25:15 +08:00
#[cfg(target_os = "macos")]
#[no_mangle]
pub extern "C" fn handle_applicationShouldOpenUntitledFile() {
2023-02-09 21:28:42 +08:00
crate::platform::macos::handle_application_should_open_untitled_file();
2023-01-20 01:25:15 +08:00
}
#[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>>,
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
renderer: VideoRenderer,
}
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum RenderType {
PixelBuffer,
#[cfg(feature = "vram")]
Texture,
}
#[derive(Default, Clone)]
pub struct FlutterHandler {
// ui session id -> display handler data
session_handlers: Arc<RwLock<HashMap<SessionID, SessionHandler>>>,
#[cfg(not(feature = "flutter_texture_render"))]
display_rgbas: Arc<RwLock<HashMap<usize, RgbaData>>>,
peer_info: Arc<RwLock<PeerInfo>>,
#[cfg(any(
not(feature = "flutter_texture_render"),
all(feature = "flutter_texture_render", feature = "plugin_framework")
))]
2023-04-17 00:54:34 +08:00
#[cfg(not(any(target_os = "android", target_os = "ios")))]
2023-04-28 14:55:40 +08:00
hooks: Arc<RwLock<HashMap<String, SessionHook>>>,
}
#[cfg(not(feature = "flutter_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,
}
#[cfg(feature = "flutter_texture_render")]
2023-03-03 14:02:49 +08:00
pub type FlutterRgbaRendererPluginOnRgba = unsafe extern "C" fn(
texture_rgba: *mut c_void,
buffer: *const u8,
len: c_int,
2023-03-03 14:02:49 +08:00
width: c_int,
height: c_int,
dst_rgba_stride: c_int,
);
2023-02-18 11:16:07 +08:00
#[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;
#[cfg(feature = "flutter_texture_render")]
pub(super) type TextureRgbaPtr = usize;
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
struct DisplaySessionInfo {
// TextureRgba pointer in flutter native.
#[cfg(feature = "flutter_texture_render")]
texture_rgba_ptr: TextureRgbaPtr,
#[cfg(feature = "flutter_texture_render")]
size: (usize, usize),
#[cfg(feature = "vram")]
gpu_output_ptr: usize,
notify_render_type: Option<RenderType>,
}
2023-02-18 11:16:07 +08:00
// Video Texture Renderer in Flutter
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
2023-02-18 11:47:18 +08:00
#[derive(Clone)]
struct VideoRenderer {
is_support_multi_ui_session: bool,
map_display_sessions: Arc<RwLock<HashMap<usize, DisplaySessionInfo>>>,
#[cfg(feature = "flutter_texture_render")]
on_rgba_func: Option<Symbol<'static, FlutterRgbaRendererPluginOnRgba>>,
#[cfg(feature = "vram")]
on_texture_func: Option<Symbol<'static, FlutterGpuTextureRendererPluginCApiSetTexture>>,
2023-02-18 11:16:07 +08:00
}
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
2023-02-18 11:47:18 +08:00
impl Default for VideoRenderer {
fn default() -> Self {
#[cfg(feature = "flutter_texture_render")]
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
2023-02-18 11:47:18 +08:00
}
};
#[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(feature = "flutter_texture_render")]
on_rgba_func,
#[cfg(feature = "vram")]
on_texture_func,
2023-02-18 11:47:18 +08:00
}
}
}
2023-02-18 11:16:07 +08:00
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
2023-02-18 11:16:07 +08:00
impl VideoRenderer {
#[inline]
#[cfg(feature = "flutter_texture_render")]
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,
},
);
}
2023-02-18 11:16:07 +08:00
}
#[cfg(feature = "flutter_texture_render")]
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();
}
#[cfg(feature = "flutter_texture_render")]
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 {
#[cfg(feature = "flutter_texture_render")]
texture_rgba_ptr: usize::default(),
#[cfg(feature = "flutter_texture_render")]
size: (0, 0),
gpu_output_ptr: ptr,
notify_render_type: None,
},
);
}
}
}
}
#[cfg(feature = "flutter_texture_render")]
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;
2023-02-18 11:16:07 +08:00
}
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.stride() 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();
2023-02-18 11:16:07 +08:00
}
2022-05-12 17:35:25 +08:00
}
impl SessionHandler {
pub fn on_waiting_for_image_dialog_show(&self) {
#[cfg(any(feature = "flutter_texture_render"))]
{
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.
2022-05-28 03:56:42 +08:00
///
/// # 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));
2022-05-31 22:09:36 +08:00
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()));
}
}
2022-05-12 17:35:25 +08:00
}
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())
}
2023-02-18 11:16:07 +08:00
#[cfg(feature = "plugin_framework")]
2023-04-17 00:54:34 +08:00
#[cfg(not(any(target_os = "android", target_os = "ios")))]
2023-04-28 14:55:40 +08:00
pub(crate) fn add_session_hook(&self, key: String, hook: SessionHook) -> bool {
2023-04-14 01:53:34 +08:00
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")]
2023-04-17 00:54:34 +08:00
#[cfg(not(any(target_os = "android", target_os = "ios")))]
2023-04-14 01:53:34 +08:00
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
}
}
2022-05-12 17:35:25 +08:00
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()),
),
],
&[],
);
2022-05-12 17:35:25 +08:00
}
fn set_cursor_id(&self, id: String) {
self.push_event("cursor_id", &[("id", &id.to_string())], &[]);
2022-05-12 17:35:25 +08:00
}
fn set_cursor_position(&self, cp: CursorPosition) {
self.push_event(
"cursor_position",
&[("x", &cp.x.to_string()), ("y", &cp.y.to_string())],
&[],
);
2022-05-12 17:35:25 +08:00
}
/// 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", &[], &[]);
2022-07-11 18:23:58 +08:00
}
fn set_permission(&self, name: &str, value: bool) {
self.push_event("permission", &[(name, &value.to_string())], &[]);
}
2022-09-05 10:27:33 +08:00
// unused in flutter
fn close_success(&self) {}
2022-08-04 17:24:02 +08:00
fn update_quality_status(&self, status: QualityStatus) {
const NULL: String = String::new();
self.push_event(
"update_quality_status",
&[
2022-08-04 17:24:02 +08:00
("speed", &status.speed.map_or(NULL, |it| it)),
(
"fps",
&serde_json::ser::to_string(&status.fps).unwrap_or(NULL.to_owned()),
),
2022-08-04 17:24:02 +08:00
("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())),
2022-08-04 17:24:02 +08:00
],
&[],
2022-08-04 17:24:02 +08:00
);
}
2022-05-12 17:35:25 +08:00
fn set_connection_type(&self, is_secured: bool, direct: bool) {
2022-05-12 17:35:25 +08:00
self.push_event(
"connection_ready",
&[
("secure", &is_secured.to_string()),
("direct", &direct.to_string()),
2022-05-12 17:35:25 +08:00
],
&[],
2022-05-12 17:35:25 +08:00
);
}
fn set_fingerprint(&self, fingerprint: String) {
self.push_event("fingerprint", &[("fingerprint", &fingerprint)], &[]);
}
fn job_error(&self, id: i32, err: String, file_num: i32) {
2022-09-05 10:27:33 +08:00
self.push_event(
"job_error",
&[
2022-09-05 10:27:33 +08:00
("id", &id.to_string()),
("err", &err),
("file_num", &file_num.to_string()),
],
&[],
2022-09-05 10:27:33 +08:00
);
2022-05-12 17:35:25 +08:00
}
fn job_done(&self, id: i32, file_num: i32) {
2022-05-12 17:35:25 +08:00
self.push_event(
2022-09-01 09:48:53 +08:00
"job_done",
&[("id", &id.to_string()), ("file_num", &file_num.to_string())],
&[],
2022-05-12 17:35:25 +08:00
);
}
2022-09-05 10:27:33 +08:00
// 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)], &[]);
}
2022-09-05 10:27:33 +08:00
fn update_folder_files(
&self,
id: i32,
2022-09-05 10:27:33 +08:00
entries: &Vec<FileEntry>,
path: String,
#[allow(unused_variables)] is_local: bool,
2022-09-05 10:27:33 +08:00
only_count: bool,
) {
2022-09-05 10:27:33 +08:00
// TODO opt
if only_count {
self.push_event(
"update_folder_files",
&[("info", &make_fd_flutter(id, entries, only_count))],
&[],
2022-09-05 10:27:33 +08:00
);
} else {
self.push_event(
"file_dir",
&[
2022-09-05 10:27:33 +08:00
("is_local", "false"),
("value", &crate::common::make_fd_to_json(id, path, entries)),
2022-09-05 10:27:33 +08:00
],
&[],
2022-09-05 10:27:33 +08:00
);
}
}
2022-09-05 10:27:33 +08:00
// unused in flutter
fn update_transfer_list(&self) {}
2022-05-12 17:35:25 +08:00
2022-09-05 10:27:33 +08:00
// unused in flutter // TEST flutter
fn confirm_delete_files(&self, _id: i32, _i: i32, _name: String) {}
2022-05-12 17:35:25 +08:00
fn override_file_confirm(
&self,
id: i32,
file_num: i32,
to: String,
is_upload: bool,
is_identical: bool,
) {
2022-09-01 09:48:53 +08:00
self.push_event(
"override_file_confirm",
&[
2022-09-01 09:48:53 +08:00
("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()),
2022-09-01 09:48:53 +08:00
],
&[],
2022-09-01 09:48:53 +08:00
);
2022-05-12 17:35:25 +08:00
}
fn job_progress(&self, id: i32, file_num: i32, speed: f64, finished_size: f64) {
2022-09-01 09:48:53 +08:00
self.push_event(
"job_progress",
&[
2022-09-01 09:48:53 +08:00
("id", &id.to_string()),
("file_num", &file_num.to_string()),
("speed", &speed.to_string()),
("finished_size", &finished_size.to_string()),
],
&[],
2022-09-01 09:48:53 +08:00
);
}
2022-09-05 10:27:33 +08:00
// unused in flutter
fn adapt_size(&self) {}
#[inline]
#[cfg(not(feature = "flutter_texture_render"))]
fn on_rgba(&self, display: usize, rgba: &mut scrap::ImageRgb) {
2023-04-28 14:55:40 +08:00
// Give a chance for plugins or etc to hook a rgba data.
#[cfg(not(any(target_os = "android", target_os = "ios")))]
2023-04-28 14:55:40 +08:00
for (key, hook) in self.hooks.read().unwrap().iter() {
match hook {
SessionHook::OnSessionRgba(cb) => {
cb(key.to_owned(), rgba);
2023-05-15 00:18:40 +08:00
}
2023-04-28 14:55:40 +08:00
}
}
// 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);
// Non-texture-render UI does not support multiple displays in the one UI session.
// It's Ok to notify each session for now.
for h in self.session_handlers.read().unwrap().values() {
if let Some(stream) = &h.event_stream {
stream.add(EventToUI::Rgba(display));
}
}
}
#[inline]
#[cfg(feature = "flutter_texture_render")]
fn on_rgba(&self, display: usize, rgba: &mut scrap::ImageRgb) {
for (_, session) in self.session_handlers.read().unwrap().iter() {
if session.renderer.on_rgba(display, rgba) {
if let Some(stream) = &session.event_stream {
stream.add(EventToUI::Rgba(display));
}
}
}
}
#[inline]
#[cfg(feature = "vram")]
fn on_texture(&self, display: usize, texture: *mut c_void) {
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));
}
}
}
2022-05-12 17:35:25 +08:00
}
2022-09-01 16:21:41 +08:00
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(feature = "flutter_texture_render")]
{
self.session_handlers
.write()
.unwrap()
.values_mut()
.for_each(|h| {
h.renderer.is_support_multi_ui_session =
crate::common::is_support_multi_ui_session(&pi.version);
});
}
self.push_event(
"peer_info",
&[
2022-09-01 16:21:41 +08:00
("username", &pi.username),
("hostname", &pi.hostname),
("platform", &pi.platform),
("sas_enabled", &pi.sas_enabled.to_string()),
("displays", &displays),
2022-09-01 16:21:41 +08:00
("version", &pi.version),
("features", &features),
2022-09-01 16:21:41 +08:00
("current_display", &pi.current_display.to_string()),
("resolutions", &resolutions),
("platform_additions", &pi.platform_additions),
2022-05-12 17:35:25 +08:00
],
&[],
);
}
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)],
&[],
)
}
refactor windows specific session (#7170) 1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same . 2. Always show physical console session on the top 3. Show running session and distinguish sessions with the same name 4. Not sub service until correct session id is ensured 5. Fix switch sides not work for multisession session 6. Remove all session string join/split except get_available_sessions in windows.rs 7. Fix prelogin, when share rdp is enabled and there is a rdp session, the console is in login screen, get_active_username will be the rdp's username and prelogin will be false, cm can't be created an that causes disconnection in a loop 8. Rename all user session to windows session Known issue: 1. Use current process session id for `run_as_user`, sahil says it can be wrong but I didn't reproduce. 2. Have not change tray process to current session 3. File transfer doesn't update home directory when session changed 4. When it's in login screen, remote file directory is empty, because cm have not start up Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
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);
}
Feat: Windows connect to a specific user session (#6825) * feat windows connect to specific user session Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix import Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix multiple user session fields Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix build Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix build Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix file transfer Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix text color on light theme Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * feat windows connect to specific user session code changes and sciter support Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * update texts Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix sciter selected user session Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * add translations Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * Use Y,N options * feat windows specific user code changes Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * Update dialog.dart * Update connection.rs * Update connection.rs * feat windows specific user code changes Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix sciter Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * use lr.union Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * remove unused peer options Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * select user only when authorised and no existing connection Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * check for multiple users only once Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * optimise and add check for client version Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * use misc option message Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * update rdp user session proto Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix show cm on user session Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * Update pl.rs * update on_message Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix cm Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * remove user_session_id Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix cm Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix multiple connections Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> --------- Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>
2024-02-14 23:59:17 +08:00
self.push_event(
refactor windows specific session (#7170) 1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same . 2. Always show physical console session on the top 3. Show running session and distinguish sessions with the same name 4. Not sub service until correct session id is ensured 5. Fix switch sides not work for multisession session 6. Remove all session string join/split except get_available_sessions in windows.rs 7. Fix prelogin, when share rdp is enabled and there is a rdp session, the console is in login screen, get_active_username will be the rdp's username and prelogin will be false, cm can't be created an that causes disconnection in a loop 8. Rename all user session to windows session Known issue: 1. Use current process session id for `run_as_user`, sahil says it can be wrong but I didn't reproduce. 2. Have not change tray process to current session 3. File transfer doesn't update home directory when session changed 4. When it's in login screen, remote file directory is empty, because cm have not start up Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
"set_multiple_windows_session",
&[(
"windows_sessions",
refactor windows specific session (#7170) 1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same . 2. Always show physical console session on the top 3. Show running session and distinguish sessions with the same name 4. Not sub service until correct session id is ensured 5. Fix switch sides not work for multisession session 6. Remove all session string join/split except get_available_sessions in windows.rs 7. Fix prelogin, when share rdp is enabled and there is a rdp session, the console is in login screen, get_active_username will be the rdp's username and prelogin will be false, cm can't be created an that causes disconnection in a loop 8. Rename all user session to windows session Known issue: 1. Use current process session id for `run_as_user`, sahil says it can be wrong but I didn't reproduce. 2. Have not change tray process to current session 3. File transfer doesn't update home directory when session changed 4. When it's in login screen, remote file directory is empty, because cm have not start up Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
&serde_json::ser::to_string(&msg_vec).unwrap_or("".to_owned()),
)],
&[],
Feat: Windows connect to a specific user session (#6825) * feat windows connect to specific user session Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix import Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix multiple user session fields Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix build Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix build Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix file transfer Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix text color on light theme Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * feat windows connect to specific user session code changes and sciter support Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * update texts Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix sciter selected user session Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * add translations Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * Use Y,N options * feat windows specific user code changes Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * Update dialog.dart * Update connection.rs * Update connection.rs * feat windows specific user code changes Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix sciter Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * use lr.union Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * remove unused peer options Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * select user only when authorised and no existing connection Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * check for multiple users only once Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * optimise and add check for client version Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * use misc option message Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * update rdp user session proto Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix show cm on user session Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * Update pl.rs * update on_message Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix cm Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * remove user_session_id Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix cm Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix multiple connections Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> --------- Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>
2024-02-14 23:59:17 +08:00
);
}
Feat: Follow remote cursor and window focus | Auto display switch (#7717) * feat: auto switch display on follow remote cursor Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * feat: auto switch display on follow remote window focus Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix build and remove unused imports Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix build Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix build Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix linux get_focused_window_id Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * lock show remote cursor when follow remote cursor is enabled Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix config Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * prevent auto display switch on show all display and displays as individual windows Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix options Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix options Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * remove unused function Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * remove unwraps and improve iterations Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * set updateCursorPos to false to avoid interrupting remote cursor Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * update lang Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix web build Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * update checks for options and enable in view mode Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * use focused display index for window focus service Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * use window center for windows display focused Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * remove unused imports Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix build Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * use libxdo instead of xdotool Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix multi monitor check Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * enable show cursor when follow cursor is default Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * remove show_all_displays,use runtime state instead Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix show cursor lock state on default Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * remove view mode with follow options Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix build Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * use separate message for follow current display Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix options Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * sciter support for follow remote cursor and window Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * add check for ui session handlers count Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * use cached displays and remove peer info write Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * No follow options when show all displays Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * No follow options when multi ui session Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * turn off follow options when not used|prevent msgs Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * use window center for switch in linux Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * use subbed display count to prevent switch msgs Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix build Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix web build Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * move subbed displays count Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix build Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * add noperms for window focus Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * add subscribe for window focus Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * remove window_focus message and unsub on multi ui Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * add multi ui session field Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> --------- Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-04-25 13:26:02 +08:00
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),
2022-05-19 21:45:25 +08:00
],
&[],
2022-05-17 20:56:36 +08:00
);
}
fn cancel_msgbox(&self, tag: &str) {
self.push_event("cancel_msgbox", &[("tag", tag)], &[]);
}
2022-09-01 09:48:53 +08:00
fn new_message(&self, msg: String) {
self.push_event("chat_client_mode", &[("text", &msg)], &[]);
}
2022-09-01 09:48:53 +08:00
fn switch_display(&self, display: &SwitchDisplay) {
let resolutions = serialize_resolutions(&display.resolutions.resolutions);
2022-09-01 09:48:53 +08:00
self.push_event(
"switch_display",
&[
("display", &display.display.to_string()),
2022-09-01 09:48:53 +08:00
("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(),
),
2022-05-12 17:35:25 +08:00
],
&[],
2022-05-12 17:35:25 +08:00
);
}
2022-09-01 09:48:53 +08:00
fn update_block_input_state(&self, on: bool) {
self.push_event(
"update_block_input_state",
&[("input_state", if on { "on" } else { "off" })],
&[],
2022-09-01 09:48:53 +08:00
);
2022-05-12 17:35:25 +08:00
}
2022-09-01 09:48:53 +08:00
#[cfg(any(target_os = "android", target_os = "ios"))]
fn clipboard(&self, content: String) {
self.push_event("clipboard", &[("content", &content)], &[]);
2022-05-12 17:35:25 +08:00
}
fn switch_back(&self, peer_id: &str) {
self.push_event("switch_back", &[("peer_id", peer_id)], &[]);
}
2023-02-06 11:42:25 +08:00
fn portable_service_running(&self, running: bool) {
self.push_event(
"portable_service_running",
&[("running", running.to_string().as_str())],
&[],
);
}
2023-02-06 15:36:36 +08:00
fn on_voice_call_started(&self) {
self.push_event::<&str>("on_voice_call_started", &[], &[]);
2023-02-06 11:42:25 +08:00
}
2023-02-06 12:14:20 +08:00
fn on_voice_call_closed(&self, reason: &str) {
let _res = self.push_event("on_voice_call_closed", &[("reason", reason)], &[]);
2023-02-06 11:42:25 +08:00
}
fn on_voice_call_waiting(&self) {
self.push_event::<&str>("on_voice_call_waiting", &[], &[]);
2023-02-06 11:42:25 +08:00
}
fn on_voice_call_incoming(&self) {
self.push_event::<&str>("on_voice_call_incoming", &[], &[]);
2023-02-06 11:42:25 +08:00
}
#[inline]
fn get_rgba(&self, _display: usize) -> *const u8 {
#[cfg(not(feature = "flutter_texture_render"))]
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) {
#[cfg(not(feature = "flutter_texture_render"))]
if let Some(rgba_data) = self.display_rgbas.write().unwrap().get_mut(&_display) {
rgba_data.valid = false;
}
}
}
2022-05-17 20:56:36 +08:00
// This function is only used for the default connection session.
pub fn session_add_existed(peer_id: String, session_id: SessionID) -> ResultType<()> {
sessions::insert_peer_session_id(peer_id, ConnType::DEFAULT_CONN, session_id);
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,
2023-05-15 00:18:40 +08:00
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 {
2023-05-15 00:18:40 +08:00
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())
};
#[cfg(feature = "vram")]
let adapter_luid = get_adapter_luid();
#[cfg(not(feature = "vram"))]
let adapter_luid = None;
session.lc.write().unwrap().initialize(
id.to_owned(),
conn_type,
switch_uuid,
force_relay,
adapter_luid,
shared_password,
);
let session = Arc::new(session.clone());
sessions::insert_session(session_id.to_owned(), conn_type, session.clone());
2022-07-11 18:23:58 +08:00
2023-05-04 13:18:19 +08:00
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 {
#[cfg(feature = "flutter_texture_render")]
log::info!(
"Session {} start, render by flutter texture rgba plugin",
id
);
#[cfg(not(feature = "flutter_texture_render"))]
log::info!("Session {} start, render by flutter paint widget", id);
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)
2022-07-11 18:23:58 +08:00
}
2022-05-12 17:35:25 +08:00
}
#[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()));
}
}
}
2022-05-12 17:35:25 +08:00
// Server Side
#[cfg(not(any(target_os = "ios")))]
2022-05-12 17:35:25 +08:00
pub mod connection_manager {
use std::collections::HashMap;
2022-05-12 17:35:25 +08:00
#[cfg(any(target_os = "android"))]
2022-09-05 20:05:23 +08:00
use hbb_common::log;
#[cfg(any(target_os = "android"))]
2022-05-12 17:35:25 +08:00
use scrap::android::call_main_service_set_by_name;
use serde_json::json;
use crate::ui_cm_interface::InvokeUiCM;
2022-05-12 17:35:25 +08:00
2022-05-31 22:09:36 +08:00
use super::GLOBAL_EVENT_STREAM;
#[derive(Clone)]
struct FlutterHandler {}
2022-08-17 17:23:55 +08:00
impl InvokeUiCM for FlutterHandler {
//TODO port_forward
fn add_connection(&self, client: &crate::ui_cm_interface::Client) {
2022-08-17 17:23:55 +08:00
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")]
2022-08-17 17:23:55 +08:00
if let Err(e) =
call_main_service_set_by_name("add_connection", Some(&client_json), None)
2022-08-17 17:23:55 +08:00
{
log::debug!("call_main_service_set_by_name fail,{}", e);
2022-08-17 17:23:55 +08:00
}
// send to UI, refresh widget
self.push_event("add_connection", &[("client", &client_json)]);
2022-08-17 17:23:55 +08:00
}
fn remove_connection(&self, id: i32, close: bool) {
self.push_event(
"on_client_remove",
&[("id", &id.to_string()), ("close", &close.to_string())],
);
2022-05-12 17:35:25 +08:00
}
fn new_message(&self, id: i32, text: String) {
self.push_event(
"chat_server_mode",
&[("id", &id.to_string()), ("text", &text)],
);
2022-05-12 17:35:25 +08:00
}
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())]);
}
2023-02-06 12:53:57 +08:00
2023-02-07 16:11:55 +08:00
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)]);
2023-02-06 12:53:57 +08:00
}
fn file_transfer_log(&self, action: &str, log: &str) {
self.push_event("cm_file_transfer_log", &[(action, log)]);
}
2022-05-12 17:35:25 +08:00
}
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));
2023-02-09 16:54:26 +08:00
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()));
2023-02-07 19:33:58 +08:00
} else {
2023-02-09 16:54:26 +08:00
println!(
"Push event {} failed. No {} event stream found.",
name,
super::APP_TYPE_CM
);
};
2022-05-12 17:35:25 +08:00
}
}
#[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) {
2022-09-05 20:32:21 +08:00
use crate::ui_cm_interface::{start_ipc, ConnectionManager};
2022-05-12 17:35:25 +08:00
#[cfg(target_os = "linux")]
2022-09-05 20:32:21 +08:00
std::thread::spawn(crate::ipc::start_pa);
2022-05-12 17:35:25 +08:00
let cm = ConnectionManager {
ui_handler: FlutterHandler {},
};
if new_thread {
std::thread::spawn(move || start_ipc(cm));
} else {
start_ipc(cm);
}
2022-05-12 17:35:25 +08:00
}
#[inline]
pub fn cm_init() {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
start_listen_ipc_thread();
}
#[cfg(target_os = "android")]
2022-09-05 20:32:21 +08:00
use hbb_common::tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
2022-05-12 17:35:25 +08:00
2022-09-05 20:05:23 +08:00
#[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;
2022-09-05 20:05:23 +08:00
let cm = crate::ui_cm_interface::ConnectionManager {
ui_handler: FlutterHandler {},
};
std::thread::spawn(move || start_listen(cm, rx, tx));
2022-05-12 17:35:25 +08:00
}
}
2022-09-05 10:27:33 +08:00
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 {
#[cfg(not(feature = "flutter_texture_render"))]
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);
}
}
2023-02-18 11:16:07 +08:00
#[inline]
pub fn session_set_size(_session_id: SessionID, _display: usize, _width: usize, _height: usize) {
#[cfg(feature = "flutter_texture_render")]
for s in sessions::get_sessions() {
if let Some(h) = s
.ui_handler
.session_handlers
.write()
.unwrap()
.get_mut(&_session_id)
{
h.renderer.set_size(_display, _width, _height);
break;
}
2023-02-18 11:16:07 +08:00
}
2023-02-18 11:47:18 +08:00
}
#[inline]
pub fn session_register_pixelbuffer_texture(_session_id: SessionID, _display: usize, _ptr: usize) {
#[cfg(feature = "flutter_texture_render")]
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;
}
}
}
#[cfg(feature = "vram")]
pub fn get_adapter_luid() -> Option<i64> {
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);
}
2023-04-20 21:12:47 +08:00
#[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();
}
}
}
2023-04-28 14:55:40 +08:00
/// Hooks for session.
#[derive(Clone)]
pub enum SessionHook {
OnSessionRgba(fn(String, &mut scrap::ImageRgb)),
2023-05-15 00:18:40 +08:00
}
#[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 {
#[cfg(feature = "flutter_texture_render")]
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);
#[cfg(not(feature = "flutter_texture_render"))]
if remove_ret.is_some() {
if write_lock.is_empty() {
remove_peer_key = Some(peer_key.clone());
}
break;
}
#[cfg(feature = "flutter_texture_render")]
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?)
}
#[cfg(feature = "flutter_texture_render")]
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 read_lock = s.ui_handler.session_handlers.read().unwrap();
if read_lock.contains_key(&session_id) {
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.
#[cfg(feature = "flutter_texture_render")]
if value.len() == 1 {
check_remove_unused_displays(
Some(value[0] as _),
&session_id,
&s,
&read_lock,
);
}
}
} else {
// Try capture all displays.
s.capture_displays(vec![], vec![], value);
}
break;
}
}
}
#[inline]
pub fn insert_session(session_id: SessionID, conn_type: ConnType, session: FlutterSession) {
SESSIONS
.write()
.unwrap()
2023-11-06 20:12:01 +08:00
.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,
) -> bool {
if let Some(s) = SESSIONS.read().unwrap().get(&(peer_id, conn_type)) {
#[cfg(not(feature = "flutter_texture_render"))]
let h = SessionHandler::default();
#[cfg(feature = "flutter_texture_render")]
let mut h = SessionHandler::default();
#[cfg(feature = "flutter_texture_render")]
{
h.renderer.is_support_multi_ui_session = crate::common::is_support_multi_ui_session(
&s.ui_handler.peer_info.read().unwrap().version,
);
}
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()),
);
}
}