updates
This commit is contained in:
parent
4ef2c9337e
commit
26abca78bb
@ -83,7 +83,7 @@ impl LesavkaClientApp {
|
|||||||
let win0 = MonitorWindow::new(0, &el).expect("win0");
|
let win0 = MonitorWindow::new(0, &el).expect("win0");
|
||||||
let win1 = MonitorWindow::new(1, &el).expect("win1");
|
let win1 = MonitorWindow::new(1, &el).expect("win1");
|
||||||
|
|
||||||
el.run(move |_: Event<()>, _| {
|
let _ = el.run(move |_: Event<()>, _| {
|
||||||
while let Ok(pkt) = video_rx.try_recv() {
|
while let Ok(pkt) = video_rx.try_recv() {
|
||||||
match pkt.id {
|
match pkt.id {
|
||||||
0 => win0.push_packet(pkt),
|
0 => win0.push_packet(pkt),
|
||||||
|
|||||||
@ -10,8 +10,21 @@ use tracing_subscriber::{filter::EnvFilter, fmt, prelude::*};
|
|||||||
|
|
||||||
use lesavka_client::LesavkaClientApp;
|
use lesavka_client::LesavkaClientApp;
|
||||||
|
|
||||||
|
fn ensure_runtime_dir() {
|
||||||
|
if env::var_os("XDG_RUNTIME_DIR").is_none() {
|
||||||
|
eprintln!(
|
||||||
|
"Error: $XDG_RUNTIME_DIR is not set. \
|
||||||
|
Launch the client from a regular desktop session or export it manually, \
|
||||||
|
e.g. `export XDG_RUNTIME_DIR=/run/user/$(id -u)`."
|
||||||
|
);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
|
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(|_| {
|
||||||
EnvFilter::new(
|
EnvFilter::new(
|
||||||
|
|||||||
@ -24,10 +24,16 @@ impl MonitorWindow {
|
|||||||
.with_decorations(false)
|
.with_decorations(false)
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// appsrc -> decode -> convert -> waylandsink
|
// appsrc -> decode -> convert -> autovideosink
|
||||||
let desc = "appsrc name=src is-live=true format=time do-timestamp=true ! \
|
let desc = if std::env::var_os("XDG_RUNTIME_DIR").is_some() {
|
||||||
queue ! h264parse ! decodebin ! videoconvert ! \
|
// graphical
|
||||||
waylandsink name=sink sync=false";
|
"appsrc name=src is-live=true format=time do-timestamp=true ! \
|
||||||
|
queue ! h264parse ! decodebin ! videoconvert ! autovideosink sync=false"
|
||||||
|
} else {
|
||||||
|
// headless / debugging over ssh
|
||||||
|
"appsrc name=src is-live=true format=time do-timestamp=true ! \
|
||||||
|
fakesink sync=false"
|
||||||
|
};
|
||||||
|
|
||||||
let pipeline = gst::parse::launch(desc)?
|
let pipeline = gst::parse::launch(desc)?
|
||||||
.downcast::<gst::Pipeline>()
|
.downcast::<gst::Pipeline>()
|
||||||
|
|||||||
@ -4,7 +4,7 @@ set -euo pipefail
|
|||||||
ORIG_USER=${SUDO_USER:-$(id -un)}
|
ORIG_USER=${SUDO_USER:-$(id -un)}
|
||||||
|
|
||||||
echo "==> 1a. Base packages"
|
echo "==> 1a. Base packages"
|
||||||
sudo pacman -Syq --needed --noconfirm git rustup protobuf gcc pipewire pipewire-pulse tailscale base-devel
|
sudo pacman -Syq --needed --noconfirm git rustup protobuf gcc pipewire pipewire-pulse tailscale base-devel gst-plugin-libav
|
||||||
if ! command -v yay >/dev/null 2>&1; then
|
if ! command -v yay >/dev/null 2>&1; then
|
||||||
echo "==> 1b. installing yay from AUR ..."
|
echo "==> 1b. installing yay from AUR ..."
|
||||||
sudo -u "$ORIG_USER" bash -c '
|
sudo -u "$ORIG_USER" bash -c '
|
||||||
@ -12,7 +12,7 @@ if ! command -v yay >/dev/null 2>&1; then
|
|||||||
cd yay && makepkg -si --noconfirm'
|
cd yay && makepkg -si --noconfirm'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "==> 2a. GC311 kernel‑driver tweaks"
|
echo "==> 2a. Kernel‑driver tweaks"
|
||||||
cat <<'EOF' | sudo tee /etc/modprobe.d/gc311-stream.conf >/dev/null
|
cat <<'EOF' | sudo tee /etc/modprobe.d/gc311-stream.conf >/dev/null
|
||||||
options uvcvideo quirks=0x200 timeout=10000
|
options uvcvideo quirks=0x200 timeout=10000
|
||||||
EOF
|
EOF
|
||||||
@ -104,7 +104,7 @@ After=network.target lesavka-core.service
|
|||||||
[Service]
|
[Service]
|
||||||
ExecStart=/usr/local/bin/lesavka-server
|
ExecStart=/usr/local/bin/lesavka-server
|
||||||
Restart=always
|
Restart=always
|
||||||
Environment="RUST_LOG=lesavka_server=trace"
|
Environment=RUST_LOG=lesavka_server=debug,lesavka_server::usb_gadget=trace
|
||||||
User=root
|
User=root
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
|||||||
@ -15,6 +15,7 @@ anyhow = "1.0"
|
|||||||
lesavka_common = { path = "../common" }
|
lesavka_common = { path = "../common" }
|
||||||
tracing = { version = "0.1", features = ["std"] }
|
tracing = { version = "0.1", features = ["std"] }
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
|
||||||
|
tracing-appender = "0.2"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
gstreamer = { version = "0.23", features = ["v1_22"] }
|
gstreamer = { version = "0.23", features = ["v1_22"] }
|
||||||
|
|||||||
@ -2,14 +2,15 @@
|
|||||||
// sever/src/main.rs
|
// sever/src/main.rs
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
use futures_util::{Stream, StreamExt};
|
|
||||||
use std::{pin::Pin, sync::Arc, time::Duration};
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::{pin::Pin, sync::Arc, time::Duration};
|
||||||
|
use futures_util::{Stream, StreamExt};
|
||||||
use tokio::{fs::{OpenOptions}, io::AsyncWriteExt, sync::Mutex};
|
use tokio::{fs::{OpenOptions}, io::AsyncWriteExt, sync::Mutex};
|
||||||
use tokio_stream::{wrappers::ReceiverStream};
|
use tokio_stream::{wrappers::ReceiverStream};
|
||||||
use tonic::{transport::Server, Request, Response, Status};
|
use tonic::{transport::Server, Request, Response, Status};
|
||||||
use tracing::{info, trace, warn};
|
use tracing::{info, trace, warn};
|
||||||
use tracing_subscriber::{fmt, EnvFilter};
|
use tracing_subscriber::{filter::EnvFilter, fmt, prelude::*};
|
||||||
|
use tracing_appender::non_blocking;
|
||||||
use udev::{MonitorBuilder};
|
use udev::{MonitorBuilder};
|
||||||
|
|
||||||
use lesavka_server::{usb_gadget::UsbGadget, video};
|
use lesavka_server::{usb_gadget::UsbGadget, video};
|
||||||
@ -20,6 +21,32 @@ use lesavka_common::lesavka::{
|
|||||||
MonitorRequest, VideoPacket,
|
MonitorRequest, VideoPacket,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ───────────────── helper ─────────────────────
|
||||||
|
fn ensure_log_file() -> anyhow::Result<()> {
|
||||||
|
let file = std::fs::OpenOptions::new()
|
||||||
|
.create(true).write(true).truncate(true)
|
||||||
|
.open("/tmp/lesavka-server.log")?;
|
||||||
|
let (file_writer, _guard) = non_blocking(file);
|
||||||
|
|
||||||
|
let env_filter = EnvFilter::try_from_default_env()
|
||||||
|
.unwrap_or_else(|_| EnvFilter::new("lesavka_server=trace"));
|
||||||
|
|
||||||
|
let console_layer = fmt::layer()
|
||||||
|
.with_target(true)
|
||||||
|
.with_thread_ids(true);
|
||||||
|
let file_layer = fmt::layer()
|
||||||
|
.with_writer(file_writer)
|
||||||
|
.with_ansi(false);
|
||||||
|
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(env_filter)
|
||||||
|
.with(console_layer)
|
||||||
|
.with(file_layer)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/*─────────────────── tonic service ─────────────────────*/
|
/*─────────────────── tonic service ─────────────────────*/
|
||||||
struct Handler {
|
struct Handler {
|
||||||
kb: Arc<Mutex<tokio::fs::File>>,
|
kb: Arc<Mutex<tokio::fs::File>>,
|
||||||
@ -30,14 +57,20 @@ struct Handler {
|
|||||||
|
|
||||||
impl Handler {
|
impl Handler {
|
||||||
async fn make(gadget: UsbGadget) -> anyhow::Result<Self> {
|
async fn make(gadget: UsbGadget) -> anyhow::Result<Self> {
|
||||||
let kb = OpenOptions::new().write(true).open("/dev/hidg0").await?;
|
gadget.cycle()?;
|
||||||
let ms = OpenOptions::new().write(true)
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
.custom_flags(libc::O_NONBLOCK)
|
|
||||||
|
let kb = OpenOptions::new()
|
||||||
|
.write(true).custom_flags(libc::O_NONBLOCK)
|
||||||
|
.open("/dev/hidg0").await?;
|
||||||
|
let ms = OpenOptions::new()
|
||||||
|
.write(true).custom_flags(libc::O_NONBLOCK)
|
||||||
.open("/dev/hidg1").await?;
|
.open("/dev/hidg1").await?;
|
||||||
|
|
||||||
Ok(Self { kb: Arc::new(Mutex::new(kb)),
|
Ok(Self { kb: Arc::new(Mutex::new(kb)),
|
||||||
ms: Arc::new(Mutex::new(ms)),
|
ms: Arc::new(Mutex::new(ms)),
|
||||||
gadget,
|
gadget,
|
||||||
did_cycle: AtomicBool::new(false),
|
did_cycle: AtomicBool::new(true),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,21 +99,19 @@ impl Relay for Handler {
|
|||||||
let mut s = req.into_inner();
|
let mut s = req.into_inner();
|
||||||
while let Some(pkt) = s.next().await.transpose()? {
|
while let Some(pkt) = s.next().await.transpose()? {
|
||||||
// kb.lock().await.write_all(&pkt.data).await?;
|
// kb.lock().await.write_all(&pkt.data).await?;
|
||||||
for attempt in 0..50 {
|
loop {
|
||||||
match kb.lock().await.write_all(&pkt.data).await {
|
match kb.lock().await.write_all(&pkt.data).await {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
trace!("⌨️ wrote {}", pkt.data.iter()
|
trace!("⌨️ wrote {}", pkt.data.iter()
|
||||||
.map(|b| format!("{b:02X}")).collect::<Vec<_>>().join(" "));
|
.map(|b| format!("{b:02X}")).collect::<Vec<_>>().join(" "));
|
||||||
break;
|
break;
|
||||||
},
|
},
|
||||||
Err(e) if e.raw_os_error() == Some(libc::EBUSY) => {
|
Err(e) if matches!(e.raw_os_error(), Some(libc::EBUSY) | Some(libc::EAGAIN)) => {
|
||||||
tokio::time::sleep(Duration::from_millis(20)).await;
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
Err(e) => return Err(Status::internal(e.to_string())),
|
Err(e) => return Err(Status::internal(e.to_string())),
|
||||||
}
|
}
|
||||||
if attempt == 49 {
|
|
||||||
return Err(Status::internal("hidg0 stayed BUSY"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
tx.send(Ok(pkt)).await;//.ok(); // best-effort echo
|
tx.send(Ok(pkt)).await;//.ok(); // best-effort echo
|
||||||
}
|
}
|
||||||
@ -108,10 +139,11 @@ impl Relay for Handler {
|
|||||||
.map(|b| format!("{b:02X}")).collect::<Vec<_>>().join(" "));
|
.map(|b| format!("{b:02X}")).collect::<Vec<_>>().join(" "));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
|
Err(e) if matches!(e.raw_os_error(), Some(libc::EBUSY) | Some(libc::EAGAIN)) => {
|
||||||
tokio::time::sleep(Duration::from_micros(500)).await;
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
Err(e) => return Err(Status::internal(format!("hidg1: {e}"))),
|
Err(e) => return Err(Status::internal(e.to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let _ = tx.send(Ok(pkt)).await;
|
let _ = tx.send(Ok(pkt)).await;
|
||||||
@ -161,6 +193,7 @@ impl Relay for Handler {
|
|||||||
#[tokio::main(flavor = "multi_thread", worker_threads = 4)]
|
#[tokio::main(flavor = "multi_thread", worker_threads = 4)]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
/* logging */
|
/* logging */
|
||||||
|
ensure_log_file()?;
|
||||||
fmt().with_env_filter(
|
fmt().with_env_filter(
|
||||||
EnvFilter::try_from_default_env()
|
EnvFilter::try_from_default_env()
|
||||||
.unwrap_or_else(|_| EnvFilter::new("lesavka_server=info")),
|
.unwrap_or_else(|_| EnvFilter::new("lesavka_server=info")),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user