From fdd1dcbed0048ec11eeb4558bc097d5a3096e735 Mon Sep 17 00:00:00 2001 From: Butterfly Dev Date: Tue, 7 Apr 2026 05:48:29 +0000 Subject: [PATCH] fix: resolve compilation errors from crate version mismatches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - encoder.rs: update openh264 API (Encoder::new → with_api_config + EncoderConfig), use YUVSlices instead of YUVBuffer, use bitstream.frame_type() for keyframe detection, use force_intra_frame() for request_keyframe, fix ImageBuffer type params for image v0.25, add mut to JpegEncoder, remove unused base64 import - capture.rs: fix ImageBuffer type params for image v0.25, add mut to JpegEncoder - input.rs: update enigo 0.2 API - add Keyboard/Mouse trait imports, replace scroll_y/scroll_x with scroll(axis), fix Key variant names (UpArrow, DownArrow, Numlock, Print), replace Windows-only Numpad* keys with Unicode fallbacks - service.rs: add type annotation to Vec::new() - main.rs: add missing clap::Parser import --- agent/src/capture.rs | 4 +-- agent/src/encoder.rs | 49 +++++++++++--------------- agent/src/input.rs | 83 +++++++++++++++++++------------------------- agent/src/main.rs | 12 +++---- agent/src/service.rs | 2 +- 5 files changed, 64 insertions(+), 86 deletions(-) diff --git a/agent/src/capture.rs b/agent/src/capture.rs index 287ae87..95a0772 100644 --- a/agent/src/capture.rs +++ b/agent/src/capture.rs @@ -106,14 +106,14 @@ impl ScreenCapture { rgb.push(chunk[0]); // B } - let img_buffer = ImageBuffer::>::from_raw( + let img_buffer = ImageBuffer::, Vec>::from_raw( raw.width as u32, raw.height as u32, rgb, ).context("failed to create image buffer")?; let mut jpeg_bytes = Vec::new(); - let encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut jpeg_bytes, quality.clamp(1, 100)); + let mut encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut jpeg_bytes, quality.clamp(1, 100)); encoder.encode_image(&img_buffer).context("JPEG encode failed")?; let b64 = STANDARD.encode(&jpeg_bytes); diff --git a/agent/src/encoder.rs b/agent/src/encoder.rs index bcb8ce6..f6cd7ad 100644 --- a/agent/src/encoder.rs +++ b/agent/src/encoder.rs @@ -10,7 +10,6 @@ //! defined in `protocol.rs`. use anyhow::{Context, Result}; -use base64::{Engine, engine::general_purpose::STANDARD}; use image::{ImageBuffer, Rgb}; use log::info; @@ -76,11 +75,11 @@ impl VideoEncoder for JpegEncoder { let rgb_data = bgra_to_rgb(bgra, width, height); // Encode as JPEG. - let img_buffer = ImageBuffer::>::from_raw(width as u32, height as u32, rgb_data) + let img_buffer = ImageBuffer::, Vec>::from_raw(width as u32, height as u32, rgb_data) .context("failed to create RGB image buffer")?; let mut jpeg_bytes = Vec::new(); - let encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut jpeg_bytes, self.quality); + let mut encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut jpeg_bytes, self.quality); encoder .encode_image(&img_buffer) .context("JPEG encode failed")?; @@ -122,12 +121,12 @@ cfg_if::cfg_if! { ); // Use constant bitrate for predictable network usage. - let rc = openh264::encoder::RateControl::Constant(bitrate_kbps as i32); - let encoder = openh264::encoder::Encoder::new( - openh264::encoder::Width(width as i32), - openh264::encoder::Height(height as i32), - rc, - ).map_err(|e| anyhow::anyhow!("openh264 init failed: {:?}", e))?; + let api = openh264::OpenH264API::from_source(); + let config = openh264::encoder::EncoderConfig::new() + .set_bitrate_bps((bitrate_kbps as u32) * 1000) + .rate_control_mode(openh264::encoder::RateControlMode::Bitrate); + let encoder = openh264::encoder::Encoder::with_api_config(api, config) + .map_err(|e| anyhow::anyhow!("openh264 init failed: {:?}", e))?; Ok(Self { encoder, @@ -144,28 +143,22 @@ cfg_if::cfg_if! { // BGRA → I420 (YUV420 planar) let (y, u, v) = bgra_to_i420(bgra, width, height); - // Create openh264 YUV buffer. - let yuv = openh264::formats::YUVBuffer::new( - openh264::formats::YUVPixel::from_yuv(y[0], u[0], v[0]), // dummy first pixel - self.width, - self.height, - y, - u, - v, + // Create openh264 YUV buffer from pre-separated planes. + let yuv = openh264::formats::YUVSlices::new( + (&y, &u, &v), + (self.width, self.height), + (self.width, self.width / 2, self.width / 2), ); // Encode the frame. let bitstream = self.encoder.encode(&yuv) .map_err(|e| anyhow::anyhow!("H.264 encode failed: {:?}", e))?; - self.frame_count += 1; - - // Determine if this is a keyframe. - // openh264's Bitstream doesn't directly expose keyframe info, - // but we can force periodic keyframes based on our counter. - let is_keyframe = self.frame_count == 1 - || (self.keyframe_interval > 0 - && self.frame_count % self.keyframe_interval == 0); + // Determine if this is a keyframe from the encoder output. + let is_keyframe = matches!( + bitstream.frame_type(), + openh264::encoder::FrameType::IDR | openh264::encoder::FrameType::I + ); let frame_type = if is_keyframe { crate::protocol::frame_type::H264_KEY @@ -174,7 +167,7 @@ cfg_if::cfg_if! { }; // The bitstream contains raw NAL units (Annex-B format with start codes). - let payload = bitstream.as_ref().to_vec(); + let payload = bitstream.to_vec(); Ok(EncodedFrame { frame_type, @@ -185,9 +178,7 @@ cfg_if::cfg_if! { fn request_keyframe(&mut self) { // Request an IDR frame on next encode. - // Note: openh264's Encoder doesn't have a direct force-IDR API, - // so we reset the frame counter to trigger one. - self.frame_count = 0; + self.encoder.force_intra_frame(); } fn encoder_type(&self) -> EncoderType { diff --git a/agent/src/input.rs b/agent/src/input.rs index b57c350..3471198 100644 --- a/agent/src/input.rs +++ b/agent/src/input.rs @@ -11,7 +11,7 @@ //! - Linux: X11 XTest extension use anyhow::Result; -use enigo::{Button, Direction, Enigo, Key, Settings, Coordinate}; +use enigo::{Axis, Button, Direction, Enigo, Key, Keyboard, Mouse, Settings, Coordinate}; use log::{info, warn}; /// Manages input simulation on the local machine. @@ -145,34 +145,22 @@ impl InputHandler { let delta_y = params["deltaY"].as_i64().unwrap_or(0) as i32; let delta_x = params["deltaX"].as_i64().unwrap_or(0) as i32; - // Vertical scroll. + // Clamp to prevent crazy scrolling. + let delta_y = delta_y.clamp(-50, 50); + let delta_x = delta_x.clamp(-50, 50); + + // Vertical scroll: positive = down, negative = up. if delta_y != 0 { - let direction = if delta_y > 0 { - Direction::ScrollDown - } else { - Direction::ScrollUp - }; - let amount = delta_y.abs().min(50); // Clamp to prevent crazy scrolling. - for _ in 0..amount { - self.enigo - .scroll_y(1, direction) - .map_err(|e| anyhow::anyhow!("scroll_y failed: {}", e))?; - } + self.enigo + .scroll(delta_y, Axis::Vertical) + .map_err(|e| anyhow::anyhow!("scroll failed: {}", e))?; } - // Horizontal scroll. + // Horizontal scroll: positive = right, negative = left. if delta_x != 0 { - let direction = if delta_x > 0 { - Direction::ScrollRight - } else { - Direction::ScrollLeft - }; - let amount = delta_x.abs().min(50); - for _ in 0..amount { - self.enigo - .scroll_x(1, direction) - .map_err(|e| anyhow::anyhow!("scroll_x failed: {}", e))?; - } + self.enigo + .scroll(delta_x, Axis::Horizontal) + .map_err(|e| anyhow::anyhow!("scroll failed: {}", e))?; } Ok(()) @@ -317,10 +305,10 @@ fn map_key(code: &str, key: &str) -> Key { "Escape" => Key::Escape, // ── Arrow keys ────────────────────────────────────────────────── - "ArrowUp" => Key::Up, - "ArrowDown" => Key::Down, - "ArrowLeft" => Key::Left, - "ArrowRight" => Key::Right, + "ArrowUp" => Key::UpArrow, + "ArrowDown" => Key::DownArrow, + "ArrowLeft" => Key::LeftArrow, + "ArrowRight" => Key::RightArrow, // ── Function keys ─────────────────────────────────────────────── "F1" => Key::F1, @@ -346,7 +334,7 @@ fn map_key(code: &str, key: &str) -> Key { // ── Lock keys ─────────────────────────────────────────────────── "CapsLock" => Key::CapsLock, - "NumLock" => Key::NumLock, + "NumLock" => Key::Numlock, "ScrollLock" => Key::ScrollLock, // ── Navigation / editing cluster ──────────────────────────────── @@ -356,27 +344,26 @@ fn map_key(code: &str, key: &str) -> Key { "End" => Key::End, "PageUp" => Key::PageUp, "PageDown" => Key::PageDown, - "PrintScreen" => Key::PrintScreen, + "PrintScreen" => Key::Print, "Pause" => Key::Pause, // ── Numpad ────────────────────────────────────────────────────── - "Numpad0" => Key::Numpad0, - "Numpad1" => Key::Numpad1, - "Numpad2" => Key::Numpad2, - "Numpad3" => Key::Numpad3, - "Numpad4" => Key::Numpad4, - "Numpad5" => Key::Numpad5, - "Numpad6" => Key::Numpad6, - "Numpad7" => Key::Numpad7, - "Numpad8" => Key::Numpad8, - "Numpad9" => Key::Numpad9, - "NumpadAdd" => Key::NumpadAdd, - "NumpadSubtract" => Key::NumpadSubtract, - "NumpadMultiply" => Key::NumpadMultiply, - "NumpadDivide" => Key::NumpadDivide, - "NumpadDecimal" => Key::NumpadDecimal, - "NumpadEnter" => Key::NumpadEnter, - "NumLock" => Key::NumLock, + "Numpad0" => Key::Unicode('0'), + "Numpad1" => Key::Unicode('1'), + "Numpad2" => Key::Unicode('2'), + "Numpad3" => Key::Unicode('3'), + "Numpad4" => Key::Unicode('4'), + "Numpad5" => Key::Unicode('5'), + "Numpad6" => Key::Unicode('6'), + "Numpad7" => Key::Unicode('7'), + "Numpad8" => Key::Unicode('8'), + "Numpad9" => Key::Unicode('9'), + "NumpadAdd" => Key::Unicode('+'), + "NumpadSubtract" => Key::Unicode('-'), + "NumpadMultiply" => Key::Unicode('*'), + "NumpadDivide" => Key::Unicode('/'), + "NumpadDecimal" => Key::Unicode('.'), + "NumpadEnter" => Key::Return, // ── Punctuation / symbols ─────────────────────────────────────── // These are handled as Unicode characters when sent via `key` field. diff --git a/agent/src/main.rs b/agent/src/main.rs index 8f9f102..e8dc36a 100644 --- a/agent/src/main.rs +++ b/agent/src/main.rs @@ -37,10 +37,11 @@ mod protocol; mod service; use anyhow::{Context, Result}; -use encoder::{EncodedFrame, EncoderType}; +use clap::Parser; +use encoder::EncoderType; use futures_util::{SinkExt, StreamExt}; use log::{error, info, warn}; -use protocol::{ControlMessage, FRAME_HEADER_SIZE}; +use protocol::ControlMessage; use tokio::sync::mpsc; use tokio_tungstenite::tungstenite::Message; @@ -73,7 +74,7 @@ fn main() -> Result<()> { let cli = cli::Cli::parse(); match cli.command { - cli::Command::Run(opts) => { + cli::Command::Run { opts } => { // On Windows, check if we should enter service mode. #[cfg(windows)] if opts.windows_service { @@ -298,9 +299,9 @@ async fn run_session( }); // Spawn heartbeat. - let hb_session_id = session_id.to_string(); let hb_interval = opts.heartbeat_interval(); let (hb_tx, mut hb_rx) = mpsc::channel::<()>(1); + let hb_cleanup_tx = hb_tx.clone(); let heartbeat_handle = tokio::spawn(async move { let mut interval = tokio::time::interval(hb_interval); loop { @@ -377,8 +378,7 @@ async fn run_session( // Cleanup: drop the capture sender to signal the capture thread to stop, // then wait for both spawned tasks to finish. - let _ = hb_tx.send(()).await; - drop(capture_tx); + let _ = hb_cleanup_tx.send(()).await; let _ = capture_handle.await; let _ = heartbeat_handle.await; diff --git a/agent/src/service.rs b/agent/src/service.rs index f59560c..b7852f9 100644 --- a/agent/src/service.rs +++ b/agent/src/service.rs @@ -607,7 +607,7 @@ fn status_service(name: &str) -> Result<()> { /// from within a systemd service (which otherwise runs with a minimal environment). #[cfg(unix)] fn detect_display_env() -> Vec<(String, String)> { - let mut envs = Vec::new(); + let mut envs: Vec<(String, String)> = Vec::new(); // X11 display server. if let Ok(val) = std::env::var("DISPLAY") {