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