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::{LogicalSize, PhysicalPosition, PhysicalSize}, event::{Event, StartCause, WindowEvent}, event_loop::{ControlFlow, EventLoop, EventLoopBuilder}, platform::macos::MonitorHandleExtMacOS, rwh_06::{HasWindowHandle, RawWindowHandle}, window::{Window, WindowBuilder, WindowId}, }; const MAXIMUM_WINDOW_LEVEL: i64 = 2147483647; struct WindowState { window: Arc, logical_size: LogicalSize, outer_position: PhysicalPosition, // A simple workaround to the (logical) cursor position. display_origin: (f64, f64), } struct Ripple { x: f64, y: f64, start_time: Instant, } fn set_window_properties(window: &Arc) -> 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 _: () = msg_send![ns_window, setIgnoresMouseEvents: true]; } } Ok(()) } fn create_windows(event_loop: &EventLoop<(String, CustomEvent)>) -> ResultType> { 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::(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, window_id: WindowId, window_ripples: &mut HashMap>, last_cursors: &HashMap, ) { 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<()> { crate::platform::hide_dock(); let event_loop = EventLoopBuilder::<(String, CustomEvent)>::with_user_event().build(); let windows = create_windows(&event_loop)?; 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(); }), }; let mut window_ripples: HashMap> = HashMap::new(); let mut last_cursors: HashMap = HashMap::new(); event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Poll; match event { Event::NewEvents(StartCause::Init) => { for window in windows.iter() { window.window.set_outer_position(window.outer_position); window.window.request_redraw(); } crate::platform::hide_dock(); } Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => { *control_flow = ControlFlow::Exit; } _ => {} }, Event::RedrawRequested(window_id) => { draw_cursors(&windows, window_id, &mut window_ripples, &last_cursors); } Event::MainEventsCleared => { for window in windows.iter() { window.window.request_redraw(); } } Event::UserEvent((k, evt)) => match evt { CustomEvent::Cursor(cursor) => { for window in windows.iter() { let (l, t, r, b) = ( window.display_origin.0, window.display_origin.1, 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; } } CustomEvent::Exit => { *control_flow = ControlFlow::Exit; } _ => {} }, _ => (), } }); }