mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-04-10 12:01:29 +03:00
Fix/cursor macos multi displays (#12791)
* fix: cursor, whiteboard, pos Signed-off-by: fufesou <linlong1266@gmail.com> * fix: whiteboard, macos, multi displays Signed-off-by: fufesou <linlong1266@gmail.com> --------- Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
@@ -2329,6 +2329,8 @@ impl Connection {
|
|||||||
self.show_my_cursor,
|
self.show_my_cursor,
|
||||||
);
|
);
|
||||||
} else if self.show_my_cursor {
|
} else if self.show_my_cursor {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
self.retina.on_mouse_event(&mut me, self.display_idx);
|
||||||
self.input_mouse(
|
self.input_mouse(
|
||||||
me,
|
me,
|
||||||
self.inner.id(),
|
self.inner.id(),
|
||||||
|
|||||||
@@ -992,8 +992,8 @@ pub fn handle_pointer_(evt: &PointerDeviceEvent, conn: i32) {
|
|||||||
pub fn handle_mouse_(
|
pub fn handle_mouse_(
|
||||||
evt: &MouseEvent,
|
evt: &MouseEvent,
|
||||||
conn: i32,
|
conn: i32,
|
||||||
username: String,
|
_username: String,
|
||||||
argb: u32,
|
_argb: u32,
|
||||||
simulate: bool,
|
simulate: bool,
|
||||||
_show_cursor: bool,
|
_show_cursor: bool,
|
||||||
) {
|
) {
|
||||||
@@ -1002,7 +1002,7 @@ pub fn handle_mouse_(
|
|||||||
}
|
}
|
||||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
#[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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,34 +7,28 @@ use piet::{kurbo::BezPath, RenderContext};
|
|||||||
use piet_coregraphics::CoreGraphicsContext;
|
use piet_coregraphics::CoreGraphicsContext;
|
||||||
use std::{collections::HashMap, sync::Arc, time::Instant};
|
use std::{collections::HashMap, sync::Arc, time::Instant};
|
||||||
use tao::{
|
use tao::{
|
||||||
dpi::{PhysicalPosition, PhysicalSize},
|
dpi::{LogicalSize, PhysicalPosition, PhysicalSize},
|
||||||
event::{Event, StartCause, WindowEvent},
|
event::{Event, StartCause, WindowEvent},
|
||||||
event_loop::{ControlFlow, EventLoopBuilder},
|
event_loop::{ControlFlow, EventLoop, EventLoopBuilder},
|
||||||
|
platform::macos::MonitorHandleExtMacOS,
|
||||||
rwh_06::{HasWindowHandle, RawWindowHandle},
|
rwh_06::{HasWindowHandle, RawWindowHandle},
|
||||||
window::{Window, WindowBuilder},
|
window::{Window, WindowBuilder, WindowId},
|
||||||
};
|
};
|
||||||
|
|
||||||
const MAXIMUM_WINDOW_LEVEL: i64 = 2147483647;
|
const MAXIMUM_WINDOW_LEVEL: i64 = 2147483647;
|
||||||
|
|
||||||
#[repr(C)]
|
struct WindowState {
|
||||||
#[derive(Debug, Copy, Clone)]
|
window: Arc<Window>,
|
||||||
struct NSRect {
|
logical_size: LogicalSize<f64>,
|
||||||
origin: NSPoint,
|
outer_position: PhysicalPosition<i32>,
|
||||||
size: NSSize,
|
// A simple workaround to the (logical) cursor position.
|
||||||
|
display_origin: (f64, f64),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
struct Ripple {
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
struct NSPoint {
|
|
||||||
x: f64,
|
x: f64,
|
||||||
y: f64,
|
y: f64,
|
||||||
}
|
start_time: Instant,
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
struct NSSize {
|
|
||||||
width: f64,
|
|
||||||
height: f64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_window_properties(window: &Arc<Window>) -> ResultType<()> {
|
fn set_window_properties(window: &Arc<Window>) -> ResultType<()> {
|
||||||
@@ -57,38 +51,153 @@ fn set_window_properties(window: &Arc<Window>) -> ResultType<()> {
|
|||||||
// NSWindowStyleMaskNonactivatingPanel
|
// NSWindowStyleMaskNonactivatingPanel
|
||||||
let new_style_mask = current_style_mask | (1 << 7);
|
let new_style_mask = current_style_mask | (1 << 7);
|
||||||
let _: () = msg_send![ns_window, setStyleMask: new_style_mask];
|
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];
|
let _: () = msg_send![ns_window, setIgnoresMouseEvents: true];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_windows(event_loop: &EventLoop<(String, CustomEvent)>) -> ResultType<Vec<WindowState>> {
|
||||||
|
let mut windows = Vec::new();
|
||||||
|
let map_display_origins: HashMap<_, _> = crate::server::display_service::try_get_displays()?
|
||||||
|
.into_iter()
|
||||||
|
.map(|display| (display.name(), display.origin()))
|
||||||
|
.collect();
|
||||||
|
// We can't use `crate::server::display_service::try_get_displays()` here.
|
||||||
|
// Because the `display` returned by `crate::server::display_service::try_get_displays()`:
|
||||||
|
// 1. `display.origin()` is the logic position.
|
||||||
|
// 2. `display.width()` and `display.height()` are the physical size.
|
||||||
|
for monitor in event_loop.available_monitors() {
|
||||||
|
let Some(origin) = map_display_origins.get(&monitor.native_id().to_string()) else {
|
||||||
|
// unreachable!
|
||||||
|
bail!(
|
||||||
|
"Failed to find display origin for monitor: {}",
|
||||||
|
monitor.native_id()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let window_builder = WindowBuilder::new()
|
||||||
|
.with_title("RustDesk whiteboard")
|
||||||
|
.with_transparent(true)
|
||||||
|
.with_decorations(false)
|
||||||
|
.with_position(monitor.position())
|
||||||
|
.with_inner_size(monitor.size());
|
||||||
|
|
||||||
|
let window = Arc::new(window_builder.build::<(String, CustomEvent)>(event_loop)?);
|
||||||
|
set_window_properties(&window)?;
|
||||||
|
|
||||||
|
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);
|
||||||
|
let inner_position = window.inner_position()?;
|
||||||
|
let outer_position = inner_position;
|
||||||
|
windows.push(WindowState {
|
||||||
|
window,
|
||||||
|
logical_size,
|
||||||
|
outer_position,
|
||||||
|
display_origin: (origin.0 as f64, origin.1 as f64),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(windows)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_cursors(
|
||||||
|
windows: &Vec<WindowState>,
|
||||||
|
window_id: WindowId,
|
||||||
|
window_ripples: &mut HashMap<WindowId, Vec<Ripple>>,
|
||||||
|
last_cursors: &HashMap<String, (WindowId, Cursor)>,
|
||||||
|
) {
|
||||||
|
for window in windows.iter() {
|
||||||
|
if window.window.id() != window_id {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(handle) = window.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,
|
||||||
|
window.logical_size.height,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
context.clear(None, piet::Color::TRANSPARENT);
|
||||||
|
|
||||||
|
if let Some(ripples) = window_ripples.get_mut(&window_id) {
|
||||||
|
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 = 25.0 * progress;
|
||||||
|
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, ripple.y), radius);
|
||||||
|
context.stroke(circle, &color, 2.0);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (wid, cursor) in last_cursors.values() {
|
||||||
|
if *wid != window.window.id() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (x, y) = (cursor.x as f64, cursor.y as f64);
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn create_event_loop() -> ResultType<()> {
|
pub(super) fn create_event_loop() -> ResultType<()> {
|
||||||
crate::platform::hide_dock();
|
crate::platform::hide_dock();
|
||||||
let event_loop = EventLoopBuilder::<(String, CustomEvent)>::with_user_event().build();
|
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()?;
|
let windows = create_windows(&event_loop)?;
|
||||||
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();
|
let proxy = event_loop.create_proxy();
|
||||||
EVENT_PROXY.write().unwrap().replace(proxy);
|
EVENT_PROXY.write().unwrap().replace(proxy);
|
||||||
@@ -99,31 +208,18 @@ pub(super) fn create_event_loop() -> ResultType<()> {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
// to-do: The scale factor may not be correct.
|
let mut window_ripples: HashMap<WindowId, Vec<Ripple>> = HashMap::new();
|
||||||
// There may be multiple monitors with different scale factors.
|
let mut last_cursors: HashMap<String, (WindowId, Cursor)> = HashMap::new();
|
||||||
// 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| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
*control_flow = ControlFlow::Poll;
|
*control_flow = ControlFlow::Poll;
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::NewEvents(StartCause::Init) => {
|
Event::NewEvents(StartCause::Init) => {
|
||||||
window.set_outer_position(PhysicalPosition::new(0, 0));
|
for window in windows.iter() {
|
||||||
window.request_redraw();
|
window.window.set_outer_position(window.outer_position);
|
||||||
|
window.window.request_redraw();
|
||||||
|
}
|
||||||
crate::platform::hide_dock();
|
crate::platform::hide_dock();
|
||||||
}
|
}
|
||||||
Event::WindowEvent { event, .. } => match event {
|
Event::WindowEvent { event, .. } => match event {
|
||||||
@@ -132,96 +228,58 @@ pub(super) fn create_event_loop() -> ResultType<()> {
|
|||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
Event::RedrawRequested(_) => {
|
Event::RedrawRequested(window_id) => {
|
||||||
if let Ok(handle) = window.window_handle() {
|
draw_cursors(&windows, window_id, &mut window_ripples, &last_cursors);
|
||||||
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 => {
|
Event::MainEventsCleared => {
|
||||||
window.request_redraw();
|
for window in windows.iter() {
|
||||||
|
window.window.request_redraw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Event::UserEvent((k, evt)) => match evt {
|
Event::UserEvent((k, evt)) => match evt {
|
||||||
CustomEvent::Cursor(cursor) => {
|
CustomEvent::Cursor(cursor) => {
|
||||||
if cursor.btns != 0 {
|
for window in windows.iter() {
|
||||||
ripples.push(Ripple {
|
let (l, t, r, b) = (
|
||||||
x: cursor.x as _,
|
window.display_origin.0,
|
||||||
y: cursor.y as _,
|
window.display_origin.1,
|
||||||
start_time: Instant::now(),
|
window.display_origin.0 + window.logical_size.width,
|
||||||
});
|
window.display_origin.1 + window.logical_size.height,
|
||||||
|
);
|
||||||
|
if (cursor.x as f64) < l
|
||||||
|
|| (cursor.x as f64) > r
|
||||||
|
|| (cursor.y as f64) < t
|
||||||
|
|| (cursor.y as f64) > b
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if cursor.btns != 0 {
|
||||||
|
let window_id = window.window.id();
|
||||||
|
let ripple = Ripple {
|
||||||
|
x: (cursor.x as f64 - window.display_origin.0),
|
||||||
|
y: (cursor.y as f64 - window.display_origin.1),
|
||||||
|
start_time: Instant::now(),
|
||||||
|
};
|
||||||
|
if let Some(ripples) = window_ripples.get_mut(&window_id) {
|
||||||
|
ripples.push(ripple);
|
||||||
|
} else {
|
||||||
|
window_ripples.insert(window_id, vec![ripple]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last_cursors.insert(
|
||||||
|
k,
|
||||||
|
(
|
||||||
|
window.window.id(),
|
||||||
|
Cursor {
|
||||||
|
x: (cursor.x - window.display_origin.0 as f32),
|
||||||
|
y: (cursor.y - window.display_origin.1 as f32),
|
||||||
|
..cursor
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
window.window.request_redraw();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
last_cursors.insert(k, cursor);
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
}
|
||||||
CustomEvent::Exit => {
|
CustomEvent::Exit => {
|
||||||
*control_flow = ControlFlow::Exit;
|
*control_flow = ControlFlow::Exit;
|
||||||
|
|||||||
Reference in New Issue
Block a user