Stable Point - AV/K/M working
This commit is contained in:
parent
981f1d1fa3
commit
f60736990b
@ -149,7 +149,7 @@ impl LesavkaClientApp {
|
|||||||
/*──────────────── keyboard stream ───────────────*/
|
/*──────────────── keyboard stream ───────────────*/
|
||||||
async fn stream_loop_keyboard(&self, ep: Channel) {
|
async fn stream_loop_keyboard(&self, ep: Channel) {
|
||||||
loop {
|
loop {
|
||||||
info!("⌨️ dial {}", self.server_addr); // LESAVKA-client
|
info!("⌨️🤙 dial {}", self.server_addr); // LESAVKA-client
|
||||||
let mut cli = RelayClient::new(ep.clone());
|
let mut cli = RelayClient::new(ep.clone());
|
||||||
|
|
||||||
// ✅ use kbd_tx here - fixes E0271
|
// ✅ use kbd_tx here - fixes E0271
|
||||||
@ -171,7 +171,7 @@ impl LesavkaClientApp {
|
|||||||
/*──────────────── mouse stream ──────────────────*/
|
/*──────────────── mouse stream ──────────────────*/
|
||||||
async fn stream_loop_mouse(&self, ep: Channel) {
|
async fn stream_loop_mouse(&self, ep: Channel) {
|
||||||
loop {
|
loop {
|
||||||
info!("🖱️ dial {}", self.server_addr);
|
info!("🖱️🤙 dial {}", self.server_addr);
|
||||||
let mut cli = RelayClient::new(ep.clone());
|
let mut cli = RelayClient::new(ep.clone());
|
||||||
|
|
||||||
let outbound = BroadcastStream::new(self.mou_tx.subscribe())
|
let outbound = BroadcastStream::new(self.mou_tx.subscribe())
|
||||||
|
|||||||
@ -64,7 +64,7 @@ impl InputAggregator {
|
|||||||
match classify_device(&dev) {
|
match classify_device(&dev) {
|
||||||
DeviceKind::Keyboard => {
|
DeviceKind::Keyboard => {
|
||||||
dev.grab().with_context(|| format!("grabbing keyboard {path:?}"))?;
|
dev.grab().with_context(|| format!("grabbing keyboard {path:?}"))?;
|
||||||
info!("Grabbed keyboard {:?}", dev.name().unwrap_or("UNKNOWN"));
|
info!("🤏🖱️ Grabbed keyboard {:?}", dev.name().unwrap_or("UNKNOWN"));
|
||||||
|
|
||||||
// pass dev_mode to aggregator
|
// pass dev_mode to aggregator
|
||||||
// let kbd_agg = KeyboardAggregator::new(dev, self.dev_mode);
|
// let kbd_agg = KeyboardAggregator::new(dev, self.dev_mode);
|
||||||
@ -75,7 +75,7 @@ impl InputAggregator {
|
|||||||
}
|
}
|
||||||
DeviceKind::Mouse => {
|
DeviceKind::Mouse => {
|
||||||
dev.grab().with_context(|| format!("grabbing mouse {path:?}"))?;
|
dev.grab().with_context(|| format!("grabbing mouse {path:?}"))?;
|
||||||
info!("Grabbed mouse {:?}", dev.name().unwrap_or("UNKNOWN"));
|
info!("🤏⌨️ Grabbed mouse {:?}", dev.name().unwrap_or("UNKNOWN"));
|
||||||
|
|
||||||
// let mouse_agg = MouseAggregator::new(dev);
|
// let mouse_agg = MouseAggregator::new(dev);
|
||||||
let mouse_agg = MouseAggregator::new(dev, self.dev_mode, self.mou_tx.clone());
|
let mouse_agg = MouseAggregator::new(dev, self.dev_mode, self.mou_tx.clone());
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// client/src/layout.rs – Wayland-only window placement utilities
|
// client/src/layout.rs - Wayland-only window placement utilities
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|||||||
@ -69,7 +69,7 @@ async fn main() -> Result<()> {
|
|||||||
.with(file_layer)
|
.with(file_layer)
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
tracing::info!("lesavka-client running in DEV mode → {}", log_path.display());
|
tracing::info!("📜 lesavka-client running in DEV mode → {}", log_path.display());
|
||||||
} else {
|
} else {
|
||||||
tracing_subscriber::registry()
|
tracing_subscriber::registry()
|
||||||
.with(env_filter)
|
.with(env_filter)
|
||||||
|
|||||||
@ -4,6 +4,7 @@ use anyhow::{Context, Result};
|
|||||||
use gstreamer as gst;
|
use gstreamer as gst;
|
||||||
use gstreamer_app as gst_app;
|
use gstreamer_app as gst_app;
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
|
use gst::MessageView::*;
|
||||||
use tracing::{error, info, warn, debug};
|
use tracing::{error, info, warn, debug};
|
||||||
|
|
||||||
use lesavka_common::lesavka::AudioPacket;
|
use lesavka_common::lesavka::AudioPacket;
|
||||||
@ -69,7 +70,6 @@ impl AudioOut {
|
|||||||
// ── 4. Log *all* warnings/errors from the bus ──────────────────────
|
// ── 4. Log *all* warnings/errors from the bus ──────────────────────
|
||||||
let bus = pipeline.bus().unwrap();
|
let bus = pipeline.bus().unwrap();
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
use gst::MessageView::*;
|
|
||||||
for msg in bus.iter_timed(gst::ClockTime::NONE) {
|
for msg in bus.iter_timed(gst::ClockTime::NONE) {
|
||||||
match msg.view() {
|
match msg.view() {
|
||||||
Error(e) => error!("💥 gst error from {:?}: {} ({})",
|
Error(e) => error!("💥 gst error from {:?}: {} ({})",
|
||||||
@ -82,8 +82,18 @@ impl AudioOut {
|
|||||||
.structure()
|
.structure()
|
||||||
.map(|s| s.to_string())
|
.map(|s| s.to_string())
|
||||||
.unwrap_or_default()),
|
.unwrap_or_default()),
|
||||||
StateChanged(s) if s.current() == gst::State::Playing =>
|
StateChanged(s) if s.current() == gst::State::Playing => {
|
||||||
info!("🔊 audio pipeline PLAYING (sink='{}')", sink),
|
if msg
|
||||||
|
.src()
|
||||||
|
.map(|s| s.is::<gst::Pipeline>())
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
info!("🔊 audio pipeline PLAYING (sink='{}')", sink);
|
||||||
|
} else {
|
||||||
|
debug!("🔊 element {} now PLAYING",
|
||||||
|
msg.src().map(|s| s.name()).unwrap_or_default());
|
||||||
|
}
|
||||||
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,12 +126,11 @@ impl Drop for AudioOut {
|
|||||||
fn pick_sink_element() -> Result<String> {
|
fn pick_sink_element() -> Result<String> {
|
||||||
// 1. Operator override
|
// 1. Operator override
|
||||||
if let Ok(s) = std::env::var("LESAVKA_AUDIO_SINK") {
|
if let Ok(s) = std::env::var("LESAVKA_AUDIO_SINK") {
|
||||||
info!("🎛️ sink overridden via LESAVKA_AUDIO_SINK={}", s);
|
info!("💪 sink overridden via LESAVKA_AUDIO_SINK={}", s);
|
||||||
return Ok(s);
|
return Ok(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Query PipeWire for default & running sinks
|
// 2. Query PipeWire for default & running sinks
|
||||||
// (works even if PulseAudio is present because PipeWire mimics it)
|
|
||||||
let sinks = list_pw_sinks(); // Vec<(name,state)>
|
let sinks = list_pw_sinks(); // Vec<(name,state)>
|
||||||
for (n, st) in &sinks {
|
for (n, st) in &sinks {
|
||||||
if *st == "RUNNING" {
|
if *st == "RUNNING" {
|
||||||
@ -132,45 +141,23 @@ fn pick_sink_element() -> Result<String> {
|
|||||||
|
|
||||||
// 3. First RUNNING sink
|
// 3. First RUNNING sink
|
||||||
if let Some((n, _)) = sinks.iter().find(|(_, st)| *st == "RUNNING") {
|
if let Some((n, _)) = sinks.iter().find(|(_, st)| *st == "RUNNING") {
|
||||||
warn!("🪄 picking first RUNNING sink '{}'", n);
|
warn!("🏃 picking first RUNNING sink '{}'", n);
|
||||||
return Ok(format!("pulsesink device={}", n));
|
|
||||||
}
|
|
||||||
// 4. Anything
|
|
||||||
if let Some((n, _)) = sinks.first() {
|
|
||||||
warn!("🪄 picking first sink '{}'", n);
|
|
||||||
return Ok(format!("pulsesink device={}", n));
|
return Ok(format!("pulsesink device={}", n));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback – let autoaudiosink try its luck
|
// 4. Anything
|
||||||
warn!("😬 no PipeWire sinks readable – falling back to autoaudiosink");
|
if let Some((n, _)) = sinks.first() {
|
||||||
|
warn!("🎲 picking first sink '{}'", n);
|
||||||
|
return Ok(format!("pulsesink device={}", n));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback - let autoaudiosink try its luck
|
||||||
|
warn!("🫣 no PipeWire sinks readable - falling back to autoaudiosink");
|
||||||
Ok("autoaudiosink".to_string())
|
Ok("autoaudiosink".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Minimal PipeWire sink enumerator (no extra crate required).
|
|
||||||
fn list_pw_sinks() -> Vec<(String, String)> {
|
fn list_pw_sinks() -> Vec<(String, String)> {
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
// if let Ok(lines) = std::process::Command::new("pw-cli")
|
|
||||||
// .args(["ls", "Node"])
|
|
||||||
// .output()
|
|
||||||
// .map(|o| String::from_utf8_lossy(&o.stdout).to_string())
|
|
||||||
// {
|
|
||||||
// for l in lines.lines() {
|
|
||||||
// // Example: " 36 │ node.alive = true │ alsa_output.pci-0000_2f_00.4.iec958-stereo │ state: SUSPENDED ..."
|
|
||||||
// if let Some(pos) = l.find("│") {
|
|
||||||
// let parts: Vec<_> = l[pos..].split('│').map(|s| s.trim()).collect();
|
|
||||||
// if parts.len() >= 3 && parts[2].starts_with("alsa_output.") {
|
|
||||||
// let name = parts[2].to_string();
|
|
||||||
// // try to parse state, else UNKNOWN
|
|
||||||
// let state = parts.get(3)
|
|
||||||
// .and_then(|s| s.split_whitespace().nth(1))
|
|
||||||
// .unwrap_or("UNKNOWN")
|
|
||||||
// .to_string();
|
|
||||||
// out.push((name, state));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
if out.is_empty() {
|
if out.is_empty() {
|
||||||
// ── PulseAudio / pactl fallback ────────────────────────────────
|
// ── PulseAudio / pactl fallback ────────────────────────────────
|
||||||
if let Ok(info) = std::process::Command::new("pactl")
|
if let Ok(info) = std::process::Command::new("pactl")
|
||||||
|
|||||||
@ -15,7 +15,7 @@ pub struct MonitorInfo {
|
|||||||
/// Enumerate monitors sorted by our desired priority.
|
/// Enumerate monitors sorted by our desired priority.
|
||||||
pub fn enumerate_monitors() -> Vec<MonitorInfo> {
|
pub fn enumerate_monitors() -> Vec<MonitorInfo> {
|
||||||
let Some(display) = gdk::Display::default() else {
|
let Some(display) = gdk::Display::default() else {
|
||||||
tracing::warn!("⚠️ no GDK display – falling back to single-monitor 0,0");
|
tracing::warn!("⚠️ no GDK display - falling back to single-monitor 0,0");
|
||||||
return vec![MonitorInfo {
|
return vec![MonitorInfo {
|
||||||
geometry: gdk::Rectangle::new(0, 0, 1920, 1080),
|
geometry: gdk::Rectangle::new(0, 0, 1920, 1080),
|
||||||
scale_factor: 1,
|
scale_factor: 1,
|
||||||
|
|||||||
@ -124,7 +124,7 @@ impl MonitorWindow {
|
|||||||
y = r.y,
|
y = r.y,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Retry in a detached thread – avoids blocking GStreamer
|
// Retry in a detached thread - avoids blocking GStreamer
|
||||||
let placename = placer.name;
|
let placename = placer.name;
|
||||||
let runner = placer.run.clone();
|
let runner = placer.run.clone();
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# lesavka‑core.sh – one‑shot USB‑gadget bring‑up (Pi‑5 / Arch‑ARM)
|
# lesavka‑core.sh - one‑shot USB‑gadget bring‑up (Pi‑5 / Arch‑ARM)
|
||||||
# Presents: • Boot‑protocol keyboard (hidg0)
|
# Presents: • Boot‑protocol keyboard (hidg0)
|
||||||
# • Boot‑protocol mouse (hidg1)
|
# • Boot‑protocol mouse (hidg1)
|
||||||
# • Stereo UAC2 speaker + microphone
|
# • Stereo UAC2 speaker + microphone
|
||||||
@ -98,7 +98,7 @@ printf '\x05\x01\x09\x02\xa1\x01\x09\x01\xa1\x00'\
|
|||||||
'\x05\x01\x09\x30\x09\x31\x09\x38\x15\x81\x25\x7f\x75\x08\x95\x03\x81\x06'\
|
'\x05\x01\x09\x30\x09\x31\x09\x38\x15\x81\x25\x7f\x75\x08\x95\x03\x81\x06'\
|
||||||
'\xc0\xc0' >"$G/functions/hid.usb1/report_desc"
|
'\xc0\xc0' >"$G/functions/hid.usb1/report_desc"
|
||||||
|
|
||||||
# ---------- UAC2 function – speaker + mic, 2×48 kHz stereo ---------
|
# ---------- UAC2 function - speaker + mic, 2×48 kHz stereo ---------
|
||||||
mkdir -p "$G/functions/uac2.usb0"
|
mkdir -p "$G/functions/uac2.usb0"
|
||||||
U="$G/functions/uac2.usb0"
|
U="$G/functions/uac2.usb0"
|
||||||
# Playback (speaker)
|
# Playback (speaker)
|
||||||
|
|||||||
@ -122,8 +122,6 @@ ExecStart=/usr/local/bin/lesavka-server
|
|||||||
Restart=always
|
Restart=always
|
||||||
Environment=RUST_LOG=lesavka_server=info,lesavka_server::audio=info,lesavka_server::video=info,lesavka_server::gadget=info
|
Environment=RUST_LOG=lesavka_server=info,lesavka_server::audio=info,lesavka_server::video=info,lesavka_server::gadget=info
|
||||||
Environment=RUST_BACKTRACE=1
|
Environment=RUST_BACKTRACE=1
|
||||||
Environment=GST_DEBUG=3,v4l2src:6,tsdemux:6,parsebin:5
|
|
||||||
Environment=GST_DEBUG_DUMP_DOT_DIR=/tmp
|
|
||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=5
|
RestartSec=5
|
||||||
StandardError=append:/tmp/lesavka-server.stderr
|
StandardError=append:/tmp/lesavka-server.stderr
|
||||||
|
|||||||
@ -39,7 +39,7 @@ impl Drop for AudioStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*───────────────────────────────────────────────────────────────────────────*/
|
/*───────────────────────────────────────────────────────────────────────────*/
|
||||||
/* ear() – capture from ALSA (“speaker”) and push AAC AUs via gRPC */
|
/* ear() - capture from ALSA (“speaker”) and push AAC AUs via gRPC */
|
||||||
/*───────────────────────────────────────────────────────────────────────────*/
|
/*───────────────────────────────────────────────────────────────────────────*/
|
||||||
|
|
||||||
pub async fn ear(alsa_dev: &str, id: u32) -> anyhow::Result<AudioStream> {
|
pub async fn ear(alsa_dev: &str, id: u32) -> anyhow::Result<AudioStream> {
|
||||||
@ -151,7 +151,6 @@ fn build_pipeline_desc(dev: &str) -> anyhow::Result<String> {
|
|||||||
})
|
})
|
||||||
.ok_or_else(|| anyhow!("no AAC encoder plugin available"))?;
|
.ok_or_else(|| anyhow!("no AAC encoder plugin available"))?;
|
||||||
|
|
||||||
// one long literal assembled with `concat!` so Rust sees *one* string
|
|
||||||
Ok(format!(
|
Ok(format!(
|
||||||
concat!(
|
concat!(
|
||||||
"alsasrc device=\"{dev}\" do-timestamp=true ! ",
|
"alsasrc device=\"{dev}\" do-timestamp=true ! ",
|
||||||
|
|||||||
@ -32,7 +32,7 @@ impl Stream for VideoStream {
|
|||||||
|
|
||||||
impl Drop for VideoStream {
|
impl Drop for VideoStream {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
// shut down nicely – avoids the “dispose element … READY/PLAYING …” spam
|
// shut down nicely - avoids the “dispose element … READY/PLAYING …” spam
|
||||||
let _ = self._pipeline.set_state(gst::State::Null);
|
let _ = self._pipeline.set_state(gst::State::Null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -93,7 +93,7 @@ pub async fn eye_ball(
|
|||||||
} else {
|
} else {
|
||||||
warn!(target:"lesavka_server::video",
|
warn!(target:"lesavka_server::video",
|
||||||
eye = %eye,
|
eye = %eye,
|
||||||
"🍪 cam_{eye} not found – skipping pad-probe");
|
"🍪 cam_{eye} not found - skipping pad-probe");
|
||||||
}
|
}
|
||||||
|
|
||||||
let eye_clone = eye.to_owned();
|
let eye_clone = eye.to_owned();
|
||||||
@ -191,7 +191,7 @@ pub async fn eye_ball(
|
|||||||
debug!(target:"lesavka_server::video",
|
debug!(target:"lesavka_server::video",
|
||||||
eye = %eye,
|
eye = %eye,
|
||||||
dropped = c,
|
dropped = c,
|
||||||
"⏳ channel full – dropping frames");
|
"⏳ channel full - dropping frames");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => error!("mpsc send err: {e}"),
|
Err(e) => error!("mpsc send err: {e}"),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user