client: headless mode and kmssink guard
This commit is contained in:
parent
57adce2696
commit
4f898ddee7
@ -32,6 +32,7 @@ pub struct LesavkaClientApp {
|
|||||||
aggregator: Option<InputAggregator>,
|
aggregator: Option<InputAggregator>,
|
||||||
server_addr: String,
|
server_addr: String,
|
||||||
dev_mode: bool,
|
dev_mode: bool,
|
||||||
|
headless: bool,
|
||||||
kbd_tx: broadcast::Sender<KeyboardReport>,
|
kbd_tx: broadcast::Sender<KeyboardReport>,
|
||||||
mou_tx: broadcast::Sender<MouseReport>,
|
mou_tx: broadcast::Sender<MouseReport>,
|
||||||
}
|
}
|
||||||
@ -39,6 +40,7 @@ pub struct LesavkaClientApp {
|
|||||||
impl LesavkaClientApp {
|
impl LesavkaClientApp {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
let dev_mode = std::env::var("LESAVKA_DEV_MODE").is_ok();
|
let dev_mode = std::env::var("LESAVKA_DEV_MODE").is_ok();
|
||||||
|
let headless = std::env::var("LESAVKA_HEADLESS").is_ok();
|
||||||
let server_addr = std::env::args()
|
let server_addr = std::env::args()
|
||||||
.nth(1)
|
.nth(1)
|
||||||
.or_else(|| std::env::var("LESAVKA_SERVER_ADDR").ok())
|
.or_else(|| std::env::var("LESAVKA_SERVER_ADDR").ok())
|
||||||
@ -47,12 +49,17 @@ impl LesavkaClientApp {
|
|||||||
let (kbd_tx, _) = broadcast::channel(1024);
|
let (kbd_tx, _) = broadcast::channel(1024);
|
||||||
let (mou_tx, _) = broadcast::channel(4096);
|
let (mou_tx, _) = broadcast::channel(4096);
|
||||||
|
|
||||||
let agg = InputAggregator::new(dev_mode, kbd_tx.clone(), mou_tx.clone());
|
let agg = if headless {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(InputAggregator::new(dev_mode, kbd_tx.clone(), mou_tx.clone()))
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
aggregator: Some(agg),
|
aggregator: agg,
|
||||||
server_addr,
|
server_addr,
|
||||||
dev_mode,
|
dev_mode,
|
||||||
|
headless,
|
||||||
kbd_tx,
|
kbd_tx,
|
||||||
mou_tx,
|
mou_tx,
|
||||||
})
|
})
|
||||||
@ -98,18 +105,25 @@ impl LesavkaClientApp {
|
|||||||
.tcp_nodelay(true)
|
.tcp_nodelay(true)
|
||||||
.connect_lazy();
|
.connect_lazy();
|
||||||
|
|
||||||
|
let mut agg_task = None;
|
||||||
|
let mut kbd_loop = None;
|
||||||
|
let mut mou_loop = None;
|
||||||
|
if !self.headless {
|
||||||
/*────────── input aggregator task (grab after handshake) ─────────────*/
|
/*────────── input aggregator task (grab after handshake) ─────────────*/
|
||||||
let mut aggregator = self.aggregator.take().expect("InputAggregator present");
|
let mut aggregator = self.aggregator.take().expect("InputAggregator present");
|
||||||
info!("⌛ grabbing input devices…");
|
info!("⌛ grabbing input devices…");
|
||||||
aggregator.init()?; // grab devices now that handshake succeeded
|
aggregator.init()?; // grab devices now that handshake succeeded
|
||||||
let agg_task = tokio::spawn(async move {
|
agg_task = Some(tokio::spawn(async move {
|
||||||
let mut a = aggregator;
|
let mut a = aggregator;
|
||||||
a.run().await
|
a.run().await
|
||||||
});
|
}));
|
||||||
|
|
||||||
/*────────── HID streams (never return) ────────*/
|
/*────────── HID streams (never return) ────────*/
|
||||||
let kbd_loop = self.stream_loop_keyboard(hid_ep.clone());
|
kbd_loop = Some(self.stream_loop_keyboard(hid_ep.clone()));
|
||||||
let mou_loop = self.stream_loop_mouse(hid_ep.clone());
|
mou_loop = Some(self.stream_loop_mouse(hid_ep.clone()));
|
||||||
|
} else {
|
||||||
|
info!("🧪 headless mode: skipping HID input capture");
|
||||||
|
}
|
||||||
|
|
||||||
/*───────── optional 300 s auto-exit in dev mode */
|
/*───────── optional 300 s auto-exit in dev mode */
|
||||||
let suicide = async {
|
let suicide = async {
|
||||||
@ -122,6 +136,7 @@ impl LesavkaClientApp {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if !self.headless {
|
||||||
/*────────── video rendering thread (winit) ────*/
|
/*────────── video rendering thread (winit) ────*/
|
||||||
let (video_tx, mut video_rx) = tokio::sync::mpsc::unbounded_channel::<VideoPacket>();
|
let (video_tx, mut video_rx) = tokio::sync::mpsc::unbounded_channel::<VideoPacket>();
|
||||||
|
|
||||||
@ -173,6 +188,9 @@ impl LesavkaClientApp {
|
|||||||
let ep_audio = vid_ep.clone();
|
let ep_audio = vid_ep.clone();
|
||||||
|
|
||||||
tokio::spawn(Self::audio_loop(ep_audio, audio_out));
|
tokio::spawn(Self::audio_loop(ep_audio, audio_out));
|
||||||
|
} else {
|
||||||
|
info!("🧪 headless mode: skipping video/audio renderers");
|
||||||
|
}
|
||||||
/*────────── camera & mic tasks (gated by caps) ───────────*/
|
/*────────── camera & mic tasks (gated by caps) ───────────*/
|
||||||
if caps.camera && std::env::var("LESAVKA_CAM_DISABLE").is_err() {
|
if caps.camera && std::env::var("LESAVKA_CAM_DISABLE").is_err() {
|
||||||
if let Some(cfg) = camera_cfg {
|
if let Some(cfg) = camera_cfg {
|
||||||
@ -190,12 +208,20 @@ impl LesavkaClientApp {
|
|||||||
)?);
|
)?);
|
||||||
tokio::spawn(Self::cam_loop(vid_ep.clone(), cam));
|
tokio::spawn(Self::cam_loop(vid_ep.clone(), cam));
|
||||||
}
|
}
|
||||||
if caps.microphone {
|
if caps.microphone && std::env::var("LESAVKA_MIC_DISABLE").is_err() {
|
||||||
let mic = Arc::new(MicrophoneCapture::new()?);
|
let mic = Arc::new(MicrophoneCapture::new()?);
|
||||||
tokio::spawn(Self::voice_loop(vid_ep.clone(), mic)); // renamed
|
tokio::spawn(Self::voice_loop(vid_ep.clone(), mic)); // renamed
|
||||||
}
|
}
|
||||||
|
|
||||||
/*────────── central reactor ───────────────────*/
|
/*────────── central reactor ───────────────────*/
|
||||||
|
if self.headless {
|
||||||
|
tokio::select! {
|
||||||
|
_ = suicide => { /* handled above */ },
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let kbd_loop = kbd_loop.expect("kbd_loop");
|
||||||
|
let mou_loop = mou_loop.expect("mou_loop");
|
||||||
|
let agg_task = agg_task.expect("agg_task");
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = kbd_loop => { warn!("⚠️⌨️ keyboard stream finished"); },
|
_ = kbd_loop => { warn!("⚠️⌨️ keyboard stream finished"); },
|
||||||
_ = mou_loop => { warn!("⚠️🖱️ mouse stream finished"); },
|
_ = mou_loop => { warn!("⚠️🖱️ mouse stream finished"); },
|
||||||
@ -209,6 +235,7 @@ impl LesavkaClientApp {
|
|||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The branches above either loop forever or exit the process; this
|
// The branches above either loop forever or exit the process; this
|
||||||
// point is unreachable but satisfies the type checker.
|
// point is unreachable but satisfies the type checker.
|
||||||
|
|||||||
@ -23,7 +23,10 @@ fn ensure_runtime_dir() {
|
|||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
|
let headless = env::var("LESAVKA_HEADLESS").is_ok();
|
||||||
|
if !headless {
|
||||||
ensure_runtime_dir();
|
ensure_runtime_dir();
|
||||||
|
}
|
||||||
|
|
||||||
/*------------- common filter & stderr layer ------------------------*/
|
/*------------- common filter & stderr layer ------------------------*/
|
||||||
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||||
|
|||||||
@ -546,7 +546,15 @@ fn build_hdmi_sink(cfg: &CameraConfig) -> anyhow::Result<gst::Element> {
|
|||||||
if gst::ElementFactory::find("kmssink").is_some() {
|
if gst::ElementFactory::find("kmssink").is_some() {
|
||||||
let sink = gst::ElementFactory::make("kmssink").build()?;
|
let sink = gst::ElementFactory::make("kmssink").build()?;
|
||||||
if let Some(connector) = cfg.hdmi.as_ref().and_then(|h| h.id) {
|
if let Some(connector) = cfg.hdmi.as_ref().and_then(|h| h.id) {
|
||||||
sink.set_property("connector-id", &connector);
|
if sink.has_property("connector-id", None) {
|
||||||
|
sink.set_property("connector-id", &(connector as i32));
|
||||||
|
} else {
|
||||||
|
tracing::warn!(
|
||||||
|
target: "lesavka_server::video",
|
||||||
|
%connector,
|
||||||
|
"kmssink does not expose connector-id property; using default connector"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sink.set_property("sync", &false);
|
sink.set_property("sync", &false);
|
||||||
return Ok(sink);
|
return Ok(sink);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user