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"
|
gstreamer-gl-wayland = "0.23"
|
||||||
winit = "0.30"
|
winit = "0.30"
|
||||||
raw-window-handle = "0.6"
|
raw-window-handle = "0.6"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
prost-build = "0.13"
|
prost-build = "0.13"
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// client/src/input/inputs.rs
|
// client/src/input/inputs.rs
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
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 tokio::{sync::broadcast::Sender, time::{interval, Duration}};
|
||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info, warn};
|
||||||
|
|
||||||
@ -9,6 +9,7 @@ use lesavka_common::lesavka::{KeyboardReport, MouseReport};
|
|||||||
|
|
||||||
use super::{keyboard::KeyboardAggregator, mouse::MouseAggregator,
|
use super::{keyboard::KeyboardAggregator, mouse::MouseAggregator,
|
||||||
camera::CameraCapture, microphone::MicrophoneCapture};
|
camera::CameraCapture, microphone::MicrophoneCapture};
|
||||||
|
use crate::layout::{Layout, apply as apply_layout};
|
||||||
|
|
||||||
pub struct InputAggregator {
|
pub struct InputAggregator {
|
||||||
kbd_tx: Sender<KeyboardReport>,
|
kbd_tx: Sender<KeyboardReport>,
|
||||||
@ -48,15 +49,11 @@ impl InputAggregator {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// attempt open
|
// ─── open the event node read‑write *without* unsafe ──────────
|
||||||
let mut dev = match Device::open_with_options(
|
let mut dev = match Device::open(&path) {
|
||||||
&path,
|
|
||||||
ReadFlag::NORMAL | ReadFlag::BLOCKING,
|
|
||||||
WriteFlag::NORMAL, // <-- write access needed for EVIOCGRAB
|
|
||||||
) {
|
|
||||||
Ok(d) => d,
|
Ok(d) => d,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("❌ open {path}: {e}");
|
warn!("❌ open {}: {e}", path.display());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -109,8 +106,11 @@ impl InputAggregator {
|
|||||||
pub async fn run(&mut self) -> Result<()> {
|
pub async fn run(&mut self) -> Result<()> {
|
||||||
// Example approach: poll each aggregator in a simple loop
|
// Example approach: poll each aggregator in a simple loop
|
||||||
let mut tick = interval(Duration::from_millis(10));
|
let mut tick = interval(Duration::from_millis(10));
|
||||||
|
let mut current = Layout::SideBySide;
|
||||||
loop {
|
loop {
|
||||||
let magic_now = self.keyboards.iter().any(|k| k.magic_grab());
|
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;
|
let mut want_kill = false;
|
||||||
for kbd in &mut self.keyboards {
|
for kbd in &mut self.keyboards {
|
||||||
kbd.process_events();
|
kbd.process_events();
|
||||||
@ -118,6 +118,14 @@ impl InputAggregator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if magic_now && !self.magic_active { self.toggle_grab(); }
|
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 {
|
if want_kill {
|
||||||
warn!("🧙 magic chord - killing 🪄 AVADA KEDAVRA!!! 💥💀⚰️");
|
warn!("🧙 magic chord - killing 🪄 AVADA KEDAVRA!!! 💥💀⚰️");
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
|
|||||||
@ -89,6 +89,18 @@ impl KeyboardAggregator {
|
|||||||
&& self.has_key(KeyCode::KEY_G)
|
&& 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 {
|
pub fn magic_kill(&self) -> bool {
|
||||||
self.has_key(KeyCode::KEY_LEFTCTRL)
|
self.has_key(KeyCode::KEY_LEFTCTRL)
|
||||||
&& self.has_key(KeyCode::KEY_ESC)
|
&& 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 app;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod output;
|
pub mod output;
|
||||||
|
pub mod layout;
|
||||||
|
|
||||||
pub use app::LesavkaClientApp;
|
pub use app::LesavkaClientApp;
|
||||||
|
|||||||
@ -45,6 +45,7 @@ pub async fn eye_ball(
|
|||||||
gst::init().context("gst init")?;
|
gst::init().context("gst init")?;
|
||||||
|
|
||||||
let desc = format!(
|
let desc = format!(
|
||||||
|
|
||||||
"v4l2src device=\"{dev}\" io-mode=mmap ! \
|
"v4l2src device=\"{dev}\" io-mode=mmap ! \
|
||||||
queue ! tsdemux name=d ! \
|
queue ! tsdemux name=d ! \
|
||||||
d. ! h264parse disable-passthrough=true config-interval=-1 ! \
|
d. ! h264parse disable-passthrough=true config-interval=-1 ! \
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user