lesavka: show client and server versions
This commit is contained in:
parent
a3e84c5c15
commit
29944a5fd7
@ -97,6 +97,7 @@ mod tests {
|
||||
let mut caps = PeerCaps {
|
||||
camera: true,
|
||||
microphone: false,
|
||||
server_version: None,
|
||||
camera_output: Some(String::from("uvc")),
|
||||
camera_codec: Some(String::from("mjpeg")),
|
||||
camera_width: Some(1280),
|
||||
|
||||
@ -12,6 +12,7 @@ use tracing::{info, warn};
|
||||
pub struct PeerCaps {
|
||||
pub camera: bool,
|
||||
pub microphone: bool,
|
||||
pub server_version: Option<String>,
|
||||
pub camera_output: Option<String>,
|
||||
pub camera_codec: Option<String>,
|
||||
pub camera_width: Option<u32>,
|
||||
@ -64,6 +65,8 @@ pub async fn negotiate(uri: &str) -> PeerCaps {
|
||||
PeerCaps {
|
||||
camera: rsp.camera,
|
||||
microphone: rsp.microphone,
|
||||
server_version: (!rsp.server_version.is_empty())
|
||||
.then_some(rsp.server_version.clone()),
|
||||
camera_output: (!rsp.camera_output.is_empty()).then_some(rsp.camera_output.clone()),
|
||||
camera_codec: (!rsp.camera_codec.is_empty()).then_some(rsp.camera_codec.clone()),
|
||||
camera_width: (rsp.camera_width != 0).then_some(rsp.camera_width),
|
||||
@ -125,6 +128,11 @@ pub async fn negotiate(uri: &str) -> PeerCaps {
|
||||
let caps = PeerCaps {
|
||||
camera: rsp.camera,
|
||||
microphone: rsp.microphone,
|
||||
server_version: if rsp.server_version.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(rsp.server_version.clone())
|
||||
},
|
||||
camera_output: if rsp.camera_output.is_empty() {
|
||||
None
|
||||
} else {
|
||||
|
||||
@ -188,6 +188,7 @@ pub struct DeviceSelection {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LauncherState {
|
||||
pub server_available: bool,
|
||||
pub server_version: Option<String>,
|
||||
pub routing: InputRouting,
|
||||
pub view_mode: ViewMode,
|
||||
pub displays: [DisplaySurface; 2],
|
||||
@ -209,6 +210,7 @@ impl Default for LauncherState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
server_available: false,
|
||||
server_version: None,
|
||||
routing: InputRouting::Remote,
|
||||
view_mode: ViewMode::Unified,
|
||||
displays: [DisplaySurface::Preview, DisplaySurface::Preview],
|
||||
@ -241,6 +243,17 @@ impl LauncherState {
|
||||
self.server_available = available;
|
||||
}
|
||||
|
||||
pub fn set_server_version(&mut self, version: Option<String>) {
|
||||
self.server_version = version.and_then(|value| {
|
||||
let trimmed = value.trim();
|
||||
if trimmed.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(trimmed.to_string())
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_view_mode(&mut self, view_mode: ViewMode) {
|
||||
self.view_mode = view_mode;
|
||||
self.displays = match view_mode {
|
||||
|
||||
@ -487,7 +487,11 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
|
||||
server_entry.connect_changed(move |_| {
|
||||
let server_addr =
|
||||
selected_server_addr(&server_entry_read, server_addr_fallback.as_ref());
|
||||
state.borrow_mut().set_server_available(false);
|
||||
{
|
||||
let mut state = state.borrow_mut();
|
||||
state.set_server_available(false);
|
||||
state.set_server_version(None);
|
||||
}
|
||||
if let Some(preview) = preview.as_ref() {
|
||||
preview.set_server_addr(server_addr.clone());
|
||||
}
|
||||
@ -1404,6 +1408,10 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
|
||||
while let Ok(message) = caps_rx.try_recv() {
|
||||
match message {
|
||||
CapsMessage::Refresh(caps) => {
|
||||
{
|
||||
let mut state = state.borrow_mut();
|
||||
state.set_server_version(caps.server_version.clone());
|
||||
}
|
||||
if let (Some(width), Some(height)) =
|
||||
(caps.eye_width, caps.eye_height)
|
||||
{
|
||||
|
||||
@ -125,21 +125,29 @@ pub fn build_launcher_view(
|
||||
hero.set_hexpand(true);
|
||||
|
||||
let brand_box = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||
let brand_row = gtk::Box::new(gtk::Orientation::Horizontal, 8);
|
||||
brand_row.set_halign(gtk::Align::Start);
|
||||
let heading = gtk::Label::new(Some("Lesavka"));
|
||||
heading.add_css_class("title-2");
|
||||
heading.set_halign(gtk::Align::Start);
|
||||
brand_box.append(&heading);
|
||||
let version_tag = gtk::Label::new(Some(&format!("v{}", crate::VERSION)));
|
||||
version_tag.add_css_class("version-tag");
|
||||
version_tag.set_halign(gtk::Align::Start);
|
||||
version_tag.set_valign(gtk::Align::End);
|
||||
brand_row.append(&heading);
|
||||
brand_row.append(&version_tag);
|
||||
brand_box.append(&brand_row);
|
||||
hero.append(&brand_box);
|
||||
|
||||
let chips = gtk::Box::new(gtk::Orientation::Horizontal, 6);
|
||||
chips.set_halign(gtk::Align::End);
|
||||
chips.set_hexpand(true);
|
||||
let (relay_chip, relay_light, relay_value) = build_status_chip_with_light("Server", "Offline");
|
||||
let (relay_chip, relay_light, relay_value) = build_status_chip_with_light("Server", "");
|
||||
let (routing_chip, routing_light, routing_value) =
|
||||
build_status_chip_with_light("Inputs", "Local");
|
||||
let (gpio_chip, gpio_light, gpio_value) = build_status_chip_with_light("GPIO", "Unknown");
|
||||
let (shortcut_chip, shortcut_value) = build_status_chip("Swap Key", "Pause");
|
||||
stabilize_chip(&relay_chip, 84);
|
||||
stabilize_chip(&relay_chip, 104);
|
||||
stabilize_chip(&routing_chip, 84);
|
||||
stabilize_chip(&gpio_chip, 84);
|
||||
stabilize_chip(&shortcut_chip, 88);
|
||||
@ -610,6 +618,11 @@ pub fn install_css(window: >k::ApplicationWindow) {
|
||||
font-weight: 700;
|
||||
opacity: 0.92;
|
||||
}
|
||||
label.version-tag {
|
||||
font-size: 0.76rem;
|
||||
opacity: 0.72;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
box.status-chip {
|
||||
background: rgba(91, 179, 162, 0.12);
|
||||
border: 1px solid rgba(91, 179, 162, 0.25);
|
||||
|
||||
@ -29,14 +29,22 @@ pub type RelayChild = Child;
|
||||
pub fn refresh_launcher_ui(widgets: &LauncherWidgets, state: &LauncherState, child_running: bool) {
|
||||
let relay_live = child_running || state.remote_active;
|
||||
set_status_light(&widgets.summary.relay_light, state.server_available);
|
||||
widgets
|
||||
.summary
|
||||
.relay_value
|
||||
.set_text(if state.server_available {
|
||||
"Online"
|
||||
} else {
|
||||
"Offline"
|
||||
});
|
||||
widgets.summary.relay_value.set_text(
|
||||
state
|
||||
.server_version
|
||||
.as_deref()
|
||||
.map(|version| version.trim())
|
||||
.filter(|version| !version.is_empty())
|
||||
.map(|version| {
|
||||
if version.starts_with('v') {
|
||||
version.to_string()
|
||||
} else {
|
||||
format!("v{version}")
|
||||
}
|
||||
})
|
||||
.as_deref()
|
||||
.unwrap_or(""),
|
||||
);
|
||||
set_status_light(
|
||||
&widgets.summary.routing_light,
|
||||
matches!(state.routing, InputRouting::Remote),
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
pub mod app;
|
||||
mod app_support;
|
||||
pub mod handshake;
|
||||
|
||||
@ -53,6 +53,7 @@ message HandshakeSet {
|
||||
uint32 eye_width = 8;
|
||||
uint32 eye_height = 9;
|
||||
uint32 eye_fps = 10;
|
||||
string server_version = 11;
|
||||
}
|
||||
|
||||
message Empty {}
|
||||
|
||||
@ -33,6 +33,7 @@ impl Handshake for HandshakeSvc {
|
||||
eye_width,
|
||||
eye_height,
|
||||
eye_fps,
|
||||
server_version: crate::VERSION.to_string(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
// server/src/lib.rs
|
||||
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
pub mod audio;
|
||||
pub mod camera;
|
||||
pub mod camera_runtime;
|
||||
|
||||
@ -7,16 +7,19 @@
|
||||
//! devices.
|
||||
|
||||
mod handshake {
|
||||
#[allow(dead_code)]
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct PeerCaps {
|
||||
pub camera: bool,
|
||||
pub microphone: bool,
|
||||
pub server_version: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn negotiate(_uri: &str) -> PeerCaps {
|
||||
PeerCaps {
|
||||
camera: std::env::var("LESAVKA_TEST_CAP_CAMERA").is_ok(),
|
||||
microphone: std::env::var("LESAVKA_TEST_CAP_MIC").is_ok(),
|
||||
server_version: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,6 +54,7 @@ async fn negotiate_against_local_server() -> PeerCaps {
|
||||
fn assert_default_caps(caps: &PeerCaps) {
|
||||
assert!(!caps.camera);
|
||||
assert!(!caps.microphone);
|
||||
assert_eq!(caps.server_version, None);
|
||||
assert_eq!(caps.camera_output, None);
|
||||
assert_eq!(caps.camera_codec, None);
|
||||
assert_eq!(caps.camera_width, None);
|
||||
@ -99,6 +100,7 @@ impl Handshake for SparseHandshakeSvc {
|
||||
Ok(Response::new(HandshakeSet {
|
||||
camera: true,
|
||||
microphone: false,
|
||||
server_version: String::new(),
|
||||
camera_output: String::new(),
|
||||
camera_codec: String::new(),
|
||||
camera_width: 0,
|
||||
@ -135,6 +137,10 @@ fn handshake_returns_uvc_caps_with_explicit_dimensions_and_fps() {
|
||||
let caps = rt.block_on(negotiate_against_local_server());
|
||||
assert!(caps.camera);
|
||||
assert!(caps.microphone);
|
||||
assert_eq!(
|
||||
caps.server_version,
|
||||
Some(lesavka_server::VERSION.to_string())
|
||||
);
|
||||
assert_eq!(caps.camera_output, Some(String::from("uvc")));
|
||||
assert_eq!(caps.camera_codec, Some(String::from("mjpeg")));
|
||||
assert_eq!(caps.camera_width, Some(1024));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user