mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-03-27 15:11:01 +03:00
feat: whiteboard, macos (#12780)
Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
124
Cargo.lock
generated
124
Cargo.lock
generated
@@ -242,6 +242,12 @@ version = "1.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b"
|
checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "associative-cache"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46016233fc1bb55c23b856fe556b7db6ccd05119a0a392e04f0b3b7c79058f16"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-broadcast"
|
name = "async-broadcast"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@@ -1280,9 +1286,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.10.0"
|
version = "0.10.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63"
|
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core-foundation-sys 0.8.7",
|
"core-foundation-sys 0.8.7",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -1392,6 +1398,18 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-text"
|
||||||
|
version = "19.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation 0.9.4",
|
||||||
|
"core-graphics 0.22.3",
|
||||||
|
"foreign-types 0.3.2",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-video-sys"
|
name = "core-video-sys"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
@@ -3809,6 +3827,15 @@ dependencies = [
|
|||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kurbo"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bd85a5776cd9500c2e2059c8c76c3b01528566b7fcbaf8098b55a33fc298849b"
|
||||||
|
dependencies = [
|
||||||
|
"arrayvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -4108,6 +4135,12 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matches"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "md5"
|
name = "md5"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@@ -5289,6 +5322,31 @@ dependencies = [
|
|||||||
"siphasher 1.0.1",
|
"siphasher 1.0.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "piet"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e381186490a3e2017a506d62b759ea8eaf4be14666b13ed53973e8ae193451b1"
|
||||||
|
dependencies = [
|
||||||
|
"kurbo",
|
||||||
|
"unic-bidi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "piet-coregraphics"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a819b41d2ddb1d8abf3e45e49422f866cba281b4abb5e2fb948bba06e2c3d3f7"
|
||||||
|
dependencies = [
|
||||||
|
"associative-cache",
|
||||||
|
"core-foundation 0.9.4",
|
||||||
|
"core-foundation-sys 0.8.7",
|
||||||
|
"core-graphics 0.22.3",
|
||||||
|
"core-text",
|
||||||
|
"foreign-types 0.3.2",
|
||||||
|
"piet",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "pin-project"
|
||||||
version = "1.1.5"
|
version = "1.1.5"
|
||||||
@@ -6269,6 +6327,7 @@ dependencies = [
|
|||||||
"flutter_rust_bridge",
|
"flutter_rust_bridge",
|
||||||
"fon",
|
"fon",
|
||||||
"fontdb",
|
"fontdb",
|
||||||
|
"foreign-types 0.3.2",
|
||||||
"fruitbasket",
|
"fruitbasket",
|
||||||
"gtk",
|
"gtk",
|
||||||
"hbb_common",
|
"hbb_common",
|
||||||
@@ -6295,6 +6354,8 @@ dependencies = [
|
|||||||
"pam",
|
"pam",
|
||||||
"parity-tokio-ipc",
|
"parity-tokio-ipc",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
|
"piet",
|
||||||
|
"piet-coregraphics",
|
||||||
"portable-pty",
|
"portable-pty",
|
||||||
"qrcode-generator",
|
"qrcode-generator",
|
||||||
"rdev",
|
"rdev",
|
||||||
@@ -6449,7 +6510,7 @@ version = "0.5.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4a5467026f437b4cb2a533865eaa73eb840019a0916f4b9ec563c6e617e086c9"
|
checksum = "4a5467026f437b4cb2a533865eaa73eb840019a0916f4b9ec563c6e617e086c9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core-foundation 0.10.0",
|
"core-foundation 0.10.1",
|
||||||
"core-foundation-sys 0.8.7",
|
"core-foundation-sys 0.8.7",
|
||||||
"jni",
|
"jni",
|
||||||
"log",
|
"log",
|
||||||
@@ -6596,7 +6657,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316"
|
checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.1",
|
"bitflags 2.9.1",
|
||||||
"core-foundation 0.10.0",
|
"core-foundation 0.10.1",
|
||||||
"core-foundation-sys 0.8.7",
|
"core-foundation-sys 0.8.7",
|
||||||
"libc",
|
"libc",
|
||||||
"security-framework-sys",
|
"security-framework-sys",
|
||||||
@@ -6879,9 +6940,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.13.2"
|
version = "1.15.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
@@ -7979,6 +8040,57 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unic-bidi"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1356b759fb6a82050666f11dce4b6fe3571781f1449f3ef78074e408d468ec09"
|
||||||
|
dependencies = [
|
||||||
|
"matches",
|
||||||
|
"unic-ucd-bidi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unic-char-property"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
|
||||||
|
dependencies = [
|
||||||
|
"unic-char-range",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unic-char-range"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unic-common"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unic-ucd-bidi"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d1d568b51222484e1f8209ce48caa6b430bf352962b877d592c29ab31fb53d8c"
|
||||||
|
dependencies = [
|
||||||
|
"unic-char-property",
|
||||||
|
"unic-char-range",
|
||||||
|
"unic-ucd-version",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unic-ucd-version"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
|
||||||
|
dependencies = [
|
||||||
|
"unic-common",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.15"
|
version = "0.3.15"
|
||||||
|
|||||||
@@ -149,6 +149,10 @@ core-graphics = "0.22"
|
|||||||
include_dir = "0.7"
|
include_dir = "0.7"
|
||||||
fruitbasket = "0.10"
|
fruitbasket = "0.10"
|
||||||
objc_id = "0.1"
|
objc_id = "0.1"
|
||||||
|
# If we use piet "0.7" here, we must also update core-graphics to "0.24".
|
||||||
|
piet = "0.6"
|
||||||
|
piet-coregraphics = "0.6"
|
||||||
|
foreign-types = "0.3"
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))'.dependencies]
|
[target.'cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))'.dependencies]
|
||||||
tray-icon = { git = "https://github.com/tauri-apps/tray-icon" }
|
tray-icon = { git = "https://github.com/tauri-apps/tray-icon" }
|
||||||
|
|||||||
@@ -1593,7 +1593,9 @@ class _KeyboardMenu extends StatelessWidget {
|
|||||||
inputSource(),
|
inputSource(),
|
||||||
Divider(),
|
Divider(),
|
||||||
viewMode(),
|
viewMode(),
|
||||||
if (pi.platform == kPeerPlatformWindows) showMyCursor(),
|
if (pi.platform == kPeerPlatformWindows ||
|
||||||
|
pi.platform == kPeerPlatformMacOS)
|
||||||
|
showMyCursor(),
|
||||||
Divider(),
|
Divider(),
|
||||||
...toolbarToggles(),
|
...toolbarToggles(),
|
||||||
...mouseSpeed(),
|
...mouseSpeed(),
|
||||||
|
|||||||
@@ -575,7 +575,7 @@ pub fn core_main() -> Option<Vec<String>> {
|
|||||||
}
|
}
|
||||||
return None;
|
return None;
|
||||||
} else if args[0] == "--whiteboard" {
|
} else if args[0] == "--whiteboard" {
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||||
{
|
{
|
||||||
crate::whiteboard::run();
|
crate::whiteboard::run();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ pub enum Data {
|
|||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
PortForwardSessionCount(Option<usize>),
|
PortForwardSessionCount(Option<usize>),
|
||||||
SocksWs(Option<Box<(Option<config::Socks5Server>, String)>>),
|
SocksWs(Option<Box<(Option<config::Socks5Server>, String)>>),
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||||
Whiteboard((String, crate::whiteboard::CustomEvent)),
|
Whiteboard((String, crate::whiteboard::CustomEvent)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ pub mod plugin;
|
|||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
mod tray;
|
mod tray;
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||||
mod whiteboard;
|
mod whiteboard;
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
|||||||
@@ -780,7 +780,7 @@ pgrep -x 'RustDesk' | grep -v {} | xargs kill -9 && rm -rf /Applications/RustDes
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_to(file: &str) -> ResultType<()> {
|
pub fn update_to(_file: &str) -> ResultType<()> {
|
||||||
update_extracted(UPDATE_TEMP_DIR)?;
|
update_extracted(UPDATE_TEMP_DIR)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3697,13 +3697,17 @@ impl Connection {
|
|||||||
self.update_terminal_persistence(q == BoolOption::Yes).await;
|
self.update_terminal_persistence(q == BoolOption::Yes).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||||
if let Ok(q) = o.show_my_cursor.enum_value() {
|
if let Ok(q) = o.show_my_cursor.enum_value() {
|
||||||
if q != BoolOption::NotSet {
|
if q != BoolOption::NotSet {
|
||||||
use crate::whiteboard;
|
use crate::whiteboard;
|
||||||
self.show_my_cursor = q == BoolOption::Yes;
|
self.show_my_cursor = q == BoolOption::Yes;
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
let is_win10_or_greater = crate::platform::windows::is_win_10_or_greater();
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
let is_win10_or_greater = false;
|
||||||
if q == BoolOption::Yes {
|
if q == BoolOption::Yes {
|
||||||
if crate::platform::windows::is_win_10_or_greater() {
|
if !cfg!(target_os = "windows") || is_win10_or_greater {
|
||||||
whiteboard::register_whiteboard(whiteboard::get_key_cursor(self.inner.id));
|
whiteboard::register_whiteboard(whiteboard::get_key_cursor(self.inner.id));
|
||||||
} else {
|
} else {
|
||||||
let mut msg_out = Message::new();
|
let mut msg_out = Message::new();
|
||||||
@@ -3718,7 +3722,7 @@ impl Connection {
|
|||||||
self.send(msg_out).await;
|
self.send(msg_out).await;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if crate::platform::windows::is_win_10_or_greater() {
|
if !cfg!(target_os = "windows") || is_win10_or_greater {
|
||||||
whiteboard::unregister_whiteboard(whiteboard::get_key_cursor(
|
whiteboard::unregister_whiteboard(whiteboard::get_key_cursor(
|
||||||
self.inner.id,
|
self.inner.id,
|
||||||
));
|
));
|
||||||
@@ -4878,7 +4882,7 @@ mod raii {
|
|||||||
scrap::wayland::pipewire::try_close_session();
|
scrap::wayland::pipewire::try_close_session();
|
||||||
}
|
}
|
||||||
Self::check_wake_lock();
|
Self::check_wake_lock();
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||||
{
|
{
|
||||||
use crate::whiteboard;
|
use crate::whiteboard;
|
||||||
whiteboard::unregister_whiteboard(whiteboard::get_key_cursor(self.0));
|
whiteboard::unregister_whiteboard(whiteboard::get_key_cursor(self.0));
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
use super::rdp_input::client::{RdpInputKeyboard, RdpInputMouse};
|
use super::rdp_input::client::{RdpInputKeyboard, RdpInputMouse};
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::input::*;
|
use crate::input::*;
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||||
use crate::whiteboard;
|
use crate::whiteboard;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use dispatch::Queue;
|
use dispatch::Queue;
|
||||||
@@ -204,6 +204,7 @@ impl LockModesHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut num_lock_changed = false;
|
let mut num_lock_changed = false;
|
||||||
|
#[allow(unused)]
|
||||||
let mut event_num_enabled = false;
|
let mut event_num_enabled = false;
|
||||||
if is_numpad_key {
|
if is_numpad_key {
|
||||||
let local_num_enabled = en.get_key_state(enigo::Key::NumLock);
|
let local_num_enabled = en.get_key_state(enigo::Key::NumLock);
|
||||||
@@ -999,7 +1000,7 @@ pub fn handle_mouse_(
|
|||||||
if simulate {
|
if simulate {
|
||||||
handle_mouse_simulation_(evt, conn);
|
handle_mouse_simulation_(evt, conn);
|
||||||
}
|
}
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||||
if _show_cursor {
|
if _show_cursor {
|
||||||
handle_mouse_show_cursor_(evt, conn, username, argb);
|
handle_mouse_show_cursor_(evt, conn, username, argb);
|
||||||
}
|
}
|
||||||
@@ -1148,7 +1149,7 @@ pub fn handle_mouse_simulation_(evt: &MouseEvent, conn: i32) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||||
pub fn handle_mouse_show_cursor_(evt: &MouseEvent, conn: i32, username: String, argb: u32) {
|
pub fn handle_mouse_show_cursor_(evt: &MouseEvent, conn: i32, username: String, argb: u32) {
|
||||||
let buttons = evt.mask >> 3;
|
let buttons = evt.mask >> 3;
|
||||||
let evt_type = evt.mask & 0x7;
|
let evt_type = evt.mask & 0x7;
|
||||||
|
|||||||
258
src/whiteboard/client.rs
Normal file
258
src/whiteboard/client.rs
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
use super::{Cursor, CustomEvent};
|
||||||
|
use crate::{
|
||||||
|
ipc::{self, Data},
|
||||||
|
CHILD_PROCESS,
|
||||||
|
};
|
||||||
|
use hbb_common::{
|
||||||
|
allow_err,
|
||||||
|
anyhow::anyhow,
|
||||||
|
bail, log, sleep,
|
||||||
|
tokio::{
|
||||||
|
self,
|
||||||
|
sync::mpsc::{unbounded_channel, UnboundedSender},
|
||||||
|
time::interval_at,
|
||||||
|
},
|
||||||
|
ResultType,
|
||||||
|
};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use std::{collections::HashMap, sync::RwLock, time::Instant};
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref TX_WHITEBOARD: RwLock<Option<UnboundedSender<(String, CustomEvent)>>> =
|
||||||
|
RwLock::new(None);
|
||||||
|
static ref CONNS: RwLock<HashMap<String, Conn>> = Default::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Conn {
|
||||||
|
last_cursor_pos: (f32, f32), // For click ripple
|
||||||
|
last_cursor_evt: LastCursorEvent,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LastCursorEvent {
|
||||||
|
evt: Option<CustomEvent>,
|
||||||
|
tm: Instant,
|
||||||
|
c: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn get_key_cursor(conn_id: i32) -> String {
|
||||||
|
format!("{}-cursor", conn_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register_whiteboard(k: String) {
|
||||||
|
std::thread::spawn(|| {
|
||||||
|
allow_err!(start_whiteboard_());
|
||||||
|
});
|
||||||
|
let mut conns = CONNS.write().unwrap();
|
||||||
|
if !conns.contains_key(&k) {
|
||||||
|
conns.insert(
|
||||||
|
k,
|
||||||
|
Conn {
|
||||||
|
last_cursor_pos: (0.0, 0.0),
|
||||||
|
last_cursor_evt: LastCursorEvent {
|
||||||
|
evt: None,
|
||||||
|
tm: Instant::now(),
|
||||||
|
c: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unregister_whiteboard(k: String) {
|
||||||
|
let mut conns = CONNS.write().unwrap();
|
||||||
|
conns.remove(&k);
|
||||||
|
let is_conns_empty = conns.is_empty();
|
||||||
|
drop(conns);
|
||||||
|
|
||||||
|
TX_WHITEBOARD.read().unwrap().as_ref().map(|tx| {
|
||||||
|
allow_err!(tx.send((k, CustomEvent::Clear)));
|
||||||
|
});
|
||||||
|
if is_conns_empty {
|
||||||
|
std::thread::spawn(|| {
|
||||||
|
let mut whiteboard = TX_WHITEBOARD.write().unwrap();
|
||||||
|
whiteboard.as_ref().map(|tx| {
|
||||||
|
allow_err!(tx.send(("".to_string(), CustomEvent::Exit)));
|
||||||
|
// Simple sleep to wait the whiteboard process exiting.
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(3_00));
|
||||||
|
});
|
||||||
|
whiteboard.take();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_whiteboard(k: String, e: CustomEvent) {
|
||||||
|
let mut conns = CONNS.write().unwrap();
|
||||||
|
let Some(conn) = conns.get_mut(&k) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
match &e {
|
||||||
|
CustomEvent::Cursor(cursor) => {
|
||||||
|
conn.last_cursor_evt.c += 1;
|
||||||
|
conn.last_cursor_evt.tm = Instant::now();
|
||||||
|
if cursor.btns == 0 {
|
||||||
|
// Send one movement event every 4.
|
||||||
|
if conn.last_cursor_evt.c > 3 {
|
||||||
|
conn.last_cursor_evt.c = 0;
|
||||||
|
conn.last_cursor_evt.evt = None;
|
||||||
|
tx_send_event(conn, k, e);
|
||||||
|
} else {
|
||||||
|
conn.last_cursor_evt.evt = Some(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let Some(evt) = conn.last_cursor_evt.evt.take() {
|
||||||
|
tx_send_event(conn, k.clone(), evt);
|
||||||
|
conn.last_cursor_evt.c = 0;
|
||||||
|
}
|
||||||
|
let click_evt = CustomEvent::Cursor(Cursor {
|
||||||
|
x: conn.last_cursor_pos.0,
|
||||||
|
y: conn.last_cursor_pos.1,
|
||||||
|
argb: cursor.argb,
|
||||||
|
btns: cursor.btns,
|
||||||
|
text: cursor.text.clone(),
|
||||||
|
});
|
||||||
|
tx_send_event(conn, k, click_evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
tx_send_event(conn, k, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn tx_send_event(conn: &mut Conn, k: String, event: CustomEvent) {
|
||||||
|
if let CustomEvent::Cursor(cursor) = &event {
|
||||||
|
if cursor.btns == 0 {
|
||||||
|
conn.last_cursor_pos = (cursor.x, cursor.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TX_WHITEBOARD.read().unwrap().as_ref().map(|tx| {
|
||||||
|
allow_err!(tx.send((k, event)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main(flavor = "current_thread")]
|
||||||
|
async fn start_whiteboard_() -> ResultType<()> {
|
||||||
|
let mut tx_whiteboard = TX_WHITEBOARD.write().unwrap();
|
||||||
|
if tx_whiteboard.is_some() {
|
||||||
|
log::warn!("Whiteboard already started");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if !crate::platform::is_prelogin() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sleep(1.).await;
|
||||||
|
}
|
||||||
|
let mut stream = None;
|
||||||
|
if let Ok(s) = ipc::connect(1000, "_whiteboard").await {
|
||||||
|
stream = Some(s);
|
||||||
|
} else {
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
#[allow(unused_assignments)]
|
||||||
|
let mut args = vec!["--whiteboard"];
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
let mut user = None;
|
||||||
|
|
||||||
|
let run_done;
|
||||||
|
if crate::platform::is_root() {
|
||||||
|
let mut res = Ok(None);
|
||||||
|
for _ in 0..10 {
|
||||||
|
#[cfg(not(any(target_os = "linux")))]
|
||||||
|
{
|
||||||
|
log::debug!("Start whiteboard");
|
||||||
|
res = crate::platform::run_as_user(args.clone());
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
log::debug!("Start whiteboard");
|
||||||
|
res = crate::platform::run_as_user(
|
||||||
|
args.clone(),
|
||||||
|
user.clone(),
|
||||||
|
None::<(&str, &str)>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if res.is_ok() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
log::error!("Failed to run whiteboard: {res:?}");
|
||||||
|
sleep(1.).await;
|
||||||
|
}
|
||||||
|
if let Some(task) = res? {
|
||||||
|
CHILD_PROCESS.lock().unwrap().push(task);
|
||||||
|
}
|
||||||
|
run_done = true;
|
||||||
|
} else {
|
||||||
|
run_done = false;
|
||||||
|
}
|
||||||
|
if !run_done {
|
||||||
|
log::debug!("Start whiteboard");
|
||||||
|
CHILD_PROCESS.lock().unwrap().push(crate::run_me(args)?);
|
||||||
|
}
|
||||||
|
for _ in 0..20 {
|
||||||
|
sleep(0.3).await;
|
||||||
|
if let Ok(s) = ipc::connect(1000, "_whiteboard").await {
|
||||||
|
stream = Some(s);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if stream.is_none() {
|
||||||
|
bail!("Failed to connect to connection manager");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut stream = stream.ok_or(anyhow!("none stream"))?;
|
||||||
|
let (tx, mut rx) = unbounded_channel();
|
||||||
|
tx_whiteboard.replace(tx);
|
||||||
|
drop(tx_whiteboard);
|
||||||
|
let _call_on_ret = crate::common::SimpleCallOnReturn {
|
||||||
|
b: true,
|
||||||
|
f: Box::new(move || {
|
||||||
|
let _ = TX_WHITEBOARD.write().unwrap().take();
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let dur = tokio::time::Duration::from_millis(300);
|
||||||
|
let mut timer = interval_at(tokio::time::Instant::now() + dur, dur);
|
||||||
|
timer.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
res = rx.recv() => {
|
||||||
|
match res {
|
||||||
|
Some(data) => {
|
||||||
|
if matches!(data.1, CustomEvent::Exit) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
allow_err!(stream.send(&Data::Whiteboard(data)).await);
|
||||||
|
timer.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
bail!("expected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ = timer.tick() => {
|
||||||
|
let mut conns = CONNS.write().unwrap();
|
||||||
|
for (k, conn) in conns.iter_mut() {
|
||||||
|
if conn.last_cursor_evt.tm.elapsed().as_millis() > 300 {
|
||||||
|
if let Some(evt) = conn.last_cursor_evt.evt.take() {
|
||||||
|
allow_err!(stream.send(&Data::Whiteboard((k.clone(), evt))).await);
|
||||||
|
conn.last_cursor_evt.c = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allow_err!(
|
||||||
|
stream
|
||||||
|
.send(&Data::Whiteboard(("".to_string(), CustomEvent::Exit)))
|
||||||
|
.await
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
234
src/whiteboard/macos.rs
Normal file
234
src/whiteboard/macos.rs
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
use super::{server::EVENT_PROXY, Cursor, CustomEvent};
|
||||||
|
use core_graphics::context::CGContextRef;
|
||||||
|
use foreign_types::ForeignTypeRef;
|
||||||
|
use hbb_common::{bail, log, ResultType};
|
||||||
|
use objc::{class, msg_send, runtime::Object, sel, sel_impl};
|
||||||
|
use piet::{kurbo::BezPath, RenderContext};
|
||||||
|
use piet_coregraphics::CoreGraphicsContext;
|
||||||
|
use std::{collections::HashMap, sync::Arc, time::Instant};
|
||||||
|
use tao::{
|
||||||
|
dpi::{PhysicalPosition, PhysicalSize},
|
||||||
|
event::{Event, StartCause, WindowEvent},
|
||||||
|
event_loop::{ControlFlow, EventLoopBuilder},
|
||||||
|
rwh_06::{HasWindowHandle, RawWindowHandle},
|
||||||
|
window::{Window, WindowBuilder},
|
||||||
|
};
|
||||||
|
|
||||||
|
const MAXIMUM_WINDOW_LEVEL: i64 = 2147483647;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
struct NSRect {
|
||||||
|
origin: NSPoint,
|
||||||
|
size: NSSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
struct NSPoint {
|
||||||
|
x: f64,
|
||||||
|
y: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
struct NSSize {
|
||||||
|
width: f64,
|
||||||
|
height: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_window_properties(window: &Arc<Window>) -> ResultType<()> {
|
||||||
|
let handle = window.window_handle()?;
|
||||||
|
if let RawWindowHandle::AppKit(appkit_handle) = handle.as_raw() {
|
||||||
|
unsafe {
|
||||||
|
let ns_view = appkit_handle.ns_view.as_ptr() as *mut Object;
|
||||||
|
if ns_view.is_null() {
|
||||||
|
bail!("Ns view of the window handle is null.");
|
||||||
|
}
|
||||||
|
let ns_window: *mut Object = msg_send![ns_view, window];
|
||||||
|
if ns_window.is_null() {
|
||||||
|
bail!("Ns window of the ns view is null.");
|
||||||
|
}
|
||||||
|
let _: () = msg_send![ns_window, setOpaque: false];
|
||||||
|
let _: () = msg_send![ns_window, setLevel: MAXIMUM_WINDOW_LEVEL];
|
||||||
|
// NSWindowCollectionBehaviorCanJoinAllSpaces | NSWindowCollectionBehaviorIgnoresCycle
|
||||||
|
let _: () = msg_send![ns_window, setCollectionBehavior: 5];
|
||||||
|
let current_style_mask: u64 = msg_send![ns_window, styleMask];
|
||||||
|
// NSWindowStyleMaskNonactivatingPanel
|
||||||
|
let new_style_mask = current_style_mask | (1 << 7);
|
||||||
|
let _: () = msg_send![ns_window, setStyleMask: new_style_mask];
|
||||||
|
let ns_screen_class = class!(NSScreen);
|
||||||
|
let main_screen: *mut Object = msg_send![ns_screen_class, mainScreen];
|
||||||
|
let screen_frame: NSRect = msg_send![main_screen, frame];
|
||||||
|
let _: () = msg_send![ns_window, setFrame: screen_frame display: true];
|
||||||
|
let ns_color_class = class!(NSColor);
|
||||||
|
let clear_color: *mut Object = msg_send![ns_color_class, clearColor];
|
||||||
|
let _: () = msg_send![ns_window, setBackgroundColor: clear_color];
|
||||||
|
let _: () = msg_send![ns_window, setIgnoresMouseEvents: true];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn create_event_loop() -> ResultType<()> {
|
||||||
|
crate::platform::hide_dock();
|
||||||
|
let event_loop = EventLoopBuilder::<(String, CustomEvent)>::with_user_event().build();
|
||||||
|
let mut window_builder = WindowBuilder::new()
|
||||||
|
.with_title("RustDesk whiteboard")
|
||||||
|
.with_transparent(true)
|
||||||
|
.with_decorations(false);
|
||||||
|
|
||||||
|
let (x, y, w, h) = super::server::get_displays_rect()?;
|
||||||
|
if w > 0 && h > 0 {
|
||||||
|
window_builder = window_builder
|
||||||
|
.with_position(PhysicalPosition::new(x, y))
|
||||||
|
.with_inner_size(PhysicalSize::new(w, h));
|
||||||
|
} else {
|
||||||
|
bail!("No valid display found, wxh: {}x{}", w, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
let window = Arc::new(window_builder.build::<(String, CustomEvent)>(&event_loop)?);
|
||||||
|
set_window_properties(&window)?;
|
||||||
|
|
||||||
|
let proxy = event_loop.create_proxy();
|
||||||
|
EVENT_PROXY.write().unwrap().replace(proxy);
|
||||||
|
let _call_on_ret = crate::common::SimpleCallOnReturn {
|
||||||
|
b: true,
|
||||||
|
f: Box::new(move || {
|
||||||
|
let _ = EVENT_PROXY.write().unwrap().take();
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
// to-do: The scale factor may not be correct.
|
||||||
|
// There may be multiple monitors with different scale factors.
|
||||||
|
// But we only have one window, and one scale factor.
|
||||||
|
let mut scale_factor = window.scale_factor();
|
||||||
|
if scale_factor == 0.0 {
|
||||||
|
scale_factor = 1.0;
|
||||||
|
}
|
||||||
|
let physical_size = window.inner_size();
|
||||||
|
let logical_size = physical_size.to_logical::<f64>(scale_factor);
|
||||||
|
|
||||||
|
struct Ripple {
|
||||||
|
x: f64,
|
||||||
|
y: f64,
|
||||||
|
start_time: Instant,
|
||||||
|
}
|
||||||
|
let mut ripples: Vec<Ripple> = Vec::new();
|
||||||
|
let mut last_cursors: HashMap<String, Cursor> = HashMap::new();
|
||||||
|
|
||||||
|
event_loop.run(move |event, _, control_flow| {
|
||||||
|
*control_flow = ControlFlow::Poll;
|
||||||
|
|
||||||
|
match event {
|
||||||
|
Event::NewEvents(StartCause::Init) => {
|
||||||
|
window.set_outer_position(PhysicalPosition::new(0, 0));
|
||||||
|
window.request_redraw();
|
||||||
|
crate::platform::hide_dock();
|
||||||
|
}
|
||||||
|
Event::WindowEvent { event, .. } => match event {
|
||||||
|
WindowEvent::CloseRequested => {
|
||||||
|
*control_flow = ControlFlow::Exit;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
Event::RedrawRequested(_) => {
|
||||||
|
if let Ok(handle) = window.window_handle() {
|
||||||
|
if let RawWindowHandle::AppKit(appkit_handle) = handle.as_raw() {
|
||||||
|
unsafe {
|
||||||
|
let ns_view = appkit_handle.ns_view.as_ptr() as *mut Object;
|
||||||
|
let current_context: *mut Object =
|
||||||
|
msg_send![class!(NSGraphicsContext), currentContext];
|
||||||
|
if !current_context.is_null() {
|
||||||
|
let cg_context_ptr: *mut std::ffi::c_void =
|
||||||
|
msg_send![current_context, CGContext];
|
||||||
|
if !cg_context_ptr.is_null() {
|
||||||
|
let cg_context_ref =
|
||||||
|
CGContextRef::from_ptr_mut(cg_context_ptr as *mut _);
|
||||||
|
let mut context = CoreGraphicsContext::new_y_up(
|
||||||
|
cg_context_ref,
|
||||||
|
logical_size.height,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
context.clear(None, piet::Color::TRANSPARENT);
|
||||||
|
|
||||||
|
let ripple_duration = std::time::Duration::from_millis(500);
|
||||||
|
ripples.retain_mut(|ripple| {
|
||||||
|
let elapsed = ripple.start_time.elapsed();
|
||||||
|
let progress =
|
||||||
|
elapsed.as_secs_f64() / ripple_duration.as_secs_f64();
|
||||||
|
let radius = 45.0 * progress / scale_factor;
|
||||||
|
let alpha = 1.0 - progress;
|
||||||
|
if alpha > 0.0 {
|
||||||
|
let color = piet::Color::rgba(1.0, 0.5, 0.5, alpha);
|
||||||
|
let circle = piet::kurbo::Circle::new(
|
||||||
|
(ripple.x / scale_factor, ripple.y / scale_factor),
|
||||||
|
radius,
|
||||||
|
);
|
||||||
|
context.stroke(circle, &color, 2.0);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for cursor in last_cursors.values() {
|
||||||
|
let (x, y) = (
|
||||||
|
cursor.x as f64 / scale_factor,
|
||||||
|
cursor.y as f64 / scale_factor,
|
||||||
|
);
|
||||||
|
let size = 1.0;
|
||||||
|
|
||||||
|
let mut pb = BezPath::new();
|
||||||
|
pb.move_to((x, y));
|
||||||
|
pb.line_to((x, y + 16.0 * size));
|
||||||
|
pb.line_to((x + 4.0 * size, y + 13.0 * size));
|
||||||
|
pb.line_to((x + 7.0 * size, y + 20.0 * size));
|
||||||
|
pb.line_to((x + 9.0 * size, y + 19.0 * size));
|
||||||
|
pb.line_to((x + 6.0 * size, y + 12.0 * size));
|
||||||
|
pb.line_to((x + 11.0 * size, y + 12.0 * size));
|
||||||
|
|
||||||
|
let color = piet::Color::rgba8(
|
||||||
|
(cursor.argb >> 16 & 0xFF) as u8,
|
||||||
|
(cursor.argb >> 8 & 0xFF) as u8,
|
||||||
|
(cursor.argb & 0xFF) as u8,
|
||||||
|
(cursor.argb >> 24 & 0xFF) as u8,
|
||||||
|
);
|
||||||
|
context.fill(pb, &color);
|
||||||
|
}
|
||||||
|
if let Err(e) = context.finish() {
|
||||||
|
log::error!("Failed to draw cursor: {}", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log::warn!("CGContext is null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _: () = msg_send![ns_view, setNeedsDisplay:true];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::MainEventsCleared => {
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
Event::UserEvent((k, evt)) => match evt {
|
||||||
|
CustomEvent::Cursor(cursor) => {
|
||||||
|
if cursor.btns != 0 {
|
||||||
|
ripples.push(Ripple {
|
||||||
|
x: cursor.x as _,
|
||||||
|
y: cursor.y as _,
|
||||||
|
start_time: Instant::now(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
last_cursors.insert(k, cursor);
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
CustomEvent::Exit => {
|
||||||
|
*control_flow = ControlFlow::Exit;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
35
src/whiteboard/mod.rs
Normal file
35
src/whiteboard/mod.rs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
mod client;
|
||||||
|
mod server;
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
mod windows;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
mod macos;
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
use windows::create_event_loop;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
use macos::create_event_loop;
|
||||||
|
|
||||||
|
pub use client::*;
|
||||||
|
pub use server::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[serde(tag = "t", content = "c")]
|
||||||
|
pub enum CustomEvent {
|
||||||
|
Cursor(Cursor),
|
||||||
|
Clear,
|
||||||
|
Exit,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[serde(tag = "t")]
|
||||||
|
pub struct Cursor {
|
||||||
|
pub x: f32,
|
||||||
|
pub y: f32,
|
||||||
|
pub argb: u32,
|
||||||
|
pub btns: i32,
|
||||||
|
pub text: String,
|
||||||
|
}
|
||||||
120
src/whiteboard/server.rs
Normal file
120
src/whiteboard/server.rs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
use super::{create_event_loop, CustomEvent};
|
||||||
|
use crate::ipc::{new_listener, Connection, Data};
|
||||||
|
use hbb_common::{
|
||||||
|
allow_err, log,
|
||||||
|
tokio::{
|
||||||
|
self,
|
||||||
|
sync::mpsc::{unbounded_channel, UnboundedReceiver},
|
||||||
|
},
|
||||||
|
ResultType,
|
||||||
|
};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use std::sync::RwLock;
|
||||||
|
use tao::event_loop::EventLoopProxy;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub(super) static ref EVENT_PROXY: RwLock<Option<EventLoopProxy<(String, CustomEvent)>>> =
|
||||||
|
RwLock::new(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run() {
|
||||||
|
let (tx_exit, rx_exit) = unbounded_channel();
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
start_ipc(rx_exit);
|
||||||
|
});
|
||||||
|
if let Err(e) = create_event_loop() {
|
||||||
|
log::error!("Failed to create event loop: {}", e);
|
||||||
|
tx_exit.send(()).ok();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main(flavor = "current_thread")]
|
||||||
|
async fn start_ipc(mut rx_exit: UnboundedReceiver<()>) {
|
||||||
|
match new_listener("_whiteboard").await {
|
||||||
|
Ok(mut incoming) => loop {
|
||||||
|
tokio::select! {
|
||||||
|
_ = rx_exit.recv() => {
|
||||||
|
log::info!("Exiting IPC");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
res = incoming.next() => match res {
|
||||||
|
Some(result) => match result {
|
||||||
|
Ok(stream) => {
|
||||||
|
log::debug!("Got new connection");
|
||||||
|
tokio::spawn(handle_new_stream(Connection::new(stream)));
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("Couldn't get whiteboard client: {:?}", err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
log::error!("Failed to get whiteboard client");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("Failed to start whiteboard ipc server: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_new_stream(mut conn: Connection) {
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
res = conn.next() => {
|
||||||
|
match res {
|
||||||
|
Err(err) => {
|
||||||
|
log::info!("whiteboard ipc connection closed: {}", err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Ok(Some(data)) => {
|
||||||
|
match data {
|
||||||
|
Data::Whiteboard((k, evt)) => {
|
||||||
|
if matches!(evt, CustomEvent::Exit) {
|
||||||
|
log::info!("whiteboard ipc connection closed");
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
EVENT_PROXY.read().unwrap().as_ref().map(|ep| {
|
||||||
|
allow_err!(ep.send_event((k, evt)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
log::info!("whiteboard ipc connection closed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EVENT_PROXY.read().unwrap().as_ref().map(|ep| {
|
||||||
|
allow_err!(ep.send_event(("".to_string(), CustomEvent::Exit)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn get_displays_rect() -> ResultType<(i32, i32, u32, u32)> {
|
||||||
|
let displays = crate::server::display_service::try_get_displays()?;
|
||||||
|
let mut min_x = i32::MAX;
|
||||||
|
let mut min_y = i32::MAX;
|
||||||
|
let mut max_x = i32::MIN;
|
||||||
|
let mut max_y = i32::MIN;
|
||||||
|
|
||||||
|
for display in displays {
|
||||||
|
let (x, y) = (display.origin().0 as i32, display.origin().1 as i32);
|
||||||
|
let (w, h) = (display.width() as i32, display.height() as i32);
|
||||||
|
min_x = min_x.min(x);
|
||||||
|
min_y = min_y.min(y);
|
||||||
|
max_x = max_x.max(x + w);
|
||||||
|
max_y = max_y.max(y + h);
|
||||||
|
}
|
||||||
|
let (x, y) = (min_x, min_y);
|
||||||
|
let (w, h) = ((max_x - min_x) as u32, (max_y - min_y) as u32);
|
||||||
|
Ok((x, y, w, h))
|
||||||
|
}
|
||||||
@@ -1,73 +1,20 @@
|
|||||||
use crate::ipc::{self, new_listener, Connection, Data};
|
use super::{server::EVENT_PROXY, Cursor, CustomEvent};
|
||||||
use hbb_common::{
|
use hbb_common::{anyhow::anyhow, bail, log, ResultType};
|
||||||
allow_err,
|
|
||||||
anyhow::anyhow,
|
|
||||||
bail, log, sleep,
|
|
||||||
tokio::{
|
|
||||||
self,
|
|
||||||
sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
|
||||||
time::interval_at,
|
|
||||||
},
|
|
||||||
ResultType,
|
|
||||||
};
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
|
||||||
use softbuffer::{Context, Surface};
|
use softbuffer::{Context, Surface};
|
||||||
use std::{
|
use std::{collections::HashMap, num::NonZeroU32, sync::Arc, time::Instant};
|
||||||
collections::HashMap,
|
|
||||||
num::NonZeroU32,
|
|
||||||
sync::{Arc, RwLock},
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
use tao::platform::unix::WindowBuilderExtUnix;
|
use tao::platform::unix::WindowBuilderExtUnix;
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
use tao::platform::windows::WindowBuilderExtWindows;
|
use tao::platform::windows::WindowBuilderExtWindows;
|
||||||
use tao::{
|
use tao::{
|
||||||
|
dpi::{PhysicalPosition, PhysicalSize},
|
||||||
event::{Event, WindowEvent},
|
event::{Event, WindowEvent},
|
||||||
event_loop::{ControlFlow, EventLoopBuilder, EventLoopProxy},
|
event_loop::{ControlFlow, EventLoopBuilder},
|
||||||
window::WindowBuilder,
|
window::WindowBuilder,
|
||||||
};
|
};
|
||||||
use tiny_skia::{Color, FillRule, Paint, PathBuilder, PixmapMut, Point, Stroke, Transform};
|
use tiny_skia::{Color, FillRule, Paint, PathBuilder, PixmapMut, Point, Stroke, Transform};
|
||||||
use ttf_parser::Face;
|
use ttf_parser::Face;
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref EVENT_PROXY: RwLock<Option<EventLoopProxy<(String, CustomEvent)>>> =
|
|
||||||
RwLock::new(None);
|
|
||||||
static ref TX_WHITEBOARD: RwLock<Option<UnboundedSender<(String, CustomEvent)>>> =
|
|
||||||
RwLock::new(None);
|
|
||||||
static ref CONNS: RwLock<HashMap<String, Conn>> = Default::default();
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Conn {
|
|
||||||
last_cursor_pos: (f32, f32), // For click ripple
|
|
||||||
last_cursor_evt: LastCursorEvent,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
#[serde(tag = "t", content = "c")]
|
|
||||||
pub enum CustomEvent {
|
|
||||||
Cursor(Cursor),
|
|
||||||
Clear,
|
|
||||||
Exit,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
#[serde(tag = "t")]
|
|
||||||
pub struct Cursor {
|
|
||||||
pub x: f32,
|
|
||||||
pub y: f32,
|
|
||||||
pub argb: u32,
|
|
||||||
pub btns: i32,
|
|
||||||
pub text: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LastCursorEvent {
|
|
||||||
evt: Option<CustomEvent>,
|
|
||||||
tm: Instant,
|
|
||||||
c: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
// A helper struct to bridge `ttf-parser` and `tiny-skia`.
|
// A helper struct to bridge `ttf-parser` and `tiny-skia`.
|
||||||
struct PathBuilderWrapper<'a> {
|
struct PathBuilderWrapper<'a> {
|
||||||
path_builder: &'a mut PathBuilder,
|
path_builder: &'a mut PathBuilder,
|
||||||
@@ -148,314 +95,6 @@ fn draw_text(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn get_key_cursor(conn_id: i32) -> String {
|
|
||||||
format!("{}-cursor", conn_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register_whiteboard(k: String) {
|
|
||||||
std::thread::spawn(|| {
|
|
||||||
allow_err!(start_whiteboard_());
|
|
||||||
});
|
|
||||||
let mut conns = CONNS.write().unwrap();
|
|
||||||
if !conns.contains_key(&k) {
|
|
||||||
conns.insert(
|
|
||||||
k,
|
|
||||||
Conn {
|
|
||||||
last_cursor_pos: (0.0, 0.0),
|
|
||||||
last_cursor_evt: LastCursorEvent {
|
|
||||||
evt: None,
|
|
||||||
tm: Instant::now(),
|
|
||||||
c: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unregister_whiteboard(k: String) {
|
|
||||||
let mut conns = CONNS.write().unwrap();
|
|
||||||
conns.remove(&k);
|
|
||||||
let is_conns_empty = conns.is_empty();
|
|
||||||
drop(conns);
|
|
||||||
|
|
||||||
TX_WHITEBOARD.read().unwrap().as_ref().map(|tx| {
|
|
||||||
allow_err!(tx.send((k, CustomEvent::Clear)));
|
|
||||||
});
|
|
||||||
if is_conns_empty {
|
|
||||||
std::thread::spawn(|| {
|
|
||||||
let mut whiteboard = TX_WHITEBOARD.write().unwrap();
|
|
||||||
whiteboard.as_ref().map(|tx| {
|
|
||||||
allow_err!(tx.send(("".to_string(), CustomEvent::Exit)));
|
|
||||||
// Simple sleep to wait the whiteboard process exiting.
|
|
||||||
std::thread::sleep(std::time::Duration::from_millis(3_00));
|
|
||||||
});
|
|
||||||
whiteboard.take();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_whiteboard(k: String, e: CustomEvent) {
|
|
||||||
let mut conns = CONNS.write().unwrap();
|
|
||||||
let Some(conn) = conns.get_mut(&k) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
match &e {
|
|
||||||
CustomEvent::Cursor(cursor) => {
|
|
||||||
conn.last_cursor_evt.c += 1;
|
|
||||||
conn.last_cursor_evt.tm = Instant::now();
|
|
||||||
if cursor.btns == 0 {
|
|
||||||
// Send one movement event every 4.
|
|
||||||
if conn.last_cursor_evt.c > 3 {
|
|
||||||
conn.last_cursor_evt.c = 0;
|
|
||||||
conn.last_cursor_evt.evt = None;
|
|
||||||
tx_send_event(conn, k, e);
|
|
||||||
} else {
|
|
||||||
conn.last_cursor_evt.evt = Some(e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if let Some(evt) = conn.last_cursor_evt.evt.take() {
|
|
||||||
tx_send_event(conn, k.clone(), evt);
|
|
||||||
conn.last_cursor_evt.c = 0;
|
|
||||||
}
|
|
||||||
let click_evt = CustomEvent::Cursor(Cursor {
|
|
||||||
x: conn.last_cursor_pos.0,
|
|
||||||
y: conn.last_cursor_pos.1,
|
|
||||||
argb: cursor.argb,
|
|
||||||
btns: cursor.btns,
|
|
||||||
text: cursor.text.clone(),
|
|
||||||
});
|
|
||||||
tx_send_event(conn, k, click_evt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
tx_send_event(conn, k, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn tx_send_event(conn: &mut Conn, k: String, event: CustomEvent) {
|
|
||||||
if let CustomEvent::Cursor(cursor) = &event {
|
|
||||||
if cursor.btns == 0 {
|
|
||||||
conn.last_cursor_pos = (cursor.x, cursor.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TX_WHITEBOARD.read().unwrap().as_ref().map(|tx| {
|
|
||||||
allow_err!(tx.send((k, event)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
|
||||||
async fn start_whiteboard_() -> ResultType<()> {
|
|
||||||
let mut tx_whiteboard = TX_WHITEBOARD.write().unwrap();
|
|
||||||
if tx_whiteboard.is_some() {
|
|
||||||
log::warn!("Whiteboard already started");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if !crate::platform::is_prelogin() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
sleep(1.).await;
|
|
||||||
}
|
|
||||||
let mut stream = None;
|
|
||||||
if let Ok(s) = ipc::connect(1000, "_whiteboard").await {
|
|
||||||
stream = Some(s);
|
|
||||||
} else {
|
|
||||||
#[allow(unused_mut)]
|
|
||||||
#[allow(unused_assignments)]
|
|
||||||
let mut args = vec!["--whiteboard"];
|
|
||||||
#[allow(unused_mut)]
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
let mut user = None;
|
|
||||||
|
|
||||||
let run_done;
|
|
||||||
if crate::platform::is_root() {
|
|
||||||
let mut res = Ok(None);
|
|
||||||
for _ in 0..10 {
|
|
||||||
#[cfg(not(any(target_os = "linux")))]
|
|
||||||
{
|
|
||||||
log::debug!("Start whiteboard");
|
|
||||||
res = crate::platform::run_as_user(args.clone());
|
|
||||||
}
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
{
|
|
||||||
log::debug!("Start whiteboard");
|
|
||||||
res = crate::platform::run_as_user(
|
|
||||||
args.clone(),
|
|
||||||
user.clone(),
|
|
||||||
None::<(&str, &str)>,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if res.is_ok() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
log::error!("Failed to run whiteboard: {res:?}");
|
|
||||||
sleep(1.).await;
|
|
||||||
}
|
|
||||||
if let Some(task) = res? {
|
|
||||||
super::CHILD_PROCESS.lock().unwrap().push(task);
|
|
||||||
}
|
|
||||||
run_done = true;
|
|
||||||
} else {
|
|
||||||
run_done = false;
|
|
||||||
}
|
|
||||||
if !run_done {
|
|
||||||
log::debug!("Start whiteboard");
|
|
||||||
super::CHILD_PROCESS
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.push(crate::run_me(args)?);
|
|
||||||
}
|
|
||||||
for _ in 0..20 {
|
|
||||||
sleep(0.3).await;
|
|
||||||
if let Ok(s) = ipc::connect(1000, "_whiteboard").await {
|
|
||||||
stream = Some(s);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if stream.is_none() {
|
|
||||||
bail!("Failed to connect to connection manager");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut stream = stream.ok_or(anyhow!("none stream"))?;
|
|
||||||
let (tx, mut rx) = unbounded_channel();
|
|
||||||
tx_whiteboard.replace(tx);
|
|
||||||
drop(tx_whiteboard);
|
|
||||||
let _call_on_ret = crate::common::SimpleCallOnReturn {
|
|
||||||
b: true,
|
|
||||||
f: Box::new(move || {
|
|
||||||
let _ = TX_WHITEBOARD.write().unwrap().take();
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
let dur = tokio::time::Duration::from_millis(300);
|
|
||||||
let mut timer = interval_at(tokio::time::Instant::now() + dur, dur);
|
|
||||||
timer.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
|
|
||||||
loop {
|
|
||||||
tokio::select! {
|
|
||||||
res = rx.recv() => {
|
|
||||||
match res {
|
|
||||||
Some(data) => {
|
|
||||||
if matches!(data.1, CustomEvent::Exit) {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
allow_err!(stream.send(&Data::Whiteboard(data)).await);
|
|
||||||
timer.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
bail!("expected");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ = timer.tick() => {
|
|
||||||
let mut conns = CONNS.write().unwrap();
|
|
||||||
for (k, conn) in conns.iter_mut() {
|
|
||||||
if conn.last_cursor_evt.tm.elapsed().as_millis() > 300 {
|
|
||||||
if let Some(evt) = conn.last_cursor_evt.evt.take() {
|
|
||||||
allow_err!(stream.send(&Data::Whiteboard((k.clone(), evt))).await);
|
|
||||||
conn.last_cursor_evt.c = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
allow_err!(
|
|
||||||
stream
|
|
||||||
.send(&Data::Whiteboard(("".to_string(), CustomEvent::Exit)))
|
|
||||||
.await
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run() {
|
|
||||||
let (tx_exit, rx_exit) = unbounded_channel();
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
start_ipc(rx_exit);
|
|
||||||
});
|
|
||||||
if let Err(e) = create_event_loop() {
|
|
||||||
log::error!("Failed to create event loop: {}", e);
|
|
||||||
tx_exit.send(()).ok();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
|
||||||
async fn start_ipc(mut rx_exit: UnboundedReceiver<()>) {
|
|
||||||
match new_listener("_whiteboard").await {
|
|
||||||
Ok(mut incoming) => loop {
|
|
||||||
tokio::select! {
|
|
||||||
_ = rx_exit.recv() => {
|
|
||||||
log::info!("Exiting IPC");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
res = incoming.next() => match res {
|
|
||||||
Some(result) => match result {
|
|
||||||
Ok(stream) => {
|
|
||||||
log::debug!("Got new connection");
|
|
||||||
tokio::spawn(handle_new_stream(Connection::new(stream)));
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
log::error!("Couldn't get whiteboard client: {:?}", err);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => {
|
|
||||||
log::error!("Failed to get whiteboard client");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(err) => {
|
|
||||||
log::error!("Failed to start whiteboard ipc server: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_new_stream(mut conn: Connection) {
|
|
||||||
loop {
|
|
||||||
tokio::select! {
|
|
||||||
res = conn.next() => {
|
|
||||||
match res {
|
|
||||||
Err(err) => {
|
|
||||||
log::info!("whiteboard ipc connection closed: {}", err);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Ok(Some(data)) => {
|
|
||||||
match data {
|
|
||||||
Data::Whiteboard((k, evt)) => {
|
|
||||||
if matches!(evt, CustomEvent::Exit) {
|
|
||||||
log::info!("whiteboard ipc connection closed");
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
EVENT_PROXY.read().unwrap().as_ref().map(|ep| {
|
|
||||||
allow_err!(ep.send_event((k, evt)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None) => {
|
|
||||||
log::info!("whiteboard ipc connection closed");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EVENT_PROXY.read().unwrap().as_ref().map(|ep| {
|
|
||||||
allow_err!(ep.send_event(("".to_string(), CustomEvent::Exit)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_font_face() -> ResultType<Face<'static>> {
|
fn create_font_face() -> ResultType<Face<'static>> {
|
||||||
let mut font_db = fontdb::Database::new();
|
let mut font_db = fontdb::Database::new();
|
||||||
font_db.load_system_fonts();
|
font_db.load_system_fonts();
|
||||||
@@ -478,7 +117,7 @@ fn create_font_face() -> ResultType<Face<'static>> {
|
|||||||
Ok(face)
|
Ok(face)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_event_loop() -> ResultType<()> {
|
pub(super) fn create_event_loop() -> ResultType<()> {
|
||||||
let face = match create_font_face() {
|
let face = match create_font_face() {
|
||||||
Ok(face) => Some(face),
|
Ok(face) => Some(face),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -492,28 +131,11 @@ fn create_event_loop() -> ResultType<()> {
|
|||||||
.with_title("RustDesk whiteboard")
|
.with_title("RustDesk whiteboard")
|
||||||
.with_transparent(true)
|
.with_transparent(true)
|
||||||
.with_always_on_top(true)
|
.with_always_on_top(true)
|
||||||
|
.with_skip_taskbar(true)
|
||||||
.with_decorations(false);
|
.with_decorations(false);
|
||||||
|
|
||||||
use tao::dpi::{PhysicalPosition, PhysicalSize};
|
|
||||||
let mut final_size = None;
|
let mut final_size = None;
|
||||||
if let Ok(displays) = crate::server::display_service::try_get_displays() {
|
if let Ok((x, y, w, h)) = super::server::get_displays_rect() {
|
||||||
let mut min_x = i32::MAX;
|
|
||||||
let mut min_y = i32::MAX;
|
|
||||||
let mut max_x = i32::MIN;
|
|
||||||
let mut max_y = i32::MIN;
|
|
||||||
|
|
||||||
for display in displays {
|
|
||||||
let (x, y) = (display.origin().0 as i32, display.origin().1 as i32);
|
|
||||||
let (w, h) = (display.width() as i32, display.height() as i32);
|
|
||||||
min_x = min_x.min(x);
|
|
||||||
min_y = min_y.min(y);
|
|
||||||
max_x = max_x.max(x + w);
|
|
||||||
max_y = max_y.max(y + h);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (x, y) = (min_x, min_y);
|
|
||||||
let (w, h) = ((max_x - min_x) as u32, (max_y - min_y) as u32);
|
|
||||||
|
|
||||||
if w > 0 && h > 0 {
|
if w > 0 && h > 0 {
|
||||||
final_size = Some(PhysicalSize::new(w, h));
|
final_size = Some(PhysicalSize::new(w, h));
|
||||||
window_builder = window_builder
|
window_builder = window_builder
|
||||||
@@ -528,11 +150,6 @@ fn create_event_loop() -> ResultType<()> {
|
|||||||
window_builder.with_fullscreen(Some(tao::window::Fullscreen::Borderless(None)));
|
window_builder.with_fullscreen(Some(tao::window::Fullscreen::Borderless(None)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
|
||||||
{
|
|
||||||
window_builder = window_builder.with_skip_taskbar(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
let window = Arc::new(window_builder.build::<(String, CustomEvent)>(&event_loop)?);
|
let window = Arc::new(window_builder.build::<(String, CustomEvent)>(&event_loop)?);
|
||||||
window.set_ignore_cursor_events(true)?;
|
window.set_ignore_cursor_events(true)?;
|
||||||
|
|
||||||
Reference in New Issue
Block a user