0.4.0 mostly complete milestone
This commit is contained in:
parent
e93b5a7bde
commit
d81eb7c460
@ -24,6 +24,8 @@ gstreamer-video = "0.23"
|
||||
gstreamer-gl-wayland = "0.23"
|
||||
winit = "0.30"
|
||||
raw-window-handle = "0.6"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
[build-dependencies]
|
||||
prost-build = "0.13"
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// client/src/input/inputs.rs
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use evdev::{Device, EventType, KeyCode, RelativeAxisCode, ReadFlag, WriteFlag};
|
||||
use evdev::{Device, EventType, KeyCode, RelativeAxisCode};
|
||||
use tokio::{sync::broadcast::Sender, time::{interval, Duration}};
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
@ -9,6 +9,7 @@ use lesavka_common::lesavka::{KeyboardReport, MouseReport};
|
||||
|
||||
use super::{keyboard::KeyboardAggregator, mouse::MouseAggregator,
|
||||
camera::CameraCapture, microphone::MicrophoneCapture};
|
||||
use crate::layout::{Layout, apply as apply_layout};
|
||||
|
||||
pub struct InputAggregator {
|
||||
kbd_tx: Sender<KeyboardReport>,
|
||||
@ -48,15 +49,11 @@ impl InputAggregator {
|
||||
continue;
|
||||
}
|
||||
|
||||
// attempt open
|
||||
let mut dev = match Device::open_with_options(
|
||||
&path,
|
||||
ReadFlag::NORMAL | ReadFlag::BLOCKING,
|
||||
WriteFlag::NORMAL, // <-- write access needed for EVIOCGRAB
|
||||
) {
|
||||
// ─── open the event node read‑write *without* unsafe ──────────
|
||||
let mut dev = match Device::open(&path) {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
warn!("❌ open {path}: {e}");
|
||||
warn!("❌ open {}: {e}", path.display());
|
||||
continue;
|
||||
}
|
||||
};
|
||||
@ -109,8 +106,11 @@ impl InputAggregator {
|
||||
pub async fn run(&mut self) -> Result<()> {
|
||||
// Example approach: poll each aggregator in a simple loop
|
||||
let mut tick = interval(Duration::from_millis(10));
|
||||
let mut current = Layout::SideBySide;
|
||||
loop {
|
||||
let magic_now = self.keyboards.iter().any(|k| k.magic_grab());
|
||||
let magic_left = self.keyboards.iter().any(|k| k.magic_left());
|
||||
let magic_right = self.keyboards.iter().any(|k| k.magic_right());
|
||||
let mut want_kill = false;
|
||||
for kbd in &mut self.keyboards {
|
||||
kbd.process_events();
|
||||
@ -118,6 +118,14 @@ impl InputAggregator {
|
||||
}
|
||||
|
||||
if magic_now && !self.magic_active { self.toggle_grab(); }
|
||||
if (magic_left || magic_right) && self.magic_active {
|
||||
current = match current {
|
||||
Layout::SideBySide => Layout::FullLeft,
|
||||
Layout::FullLeft => Layout::FullRight,
|
||||
Layout::FullRight => Layout::SideBySide,
|
||||
};
|
||||
apply_layout(current);
|
||||
}
|
||||
if want_kill {
|
||||
warn!("🧙 magic chord - killing 🪄 AVADA KEDAVRA!!! 💥💀⚰️");
|
||||
std::process::exit(0);
|
||||
|
||||
@ -89,6 +89,18 @@ impl KeyboardAggregator {
|
||||
&& self.has_key(KeyCode::KEY_G)
|
||||
}
|
||||
|
||||
pub fn magic_left(&self) -> bool {
|
||||
self.has_key(KeyCode::KEY_LEFTCTRL)
|
||||
&& self.has_key(KeyCode::KEY_LEFTSHIFT)
|
||||
&& self.has_key(KeyCode::KEY_LEFT)
|
||||
}
|
||||
|
||||
pub fn magic_right(&self) -> bool {
|
||||
self.has_key(KeyCode::KEY_LEFTCTRL)
|
||||
&& self.has_key(KeyCode::KEY_LEFTSHIFT)
|
||||
&& self.has_key(KeyCode::KEY_RIGHT)
|
||||
}
|
||||
|
||||
pub fn magic_kill(&self) -> bool {
|
||||
self.has_key(KeyCode::KEY_LEFTCTRL)
|
||||
&& self.has_key(KeyCode::KEY_ESC)
|
||||
|
||||
67
client/src/layout.rs
Normal file
67
client/src/layout.rs
Normal file
@ -0,0 +1,67 @@
|
||||
// client/src/layout.rs – Wayland‑only window placement utilities
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use std::process::Command;
|
||||
use tracing::{info, warn};
|
||||
use serde_json::Value;
|
||||
|
||||
/// The three layouts we cycle through.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Layout {
|
||||
SideBySide, // two halves on the current monitor
|
||||
FullLeft, // left eye full‑screen, right hidden
|
||||
FullRight, // right eye full‑screen, left hidden
|
||||
}
|
||||
|
||||
/// Move/resize a window titled “Lesavka‑eye‑{eye}” using `swaymsg`.
|
||||
fn place_window(eye: u32, x: i32, y: i32, w: i32, h: i32) {
|
||||
let title = format!("Lesavka‑eye‑{eye}");
|
||||
let cmd = format!(
|
||||
r#"[title="^{title}$"] resize set {w} {h}; move position {x} {y}"#
|
||||
);
|
||||
|
||||
match Command::new("swaymsg").arg(cmd).status() {
|
||||
Ok(st) if st.success() => info!("✅ placed eye{eye} {w}×{h}@{x},{y}"),
|
||||
Ok(st) => warn!("⚠️ swaymsg exited with {st}"),
|
||||
Err(e) => warn!("⚠️ swaymsg failed: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply a layout on the **currently‑focused** output.
|
||||
/// No `?` operators → the function can stay `-> ()`.
|
||||
pub fn apply(layout: Layout) {
|
||||
let out = match Command::new("swaymsg")
|
||||
.args(["-t", "get_outputs", "-r"])
|
||||
.output()
|
||||
{
|
||||
Ok(o) => o.stdout,
|
||||
Err(e) => { warn!("get_outputs failed: {e}"); return; }
|
||||
};
|
||||
|
||||
let Ok(Value::Array(outputs)) = serde_json::from_slice::<Value>(&out) else {
|
||||
warn!("unexpected JSON from swaymsg"); return;
|
||||
};
|
||||
let Some(rect) = outputs.iter()
|
||||
.find(|o| o.get("focused").and_then(Value::as_bool) == Some(true))
|
||||
.and_then(|o| o.get("rect")) else { return; };
|
||||
|
||||
// helper to read an i64 → i32 with defaults
|
||||
let g = |k: &str| rect.get(k).and_then(Value::as_i64).unwrap_or(0) as i32;
|
||||
let (x, y, w, h) = (g("x"), g("y"), g("width"), g("height"));
|
||||
|
||||
match layout {
|
||||
Layout::SideBySide => {
|
||||
let w_half = w / 2;
|
||||
place_window(0, x, y, w_half, h);
|
||||
place_window(1, x + w_half, y, w_half, h);
|
||||
}
|
||||
Layout::FullLeft => {
|
||||
place_window(0, x, y, w, h);
|
||||
place_window(1, x + w * 2, y + h * 2, 1, 1); // off‑screen
|
||||
}
|
||||
Layout::FullRight => {
|
||||
place_window(1, x, y, w, h);
|
||||
place_window(0, x + w * 2, y + h * 2, 1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,5 +5,6 @@
|
||||
pub mod app;
|
||||
pub mod input;
|
||||
pub mod output;
|
||||
pub mod layout;
|
||||
|
||||
pub use app::LesavkaClientApp;
|
||||
|
||||
@ -45,6 +45,7 @@ pub async fn eye_ball(
|
||||
gst::init().context("gst init")?;
|
||||
|
||||
let desc = format!(
|
||||
|
||||
"v4l2src device=\"{dev}\" io-mode=mmap ! \
|
||||
queue ! tsdemux name=d ! \
|
||||
d. ! h264parse disable-passthrough=true config-interval=-1 ! \
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user