Files
lan-mouse/lan-mouse-gtk/src/client_object/imp.rs
Jon Kinney 3b7570c062 feat: peer version exchange with soft-warn UI indicator
Adds a one-shot Hello message to the lan-mouse wire protocol so each
peer can display the other end's build commit hash and warn on
version mismatch. Soft-warn only — mismatched versions never refuse
traffic.

Wire change (lan-mouse-proto)
* `ProtoEvent::Hello { commit: [u8; 8] }` carries the 8-byte ASCII
  short commit from shadow_rs's `SHORT_COMMIT`. Encoded/decoded
  alongside the existing event variants.
* `EventType::Hello` is appended to the enum so existing IDs are
  untouched. Old peers receive the event, hit `InvalidEventId`, and
  silently skip it via the forward-compat handler in
  `connect.rs::receive_loop` — the connection is unaffected.

Daemon
* Connect side sends one Hello immediately after the DTLS handshake
  authenticates and before the ping_pong loop starts. Best-effort,
  fire-and-forget — `log::debug!` on send error.
* Listen side mirrors the peer's Hello with its own (same shape as
  the existing Ping → Pong reply), so the peer's connect-side
  receive_loop populates `ClientState::peer_commit` for that
  handle.
* The disconnect path clears `peer_commit` so a stale hash isn't
  shown after the connection drops.

IPC
* `ClientState::peer_commit: Option<[u8; 8]>`. `None` means the
  peer hasn't sent Hello yet — either fresh connection or older
  build that predates the event.

GTK
* `ClientObject` exposes `peer-commit` as an `Option<String>`
  property; `peer_commit_to_string` converts the wire `[u8; 8]` to
  the displayable hex.
* `lan_mouse_gtk::run` now takes the local commit and stashes it in
  a `OnceLock` so per-row UI can compare against each peer's hash.
* `ClientRow::refresh_version_status` re-renders the collapsed
  subtitle with Pango markup whenever the property changes:
   - matched → green   "peer version: <hex> · matched"
   - mismatch → orange "peer version: <hex> · ours: <hex>"
   - unknown → orange  "peer version: unknown · ours: <hex>"
* Window invokes `refresh_version_status` from
  `update_client_state` after writing the new property, and
  `bind` calls it once on row construction so the initial
  subtitle isn't blank.

Known limitation: state-change broadcasts from the network side
(set_alive / set_active_addr / set_peer_commit) don't currently
trigger a `FrontendEvent::State` directly; the UI picks up the
latest values on the next user-driven broadcast. Same pre-existing
behavior as the alive/active_addr fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-14 18:10:22 +02:00

34 lines
1.2 KiB
Rust

use std::cell::RefCell;
use glib::Properties;
use gtk::glib;
use gtk::prelude::*;
use gtk::subclass::prelude::*;
use lan_mouse_ipc::ClientHandle;
use super::ClientData;
#[derive(Properties, Default)]
#[properties(wrapper_type = super::ClientObject)]
pub struct ClientObject {
#[property(name = "handle", get, set, type = ClientHandle, member = handle)]
#[property(name = "hostname", get, set, type = Option<String>, member = hostname)]
#[property(name = "port", get, set, type = u32, member = port, maximum = u16::MAX as u32)]
#[property(name = "active", get, set, type = bool, member = active)]
#[property(name = "position", get, set, type = String, member = position)]
#[property(name = "resolving", get, set, type = bool, member = resolving)]
#[property(name = "ips", get, set, type = Vec<String>, member = ips)]
#[property(name = "peer-commit", get, set, type = Option<String>, member = peer_commit)]
pub data: RefCell<ClientData>,
}
#[glib::object_subclass]
impl ObjectSubclass for ClientObject {
const NAME: &'static str = "ClientObject";
type Type = super::ClientObject;
}
#[glib::derived_properties]
impl ObjectImpl for ClientObject {}