Footage budget, clip hotkey length and bookmark before/after are now typed fields (m:ss or seconds), validated on Enter or blur and reverted when out of range. Scrolling over the session timeline zooms around the pointer (down to 10 s) and scrolling sideways or with Shift pans, with a Fit button. Settings felt slow because GPUI ran unoptimised: dev builds now optimise dependencies, taking first frames from 200-300 ms to about 15 ms. Adds rig-pointer, a virtual pointer for driving the interface in the headless rig, which verified the fields, zoom and timeline dragging.
105 lines
3.8 KiB
Rust
105 lines
3.8 KiB
Rust
//! Drives a virtual pointer on a wlroots compositor (the headless rig's Sway),
|
|
//! which has no input devices of its own. Reads one command per line on stdin:
|
|
//!
|
|
//! move <x> <y> move to output coordinates
|
|
//! click press and release the left button
|
|
//! down / up press or release the left button
|
|
//! scroll <dy> scroll vertically (negative scrolls up), in wheel steps
|
|
//! sleep <seconds>
|
|
//!
|
|
//! echo -e "move 100 200\nclick" | rig-pointer <output-width> <output-height>
|
|
|
|
use std::{io::BufRead, thread, time::Duration};
|
|
|
|
use wayland_client::{
|
|
Connection, Dispatch, QueueHandle,
|
|
globals::{GlobalListContents, registry_queue_init},
|
|
protocol::{wl_pointer, wl_registry, wl_seat::WlSeat},
|
|
};
|
|
use wayland_protocols_wlr::virtual_pointer::v1::client::{
|
|
zwlr_virtual_pointer_manager_v1::ZwlrVirtualPointerManagerV1,
|
|
zwlr_virtual_pointer_v1::ZwlrVirtualPointerV1,
|
|
};
|
|
|
|
const BUTTON_LEFT: u32 = 0x110;
|
|
|
|
struct State;
|
|
|
|
impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for State {
|
|
fn event(
|
|
_: &mut Self,
|
|
_: &wl_registry::WlRegistry,
|
|
_: wl_registry::Event,
|
|
_: &GlobalListContents,
|
|
_: &Connection,
|
|
_: &QueueHandle<Self>,
|
|
) {
|
|
}
|
|
}
|
|
|
|
macro_rules! ignore_events {
|
|
($($proxy:ty),*) => {$(
|
|
impl Dispatch<$proxy, ()> for State {
|
|
fn event(_: &mut Self, _: &$proxy, _: <$proxy as wayland_client::Proxy>::Event, _: &(), _: &Connection, _: &QueueHandle<Self>) {}
|
|
}
|
|
)*};
|
|
}
|
|
ignore_events!(WlSeat, ZwlrVirtualPointerManagerV1, ZwlrVirtualPointerV1);
|
|
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
let args: Vec<u32> = std::env::args()
|
|
.skip(1)
|
|
.map(|arg| arg.parse())
|
|
.collect::<Result<_, _>>()?;
|
|
let [width, height] = args[..] else {
|
|
return Err("usage: rig-pointer <output-width> <output-height> < commands".into());
|
|
};
|
|
let connection = Connection::connect_to_env()?;
|
|
let (globals, mut queue) = registry_queue_init::<State>(&connection)?;
|
|
let handle = queue.handle();
|
|
let seat: WlSeat = globals.bind(&handle, 1..=7, ())?;
|
|
let manager: ZwlrVirtualPointerManagerV1 = globals.bind(&handle, 1..=2, ())?;
|
|
let pointer = manager.create_virtual_pointer(Some(&seat), &handle, ());
|
|
queue.roundtrip(&mut State)?;
|
|
|
|
let start = std::time::Instant::now();
|
|
let time = || start.elapsed().as_millis() as u32;
|
|
for line in std::io::stdin().lock().lines() {
|
|
let line = line?;
|
|
let words: Vec<&str> = line.split_whitespace().collect();
|
|
let button = |state| {
|
|
pointer.button(time(), BUTTON_LEFT, state);
|
|
pointer.frame();
|
|
};
|
|
match words[..] {
|
|
["move", x, y] => {
|
|
pointer.motion_absolute(time(), x.parse()?, y.parse()?, width, height);
|
|
pointer.frame();
|
|
}
|
|
["down"] => button(wl_pointer::ButtonState::Pressed),
|
|
["up"] => button(wl_pointer::ButtonState::Released),
|
|
["click"] => {
|
|
button(wl_pointer::ButtonState::Pressed);
|
|
button(wl_pointer::ButtonState::Released);
|
|
}
|
|
["scroll", steps] => {
|
|
let steps: i32 = steps.parse()?;
|
|
pointer.axis_source(wl_pointer::AxisSource::Wheel);
|
|
pointer.axis_discrete(
|
|
time(),
|
|
wl_pointer::Axis::VerticalScroll,
|
|
15. * steps as f64,
|
|
steps,
|
|
);
|
|
pointer.frame();
|
|
}
|
|
["sleep", seconds] => thread::sleep(Duration::from_secs_f64(seconds.parse()?)),
|
|
[] => continue,
|
|
_ => return Err(format!("unknown command: {line}").into()),
|
|
}
|
|
queue.roundtrip(&mut State)?;
|
|
}
|
|
thread::sleep(Duration::from_millis(200));
|
|
Ok(())
|
|
}
|