use super::{
display::{CursorData, Display, PixelFormat},
input::{InputEvent, KeyboardEvent, MouseButton, MouseEvent},
Result,
};
use crate::channels::cursor::CursorShape;
use crate::channels::MouseButton as SpiceMouseButton;
use crate::SpiceClientShared;
use std::sync::Arc;
use tokio::sync::Mutex;
use tracing::{debug, warn};
pub struct SpiceDisplayAdapter {
client: SpiceClientShared,
backend_display: Arc<Mutex<Box<dyn Display + Send>>>,
channel_id: u8,
current_dimensions: Arc<Mutex<(u32, u32)>>,
last_frame_hash: Arc<Mutex<Option<u64>>>,
}
impl SpiceDisplayAdapter {
pub fn new(
client: SpiceClientShared,
backend_display: Box<dyn Display + Send>,
channel_id: u8,
) -> Self {
Self {
client,
backend_display: Arc::new(Mutex::new(backend_display)),
channel_id,
current_dimensions: Arc::new(Mutex::new((1024, 768))), last_frame_hash: Arc::new(Mutex::new(None)),
}
}
pub async fn update_display(&self) -> Result<()> {
eprintln!("SpiceDisplayAdapter: update_display called");
if let Some(surface) = self.client.get_display_surface(self.channel_id).await {
eprintln!(
"SpiceDisplayAdapter: Got surface {}x{} with {} bytes",
surface.width,
surface.height,
surface.data.len()
);
let mut hash = 0u64;
for (_i, &byte) in surface.data.iter().enumerate().step_by(1024) {
hash = hash.wrapping_mul(31).wrapping_add(byte as u64);
}
let mut last_hash = self.last_frame_hash.lock().await;
let changed = match *last_hash {
Some(prev_hash) => prev_hash != hash,
None => true,
};
if changed {
eprintln!(
"SpiceDisplayAdapter: Display surface changed (hash: {}), updating display",
hash
);
debug!("Display surface changed, updating display");
*last_hash = Some(hash);
let mut current_dims = self.current_dimensions.lock().await;
if (surface.width, surface.height) != *current_dims {
debug!(
"Display dimensions changed from {:?} to ({}, {})",
current_dims, surface.width, surface.height
);
let mut display = self.backend_display.lock().await;
display.resize(surface.width, surface.height)?;
*current_dims = (surface.width, surface.height);
}
let pixel_format = match surface.format {
1 => PixelFormat::Rgba8888, 8 => PixelFormat::Rgba8888, _ => {
warn!(
"Unknown SPICE surface format: {}, assuming RGBA",
surface.format
);
PixelFormat::Rgba8888
}
};
let mut display = self.backend_display.lock().await;
display.present_frame(&surface.data, pixel_format)?;
}
} else {
eprintln!("SpiceDisplayAdapter: No surface available from SPICE client");
}
Ok(())
}
pub fn get_backend_display(&self) -> Arc<Mutex<Box<dyn Display + Send>>> {
self.backend_display.clone()
}
pub async fn update_cursor(&self, cursor_shape: Option<&CursorShape>) -> Result<()> {
let mut display = self.backend_display.lock().await;
if let Some(shape) = cursor_shape {
let cursor_data = CursorData {
width: shape.width as u32,
height: shape.height as u32,
hotspot_x: shape.hot_spot_x as u32,
hotspot_y: shape.hot_spot_y as u32,
data: shape.data.clone(),
format: PixelFormat::Rgba8888, };
debug!(
"Updating cursor: {}x{}, hotspot: ({}, {})",
cursor_data.width, cursor_data.height, cursor_data.hotspot_x, cursor_data.hotspot_y
);
display.set_cursor(Some(cursor_data))?;
} else {
debug!("Hiding cursor");
display.set_cursor(None)?;
}
Ok(())
}
}
#[allow(dead_code)]
fn convert_spice_format(spice_format: u32) -> PixelFormat {
match spice_format {
1 => PixelFormat::Rgba8888, 8 => PixelFormat::Rgba8888, 16 => PixelFormat::Rgb565, _ => {
warn!(
"Unknown SPICE surface format: {}, defaulting to RGBA",
spice_format
);
PixelFormat::Rgba8888
}
}
}
pub struct SpiceInputAdapter {
client: SpiceClientShared,
channel_id: u8,
}
impl SpiceInputAdapter {
pub fn new(client: SpiceClientShared, channel_id: u8) -> Self {
Self { client, channel_id }
}
pub async fn send_event(&self, event: InputEvent) -> Result<()> {
match event {
InputEvent::Keyboard(kbd_event) => match kbd_event {
KeyboardEvent::KeyDown { scancode, .. } => {
debug!("Forward key down: scancode {}", scancode);
self.client.send_key_down(self.channel_id, scancode).await?;
}
KeyboardEvent::KeyUp { scancode, .. } => {
debug!("Forward key up: scancode {}", scancode);
self.client.send_key_up(self.channel_id, scancode).await?;
}
},
InputEvent::Mouse(mouse_event) => match mouse_event {
MouseEvent::Motion { x, y, .. } => {
debug!("Forward mouse motion: ({}, {})", x, y);
self.client
.send_mouse_motion(self.channel_id, x as i32, y as i32)
.await?;
}
MouseEvent::Button {
button,
pressed,
x,
y,
} => {
let spice_button = convert_mouse_button(button);
debug!(
"Forward mouse button {:?}: {} at ({}, {})",
button, pressed, x, y
);
self.client
.send_mouse_button(self.channel_id, spice_button, pressed)
.await?;
}
MouseEvent::Wheel { delta_x, delta_y } => {
debug!("Forward mouse wheel: ({}, {})", delta_x, delta_y);
self.client
.send_mouse_wheel(self.channel_id, delta_x, delta_y)
.await?;
}
},
}
Ok(())
}
}
fn convert_mouse_button(button: MouseButton) -> SpiceMouseButton {
match button {
MouseButton::Left => SpiceMouseButton::Left,
MouseButton::Middle => SpiceMouseButton::Middle,
MouseButton::Right => SpiceMouseButton::Right,
MouseButton::X1 | MouseButton::X2 => SpiceMouseButton::Middle,
}
}