many fixes
This commit is contained in:
parent
00696baa5e
commit
1c7640f979
@ -68,11 +68,11 @@ impl LesavkaClientApp {
|
||||
let kbd_loop = self.stream_loop_keyboard(hid_ep.clone());
|
||||
let mou_loop = self.stream_loop_mouse(hid_ep.clone());
|
||||
|
||||
/*────────── optional 30 s auto‑exit in dev mode */
|
||||
/*────────── optional 30 s auto-exit in dev mode */
|
||||
let suicide = async {
|
||||
if self.dev_mode {
|
||||
tokio::time::sleep(Duration::from_secs(30)).await;
|
||||
warn!("💀 dev‑mode timeout");
|
||||
warn!("💀 dev-mode timeout");
|
||||
std::process::exit(0);
|
||||
} else {
|
||||
std::future::pending::<()>().await
|
||||
@ -142,7 +142,7 @@ impl LesavkaClientApp {
|
||||
info!("⌨️ dial {}", self.server_addr); // LESAVKA-client
|
||||
let mut cli = RelayClient::new(ep.clone());
|
||||
|
||||
// ✅ use kbd_tx here – fixes E0271
|
||||
// ✅ use kbd_tx here - fixes E0271
|
||||
let outbound = BroadcastStream::new(self.kbd_tx.subscribe())
|
||||
.filter_map(|r| r.ok());
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
use anyhow::{bail, Context, Result};
|
||||
use evdev::{Device, EventType, KeyCode, RelativeAxisCode};
|
||||
use tokio::{sync::broadcast::Sender, time::{interval, Duration}};
|
||||
use tracing::{debug, info};
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
use lesavka_common::lesavka::{KeyboardReport, MouseReport};
|
||||
|
||||
@ -14,6 +14,7 @@ pub struct InputAggregator {
|
||||
kbd_tx: Sender<KeyboardReport>,
|
||||
mou_tx: Sender<MouseReport>,
|
||||
dev_mode: bool,
|
||||
released: bool,
|
||||
keyboards: Vec<KeyboardAggregator>,
|
||||
mice: Vec<MouseAggregator>,
|
||||
camera: Option<CameraCapture>,
|
||||
@ -24,7 +25,7 @@ impl InputAggregator {
|
||||
pub fn new(dev_mode: bool,
|
||||
kbd_tx: Sender<KeyboardReport>,
|
||||
mou_tx: Sender<MouseReport>) -> Self {
|
||||
Self { kbd_tx, mou_tx, dev_mode,
|
||||
Self { kbd_tx, mou_tx, dev_mode, released: false,
|
||||
keyboards: Vec::new(), mice: Vec::new(),
|
||||
camera: None, mic: None }
|
||||
}
|
||||
@ -55,7 +56,7 @@ impl InputAggregator {
|
||||
}
|
||||
};
|
||||
|
||||
// non‑blocking so fetch_events never stalls the whole loop
|
||||
// non-blocking so fetch_events never stalls the whole loop
|
||||
dev.set_nonblocking(true).with_context(|| format!("set_non_blocking {:?}", path))?;
|
||||
|
||||
match classify_device(&dev) {
|
||||
@ -104,9 +105,20 @@ impl InputAggregator {
|
||||
// Example approach: poll each aggregator in a simple loop
|
||||
let mut tick = interval(Duration::from_millis(10));
|
||||
loop {
|
||||
let mut want_toggle = false;
|
||||
let mut want_kill = false;
|
||||
for kbd in &mut self.keyboards {
|
||||
kbd.process_events();
|
||||
want_toggle |= kbd.magic_grab();
|
||||
want_kill |= kbd.magic_kill();
|
||||
}
|
||||
|
||||
if want_toggle { self.toggle_grab(); }
|
||||
if want_kill {
|
||||
warn!("🧙 magic chord - killing 🪄 AVADA KEDAVRA!!! 💥💀⚰️");
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
for mouse in &mut self.mice {
|
||||
mouse.process_events();
|
||||
}
|
||||
@ -114,6 +126,19 @@ impl InputAggregator {
|
||||
tick.tick().await;
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_grab(&mut self) {
|
||||
if self.released {
|
||||
for k in &mut self.keyboards { k.dev.grab().ok(); }
|
||||
for m in &mut self.mice { m.dev.grab().ok(); }
|
||||
tracing::info!("🧙 magic chord - restricting devices 🪄 IMPERIUS!!! 🎮🔒");
|
||||
} else {
|
||||
for k in &mut self.keyboards { k.dev.ungrab().ok(); }
|
||||
for m in &mut self.mice { m.dev.ungrab().ok(); }
|
||||
tracing::info!("🧙 magic chord - freeing devices 🪄 EXPELLIARMUS!!! 🔓🕊️");
|
||||
}
|
||||
self.released = !self.released;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
@ -17,13 +17,13 @@ pub struct KeyboardAggregator {
|
||||
}
|
||||
|
||||
/*───────── helpers ───────────────────────────────────────────────────*/
|
||||
/// Monotonically‑increasing ID that can be logged on server & client.
|
||||
/// Monotonically-increasing ID that can be logged on server & client.
|
||||
static SEQ: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
impl KeyboardAggregator {
|
||||
pub fn new(dev: Device, dev_mode: bool, tx: Sender<KeyboardReport>) -> Self {
|
||||
let _ = dev.set_nonblocking(true);
|
||||
Self { dev, tx, dev_mode, pressed_keys: HashSet::new() }
|
||||
Self { dev, tx, dev_mode, pressed_keys: HashSet::new(), released: false}
|
||||
}
|
||||
|
||||
pub fn process_events(&mut self) {
|
||||
@ -48,17 +48,12 @@ impl KeyboardAggregator {
|
||||
}
|
||||
|
||||
let report = self.build_report();
|
||||
// Generate a local sequence number for debugging/log‑merge only.
|
||||
// Generate a local sequence number for debugging/log-merge only.
|
||||
let id = SEQ.fetch_add(1, Ordering::Relaxed);
|
||||
if self.dev_mode { debug!(seq = id, ?report, "kbd"); }
|
||||
let _ = self.tx.send(KeyboardReport {
|
||||
data: report.to_vec()
|
||||
});
|
||||
|
||||
if self.is_magic() {
|
||||
warn!("🧙 magic chord – exiting 🪄 AVADA KEDAVRA!!! 💥💀");
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,9 +72,16 @@ impl KeyboardAggregator {
|
||||
out
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_magic(&self) -> bool {
|
||||
self.pressed_keys.contains(&KeyCode::KEY_LEFTCTRL)
|
||||
&& self.pressed_keys.contains(&KeyCode::KEY_ESC)
|
||||
pub fn has_key(&self, kc: KeyCode) -> bool { self.pressed_keys.contains(&kc) }
|
||||
|
||||
pub fn magic_grab(&self) -> bool {
|
||||
self.has_key(KeyCode::KEY_LEFTCTRL)
|
||||
&& self.has_key(KeyCode::KEY_LEFTSHIFT)
|
||||
&& self.has_key(KeyCode::KEY_G)
|
||||
}
|
||||
|
||||
pub fn magic_kill(&self) -> bool {
|
||||
self.has_key(KeyCode::KEY_LEFTCTRL)
|
||||
&& self.has_key(KeyCode::KEY_ESC)
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ pub fn keycode_to_usage(key: KeyCode) -> Option<u8> {
|
||||
KeyCode::KEY_F12 => Some(0x45),
|
||||
|
||||
// --- Navigation / editing cluster ---------------------------------
|
||||
KeyCode::KEY_SYSRQ => Some(0x46), // Print‑Screen
|
||||
KeyCode::KEY_SYSRQ => Some(0x46), // Print-Screen
|
||||
KeyCode::KEY_SCROLLLOCK => Some(0x47),
|
||||
KeyCode::KEY_PAUSE => Some(0x48),
|
||||
KeyCode::KEY_INSERT => Some(0x49),
|
||||
@ -93,7 +93,7 @@ pub fn keycode_to_usage(key: KeyCode) -> Option<u8> {
|
||||
KeyCode::KEY_DOWN => Some(0x51),
|
||||
KeyCode::KEY_UP => Some(0x52),
|
||||
|
||||
// --- Keypad / Num‑lock block --------------------------------------
|
||||
// --- Keypad / Num-lock block --------------------------------------
|
||||
KeyCode::KEY_NUMLOCK => Some(0x53),
|
||||
KeyCode::KEY_KPSLASH => Some(0x54),
|
||||
KeyCode::KEY_KPASTERISK => Some(0x55),
|
||||
|
||||
@ -7,7 +7,7 @@ use tracing::{debug, error, warn, trace};
|
||||
|
||||
use lesavka_common::lesavka::MouseReport;
|
||||
|
||||
const SEND_INTERVAL: Duration = Duration::from_micros(16);
|
||||
const SEND_INTERVAL: Duration = Duration::from_micros(10);
|
||||
|
||||
pub struct MouseAggregator {
|
||||
dev: Device,
|
||||
|
||||
@ -48,7 +48,7 @@ async fn main() -> Result<()> {
|
||||
if dev_mode {
|
||||
let log_path = Path::new("/tmp").join("lesavka-client.log");
|
||||
|
||||
// file → non‑blocking writer (+ guard)
|
||||
// file → non-blocking writer (+ guard)
|
||||
let file = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
|
||||
@ -41,7 +41,7 @@ impl MonitorWindow {
|
||||
|
||||
// let pipeline = gst::parse::launch(DESC)?
|
||||
// .downcast::<gst::Pipeline>()
|
||||
// .expect("pipeline down‑cast");
|
||||
// .expect("pipeline down-cast");
|
||||
let pipeline: gst::Pipeline = gst::parse::launch(DESC)?
|
||||
.downcast()
|
||||
.expect("pipeline");
|
||||
@ -50,7 +50,7 @@ impl MonitorWindow {
|
||||
// .by_name("src")
|
||||
// .expect("appsrc element not found")
|
||||
// .downcast::<gst_app::AppSrc>()
|
||||
// .expect("appsrc down‑cast");
|
||||
// .expect("appsrc down-cast");
|
||||
let src: gst_app::AppSrc = pipeline.by_name("src")
|
||||
.expect("appsrc")
|
||||
.downcast()
|
||||
@ -68,7 +68,7 @@ impl MonitorWindow {
|
||||
Ok(Self { id, _window: window, src })
|
||||
}
|
||||
|
||||
/// Feed one H.264 access‑unit into the pipeline.
|
||||
/// Feed one H.264 access-unit into the pipeline.
|
||||
pub fn push_packet(&self, pkt: VideoPacket) {
|
||||
// Mutable so we can set the PTS:
|
||||
let mut buf = gst::Buffer::from_slice(pkt.data);
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
# Proven Pi-5 configfs gadget: HID keyboard+mouse
|
||||
# Still need Web Cam Support + stereo UAC2
|
||||
|
||||
# lesavka-core – one-shot gadget bring-up for Pi-5 / Arch-ARM
|
||||
# lesavka-core - one-shot gadget bring-up for Pi-5 / Arch-ARM
|
||||
set -euo pipefail
|
||||
|
||||
# 1) Ensure the dwc2 peripheral overlay is active exactly once
|
||||
@ -29,7 +29,7 @@ for _ in {1..100}; do # 100 × 100ms = 10s
|
||||
done
|
||||
|
||||
if [[ -z $UDC ]]; then
|
||||
echo "[lesavka-core] ⚠️ UDC still absent – trying manual bind"
|
||||
echo "[lesavka-core] ⚠️ UDC still absent - trying manual bind"
|
||||
for drv in dwc2 dwc3; do
|
||||
drv_root="/sys/bus/platform/drivers/$drv"
|
||||
[[ -d $drv_root ]] || continue
|
||||
@ -38,7 +38,7 @@ if [[ -z $UDC ]]; then
|
||||
echo "$node" >"$drv_root/bind" 2>/dev/null || continue
|
||||
done
|
||||
done
|
||||
# re‑check for another 5 s
|
||||
# re-check for another 5 s
|
||||
for i in {1..50}; do
|
||||
UDC=$(ls /sys/class/udc 2>/dev/null | head -n1) && [[ -n $UDC ]] && break
|
||||
sleep 0.1
|
||||
|
||||
130
scripts/install-server.sh
Normal file
130
scripts/install-server.sh
Normal file
@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env bash
|
||||
# install-server.sh - install and setup all server related apps and environments
|
||||
set -euo pipefail
|
||||
ORIG_USER=${SUDO_USER:-$(id -un)}
|
||||
|
||||
echo "==> 1a. Base packages"
|
||||
sudo pacman -Syq --needed --noconfirm git rustup protobuf gcc pipewire pipewire-pulse tailscale base-devel gstreamer gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav
|
||||
if ! command -v yay >/dev/null 2>&1; then
|
||||
echo "==> 1b. installing yay from AUR ..."
|
||||
sudo -u "$ORIG_USER" bash -c '
|
||||
cd /tmp && git clone --depth 1 https://aur.archlinux.org/yay.git &&
|
||||
cd yay && makepkg -si --noconfirm'
|
||||
fi
|
||||
yay -S --noconfirm grpcurl-bin
|
||||
|
||||
echo "==> 2a. Kernel-driver tweaks"
|
||||
cat <<'EOF' | sudo tee /etc/modprobe.d/gc311-stream.conf >/dev/null
|
||||
options uvcvideo quirks=0x200 timeout=10000
|
||||
EOF
|
||||
|
||||
echo "==> 2b. Predictable /dev names for each capture card"
|
||||
# probe all v4l2 devices, keep only the two GC311 capture cards
|
||||
mapfile -t GC_VIDEOS < <(
|
||||
sudo v4l2-ctl --list-devices |
|
||||
awk '/Live Gamer MINI/{getline; print $1}'
|
||||
)
|
||||
|
||||
if [ "${#GC_VIDEOS[@]}" -ne 2 ]; then
|
||||
echo "❌ Exactly two GC311 capture cards (index0) must be attached!" >&2
|
||||
printf ' Detected: %s\n' "${GC_VIDEOS[@]}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mapfile -t TAGS < <(
|
||||
for v in "${GC_VIDEOS[@]}"; do
|
||||
sudo udevadm info -q property -n "$v" |
|
||||
awk -F= '/^ID_PATH_TAG=/{print $2}'
|
||||
done
|
||||
)
|
||||
|
||||
printf ' ↪ Left card: %s (%s)\n' "${GC_VIDEOS[0]}" "${TAGS[0]}"
|
||||
printf ' ↪ Right card: %s (%s)\n' "${GC_VIDEOS[1]}" "${TAGS[1]}"
|
||||
|
||||
LEFT_TAG=${TAGS[0]}
|
||||
RIGHT_TAG=${TAGS[1]}
|
||||
|
||||
sudo tee /etc/udev/rules.d/85-gc311.rules >/dev/null <<EOF
|
||||
# auto-generated by lesavka/scripts/install-server.sh - DO NOT EDIT
|
||||
SUBSYSTEM=="video4linux", ENV{ID_PATH_TAG}=="$LEFT_TAG", SYMLINK+="lesavka_l_eye"
|
||||
SUBSYSTEM=="video4linux", ENV{ID_PATH_TAG}=="$RIGHT_TAG", SYMLINK+="lesavka_r_eye"
|
||||
EOF
|
||||
|
||||
sudo udevadm control --reload
|
||||
sudo udevadm trigger --subsystem-match=video4linux
|
||||
sudo udevadm settle
|
||||
|
||||
echo "==> 3. Rust toolchain"
|
||||
sudo rustup default stable
|
||||
sudo -u "$ORIG_USER" rustup default stable
|
||||
|
||||
echo "==> 4a. Source checkout"
|
||||
SRC_DIR=/var/src/lesavka
|
||||
REPO_URL=ssh://git@scm.bstein.dev:2242/brad_stein/lesavka.git
|
||||
if [[ ! -d $SRC_DIR ]]; then
|
||||
sudo mkdir -p /var/src
|
||||
sudo chown "$ORIG_USER":"$ORIG_USER" /var/src
|
||||
fi
|
||||
if [[ -d $SRC_DIR/.git ]]; then
|
||||
sudo -u "$ORIG_USER" git -C "$SRC_DIR" pull --ff-only
|
||||
else
|
||||
sudo -u "$ORIG_USER" git clone "$REPO_URL" "$SRC_DIR"
|
||||
fi
|
||||
|
||||
echo "==> 4b. Source build"
|
||||
sudo -u "$ORIG_USER" bash -c "cd '$SRC_DIR/server' && cargo clean && cargo build --release"
|
||||
|
||||
echo "==> 5. Install binaries"
|
||||
sudo install -Dm755 "$SRC_DIR/server/target/release/lesavka-server" /usr/local/bin/lesavka-server
|
||||
sudo install -Dm755 "$SRC_DIR/scripts/lesavka-core.sh" /usr/local/bin/lesavka-core.sh
|
||||
|
||||
echo "==> 6a. Systemd units - lesavka-core"
|
||||
cat <<'UNIT' | sudo tee /etc/systemd/system/lesavka-core.service >/dev/null
|
||||
[Unit]
|
||||
Description=lesavka USB gadget bring-up
|
||||
After=sys-kernel-config.mount
|
||||
Requires=sys-kernel-config.mount
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/lesavka-core.sh
|
||||
RemainAfterExit=yes
|
||||
CapabilityBoundingSet=CAP_SYS_ADMIN
|
||||
MountFlags=slave
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
UNIT
|
||||
|
||||
echo "==> 6b. Systemd units - lesavka-server"
|
||||
cat <<'UNIT' | sudo tee /etc/systemd/system/lesavka-server.service >/dev/null
|
||||
[Unit]
|
||||
Description=lesavka gRPC relay
|
||||
After=network.target lesavka-core.service
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/local/bin/lesavka-server
|
||||
Restart=always
|
||||
Environment=RUST_LOG=lesavka_server=debug,lesavka_server::usb_gadget=info
|
||||
Environment=RUST_BACKTRACE=1
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
StandardError=append:/tmp/lesavka-server.log
|
||||
StartLimitIntervalSec=30
|
||||
StartLimitBurst=10
|
||||
User=root
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
UNIT
|
||||
|
||||
echo "==> 6c. Systemd units - initialization"
|
||||
sudo truncate -s 0 /tmp/lesavka-server.log
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now lesavka-core
|
||||
sudo systemctl restart lesavka-core
|
||||
echo "✅ lesavka-core installed and restarted..."
|
||||
|
||||
sudo systemctl enable --now lesavka-server
|
||||
sudo systemctl restart lesavka-server
|
||||
echo "✅ lesavka-server installed and restarted..."
|
||||
@ -13,7 +13,7 @@ if ! command -v yay >/dev/null 2>&1; then
|
||||
fi
|
||||
# yay -S --noconfirm grpcurl-bin
|
||||
|
||||
echo "==> 2a. Kernel‑driver tweaks"
|
||||
echo "==> 2a. Kernel-driver tweaks"
|
||||
cat <<'EOF' | sudo tee /etc/modprobe.d/gc311-stream.conf >/dev/null
|
||||
options uvcvideo quirks=0x200 timeout=10000
|
||||
EOF
|
||||
|
||||
114
scripts/lesavka-core.sh
Normal file
114
scripts/lesavka-core.sh
Normal file
@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env bash
|
||||
# lesavka-core.sh - background stealth daemon to present gadget as usb hub of genuine devices
|
||||
# Proven Pi-5 configfs gadget: HID keyboard+mouse
|
||||
# Still need Web Cam Support + stereo UAC2
|
||||
|
||||
# lesavka-core - one-shot gadget bring-up for Pi-5 / Arch-ARM
|
||||
set -euo pipefail
|
||||
|
||||
# 1) Ensure the dwc2 peripheral overlay is active exactly once
|
||||
CFG=/boot/config.txt
|
||||
grep -q 'dtoverlay=dwc2,dr_mode=peripheral' "$CFG" || echo 'dtoverlay=dwc2,dr_mode=peripheral' >> "$CFG"
|
||||
|
||||
# 2) Load kernel modules (idempotent)
|
||||
modprobe dwc2 || { echo "dwc2 not in kernel; abort" >&2; exit 1; }
|
||||
modprobe libcomposite || { echo "libcomposite not in kernel; abort" >&2; exit 1; }
|
||||
|
||||
modprobe -r uvcvideo 2>/dev/null || true
|
||||
modprobe uvcvideo || { echo "uvcvideo not in kernel; abort" >&2; exit 1; }
|
||||
|
||||
udevadm control --reload
|
||||
udevadm trigger --subsystem-match=video4linux
|
||||
udevadm settle
|
||||
|
||||
echo "[lesavka-core] ⏳ waiting for UDC to register ..."
|
||||
UDC=""
|
||||
for _ in {1..100}; do # 100 × 100ms = 10s
|
||||
UDC=$(ls /sys/class/udc 2>/dev/null | head -n1) && [[ -n $UDC ]] && break
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
if [[ -z $UDC ]]; then
|
||||
echo "[lesavka-core] ⚠️ UDC still absent - trying manual bind"
|
||||
for drv in dwc2 dwc3; do
|
||||
drv_root="/sys/bus/platform/drivers/$drv"
|
||||
[[ -d $drv_root ]] || continue
|
||||
for node in /sys/bus/platform/devices/*usb*; do
|
||||
node=${node##*/} # strip path → “1000480000.usb”
|
||||
echo "$node" >"$drv_root/bind" 2>/dev/null || continue
|
||||
done
|
||||
done
|
||||
# re-check for another 5 s
|
||||
for i in {1..50}; do
|
||||
UDC=$(ls /sys/class/udc 2>/dev/null | head -n1) && [[ -n $UDC ]] && break
|
||||
sleep 0.1
|
||||
done
|
||||
fi
|
||||
|
||||
[[ -n $UDC ]] || { echo "❌ UDC not present after manual bind"; exit 1; }
|
||||
echo "[lesavka-core] ✅ UDC detected: $UDC"
|
||||
|
||||
# 3) Mount configfs once
|
||||
mountpoint -q /sys/kernel/config || mount -t configfs none /sys/kernel/config
|
||||
G=/sys/kernel/config/usb_gadget/lesavka
|
||||
|
||||
# 4) Tear down any previous half-built gadget
|
||||
if [[ -d $G ]]; then
|
||||
echo '' >"$G/UDC" 2>/dev/null || true
|
||||
sleep 0.2
|
||||
find "$G/configs" -type l -delete 2>/dev/null || true
|
||||
rm -rf "$G" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# 5) Create gadget (boot-keyboard + UAC2 mic/spkr, 500 mA max)
|
||||
mkdir -p "$G"
|
||||
echo 0x1d6b >"$G/idVendor" # Linux Foundation
|
||||
echo 0x0104 >"$G/idProduct" # Multifunction Composite Gadget
|
||||
echo 0x0200 >"$G/bcdUSB"
|
||||
|
||||
mkdir -p "$G/strings/0x409"
|
||||
echo "$(cat /proc/sys/kernel/random/uuid)" >"$G/strings/0x409/serialnumber"
|
||||
echo "Lesavka" >"$G/strings/0x409/manufacturer"
|
||||
echo "Lesavka Composite" >"$G/strings/0x409/product"
|
||||
|
||||
# ----------------------- HID keyboard (usb0) -----------------------
|
||||
mkdir -p "$G/functions/hid.usb0"
|
||||
echo 1 >"$G/functions/hid.usb0/protocol"
|
||||
echo 1 >"$G/functions/hid.usb0/subclass"
|
||||
echo 8 >"$G/functions/hid.usb0/report_length"
|
||||
printf '\x05\x01\x09\x06\xa1\x01\x05\x07\x19\xe0\x29\xe7\x15\x00\x25\x01'\
|
||||
'\x75\x01\x95\x08\x81\x02\x95\x01\x75\x08\x81\x01\x95\x05\x75\x01\x05'\
|
||||
'\x08\x19\x01\x29\x05\x91\x02\x95\x01\x75\x03\x91\x01\x95\x06\x75\x08'\
|
||||
'\x15\x00\x25\x65\x05\x07\x19\x00\x29\x65\x81\x00\xc0' \
|
||||
>"$G/functions/hid.usb0/report_desc"
|
||||
|
||||
# ----------------------- HID mouse (usb1) --------------------------
|
||||
mkdir -p "$G/functions/hid.usb1"
|
||||
echo 2 > "$G/functions/hid.usb1/protocol" # Boot mouse
|
||||
echo 1 > "$G/functions/hid.usb1/subclass"
|
||||
echo 4 > "$G/functions/hid.usb1/report_length"
|
||||
printf '\x05\x01\x09\x02\xa1\x01\x09\x01\xa1\x00'\
|
||||
'\x05\x09\x19\x01\x29\x03\x15\x00\x25\x01\x95\x03\x75\x01\x81\x02'\
|
||||
'\x95\x01\x75\x05\x81\x03'\
|
||||
'\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"
|
||||
|
||||
# # -- UAC2 Audio
|
||||
# mkdir -p $G/functions/uac2.usb0
|
||||
# echo 48000 > $G/functions/uac2.usb0/c_srate
|
||||
# echo 2 > $G/functions/uac2.usb0/c_ssize
|
||||
# echo 2 > $G/functions/uac2.usb0/p_chmask
|
||||
|
||||
# ----------------------- configuration -----------------------------
|
||||
mkdir -p "$G/configs/c.1/strings/0x409"
|
||||
echo 500 > "$G/configs/c.1/MaxPower"
|
||||
echo "Config 1" > "$G/configs/c.1/strings/0x409/configuration"
|
||||
|
||||
# 6) Finally bind to first available UDC
|
||||
ln -s $G/functions/hid.usb0 $G/configs/c.1/
|
||||
ln -s $G/functions/hid.usb1 $G/configs/c.1/
|
||||
# ln -s $G/functions/uac2.usb0 $G/configs/c.1/
|
||||
|
||||
# 7) Finally bind to first available UDC
|
||||
echo "$UDC" > "$G/UDC"
|
||||
echo "[lesavka-core] 🎉 gadget ready on $UDC (keyboard: hidg0, mouse: hidg1)"
|
||||
@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/manual/usb-reset.sh
|
||||
|
||||
grpcurl \
|
||||
-plaintext \
|
||||
-import-path ./../../common/proto \
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
PI_HOST="nikto@192.168.42.253" # user@IP‑of lesavka
|
||||
# scripts/manual/video-stream.sh
|
||||
|
||||
PI_HOST="nikto@192.168.42.253" # user@IP-of lesavka
|
||||
REMOTE_DIR="/tmp" # where eye*-idr.h264 are written
|
||||
FIRST_FEW=10
|
||||
|
||||
set -eu
|
||||
WORKDIR="$(mktemp -d)"
|
||||
@ -9,9 +12,11 @@ scp "${PI_HOST}:${REMOTE_DIR}/eye*.h264" "$WORKDIR/"
|
||||
|
||||
echo "🎞️ converting to PNG ..."
|
||||
for h264 in "$WORKDIR"/eye*.h264; do
|
||||
(( FIRST_FEW == 0 )) && break
|
||||
png="${h264%.h264}.png"
|
||||
ffmpeg -loglevel error -y -f h264 -i "$h264" -frames:v 1 "$png"
|
||||
echo "🖼️ $(basename "$png") ready"
|
||||
xdg-open "$png" >/dev/null 2>&1 &
|
||||
(( FIRST_FEW-- ))
|
||||
done
|
||||
echo "✅ done - images are opening (directory: $WORKDIR)"
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/manual/video-stream.sh
|
||||
|
||||
grpcurl -plaintext \
|
||||
-d '{"id":0,"max_bitrate":6000}' \
|
||||
-import-path ./../../common/proto -proto lesavka.proto \
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user