349 lines
12 KiB
Rust
349 lines
12 KiB
Rust
use super::{server::EVENT_PROXY, Cursor, CustomEvent};
|
|
use hbb_common::{anyhow::anyhow, bail, log, ResultType};
|
|
use softbuffer::{Context, Surface};
|
|
use std::{collections::HashMap, num::NonZeroU32, sync::Arc, time::Instant};
|
|
#[cfg(target_os = "linux")]
|
|
use tao::platform::unix::WindowBuilderExtUnix;
|
|
#[cfg(target_os = "windows")]
|
|
use tao::platform::windows::WindowBuilderExtWindows;
|
|
use tao::{
|
|
dpi::{PhysicalPosition, PhysicalSize},
|
|
event::{Event, WindowEvent},
|
|
event_loop::{ControlFlow, EventLoopBuilder},
|
|
window::WindowBuilder,
|
|
};
|
|
use tiny_skia::{Color, FillRule, Paint, PathBuilder, PixmapMut, Point, Stroke, Transform};
|
|
use ttf_parser::Face;
|
|
|
|
// A helper struct to bridge `ttf-parser` and `tiny-skia`.
|
|
struct PathBuilderWrapper<'a> {
|
|
path_builder: &'a mut PathBuilder,
|
|
transform: Transform,
|
|
}
|
|
|
|
impl ttf_parser::OutlineBuilder for PathBuilderWrapper<'_> {
|
|
fn move_to(&mut self, x: f32, y: f32) {
|
|
let mut pt = Point::from_xy(x, y);
|
|
self.transform.map_point(&mut pt);
|
|
self.path_builder.move_to(pt.x, pt.y);
|
|
}
|
|
|
|
fn line_to(&mut self, x: f32, y: f32) {
|
|
let mut pt = Point::from_xy(x, y);
|
|
self.transform.map_point(&mut pt);
|
|
self.path_builder.line_to(pt.x, pt.y);
|
|
}
|
|
|
|
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
|
|
let mut pt1 = Point::from_xy(x1, y1);
|
|
self.transform.map_point(&mut pt1);
|
|
let mut pt = Point::from_xy(x, y);
|
|
self.transform.map_point(&mut pt);
|
|
self.path_builder.quad_to(pt1.x, pt1.y, pt.x, pt.y);
|
|
}
|
|
|
|
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
|
|
let mut pt1 = Point::from_xy(x1, y1);
|
|
self.transform.map_point(&mut pt1);
|
|
let mut pt2 = Point::from_xy(x2, y2);
|
|
self.transform.map_point(&mut pt2);
|
|
let mut pt = Point::from_xy(x, y);
|
|
self.transform.map_point(&mut pt);
|
|
self.path_builder
|
|
.cubic_to(pt1.x, pt1.y, pt2.x, pt2.y, pt.x, pt.y);
|
|
}
|
|
|
|
fn close(&mut self) {
|
|
self.path_builder.close();
|
|
}
|
|
}
|
|
|
|
// Draws a string of text onto the pixmap.
|
|
fn draw_text(
|
|
pixmap: &mut PixmapMut,
|
|
face: &Face,
|
|
text: &str,
|
|
x: f32,
|
|
y: f32,
|
|
paint: &Paint,
|
|
font_size: f32,
|
|
) {
|
|
let units_per_em = face.units_per_em() as f32;
|
|
let scale = font_size / units_per_em;
|
|
let transform = Transform::from_translate(x, y).pre_scale(scale, -scale);
|
|
|
|
let mut path_builder = PathBuilder::new();
|
|
let mut current_x = 0.0;
|
|
|
|
for ch in text.chars() {
|
|
let glyph_id = face.glyph_index(ch).unwrap_or_default();
|
|
|
|
let mut builder = PathBuilderWrapper {
|
|
path_builder: &mut path_builder,
|
|
transform: transform.post_translate(current_x, 0.0),
|
|
};
|
|
|
|
face.outline_glyph(glyph_id, &mut builder);
|
|
|
|
if let Some(h_advance) = face.glyph_hor_advance(glyph_id) {
|
|
current_x += h_advance as f32 * scale;
|
|
}
|
|
}
|
|
|
|
if let Some(path) = path_builder.finish() {
|
|
pixmap.fill_path(&path, paint, FillRule::Winding, Transform::identity(), None);
|
|
}
|
|
}
|
|
|
|
fn create_font_face() -> ResultType<Face<'static>> {
|
|
let mut font_db = fontdb::Database::new();
|
|
font_db.load_system_fonts();
|
|
let query = fontdb::Query {
|
|
families: &[fontdb::Family::Monospace],
|
|
..fontdb::Query::default()
|
|
};
|
|
let Some(font_id) = font_db.query(&query) else {
|
|
bail!("No monospace font found!");
|
|
};
|
|
let Some((font_source, face_index)) = font_db.face_source(font_id) else {
|
|
bail!("No face found for font!");
|
|
};
|
|
let font_data: &'static [u8] = Box::leak(match font_source {
|
|
fontdb::Source::File(path) => std::fs::read(path)?.into_boxed_slice(),
|
|
fontdb::Source::Binary(data) => data.as_ref().as_ref().to_vec().into_boxed_slice(),
|
|
fontdb::Source::SharedFile(path, _) => std::fs::read(path)?.into_boxed_slice(),
|
|
});
|
|
let face = Face::parse(font_data, face_index)?;
|
|
Ok(face)
|
|
}
|
|
|
|
pub(super) fn create_event_loop() -> ResultType<()> {
|
|
let face = match create_font_face() {
|
|
Ok(face) => Some(face),
|
|
Err(err) => {
|
|
log::error!("Failed to create font face: {}", err);
|
|
None
|
|
}
|
|
};
|
|
|
|
let event_loop = EventLoopBuilder::<(String, CustomEvent)>::with_user_event().build();
|
|
let mut window_builder = WindowBuilder::new()
|
|
.with_title("RustDesk whiteboard")
|
|
.with_transparent(true)
|
|
.with_always_on_top(true)
|
|
.with_skip_taskbar(true)
|
|
.with_decorations(false);
|
|
|
|
let mut final_size = None;
|
|
if let Ok((x, y, w, h)) = super::server::get_displays_rect() {
|
|
if w > 0 && h > 0 {
|
|
final_size = Some(PhysicalSize::new(w, h));
|
|
window_builder = window_builder
|
|
.with_position(PhysicalPosition::new(x, y))
|
|
.with_inner_size(PhysicalSize::new(1, 1));
|
|
} else {
|
|
window_builder =
|
|
window_builder.with_fullscreen(Some(tao::window::Fullscreen::Borderless(None)));
|
|
}
|
|
} else {
|
|
window_builder =
|
|
window_builder.with_fullscreen(Some(tao::window::Fullscreen::Borderless(None)));
|
|
}
|
|
|
|
let window = Arc::new(window_builder.build::<(String, CustomEvent)>(&event_loop)?);
|
|
window.set_ignore_cursor_events(true)?;
|
|
|
|
let context = Context::new(window.clone()).map_err(|e| {
|
|
log::error!("Failed to create context: {}", e);
|
|
anyhow!(e.to_string())
|
|
})?;
|
|
let mut surface = Surface::new(&context, window.clone()).map_err(|e| {
|
|
log::error!("Failed to create surface: {}", e);
|
|
anyhow!(e.to_string())
|
|
})?;
|
|
|
|
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();
|
|
}),
|
|
};
|
|
|
|
struct Ripple {
|
|
x: f32,
|
|
y: f32,
|
|
start_time: Instant,
|
|
}
|
|
let mut ripples: Vec<Ripple> = Vec::new();
|
|
let mut last_cursors: HashMap<String, Cursor> = HashMap::new();
|
|
let mut resized = final_size.is_none();
|
|
|
|
event_loop.run(move |event, _, control_flow| {
|
|
*control_flow = ControlFlow::Poll;
|
|
|
|
match event {
|
|
Event::WindowEvent { event, .. } => match event {
|
|
WindowEvent::CloseRequested => {
|
|
*control_flow = ControlFlow::Exit;
|
|
}
|
|
_ => {}
|
|
},
|
|
Event::RedrawRequested(_) => {
|
|
if !resized {
|
|
if let Some(size) = final_size.take() {
|
|
window.set_inner_size(size);
|
|
}
|
|
resized = true;
|
|
return;
|
|
}
|
|
|
|
let (width, height) = {
|
|
let size = window.inner_size();
|
|
(size.width, size.height)
|
|
};
|
|
|
|
let (Some(width), Some(height)) = (NonZeroU32::new(width), NonZeroU32::new(height))
|
|
else {
|
|
return;
|
|
};
|
|
if let Err(e) = surface.resize(width, height) {
|
|
log::error!("Failed to resize surface: {}", e);
|
|
return;
|
|
}
|
|
|
|
let mut buffer = match surface.buffer_mut() {
|
|
Ok(buf) => buf,
|
|
Err(e) => {
|
|
log::error!("Failed to get buffer: {}", e);
|
|
return;
|
|
}
|
|
};
|
|
let Some(mut pixmap) = PixmapMut::from_bytes(
|
|
bytemuck::cast_slice_mut(&mut buffer),
|
|
width.get(),
|
|
height.get(),
|
|
) else {
|
|
log::error!("Failed to create pixmap from buffer");
|
|
return;
|
|
};
|
|
pixmap.fill(Color::TRANSPARENT);
|
|
|
|
let ripple_duration = std::time::Duration::from_millis(500);
|
|
ripples.retain(|r| r.start_time.elapsed() < ripple_duration);
|
|
|
|
for ripple in &ripples {
|
|
let elapsed = ripple.start_time.elapsed();
|
|
let progress = elapsed.as_secs_f32() / ripple_duration.as_secs_f32();
|
|
let radius = 45.0 * progress;
|
|
let alpha = 1.0 - progress;
|
|
|
|
let mut ripple_paint = Paint::default();
|
|
// Note: The real color is bgra here.
|
|
ripple_paint.set_color_rgba8(128, 128, 255, (alpha * 128.0) as u8);
|
|
ripple_paint.anti_alias = true;
|
|
|
|
let mut ripple_pb = PathBuilder::new();
|
|
let (rx, ry) = (ripple.x as f64, ripple.y as f64);
|
|
ripple_pb.push_circle(rx as f32, ry as f32, radius as f32);
|
|
if let Some(path) = ripple_pb.finish() {
|
|
pixmap.fill_path(
|
|
&path,
|
|
&ripple_paint,
|
|
FillRule::Winding,
|
|
Transform::identity(),
|
|
None,
|
|
);
|
|
}
|
|
}
|
|
|
|
for cursor in last_cursors.values() {
|
|
let (x, y) = (cursor.x as f64, cursor.y as f64);
|
|
let (x, y) = (x as f32, y as f32);
|
|
let size = 1.5 as f32;
|
|
|
|
let mut pb = PathBuilder::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);
|
|
pb.close();
|
|
|
|
if let Some(path) = pb.finish() {
|
|
let mut arrow_paint = Paint::default();
|
|
// Note: The real color is bgra here.
|
|
arrow_paint.set_color_rgba8(
|
|
(cursor.argb & 0xFF) as u8,
|
|
(cursor.argb >> 8 & 0xFF) as u8,
|
|
(cursor.argb >> 16 & 0xFF) as u8,
|
|
(cursor.argb >> 24 & 0xFF) as u8,
|
|
);
|
|
arrow_paint.anti_alias = true;
|
|
pixmap.fill_path(
|
|
&path,
|
|
&arrow_paint,
|
|
FillRule::Winding,
|
|
Transform::identity(),
|
|
None,
|
|
);
|
|
|
|
let mut black_paint = Paint::default();
|
|
black_paint.set_color_rgba8(0, 0, 0, 255);
|
|
black_paint.anti_alias = true;
|
|
let mut stroke = Stroke::default();
|
|
stroke.width = 1.0 as f32;
|
|
pixmap.stroke_path(
|
|
&path,
|
|
&black_paint,
|
|
&stroke,
|
|
Transform::identity(),
|
|
None,
|
|
);
|
|
|
|
face.as_ref().map(|face| {
|
|
draw_text(
|
|
&mut pixmap,
|
|
face,
|
|
&cursor.text,
|
|
x + 24.0 * size,
|
|
y + 24.0 * size,
|
|
&arrow_paint,
|
|
24.0 as f32,
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
if let Err(e) = buffer.present() {
|
|
log::error!("Failed to present surface: {}", e);
|
|
return;
|
|
}
|
|
}
|
|
Event::MainEventsCleared => {
|
|
window.request_redraw();
|
|
}
|
|
Event::UserEvent((k, evt)) => match evt {
|
|
CustomEvent::Cursor(cursor) => {
|
|
if cursor.btns != 0 {
|
|
ripples.push(Ripple {
|
|
x: cursor.x,
|
|
y: cursor.y,
|
|
start_time: Instant::now(),
|
|
});
|
|
}
|
|
last_cursors.insert(k, cursor);
|
|
}
|
|
CustomEvent::Exit => {
|
|
*control_flow = ControlFlow::Exit;
|
|
}
|
|
_ => {}
|
|
},
|
|
_ => (),
|
|
}
|
|
});
|
|
}
|