fix: resolve compilation errors from crate version mismatches

- 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
This commit is contained in:
Butterfly Dev 2026-04-07 05:48:29 +00:00
parent 84f559c1a7
commit fdd1dcbed0
5 changed files with 64 additions and 86 deletions

View File

@ -106,14 +106,14 @@ impl ScreenCapture {
rgb.push(chunk[0]); // B
}
let img_buffer = ImageBuffer::<Rgb<u8>>::from_raw(
let img_buffer = ImageBuffer::<Rgb<u8>, Vec<u8>>::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);

View File

@ -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::<Rgb<u8>>::from_raw(width as u32, height as u32, rgb_data)
let img_buffer = ImageBuffer::<Rgb<u8>, Vec<u8>>::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 {

View File

@ -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))?;
}
.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))?;
}
.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.

View File

@ -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;

View File

@ -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") {