From 261fdbb8e12485e9f6676a4b89257fc68cc37b36 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:01:25 -0700 Subject: [PATCH 01/52] Keep test runs out of the real BroLink data directory The core config tests saved and deleted files in the user's real ~/Library/Application Support/dev.brolink.BroLink, and every test that built a Service, a HostApp or a ClientApp read that install's host.toml and client.toml (and would have rewritten them had a test changed a setting). data_dir() now honours BROLINK_DATA_DIR, and the workspace's .cargo/config.toml sets it to target/brolink-data for cargo test and cargo run. A shipped binary never sees the variable. Co-Authored-By: Claude Opus 5.5 (1M context) --- .cargo/config.toml | 7 ++++++- crates/core/src/config.rs | 30 ++++++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index d9d33e9..77c4490 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,6 +1,11 @@ -# audiopus_sys ships a libopus whose CMake files predate CMake 4. [env] +# audiopus_sys ships a libopus whose CMake files predate CMake 4. CMAKE_POLICY_VERSION_MINIMUM = "3.5" +# `cargo test` and `cargo run` keep settings, identities and logs under +# target/ rather than in the real per-user data directory, so a test run +# never reads or rewrites an installed BroLink's files. A shipped binary +# does not see this; see `brolink_core::config::data_dir`. +BROLINK_DATA_DIR = { value = "target/brolink-data", relative = true } # The host is a standalone exe. Without this it needs vcruntime140.dll, # which is not inbox on a fresh Windows install. diff --git a/crates/core/src/config.rs b/crates/core/src/config.rs index 95124cf..3999223 100644 --- a/crates/core/src/config.rs +++ b/crates/core/src/config.rs @@ -1,6 +1,6 @@ //! Where each app keeps its few settings: one TOML file in the per-user data //! directory (`%LOCALAPPDATA%\BroLink` on Windows, `~/Library/Application -//! Support/BroLink` on macOS). +//! Support/dev.brolink.BroLink` on macOS, `~/.local/share/brolink` on Linux). use anyhow::{Context, Result}; use serde::{de::DeserializeOwned, Serialize}; @@ -14,8 +14,16 @@ use std::sync::{ static CONFIG_LOCK: Mutex<()> = Mutex::new(()); static TEMP_ID: AtomicU64 = AtomicU64::new(0); +/// The per-user data directory, created private to this user. +/// +/// `BROLINK_DATA_DIR` replaces it when set. The workspace's +/// `.cargo/config.toml` sets it for `cargo test` and `cargo run`, so a test +/// run on a machine with BroLink installed leaves that install's settings, +/// pairing identity and logs alone. pub fn data_dir() -> Result { - let dir = if cfg!(windows) { + let dir = if let Some(dir) = std::env::var_os("BROLINK_DATA_DIR").filter(|d| !d.is_empty()) { + Some(PathBuf::from(dir)) + } else if cfg!(windows) { std::env::var_os("LOCALAPPDATA") .map(PathBuf::from) .map(|p| p.join(crate::APP_NAME)) @@ -164,8 +172,7 @@ mod tests { assert_eq!(load::(&name), Cfg { n: 7 }); std::fs::write(dir.join(&name), "not = [toml").unwrap(); assert_eq!(load::(&name), Cfg::default()); - // Loading junk moves it aside as `.bad-*`. This runs against the - // user's real data directory, so remove what the test left behind. + // Loading junk moves it aside as `.bad-*`, exactly once. let prefix = format!("{stem}.bad-"); let preserved: Vec = std::fs::read_dir(&dir) .unwrap() @@ -216,6 +223,21 @@ mod tests { std::fs::remove_file(path).unwrap(); } + /// `.cargo/config.toml` points `cargo test` at `target/brolink-data`. + /// Without it every test that loads or saves settings would read and + /// rewrite the real install's files on the machine running the tests. + #[test] + fn tests_run_against_a_data_directory_under_target() { + let want = std::env::var_os("BROLINK_DATA_DIR").expect("cargo test sets BROLINK_DATA_DIR"); + let dir = data_dir().unwrap(); + assert_eq!(dir, PathBuf::from(want)); + assert!( + dir.components().any(|c| c.as_os_str() == "target"), + "{}", + dir.display() + ); + } + #[test] fn machine_name_is_never_empty() { assert!(!machine_name().is_empty()); From 9caed05027319c9356c01493c07b8714701b11b9 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:02:59 -0700 Subject: [PATCH 02/52] Answer only the same Tailscale account on the control API again a2f2ebe loosened the rule from "whois says the same user" to "whois knows the peer at all", so the relay VPS (a tagged node, whose own user is tagged-devices) would answer the Mac. That also admitted every machine shared in from another tailnet: it could pair with Sunshine through /v1/pin, sleep or restart the PC, read and write its clipboard, and POST /v1/update with any executable whose SHA-256 it sent alongside. The service now admits the account it is signed in as. A tagged node has no account, so it admits members of its own tailnet: the login that names a personal tailnet, or the users of an organisation's domain. Refusals are cached like approvals, and logged as before 3.x. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 10 ++ crates/core/src/api.rs | 6 + crates/core/src/tailscale.rs | 20 ++++ crates/host/src/service.rs | 209 ++++++++++++++++++++++++++++------- 4 files changed, 203 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b8f0f2..f2ce660 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ The release workflow publishes the section that matches the tag as the GitHub release notes, so each version gets a heading of the form `## X.Y.Z (date)`. +## Unreleased + +- The control service again answers only machines signed in to the same + Tailscale account. Since 3.x it answered any machine Tailscale could + name, so a machine shared in from someone else's tailnet could pair with + the PC, sleep or restart it, read and write its clipboard, and push it a + new `brolink-host.exe`. A tagged server such as the relay VPS belongs to + no account, so it answers the members of its own tailnet, which keeps a + Mac able to open the VPS desktop. + ## 4.0.2 (2026-09-22) - `panel.log` and `service.log` no longer grow without end. Both were diff --git a/crates/core/src/api.rs b/crates/core/src/api.rs index 678e7d7..a071f37 100644 --- a/crates/core/src/api.rs +++ b/crates/core/src/api.rs @@ -13,6 +13,12 @@ //! | POST | /v1/update | `brolink-host.exe` bytes | [`Ack`] | //! | GET | /v1/clipboard | | [`Clipboard`] | //! | POST | /v1/clipboard | [`Clipboard`] | [`Ack`] | +//! | GET | /v1/display | | display report (JSON) | +//! | POST | /v1/display | [`DisplayRequest`] | display report (JSON) | +//! +//! Only machines signed in to the same Tailscale account get an answer +//! (a tagged server answers its tailnet's members); see the host's +//! `service` module. //! //! `/v1/update` carries the new executable itself, with its version in //! [`UPDATE_VERSION_HEADER`] and its SHA-256 in [`UPDATE_SHA256_HEADER`]. A diff --git a/crates/core/src/tailscale.rs b/crates/core/src/tailscale.rs index 8af6501..10e9279 100644 --- a/crates/core/src/tailscale.rs +++ b/crates/core/src/tailscale.rs @@ -68,6 +68,16 @@ pub struct Status { pub self_node: Node, pub peer: BTreeMap, pub user: BTreeMap, + /// The tailnet this machine is in. Absent before Tailscale 1.26. + pub current_tailnet: Option, +} + +#[derive(Debug, Clone, Default, Deserialize)] +#[serde(default, rename_all = "PascalCase")] +pub struct Tailnet { + /// The owner's login for a personal tailnet ("ada@example.com"), the + /// domain for an organisation's ("example.com"). + pub name: String, } #[derive(Debug, Clone, Default, Deserialize, PartialEq)] @@ -714,6 +724,16 @@ mod tests { assert_eq!(output.len(), 1048576); } + #[test] + fn the_tailnet_name_is_read_when_present() { + let st = parse_status( + r#"{"BackendState":"Running","CurrentTailnet":{"Name":"user@example.com","MagicDNSSuffix":"tail0.ts.net"}}"#, + ) + .unwrap(); + assert_eq!(st.current_tailnet.unwrap().name, "user@example.com"); + assert!(parse_status(SAMPLE).unwrap().current_tailnet.is_none()); + } + #[test] fn whois_is_parsed() { let w: WhoIs = serde_json::from_str( diff --git a/crates/host/src/service.rs b/crates/host/src/service.rs index 4c0a2b0..5f73566 100644 --- a/crates/host/src/service.rs +++ b/crates/host/src/service.rs @@ -1,8 +1,13 @@ //! The background control service: what a Mac on the tailnet talks to. //! //! One TCP listener on every interface, one rule for who gets an answer: -//! loopback (the host's own control panel) or a tailnet address that -//! `tailscale whois` recognises. Everyone else gets a 403 and nothing more. +//! loopback (the host's own control panel), or a tailnet machine that +//! `tailscale whois` says belongs to the account this one is signed in as. +//! A tagged machine (the relay VPS) belongs to no account, so it answers +//! the members of its own tailnet. Everyone else gets a 403 and nothing +//! more: a machine shared in from someone else's tailnet can reach the +//! port, but may not pair, sleep the PC, read its clipboard or push it an +//! executable. use crate::clipboard; use crate::config::HostConfig; @@ -43,7 +48,8 @@ pub struct Service { install: Mutex>, streamer: Mutex, log: Mutex>, - auth: Mutex)>>, + /// `whois` answers for recent peers: when, and whether they may ask. + auth: Mutex>, /// This PC's side of the NAT story, from `tailscale netcheck`. nat: Mutex>, nat_running: AtomicBool, @@ -469,10 +475,9 @@ impl Service { } } - /// Loopback is the control panel. A tailnet peer is anyone `whois` - /// recognises: user machines and `tag:relay` nodes share a tailnet but - /// not a Tailscale user id, and a VPS desktop on the relay box has to - /// accept the Macs that found it. + /// Loopback is the control panel; a tailnet peer must pass + /// [`Owner::admits`]. A `whois` answer is remembered for [`AUTH_TTL`], + /// a refusal included; a failed `whois` is not remembered. fn authorized(&self, ip: IpAddr) -> bool { if ip.is_loopback() { return true; @@ -480,50 +485,54 @@ impl Service { if !is_tailnet_ip(ip) { return false; } - if self.tailscale.lock().is_err() { - return false; - } let now = Instant::now(); let cached = self .auth .lock() .get(&ip) .filter(|(at, _)| now - *at < AUTH_TTL) - .map(|(_, u)| *u); - let user = match cached { - Some(u) => u, - None => { - let u = match tailscale::whois(ip) { - Ok(w) => { - self.log(format!( - "{} ({}) asked", - w.node.computed_name, w.user_profile.login_name - )); - Some(w.user_profile.id) - } - Err(e) => { - self.log(format!("whois {ip}: {e}")); - None - } - }; - let mut cache = self.auth.lock(); - cache.retain(|_, (at, _)| at.elapsed() < AUTH_TTL); - if cache.len() >= AUTH_CACHE_LIMIT { - if let Some(oldest) = cache - .iter() - .min_by_key(|(_, (at, _))| *at) - .map(|(ip, _)| *ip) - { - cache.remove(&oldest); + .map(|(_, allowed)| *allowed); + if let Some(allowed) = cached { + return allowed; + } + // Copied out so the lock is not held across the `whois` call. + let owner = match &*self.tailscale.lock() { + Ok(st) => Owner::of(st), + Err(_) => return false, + }; + let allowed = match tailscale::whois(ip) { + Ok(w) => { + let allowed = owner.admits(&w); + self.log(format!( + "{} ({}) asked{}", + w.node.computed_name, + w.user_profile.login_name, + if allowed { + "" + } else { + ": not this account, refused" } - } - if u.is_some() { - cache.insert(ip, (Instant::now(), u)); - } - u + )); + allowed + } + Err(e) => { + self.log(format!("whois {ip}: {e}")); + return false; } }; - user.is_some() + let mut cache = self.auth.lock(); + cache.retain(|_, (at, _)| at.elapsed() < AUTH_TTL); + if cache.len() >= AUTH_CACHE_LIMIT { + if let Some(oldest) = cache + .iter() + .min_by_key(|(_, (at, _))| *at) + .map(|(ip, _)| *ip) + { + cache.remove(&oldest); + } + } + cache.insert(ip, (Instant::now(), allowed)); + allowed } fn handle(&self, peer: SocketAddr, req: &Request) -> Response { @@ -781,6 +790,56 @@ pub fn describe_nat(n: &NatReport) -> String { } } +/// Whose machine this is, for deciding who may use the control API. +#[derive(Debug, Clone, PartialEq)] +struct Owner { + /// The Tailscale user this machine is signed in as; for a tagged + /// machine, Tailscale's shared "tagged-devices" user. + user: u64, + /// The machine carries ACL tags, so no person owns it. + tagged: bool, + /// `CurrentTailnet.Name`: the owner's login for a personal tailnet, + /// the domain for an organisation's. + tailnet: String, +} + +impl Owner { + fn of(st: &tailscale::Status) -> Self { + Self { + user: st.self_node.user_id, + tagged: !st.self_node.tags.is_empty(), + tailnet: st + .current_tailnet + .as_ref() + .map(|t| t.name.clone()) + .unwrap_or_default(), + } + } + + /// The same account may ask. A tagged machine has no account of its + /// own, so it takes the members of its tailnet: the owner of a + /// personal tailnet, the users of an organisation's domain. A machine + /// shared in from another tailnet is neither, even though `whois` + /// knows it and the tailnet's rules let it reach this port. + fn admits(&self, peer: &tailscale::WhoIs) -> bool { + let user = peer.user_profile.id; + if user == 0 { + return false; + } + if user == self.user { + return true; + } + if !self.tagged || self.tailnet.is_empty() { + return false; + } + let login = peer.user_profile.login_name.as_str(); + login.eq_ignore_ascii_case(&self.tailnet) + || login + .rsplit_once('@') + .is_some_and(|(_, domain)| domain.eq_ignore_ascii_case(&self.tailnet)) + } +} + /// True when a service answers on loopback. pub fn service_alive() -> bool { http::request( @@ -882,6 +941,72 @@ mod tests { assert!(!is_tailnet_ip("2001:4860:4860::8888".parse().unwrap())); } + fn whois(id: u64, login: &str) -> tailscale::WhoIs { + tailscale::WhoIs { + user_profile: tailscale::User { + id, + login_name: login.into(), + ..Default::default() + }, + ..Default::default() + } + } + + /// The shapes of a real tailnet: a PC and a Mac signed in as one + /// person, a relay VPS tagged `tag:relay`, and a friend's machine + /// shared in from their own tailnet. + #[test] + fn only_the_account_or_for_a_tagged_machine_its_tailnet_may_ask() { + const ADA: u64 = 21; + const TAGGED: u64 = 18; + let status = |user: u64, tags: &str| { + tailscale::parse_status(&format!( + r#"{{"BackendState":"Running","Self":{{"UserID":{user},"Tags":[{tags}]}}, + "CurrentTailnet":{{"Name":"ada@example.com"}}}}"# + )) + .unwrap() + }; + let mac = whois(ADA, "ada@example.com"); + let relay = whois(TAGGED, "tagged-devices"); + let friend = whois(77, "bob@example.net"); + + let pc = Owner::of(&status(ADA, "")); + assert!(!pc.tagged); + assert!(pc.admits(&mac), "the owner's Mac"); + assert!( + !pc.admits(&friend), + "a shared-in machine may not pair, sleep or update the PC" + ); + assert!( + !pc.admits(&relay), + "an internet-facing relay may not push the PC an executable" + ); + assert!(!pc.admits(&whois(0, "")), "whois without a user"); + + let vps = Owner::of(&status(TAGGED, r#""tag:relay""#)); + assert!(vps.tagged); + assert!(vps.admits(&mac), "a tagged VPS serves its tailnet's owner"); + assert!( + vps.admits(&relay), + "and machines tagged in the same tailnet" + ); + assert!(!vps.admits(&friend)); + + // An organisation's tailnet is named after its domain. + let org = Owner { + tailnet: "example.com".into(), + ..vps.clone() + }; + assert!(org.admits(&whois(5, "ada@EXAMPLE.com"))); + assert!(!org.admits(&whois(6, "eve@example.com.evil"))); + // Without a tailnet name a tagged machine takes nobody but its kind. + let unnamed = Owner { + tailnet: String::new(), + ..vps + }; + assert!(!unnamed.admits(&mac)); + } + #[test] fn browsers_and_rebound_hostnames_cannot_read_or_write_the_api() { let svc = Service::new(); From bb18995bd17da425a216e17839a08d1e3bd95551 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:03:49 -0700 Subject: [PATCH 03/52] Carry copies from Mac and Linux hosts to the viewer The viewer tells a new copy on the host from an old one by the clipboard's sequence number, and only Windows has one: elsewhere read() reported 0 every time, so after the first poll no copy on a Mac or on the Linux VPS desktop ever reached the viewer. The sequence number is now derived from the text itself. The host also opened and dropped an arboard::Clipboard for every read and write. On X11 the selection belongs to that handle, and dropping the last one discards the text unless a clipboard manager takes it within 100 ms, so text pasted in from the viewer could be gone before the desktop pasted it. One handle now lives for the whole process. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 8 +++++- crates/host/src/clipboard.rs | 55 ++++++++++++++++++++++++++++++++---- 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2ce660..54f7e42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,18 @@ GitHub release notes, so each version gets a heading of the form ## Unreleased - The control service again answers only machines signed in to the same - Tailscale account. Since 3.x it answered any machine Tailscale could + Tailscale account. Since 4.0.0 it answered any machine Tailscale could name, so a machine shared in from someone else's tailnet could pair with the PC, sleep or restart it, read and write its clipboard, and push it a new `brolink-host.exe`. A tagged server such as the relay VPS belongs to no account, so it answers the members of its own tailnet, which keeps a Mac able to open the VPS desktop. +- Text copied on a Mac or Linux machine you are streaming from now reaches + the machine you are watching on. Only Windows reported a clipboard + sequence number; everywhere else it was always 0, so the viewer took + every copy for the one it had already seen. On a Linux desktop, text + pasted in from the viewer also stays on the clipboard now instead of + vanishing when no clipboard manager picks it up. ## 4.0.2 (2026-09-22) diff --git a/crates/host/src/clipboard.rs b/crates/host/src/clipboard.rs index e138cca..abc52c1 100644 --- a/crates/host/src/clipboard.rs +++ b/crates/host/src/clipboard.rs @@ -111,18 +111,54 @@ unsafe fn open() -> Result<()> { ) } +/// One clipboard handle for the life of the process. On X11 the text +/// belongs to the `arboard::Clipboard` that set it: when the last one is +/// dropped the text goes to a clipboard manager if one takes it within +/// 100 ms, and is otherwise gone before anything on the desktop can paste +/// it. A handle whose connection failed is replaced on the next call. +#[cfg(not(windows))] +fn with_clipboard( + f: impl FnOnce(&mut arboard::Clipboard) -> Result, +) -> Result { + static CLIPBOARD: parking_lot::Mutex> = + parking_lot::Mutex::new(None); + let mut slot = CLIPBOARD.lock(); + let clipboard = match slot.as_mut() { + Some(c) => c, + None => slot.insert(arboard::Clipboard::new()?), + }; + let result = f(clipboard); + if result.is_err() { + *slot = None; + } + Ok(result?) +} + #[cfg(not(windows))] pub fn read() -> Result { - let mut clip = arboard::Clipboard::new()?; - let text = clip.get_text().unwrap_or_default(); - Ok(Clipboard::fit(&text, 0)) + let text = with_clipboard(|c| match c.get_text() { + // No text on the clipboard (an image, or nothing) reads as empty. + Err(arboard::Error::ContentNotAvailable) => Ok(String::new()), + other => other, + })?; + Ok(Clipboard::fit(&text, text_seq(&text))) } #[cfg(not(windows))] pub fn write(text: &str) -> Result<()> { - let mut clip = arboard::Clipboard::new()?; - clip.set_text(text.to_string())?; - Ok(()) + with_clipboard(|c| c.set_text(text.to_string())) +} + +/// macOS and X11 have no sequence number a program can read, so the text +/// stands in for one: it changes whenever different text is copied, which +/// is all a reader needs to tell a new copy from one it has seen. A fixed +/// `seq` made every copy on a Mac or Linux desktop look like the last. +#[cfg_attr(windows, allow(dead_code))] +fn text_seq(text: &str) -> u64 { + use std::hash::{Hash, Hasher}; + let mut h = std::collections::hash_map::DefaultHasher::new(); + text.hash(&mut h); + h.finish() } #[cfg(test)] @@ -137,4 +173,11 @@ mod tests { assert_eq!(from_windows(&to_windows("x\ny\n")), "x\ny\n"); assert_eq!(to_windows(""), ""); } + + #[test] + fn a_new_copy_gets_a_new_sequence_number() { + assert_eq!(text_seq("hello"), text_seq("hello")); + assert_ne!(text_seq("hello"), text_seq("hello!")); + assert_ne!(text_seq(""), text_seq(" ")); + } } From 928a33b80c596ca4e76bce416e080cba96f27103 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:04:23 -0700 Subject: [PATCH 04/52] Keep the old host when a pushed update dies on start apply() only rolled back when Windows refused to create the new process at all. A replacement that started and then crashed (panic = "abort", a missing DLL) left the PC with the new file in place and no service: the old one exits right after apply() returns, and nothing starts the new one again until the next logon, which on an unattended PC is never. The new copy waits for the old one's port, so a healthy one is still running a moment after launch. apply() now waits two seconds and, if the child has already exited, moves it aside and restores the old executable while the old service keeps serving. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 5 ++++ crates/host/src/update.rs | 53 ++++++++++++++++++++++++++++++++++----- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54f7e42..0fb3414 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,11 @@ GitHub release notes, so each version gets a heading of the form every copy for the one it had already seen. On a Linux desktop, text pasted in from the viewer also stays on the clipboard now instead of vanishing when no clipboard manager picks it up. +- A Windows PC whose pushed update crashes on start keeps the version it + had. The service used to exit as soon as the new executable had been + launched, so a replacement that died straight away left the PC with no + BroLink service until someone logged in again. It now waits two seconds, + and puts the previous executable back if the new one has already exited. ## 4.0.2 (2026-09-22) diff --git a/crates/host/src/update.rs b/crates/host/src/update.rs index 6ff6912..d113cd7 100644 --- a/crates/host/src/update.rs +++ b/crates/host/src/update.rs @@ -8,6 +8,8 @@ //! Windows lets a running executable be renamed but not overwritten. The new //! exe is started with `--replaces `, which makes it wait for the old //! service to let go of the port, and the old one exits after answering. +//! A new exe that cannot start, or exits at once, is moved aside and the +//! old one put back, still serving. use anyhow::{Context, Result}; use brolink_core::api::{UPDATE_MAX_BYTES, UPDATE_SHA256_HEADER, UPDATE_VERSION_HEADER}; @@ -20,6 +22,10 @@ use std::time::Duration; /// Anything smaller is not the host. const MIN_SIZE: usize = 1024 * 1024; +/// How long a new copy must stay up before the old one lets go. It waits +/// for the old one's port, so it is still running after this unless it +/// could not start at all. +const STARTUP_GRACE: Duration = Duration::from_secs(2); /// Why an upload was not staged, with the HTTP status it earns. #[derive(Debug, PartialEq, Eq)] @@ -134,6 +140,10 @@ fn is_host_executable(bytes: &[u8]) -> bool { /// Swap the staged executable in and start it. The caller exits afterwards; /// the new process waits for that before it takes the port. pub fn apply(exe: &Path) -> Result<()> { + apply_within(exe, STARTUP_GRACE) +} + +fn apply_within(exe: &Path, grace: Duration) -> Result<()> { let new = staged(exe); anyhow::ensure!(new.exists(), "nothing is staged at {}", new.display()); let old = { @@ -152,6 +162,14 @@ pub fn apply(exe: &Path) -> Result<()> { rename_retry(&old, exe).context("restore the previous executable after a failed swap")?; return Err(e).context("move the new executable in"); } + // Keep the old service installed if the replacement cannot run: this + // process is still serving requests, and a PC nobody can reach in + // person must not be left with a service that never starts. + let restore = |why: anyhow::Error| -> Result<()> { + rename_retry(exe, &new).context("move the failed replacement aside")?; + rename_retry(&old, exe).context("restore the previous executable")?; + Err(why.context("the previous executable is back in place")) + }; let mut c = std::process::Command::new(exe); c.arg("--background") .arg("--replaces") @@ -164,12 +182,15 @@ pub fn apply(exe: &Path) -> Result<()> { use std::os::windows::process::CommandExt; c.creation_flags(0x0000_0008 | 0x0800_0000); // DETACHED_PROCESS | CREATE_NO_WINDOW } - if let Err(e) = c.spawn() { - // Keep the old service installed if Windows cannot start the - // replacement. The current process is still serving requests. - rename_retry(exe, &new).context("move failed replacement aside")?; - rename_retry(&old, exe).context("restore previous executable after launch failure")?; - return Err(e).context("start the new service (previous executable restored)"); + let mut child = match c.spawn() { + Ok(child) => child, + Err(e) => return restore(anyhow::Error::new(e).context("start the new service")), + }; + // One that has already exited could not start (a crash, a missing + // library): it would leave the PC with no service after this one quits. + std::thread::sleep(grace); + if let Ok(Some(status)) = child.try_wait() { + return restore(anyhow::anyhow!("the new service exited at once ({status})")); } Ok(()) } @@ -365,6 +386,26 @@ mod tests { assert!(apply(&exe).is_err()); assert_eq!(std::fs::read(&exe).unwrap(), b"old"); assert_eq!(std::fs::read(staged(&exe)).unwrap(), b"new"); + // A replacement that starts and dies at once is put back too. + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let script = |body: &str| { + std::fs::write(staged(&exe), body).unwrap(); + std::fs::set_permissions(staged(&exe), std::fs::Permissions::from_mode(0o755)) + .unwrap(); + }; + script("#!/bin/sh\nexit 3\n"); + let e = apply_within(&exe, Duration::from_millis(500)).unwrap_err(); + assert!(format!("{e:#}").contains("exited at once"), "{e:#}"); + assert_eq!(std::fs::read(&exe).unwrap(), b"old"); + assert!(staged(&exe).exists()); + // One that keeps running is the new service. + script("#!/bin/sh\nexec sleep 2\n"); + apply_within(&exe, Duration::from_millis(300)).unwrap(); + assert!(std::fs::read(&exe).unwrap().starts_with(b"#!/bin/sh")); + assert_eq!(std::fs::read(retired(&exe)).unwrap(), b"old"); + } std::fs::write(retired(&exe), b"retired").unwrap(); std::fs::write(dir.join("brolink-host.exe.old-notes"), b"keep").unwrap(); tidy(&exe); From cdd9842e52d136c0702f3014f3f2b42c83f0fae7 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:05:03 -0700 Subject: [PATCH 05/52] Try each resolved address when connecting, not only the first http::request, request_with and the GitHub fetch all connected to the first address a name resolved to and gave up if it failed. GitHub's download hosts resolve to several addresses, and a network with an IPv6 address but no IPv6 route lists an unusable one first. A shared connect() now walks the list. request() also goes through request_with() instead of duplicating it, and takes its Host header from the connected peer. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 3 +++ crates/core/src/http.rs | 56 +++++++++++++++++++++++++++------------ crates/core/src/update.rs | 10 ++----- 3 files changed, 44 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fb3414..7f1dfc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,9 @@ GitHub release notes, so each version gets a heading of the form launched, so a replacement that died straight away left the PC with no BroLink service until someone logged in again. It now waits two seconds, and puts the previous executable back if the new one has already exited. +- Update downloads try each of a server's addresses in turn. Only the first + one the name resolved to was ever tried, so one unreachable address (an + IPv6 one on a network without IPv6, say) failed the whole update check. ## 4.0.2 (2026-09-22) diff --git a/crates/core/src/http.rs b/crates/core/src/http.rs index 6048b13..436eb3a 100644 --- a/crates/core/src/http.rs +++ b/crates/core/src/http.rs @@ -267,6 +267,20 @@ impl Drop for ConnectionSlot { } } +/// A connection to the first of `addr`'s addresses that accepts one, each +/// given `timeout`. A name can resolve to an IPv6 address first that this +/// network has no route to, ahead of an IPv4 one that works. +pub(crate) fn connect(addr: impl ToSocketAddrs, timeout: Duration) -> Result { + let mut last = None; + for a in addr.to_socket_addrs()? { + match TcpStream::connect_timeout(&a, timeout) { + Ok(s) => return Ok(s), + Err(e) => last = Some(e), + } + } + Err(last.map(io_err).unwrap_or_else(|| anyhow!("no address"))) +} + /// One request, one reply, within `timeout` for connect and for the read. pub fn request( addr: impl ToSocketAddrs, @@ -275,19 +289,13 @@ pub fn request( body: Option<&str>, timeout: Duration, ) -> Result { - let addr = addr - .to_socket_addrs()? - .next() - .ok_or_else(|| anyhow!("no address"))?; - let mut stream = TcpStream::connect_timeout(&addr, timeout).map_err(io_err)?; - stream.set_read_timeout(Some(timeout)).map_err(io_err)?; - stream.set_write_timeout(Some(timeout)).map_err(io_err)?; - exchange( - &mut stream, + request_with( + addr, method, path, - &addr.to_string(), - body.unwrap_or(""), + &[("Content-Type", "application/json")], + body.unwrap_or("").as_bytes(), + timeout, ) } @@ -300,14 +308,11 @@ pub fn request_with( body: &[u8], timeout: Duration, ) -> Result { - let addr = addr - .to_socket_addrs()? - .next() - .ok_or_else(|| anyhow!("no address"))?; - let mut stream = TcpStream::connect_timeout(&addr, timeout).map_err(io_err)?; + let mut stream = connect(addr, timeout)?; stream.set_read_timeout(Some(timeout)).map_err(io_err)?; stream.set_write_timeout(Some(timeout)).map_err(io_err)?; - exchange_with(&mut stream, method, path, &addr.to_string(), headers, body) + let host = stream.peer_addr().map_err(io_err)?.to_string(); + exchange_with(&mut stream, method, path, &host, headers, body) } /// One HTTP/1.1 request with a JSON body and its reply over any stream (TCP, @@ -669,6 +674,23 @@ mod tests { } } + #[test] + fn an_address_that_refuses_is_skipped_for_the_next_one() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let open = listener.local_addr().unwrap(); + let closed = TcpListener::bind("127.0.0.1:0") + .unwrap() + .local_addr() + .unwrap(); + std::thread::spawn(move || serve(listener, |_, _| Response::json(200, &Ack::ok()))); + let t = Duration::from_secs(2); + let r = request(&[closed, open][..], "GET", "/", None, t).unwrap(); + assert_eq!(r.status, 200, "{}", r.body); + let e = request(closed, "GET", "/", None, t).unwrap_err(); + assert!(!e.to_string().is_empty()); + assert!(connect(&[][..] as &[SocketAddr], t).is_err()); + } + #[test] fn garbage_is_refused_not_panicked() { let listener = TcpListener::bind("127.0.0.1:0").unwrap(); diff --git a/crates/core/src/update.rs b/crates/core/src/update.rs index 023c85d..8899a1b 100644 --- a/crates/core/src/update.rs +++ b/crates/core/src/update.rs @@ -14,7 +14,7 @@ use semver::Version; use serde::Deserialize; use sha2::{Digest, Sha256}; use std::io::{BufReader, Write}; -use std::net::{TcpStream, ToSocketAddrs}; +use std::net::TcpStream; use std::path::Path; use std::sync::Arc; use std::time::Duration; @@ -370,13 +370,7 @@ fn split_url(url: &str) -> Result<(String, String)> { } fn connect(host: &str) -> Result> { - let addr = (host, 443u16) - .to_socket_addrs() - .with_context(|| format!("resolve {host}"))? - .next() - .ok_or_else(|| anyhow!("{host} has no address"))?; - let tcp = TcpStream::connect_timeout(&addr, TIMEOUT) - .map_err(crate::http::io_err) + let tcp = crate::http::connect((host, 443u16), TIMEOUT) .with_context(|| format!("connect to {host}"))?; tcp.set_read_timeout(Some(TIMEOUT)) .map_err(crate::http::io_err)?; From 5784ac1b1f2aaa15326c816d1214a74a5694e7b3 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:05:12 -0700 Subject: [PATCH 06/52] Name the right licence in the Windows executable's version block build.rs wrote "MIT license" into brolink-host.exe's LegalCopyright string. BroLink is GPL-3.0-or-later, as is the moonlight-common-c it links, and that string is what Explorer, Task Manager and installers show. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 3 +++ crates/host/build.rs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f1dfc8..c626557 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,9 @@ GitHub release notes, so each version gets a heading of the form - Update downloads try each of a server's addresses in turn. Only the first one the name resolved to was ever tried, so one unreachable address (an IPv6 one on a network without IPv6, say) failed the whole update check. +- `brolink-host.exe`'s version details (Explorer's Details tab) named the + wrong licence, MIT; they now say GPL-3.0-or-later, the licence BroLink + and moonlight-common-c are under. ## 4.0.2 (2026-09-22) diff --git a/crates/host/build.rs b/crates/host/build.rs index a19949d..4ccbd10 100644 --- a/crates/host/build.rs +++ b/crates/host/build.rs @@ -23,7 +23,7 @@ fn main() { .set("CompanyName", "BroLink Contributors") .set( "LegalCopyright", - "Copyright (c) 2026 BroLink Contributors, MIT license", + "Copyright (c) 2026 BroLink Contributors. GPL-3.0-or-later", ) .set("OriginalFilename", "brolink-host.exe"); if let Err(e) = res.compile() { From f907166aecf270ee709dd27f5ce042f5b4b494b7 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:05:42 -0700 Subject: [PATCH 07/52] Stop running a program for every status request on Mac and Linux MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Service::status() asked machine_name() for the name on every request, and off Windows that runs `scutil --get ComputerName` or `hostname`. The window polls /v1/status every second and every other machine's lobby every three, so the service spawned a process one to two times a second for a name that almost never changes. It is now read at start and then once a minute with the other slow probes. status() went from 3.4 ms to 0.65 µs a call on this Mac (200 calls, test build). Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/host/src/service.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/host/src/service.rs b/crates/host/src/service.rs index 5f73566..4d0d8d3 100644 --- a/crates/host/src/service.rs +++ b/crates/host/src/service.rs @@ -38,6 +38,9 @@ const NETCHECK_TICKS: u64 = 120; pub struct Service { cfg: Mutex, + /// This machine's name, re-read now and then: asking macOS or Linux + /// runs a program, which every status request used to do. + name: Mutex, tailscale: Mutex>, wake: Mutex, /// When a magic packet for this PC last arrived. @@ -60,6 +63,7 @@ impl Service { pub fn new() -> Self { Self { cfg: Mutex::new(HostConfig::load()), + name: Mutex::new(brolink_core::config::machine_name()), tailscale: Mutex::new(Err("not checked yet".into())), wake: Mutex::new(WakeInfo::default()), wake_seen: Mutex::new(None), @@ -330,6 +334,7 @@ impl Service { } if tick.is_multiple_of(12) { + *self.name.lock() = brolink_core::config::machine_name(); let w = wake::probe(); let mut cur = self.wake.lock(); if *cur != w { @@ -430,7 +435,7 @@ impl Service { Status { app: "brolink".into(), version: env!("CARGO_PKG_VERSION").into(), - name: brolink_core::config::machine_name(), + name: self.name.lock().clone(), os: ts .as_ref() .ok() From 8abc71bbff6a64f5ab80ae445df448d5f56a0006 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:06:50 -0700 Subject: [PATCH 08/52] Make VideoToolbox's frame callback unable to outlive its target decode() hands VideoToolbox a pointer to a Decoded on its own stack as the frame refcon, and the output callback writes the picture through it. That is only sound while every callback for the frame runs before decode() returns. Synchronous decode does call it before returning, but a decoder is allowed to hold a frame back and emit it during a later call, which would write through a stack frame that no longer exists. decode() now calls VTDecompressionSessionWaitForAsynchronousFrames after a successful submit, as FFmpeg's VideoToolbox hwaccel does. Also drops what was never used: VideoToolbox::full_range (always false), the width and height every decoder constructor ignored, and OpenH264's intermediate chroma buffer, which cost a copy of the UV plane per frame on Windows and Linux. nal_units and interleave_uv are private to the platform that uses them. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/stream/src/session.rs | 2 +- crates/stream/src/video.rs | 13 ++++++------ crates/stream/src/video/openh264.rs | 6 +----- crates/stream/src/video/videotoolbox.rs | 28 +++++++++++++++---------- 4 files changed, 25 insertions(+), 24 deletions(-) diff --git a/crates/stream/src/session.rs b/crates/stream/src/session.rs index ff45363..dd7b601 100644 --- a/crates/stream/src/session.rs +++ b/crates/stream/src/session.rs @@ -546,7 +546,7 @@ unsafe extern "C" fn video_setup( _fps: c_int, ) -> c_int { let Some(inner) = current() else { return -1 }; - match video::new_decoder(format, w as u32, h as u32) { + match video::new_decoder(format) { Ok(d) => { let mut st = inner.stats.lock(); st.width = w as u32; diff --git a/crates/stream/src/video.rs b/crates/stream/src/video.rs index f4c85b9..ef44e2b 100644 --- a/crates/stream/src/video.rs +++ b/crates/stream/src/video.rs @@ -170,22 +170,20 @@ pub fn capabilities() -> i32 { } } -pub fn new_decoder(format: i32, width: u32, height: u32) -> Result> { +pub fn new_decoder(format: i32) -> Result> { #[cfg(target_os = "macos")] { - Ok(Box::new(videotoolbox::VideoToolbox::new( - format, width, height, - )?)) + Ok(Box::new(videotoolbox::VideoToolbox::new(format)?)) } #[cfg(not(target_os = "macos"))] { - let _ = (width, height); Ok(Box::new(openh264::OpenH264::new(format)?)) } } /// Split an Annex B byte stream into NAL units without their start codes. -pub fn nal_units(data: &[u8]) -> Vec<&[u8]> { +#[cfg(any(target_os = "macos", test))] +fn nal_units(data: &[u8]) -> Vec<&[u8]> { let mut starts = Vec::new(); let mut i = 0; while i + 3 <= data.len() { @@ -211,7 +209,8 @@ pub fn nal_units(data: &[u8]) -> Vec<&[u8]> { } /// Interleave I420 chroma planes into one NV12 UV plane. -pub fn interleave_uv( +#[cfg(any(not(target_os = "macos"), test))] +fn interleave_uv( u: &[u8], v: &[u8], stride: usize, diff --git a/crates/stream/src/video/openh264.rs b/crates/stream/src/video/openh264.rs index 10a4ccb..c5941a5 100644 --- a/crates/stream/src/video/openh264.rs +++ b/crates/stream/src/video/openh264.rs @@ -7,7 +7,6 @@ use openh264::formats::YUVSource; pub struct OpenH264 { dec: openh264::decoder::Decoder, - uv: Vec, } impl OpenH264 { @@ -17,7 +16,6 @@ impl OpenH264 { } Ok(Self { dec: openh264::decoder::Decoder::new()?, - uv: Vec::new(), }) } } @@ -30,7 +28,6 @@ impl Decoder for OpenH264 { let (w, h) = yuv.dimensions(); let (ys, us, _) = yuv.strides(); let (cw, ch) = (w.div_ceil(2), h.div_ceil(2)); - interleave_uv(yuv.u(), yuv.v(), us, cw, ch, &mut self.uv); let mut frame = spare.unwrap_or_default(); frame.width = w as u32; frame.height = h as u32; @@ -41,8 +38,7 @@ impl Decoder for OpenH264 { for row in yuv.y().chunks(ys).take(h) { frame.y.extend_from_slice(&row[..w]); } - frame.uv.clear(); - frame.uv.extend_from_slice(&self.uv); + interleave_uv(yuv.u(), yuv.v(), us, cw, ch, &mut frame.uv); Ok(Some(frame)) } diff --git a/crates/stream/src/video/videotoolbox.rs b/crates/stream/src/video/videotoolbox.rs index 60d68d9..0e7934b 100644 --- a/crates/stream/src/video/videotoolbox.rs +++ b/crates/stream/src/video/videotoolbox.rs @@ -3,8 +3,9 @@ //! //! Each access unit arrives in Annex B. Parameter sets go into a format //! description; the slices are re-framed with 4-byte lengths (AVCC/HVCC) into -//! one sample buffer and decoded synchronously, so the output callback runs -//! on this thread before `decode` returns. +//! one sample buffer and decoded synchronously; `decode` also waits out any +//! frame the decoder held back, so the output callback has always run, on +//! this thread, before `decode` returns. use super::{nal_units, Decoder, Frame}; use anyhow::{anyhow, bail, Result}; @@ -139,6 +140,9 @@ extern "C" { frame_refcon: *mut c_void, info_flags_out: *mut u32, ) -> OSStatus; + fn VTDecompressionSessionWaitForAsynchronousFrames( + session: VTDecompressionSessionRef, + ) -> OSStatus; fn VTDecompressionSessionInvalidate(session: VTDecompressionSessionRef); fn VTDecompressionSessionCanAcceptFormatDescription( session: VTDecompressionSessionRef, @@ -157,7 +161,6 @@ extern "C" { pub struct VideoToolbox { hevc: bool, - full_range: bool, hardware: bool, format: CMFormatDescriptionRef, session: VTDecompressionSessionRef, @@ -169,14 +172,13 @@ pub struct VideoToolbox { unsafe impl Send for VideoToolbox {} impl VideoToolbox { - pub fn new(format: i32, _width: u32, _height: u32) -> Result { + pub fn new(format: i32) -> Result { let hevc = format & crate::ffi::VIDEO_FORMAT_MASK_H265 != 0; if !hevc && format & crate::ffi::VIDEO_FORMAT_MASK_H264 == 0 { bail!("unsupported video format {format:#x}"); } Ok(Self { hevc, - full_range: false, hardware: false, format: ptr::null(), session: ptr::null(), @@ -263,11 +265,8 @@ impl VideoToolbox { fn create_session(&mut self) -> Result<()> { unsafe { - let pixel_format: i32 = if self.full_range { - K_CV_PIXEL_FORMAT_420F - } else { - K_CV_PIXEL_FORMAT_420V - } as i32; + // The stream is limited range (`COLOR_RANGE_LIMITED`). + let pixel_format = K_CV_PIXEL_FORMAT_420V as i32; let number = CFNumberCreate( ptr::null(), K_CF_NUMBER_SINT32, @@ -495,6 +494,13 @@ impl Decoder for VideoToolbox { ptr::null_mut(), ); CFRelease(sample); + // `decoded` lives on this stack frame and the callback writes + // through it. Synchronous decode calls it before returning, but + // a decoder may still hold a frame back; wait it out here so + // the callback can never run after this frame is gone. + if status == 0 { + VTDecompressionSessionWaitForAsynchronousFrames(self.session); + } let status = if status != 0 { status } else { decoded.status }; if status != 0 { // -12903 kVTInvalidSessionErr, -12911 kVTVideoDecoderMalfunctionErr @@ -531,7 +537,7 @@ mod tests { fn decodes_visible_pixels_from_h264() { // 64x64, white top half and black bottom half, generated with OpenH264. let encoded = include_bytes!("../../tests/fixtures/gray-bars.h264"); - let mut decoder = VideoToolbox::new(crate::ffi::VIDEO_FORMAT_H264, 64, 64).unwrap(); + let mut decoder = VideoToolbox::new(crate::ffi::VIDEO_FORMAT_H264).unwrap(); let frame = decoder .decode(encoded, true, None) .unwrap() From 5a1c340d9a4477c9cdd8545d0df84c74beef706e Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:07:35 -0700 Subject: [PATCH 09/52] Stop rewriting the engine helpers every 30 seconds on a silent PC While Sunshine reports no audio device, the service calls take_over_engine() on every sixth tick, and install_helpers() rewrote take-over-engine.ps1, .vbs and .cmd and ran `schtasks /Create /F` each time. The files are now written only when they differ from what this build carries, and the logon task is registered once per process. brand.rs also read VerQueryValue's UTF-16 strings and translation pair by turning a pointer into a Vec into a &[u16], which assumes an alignment a byte buffer does not promise. They are read unaligned now. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/host/src/audio.rs | 48 +++++++++++++++++++++++----------------- crates/host/src/brand.rs | 18 ++++++++++++--- 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/crates/host/src/audio.rs b/crates/host/src/audio.rs index 3fc5104..9264a4d 100644 --- a/crates/host/src/audio.rs +++ b/crates/host/src/audio.rs @@ -7,43 +7,51 @@ mod win { use anyhow::{bail, Context, Result}; use std::os::windows::process::CommandExt; - use std::path::PathBuf; + use std::path::{Path, PathBuf}; use std::process::Command; + use std::sync::atomic::{AtomicBool, Ordering}; const TASK: &str = "BroLinkEngineUser"; const CREATE_NO_WINDOW: u32 = 0x0800_0000; - fn helper_dir() -> Result { - let dir = brolink_core::config::data_dir()?; - std::fs::create_dir_all(&dir).with_context(|| dir.display().to_string())?; - Ok(dir) - } - + /// The helper scripts, written into the data directory when they are + /// not there as this build has them, and the logon task that runs + /// them, registered once per process. The service calls this every 30 + /// seconds while the engine has no sound, and it used to rewrite all + /// three files and re-register the task each time. pub fn install_helpers() -> Result { - let dir = helper_dir()?; - std::fs::write( - dir.join("take-over-engine.ps1"), - include_str!("../windows/take-over-engine.ps1"), - )?; + static TASK_REGISTERED: AtomicBool = AtomicBool::new(false); + let dir = brolink_core::config::data_dir()?; + let script = dir.join("take-over-engine.ps1"); + write_if_changed(&script, include_str!("../windows/take-over-engine.ps1"))?; let launcher = dir.join("take-over-engine.vbs"); - std::fs::write(&launcher, include_str!("../windows/take-over-engine.vbs"))?; + write_if_changed(&launcher, include_str!("../windows/take-over-engine.vbs"))?; // Earlier versions pointed the task at a .cmd, which kept a terminal // on the desktop for as long as the helper ran. A task an // administrator registered cannot be re-pointed from here, so the // .cmd stays, reduced to handing off to the windowless launcher. - std::fs::write( - dir.join("take-over-engine.cmd"), + write_if_changed( + &dir.join("take-over-engine.cmd"), include_str!("../windows/take-over-engine.cmd"), )?; - let script = dir.join("take-over-engine.ps1"); - let tr = format!("wscript.exe //B //Nologo \"{}\"", launcher.display()); - let ok = create_logon_task(&tr); - if !ok { - tracing::info!("could not register {TASK}; the host will start the engine itself"); + if !TASK_REGISTERED.load(Ordering::Relaxed) { + let tr = format!("wscript.exe //B //Nologo \"{}\"", launcher.display()); + if create_logon_task(&tr) { + TASK_REGISTERED.store(true, Ordering::Relaxed); + } else { + tracing::info!("could not register {TASK}; the host will start the engine itself"); + } } Ok(script) } + fn write_if_changed(path: &Path, text: &str) -> Result<()> { + if std::fs::read(path).is_ok_and(|have| have == text.as_bytes()) { + return Ok(()); + } + std::fs::write(path, text).with_context(|| path.display().to_string()) + } + fn create_logon_task(tr: &str) -> bool { // These helpers live in the user's profile and must never gain an // elevated token through a scheduled task, including on upgrades. diff --git a/crates/host/src/brand.rs b/crates/host/src/brand.rs index 490ed24..5126809 100644 --- a/crates/host/src/brand.rs +++ b/crates/host/src/brand.rs @@ -256,6 +256,18 @@ mod win { (ok.as_bool() && !p.is_null() && len > 0).then_some((p as *const c_void, len)) } + /// `n` UTF-16 units at `p`, which points into a `Vec` and so has no + /// alignment a `&[u16]` could rely on. + /// + /// # Safety + /// `p` must point to `n` readable `u16`s. + unsafe fn read_u16s(p: *const c_void, n: usize) -> Vec { + let p = p as *const u16; + (0..n) + .map(|i| unsafe { p.add(i).read_unaligned() }) + .collect() + } + /// The fixed info, the strings and the language of `exe`'s version /// block; `None` when it has no block at all. #[allow(clippy::type_complexity)] @@ -285,7 +297,7 @@ mod win { } let (lang, cp) = match query(&buf, "\\VarFileInfo\\Translation") { Some((p, len)) if len >= 4 => { - let t = unsafe { std::slice::from_raw_parts(p as *const u16, 2) }; + let t = unsafe { read_u16s(p, 2) }; (t[0], t[1]) } _ => (verinfo::LANG, verinfo::CODEPAGE), @@ -296,8 +308,8 @@ mod win { // the usual one; look in both. for table in [format!("{lang:04X}{cp:04X}"), "040904B0".to_string()] { if let Some((p, len)) = query(&buf, &format!("\\StringFileInfo\\{table}\\{key}")) { - let u = unsafe { std::slice::from_raw_parts(p as *const u16, len as usize) }; - let s = String::from_utf16_lossy(u) + let u = unsafe { read_u16s(p, len as usize) }; + let s = String::from_utf16_lossy(&u) .trim_end_matches('\0') .to_string(); if !s.is_empty() { From 43846ab0d477d357ee513427dc10b7a69df585b5 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:08:46 -0700 Subject: [PATCH 10/52] Let Cancel stop the pairing retries, and name the codec actually used connect() retried server_info() eight times, 700 ms apart and up to 6 s each, without looking at the cancel flag, so a Cancel while the PC was not answering waited out the whole retry. retry() now checks the attempt between tries. The Live stream's codec label said HEVC whenever the PC offered it and the setting allowed it, but Windows and Linux mask HEVC out of the formats they ask for, so they streamed H.264 under an HEVC label. The choice now also asks whether this machine decodes HEVC. Removes what never had an effect: stream::Settings::remote (every stream is flagged local, see the packet-size note in session.rs), with its BROLINK_TEST_REMOTE hook, lan_like_stream() and tailscale::overlay_or_lan(), and fixes two doc comments that sat on the wrong items. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 6 +++ crates/client/src/app.rs | 1 - crates/client/src/config.rs | 4 +- crates/client/src/session.rs | 80 +++++++++++++++++++++++++++++++----- crates/client/src/stream.rs | 1 - crates/core/src/tailscale.rs | 17 -------- crates/stream/src/session.rs | 11 +---- 7 files changed, 78 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c626557..2c50a29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,12 @@ GitHub release notes, so each version gets a heading of the form - `brolink-host.exe`'s version details (Explorer's Details tab) named the wrong licence, MIT; they now say GPL-3.0-or-later, the licence BroLink and moonlight-common-c are under. +- Cancel stops a connection attempt straight away while the PC is not + answering. The pairing check retried eight times regardless, which could + hold the window on "Checking pairing…" for most of a minute after Cancel. +- A Windows or Linux viewer no longer calls its stream HEVC: those decode + H.264 only, and the toolbar named the codec the PC could offer instead + of the one in use. ## 4.0.2 (2026-09-22) diff --git a/crates/client/src/app.rs b/crates/client/src/app.rs index a3192b6..d044003 100644 --- a/crates/client/src/app.rs +++ b/crates/client/src/app.rs @@ -2124,7 +2124,6 @@ mod snapshots { fps: 30, bitrate_kbps: 4000, hevc: true, - remote: true, }, [0; 16], [0; 16], diff --git a/crates/client/src/config.rs b/crates/client/src/config.rs index b4b476a..ddc0e1f 100644 --- a/crates/client/src/config.rs +++ b/crates/client/src/config.rs @@ -223,8 +223,8 @@ impl ClientConfig { c } - /// Keep a hand-edited or older file within what the UI offers. The - /// bitrate floor is the slider's 2 Mbps, under the Smooth preset's 4. + /// Keep a hand-edited or older file within what the UI offers: the + /// frame rates it lists and the bitrate slider's 2 to 150 Mbps. fn normalise(&mut self) { self.stream.fps = self.stream.fps.clamp(30, 240); self.stream.bitrate_kbps = self.stream.bitrate_kbps.clamp(2_000, 150_000); diff --git a/crates/client/src/session.rs b/crates/client/src/session.rs index 56a3d0f..a048350 100644 --- a/crates/client/src/session.rs +++ b/crates/client/src/session.rs @@ -641,9 +641,9 @@ fn run(c: &Connect, generation: u64) -> Result<()> { tracing::warn!("could not forget the PC's old certificate: {save_error:#}"); } client = Client::new(&identity, IpAddr::V4(t.ip), None)?; - retry(8, || client.server_info())? + retry(8, &c.progress, generation, || client.server_info())? } - Err(_) => retry(8, || client.server_info())?, + Err(_) => retry(8, &c.progress, generation, || client.server_info())?, }; if !info.paired || client.server_cert().is_none() { let pin = format!("{:04}", rand::random::() % 10_000); @@ -727,7 +727,7 @@ fn run(c: &Connect, generation: u64) -> Result<()> { let _ = client.quit(); bail!("cancelled"); } - let hevc = settings.codec != Codec::H264 && info.codec_mode_support & 0x0F00 != 0; + let hevc = wants_hevc(settings.codec, info.codec_mode_support); let frames = Arc::new(FrameSlot::default()); let (tx, rx) = std::sync::mpsc::channel(); let ctx = c.ctx.clone(); @@ -745,7 +745,6 @@ fn run(c: &Connect, generation: u64) -> Result<()> { fps, bitrate_kbps: settings.bitrate_kbps, hevc, - remote: false, }, ri_key, ri_iv, @@ -778,16 +777,37 @@ fn run(c: &Connect, generation: u64) -> Result<()> { Ok(()) } -fn retry(times: u32, mut f: impl FnMut() -> Result) -> Result { - let mut last = None; - for _ in 0..times { +/// HEVC when the settings allow it, the PC can encode it (Sunshine's +/// `ServerCodecModeSupport` HEVC bits) and this machine can decode it. +/// Windows and Linux decode H.264 only, whatever the PC offers. +fn wants_hevc(codec: Codec, server_codecs: i32) -> bool { + const SCM_HEVC_MASK: i32 = 0x0F00; + codec != Codec::H264 + && server_codecs & SCM_HEVC_MASK != 0 + && brolink_stream::video::supported_formats() & brolink_stream::ffi::VIDEO_FORMAT_MASK_H265 + != 0 +} + +/// `f` until it succeeds, `times` tries at most, giving up early once the +/// attempt is cancelled. +fn retry( + times: u32, + progress: &Mutex, + generation: u64, + mut f: impl FnMut() -> Result, +) -> Result { + let mut last = anyhow!("not tried"); + for attempt in 1..=times { match f() { Ok(v) => return Ok(v), - Err(e) => last = Some(e), + Err(e) => last = e, + } + if attempt == times || stale(progress, generation) { + break; } std::thread::sleep(Duration::from_millis(700)); } - Err(last.unwrap()) + Err(last) } /// Hand the PIN to BroLink Host on the PC, which types it into Sunshine. If @@ -869,8 +889,6 @@ pub fn wake_only(pc: &Pc) -> Result { mod tests { use super::*; - /// Against BroLink Host running on this machine: the packet goes out on - /// the LAN and the host reports it. `cargo test -p brolink-client wake_test_real -- --ignored` #[test] fn update_reservation_blocks_connect_in_every_idle_state() { for step in [Step::Idle, Step::Ended { error: None }] { @@ -897,6 +915,8 @@ mod tests { } } + /// Against BroLink Host running on this machine: the packet goes out on + /// the LAN and the host reports it. `cargo test -p brolink-client wake_test_real -- --ignored` #[test] #[ignore = "needs BroLink Host running on this machine"] fn wake_test_real() { @@ -981,6 +1001,44 @@ mod tests { assert!(d.error.is_some() || !d.login.is_empty()); } + #[test] + fn hevc_needs_the_setting_the_pc_and_this_decoder() { + let hevc_pc = 0x0101; + assert!(!wants_hevc(Codec::H264, hevc_pc)); + assert!(!wants_hevc(Codec::Auto, 0x0001), "an H.264-only PC"); + assert_eq!( + wants_hevc(Codec::Auto, hevc_pc), + cfg!(target_os = "macos"), + "only VideoToolbox decodes HEVC here" + ); + } + + #[test] + fn a_cancelled_retry_stops_at_once() { + let progress = Mutex::new(Progress { + generation: 3, + ..Default::default() + }); + let mut tries = 0; + let r: Result<()> = retry(8, &progress, 3, || { + tries += 1; + progress.lock().cancel = true; + bail!("the PC is not answering") + }); + assert_eq!(tries, 1); + assert_eq!(r.unwrap_err().to_string(), "the PC is not answering"); + progress.lock().cancel = false; + let mut tries = 0; + let r = retry(3, &progress, 3, || { + tries += 1; + if tries < 2 { + bail!("not yet") + } + Ok(tries) + }); + assert_eq!(r.unwrap(), 2); + } + #[test] fn progress_tracks_step_changes() { let mut p = Progress::default(); diff --git a/crates/client/src/stream.rs b/crates/client/src/stream.rs index 49de0a4..aac08a2 100644 --- a/crates/client/src/stream.rs +++ b/crates/client/src/stream.rs @@ -1257,7 +1257,6 @@ mod tests { fps: 30, bitrate_kbps: 4000, hevc: true, - remote: true, }, [0; 16], [0; 16], diff --git a/crates/core/src/tailscale.rs b/crates/core/src/tailscale.rs index 10e9279..41c3376 100644 --- a/crates/core/src/tailscale.rs +++ b/crates/core/src/tailscale.rs @@ -340,19 +340,6 @@ pub fn netcheck() -> Result { } /// The three-letter code of a DERP region, as `tailscale status` prints it. -/// True for loopback, RFC 1918, or Tailscale's CGNAT range (`100.64/10`). -/// BroLink never rides the raw internet, so these addresses are a LAN as -/// far as the stream protocol is concerned. -pub fn overlay_or_lan(ip: Ipv4Addr) -> bool { - if ip.is_loopback() || ip.is_private() { - return true; - } - // 100.64.0.0/10 (shared address space). `Ipv4Addr::is_shared` is not - // stable on the toolchain BroLink pins. - let o = ip.octets(); - o[0] == 100 && o[1] >= 64 && o[1] <= 127 -} - pub fn derp_code(region: i32) -> &'static str { match region { 1 => "nyc", @@ -540,10 +527,6 @@ mod tests { ); assert_eq!(st.peer["nodekey:a"].os_label(), "macOS"); assert_eq!(st.peer["nodekey:b"].os_label(), "Windows"); - assert!(overlay_or_lan("100.64.0.10".parse().unwrap())); - assert!(overlay_or_lan("192.168.1.10".parse().unwrap())); - assert!(overlay_or_lan("127.0.0.1".parse().unwrap())); - assert!(!overlay_or_lan("8.8.8.8".parse().unwrap())); assert_eq!( pcs[2].ipv4(), Some("100.64.0.30".parse().unwrap()), diff --git a/crates/stream/src/session.rs b/crates/stream/src/session.rs index dd7b601..b5e9087 100644 --- a/crates/stream/src/session.rs +++ b/crates/stream/src/session.rs @@ -63,8 +63,6 @@ pub struct Settings { pub bitrate_kbps: u32, /// Ask for HEVC when both sides can; otherwise H.264. pub hevc: bool, - /// The PC is not on this LAN: smaller packets, remote pacing. - pub remote: bool, } /// What `Session::start` needs from `/serverinfo` and `/launch`. @@ -450,11 +448,6 @@ fn sunshine_encoder_kbps(request: u64) -> u64 { kbps } -/// Tailscale (and any RFC 1918 path) is a LAN to the stream protocol. -pub fn lan_like_stream(ip: std::net::Ipv4Addr) -> bool { - brolink_core::tailscale::overlay_or_lan(ip) -} - fn run(inner: Arc, server: Server, s: Settings, ri_key: [u8; 16], ri_iv: [u8; 16]) { let _one_at_a_time = CONNECTION.lock(); if *inner.done.lock() { @@ -969,9 +962,8 @@ mod real { let fps = number("BROLINK_TEST_FPS", 60); let bitrate_kbps = number("BROLINK_TEST_BITRATE_KBPS", 10_000); let seconds = number("BROLINK_TEST_SECONDS", 12); - let remote = std::env::var_os("BROLINK_TEST_REMOTE").is_some(); assert!(width > 0 && height > 0 && fps > 0 && bitrate_kbps > 0 && seconds >= 12); - eprintln!("requested: {width}x{height} {fps} fps {bitrate_kbps} kbps remote={remote} duration={seconds}s"); + eprintln!("requested: {width}x{height} {fps} fps {bitrate_kbps} kbps duration={seconds}s"); let dir = std::env::var_os("BROLINK_TEST_DIR") .map(std::path::PathBuf::from) .unwrap_or_else(|| std::env::temp_dir().join("brolink-pair-test")); @@ -1043,7 +1035,6 @@ mod real { fps, bitrate_kbps, hevc: std::env::var_os("BROLINK_TEST_HEVC").is_some(), - remote, }, ri_key, ri_iv, From b310ee55b4175fe69f7f3039a948206f5c2643bc Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:10:33 -0700 Subject: [PATCH 11/52] Remove API that nothing outside the tests called - Session's own mouse, key, text and scroll methods duplicated Input's; every caller goes through Session::input(). - tailscale::Status::windows_peers, superseded by machine_peers since every OS can share. - http::serve (serve_with_peer_check is the one the service uses) and nvhttp::Client::pair (the app pairs through pair_cancellable) are now test-only helpers. - ffi declarations for constants and moonlight functions BroLink never uses (STREAM_CFG_REMOTE/AUTO, COLOR_RANGE_FULL, the AVC reference invalidation flag, CONN_STATUS_OKAY, LiGetPendingVideoFrames, LiRequestIdrFrame), and the module-wide allow(dead_code) that hid them. - audio::Player's decoded counter and its packets()/decoded() accessors, which only the tests read; the test checks the queue instead. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/core/src/http.rs | 12 +++++++----- crates/core/src/tailscale.rs | 25 +++++++++++-------------- crates/stream/src/audio.rs | 21 ++++----------------- crates/stream/src/ffi.rs | 9 --------- crates/stream/src/nvhttp.rs | 12 +++++++----- crates/stream/src/session.rs | 31 +++---------------------------- 6 files changed, 32 insertions(+), 78 deletions(-) diff --git a/crates/core/src/http.rs b/crates/core/src/http.rs index 436eb3a..9b3e0c0 100644 --- a/crates/core/src/http.rs +++ b/crates/core/src/http.rs @@ -191,17 +191,19 @@ pub fn write_response(stream: &mut TcpStream, resp: &Response) -> Result<()> { ) } -/// Accept forever, with a bounded number of active connections. `handler` sees the peer -/// address so it can decide who is allowed to ask. -pub fn serve(listener: TcpListener, handler: F) +/// [`serve_with_peer_check`] for everyone, for the tests. +#[cfg(test)] +fn serve(listener: TcpListener, handler: F) where F: Fn(SocketAddr, &Request) -> Response + Send + Sync + 'static, { serve_with_peer_check(listener, |_| true, handler); } -/// Authorize the peer before reading headers or allocating a request body. -/// The check runs in a bounded connection worker, so it may perform I/O. +/// Accept forever, with a bounded number of active connections. `authorize` +/// sees the peer before its headers are read or a body is allocated; it +/// runs in the connection's worker, so it may do I/O. `handler` sees the +/// peer too, so it can decide per route. pub fn serve_with_peer_check(listener: TcpListener, authorize: A, handler: F) where A: Fn(SocketAddr) -> bool + Send + Sync + 'static, diff --git a/crates/core/src/tailscale.rs b/crates/core/src/tailscale.rs index 41c3376..19ff41c 100644 --- a/crates/core/src/tailscale.rs +++ b/crates/core/src/tailscale.rs @@ -192,17 +192,6 @@ impl Status { .get(&self.self_node.user_id.to_string()) .map(|u| u.login_name.as_str()) } - /// Peers running Windows. Relay-tagged nodes are omitted even when they - /// report Windows. Prefer [`machine_peers`] for the streamable list. - pub fn windows_peers(&self) -> Vec<&Node> { - let mut v: Vec<&Node> = self - .peer - .values() - .filter(|n| n.is_windows() && !n.is_relay()) - .collect(); - v.sort_by(|a, b| a.host_name.cmp(&b.host_name)); - v - } /// Every tailnet peer that can share a desktop: any OS, minus `tag:relay` /// nodes that exist only to carry packets. Sorted by hostname. pub fn machine_peers(&self) -> Vec<&Node> { @@ -487,6 +476,14 @@ fn run_limited(cmd: &mut Command, limit: Duration) -> Result { mod tests { use super::*; + /// The Windows machines in the list, as the tests below look at them. + fn windows_peers(st: &Status) -> Vec<&Node> { + st.machine_peers() + .into_iter() + .filter(|n| n.is_windows()) + .collect() + } + const SAMPLE: &str = r#"{ "Version": "1.102.3", "BackendState": "Running", "Self": {"ID": "nSELF", "HostName": "Gaming-PC", "DNSName": "gaming-pc.example.ts.net.", "OS": "windows", @@ -511,7 +508,7 @@ mod tests { assert!(st.running()); assert_eq!(st.self_node.ipv4(), Some("100.64.0.10".parse().unwrap())); assert_eq!(st.self_login(), Some("user@example.com")); - let pcs = st.windows_peers(); + let pcs = windows_peers(&st); assert_eq!( pcs.iter().map(|n| n.host_name.as_str()).collect::>(), ["Den", "Gaming-PC-2", "Office"] @@ -580,7 +577,7 @@ mod tests { fn a_stopped_daemon_is_not_running() { let st = parse_status(r#"{"BackendState":"Stopped"}"#).unwrap(); assert!(!st.running()); - assert!(st.windows_peers().is_empty()); + assert!(windows_peers(&st).is_empty()); assert_eq!(st.self_login(), None); } @@ -634,7 +631,7 @@ mod tests { ) .unwrap(); - let pcs = st.windows_peers(); + let pcs = windows_peers(&st); assert_eq!( pcs.iter().map(|n| n.host_name.as_str()).collect::>(), ["Gaming-PC", "tagged-pc"], diff --git a/crates/stream/src/audio.rs b/crates/stream/src/audio.rs index fd13f77..adcd454 100644 --- a/crates/stream/src/audio.rs +++ b/crates/stream/src/audio.rs @@ -48,7 +48,6 @@ pub struct Player { keep: usize, packets: u64, lost: u64, - decoded: u64, undecodable: u64, } @@ -134,7 +133,6 @@ impl Player { keep, packets: 0, lost: 0, - decoded: 0, undecodable: 0, }) } @@ -173,7 +171,6 @@ impl Player { } return; } - self.decoded += n as u64; let samples = &self.pcm[..n as usize * self.channels]; let mut q = self.queue.lock(); q.extend(samples); @@ -187,16 +184,6 @@ impl Player { self.output.lock().clone() } - /// Packets received from the PC, lost ones not counted. - pub fn packets(&self) -> u64 { - self.packets - } - - /// Samples per channel decoded so far. - pub fn decoded(&self) -> u64 { - self.decoded - } - /// Frames the output device has taken so far. pub fn pulled(&self) -> u64 { self.pulled.load(Ordering::Relaxed) @@ -547,12 +534,12 @@ mod tests { for _ in 0..10 { p.push(&packet); } - assert_eq!(p.packets(), 10); - assert_eq!(p.decoded(), 10 * 480); + assert_eq!(p.packets, 10); + assert_eq!(p.queue.lock().len(), 10 * 480 * 2, "480 stereo frames each"); // A lost packet is concealed at the stream's frame size. p.push(&[]); - assert_eq!(p.decoded(), 11 * 480); - assert_eq!(p.undecodable, 0); + assert_eq!(p.queue.lock().len(), 11 * 480 * 2); + assert_eq!((p.lost, p.undecodable), (1, 0)); // The queue never grows past the cap, and never by a partial frame. for _ in 0..100 { p.push(&packet); diff --git a/crates/stream/src/ffi.rs b/crates/stream/src/ffi.rs index d2e0144..423cfc1 100644 --- a/crates/stream/src/ffi.rs +++ b/crates/stream/src/ffi.rs @@ -1,8 +1,6 @@ //! Bindings to the C shim (`csrc/shim.h`) and to the moonlight-common-c input //! functions, which take only primitive arguments and need no shim. -#![allow(non_camel_case_types, dead_code)] - use std::os::raw::{c_char, c_int, c_uint, c_void}; #[repr(C)] @@ -50,11 +48,8 @@ pub struct ServerInfo { } pub const STREAM_CFG_LOCAL: c_int = 0; -pub const STREAM_CFG_REMOTE: c_int = 1; -pub const STREAM_CFG_AUTO: c_int = 2; pub const COLORSPACE_REC_709: c_int = 1; pub const COLOR_RANGE_LIMITED: c_int = 0; -pub const COLOR_RANGE_FULL: c_int = 1; pub const ENCFLG_ALL: c_int = -1; pub const VIDEO_FORMAT_H264: c_int = 0x0001; @@ -65,7 +60,6 @@ pub const VIDEO_FORMAT_MASK_H265: c_int = 0x0F00; pub const CAPABILITY_DIRECT_SUBMIT: c_int = 0x1; /// The decoder copes with a frame that references one older than the last /// (the host then repairs a lost frame without a whole new keyframe). -pub const CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC: c_int = 0x2; pub const CAPABILITY_REFERENCE_FRAME_INVALIDATION_HEVC: c_int = 0x4; pub const CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION: c_int = 0x10; @@ -80,7 +74,6 @@ pub const ML_ERROR_UNEXPECTED_EARLY_TERMINATION: c_int = -102; pub const ML_ERROR_PROTECTED_CONTENT: c_int = -103; pub const ML_ERROR_FRAME_CONVERSION: c_int = -104; -pub const CONN_STATUS_OKAY: c_int = 0; pub const CONN_STATUS_POOR: c_int = 1; pub const BUTTON_ACTION_PRESS: c_char = 0x07; @@ -131,9 +124,7 @@ extern "C" { pub fn LiSendHighResHScrollEvent(amount: i16) -> c_int; pub fn LiGetEstimatedRttInfo(rtt: *mut u32, variance: *mut u32) -> bool; pub fn LiGetMicroseconds() -> u64; - pub fn LiGetPendingVideoFrames() -> c_int; pub fn LiGetRTPVideoStats() -> *const RtpVideoStats; - pub fn LiRequestIdrFrame(); } pub fn stage_name(stage: c_int) -> String { diff --git a/crates/stream/src/nvhttp.rs b/crates/stream/src/nvhttp.rs index 1c0c5b3..36d5ee4 100644 --- a/crates/stream/src/nvhttp.rs +++ b/crates/stream/src/nvhttp.rs @@ -208,10 +208,8 @@ impl<'a> Client<'a> { }) } - /// The Moonlight pairing handshake. `pending` is called once the host is - /// holding our first request and waiting for someone to enter `pin` on - /// its side. On success the host certificate is pinned for the later - /// HTTPS calls and returned. + /// [`Client::pair_cancellable`] without the cancel, for the live tests. + #[cfg(test)] pub fn pair( &mut self, pin: &str, @@ -224,7 +222,11 @@ impl<'a> Client<'a> { }) } - /// Like `pair`, but the PIN callback can abort the outstanding socket read. + /// The Moonlight pairing handshake. `pending` is called once the host is + /// holding our first request and waiting for someone to enter `pin` on + /// its side; it returns `true` to cancel, which aborts that request. On + /// success the host certificate is pinned for the later HTTPS calls and + /// returned. pub fn pair_cancellable( &mut self, pin: &str, diff --git a/crates/stream/src/session.rs b/crates/stream/src/session.rs index b5e9087..349248c 100644 --- a/crates/stream/src/session.rs +++ b/crates/stream/src/session.rs @@ -360,33 +360,6 @@ impl Session { inner: self.inner.clone(), } } - - pub fn mouse_move(&self, dx: i16, dy: i16) { - self.input().mouse_move(dx, dy); - } - - pub fn mouse_position(&self, x: i16, y: i16, width: i16, height: i16) { - self.input().mouse_position(x, y, width, height); - } - - /// `button` is one of `ffi::BUTTON_*`. - pub fn mouse_button(&self, button: c_int, down: bool) { - self.input().mouse_button(button, down); - } - - /// `vk` is a Windows virtual-key code; `modifiers` a mask of `ffi::MODIFIER_*`. - pub fn key(&self, vk: i16, down: bool, modifiers: c_char) { - self.input().key(vk, down, modifiers); - } - - pub fn text(&self, text: &str) { - self.input().text(text); - } - - /// Vertical and horizontal scroll in 1/120ths of a wheel click. - pub fn scroll(&self, vertical: i16, horizontal: i16) { - self.input().scroll(vertical, horizontal); - } } impl Drop for Session { @@ -1060,7 +1033,9 @@ mod real { // raw input sees continuous relative motion. Watch the PC's // cursor (GetCursorPos) to confirm the relative injection lands. let phase = (start.elapsed().as_millis() / 400) % 2; - session.mouse_move(if phase == 0 { 40 } else { -40 }, 0); + session + .input() + .mouse_move(if phase == 0 { 40 } else { -40 }, 0); } if let Some(f) = frames.take() { if frames.seq() % 60 == 1 { From c46bc31d4d3f9eccae1d5021e6bfee7d5ca502a2 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:11:06 -0700 Subject: [PATCH 12/52] Drop the path argument that Auto quality never read path::effective took the measured path and ignored it: since 3.3 Auto asks for the same quality whatever the route. The parameter made every caller build a Path it did not need, and the test looped over paths that could not change the answer. Also removes a doc line left over from a deleted function in stream.rs. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/client/src/app.rs | 4 +-- crates/client/src/path.rs | 48 ++++++++++++++--------------------- crates/client/src/session.rs | 2 +- crates/client/src/settings.rs | 2 +- crates/client/src/stream.rs | 1 - 5 files changed, 23 insertions(+), 34 deletions(-) diff --git a/crates/client/src/app.rs b/crates/client/src/app.rs index d044003..3995713 100644 --- a/crates/client/src/app.rs +++ b/crates/client/src/app.rs @@ -611,7 +611,7 @@ impl eframe::App for ClientApp { ui.add_space(14.0); self.pcs_card(ui, ctx, &disc, &prog); ui::titled_card(ui, "Next session", None, |ui| { - let settings = path::effective(&self.cfg.stream, &path::Path::default()); + let settings = path::effective(&self.cfg.stream); let (w, h) = settings.resolution.pixels(Self::native_pixels(ctx)); ui.horizontal_wrapped(|ui| { ui::status_pill(ui, &format!("{w} × {h}"), Tone::Info); @@ -2109,7 +2109,7 @@ mod snapshots { rtt_ms: Some(210), ..Default::default() }; - let settings = crate::path::effective(&crate::config::StreamSettings::default(), &path); + let settings = crate::path::effective(&crate::config::StreamSettings::default()); let session = brolink_stream::Session::start( brolink_stream::session::Server { address: "10.255.255.1".into(), diff --git a/crates/client/src/path.rs b/crates/client/src/path.rs index eba5cfe..89902ac 100644 --- a/crates/client/src/path.rs +++ b/crates/client/src/path.rs @@ -87,7 +87,7 @@ impl Path { /// Recommended starting quality. RTT measures delay, not throughput: neither /// a VPS relay nor a distant direct connection implies a bandwidth cap. -pub fn effective(settings: &StreamSettings, _path: &Path) -> StreamSettings { +pub fn effective(settings: &StreamSettings) -> StreamSettings { let mut s = settings.clone(); if s.quality == Quality::Auto { s.resolution = Resolution::Native; @@ -278,35 +278,25 @@ mod tests { ); } + /// The path is not an input at all: latency and relays never lower + /// what Auto asks for. #[test] - fn latency_and_relay_type_never_throttle_quality() { - for direct in [None, Some(true), Some(false)] { - for rtt_ms in [None, Some(2), Some(80), Some(200), Some(400)] { - for peer_relay in ["", "100.64.0.40:40000:vni:17"] { - let path = Path { - direct, - rtt_ms, - peer_relay: peer_relay.into(), - ..Default::default() - }; - let auto = StreamSettings { - fps: 120, - ..Default::default() - }; - let s = effective(&auto, &path); - assert_eq!( - (s.resolution, s.fps, s.bitrate_kbps), - (Resolution::Native, 120, 50_000) - ); - let custom = StreamSettings { - quality: Quality::Custom, - bitrate_kbps: 150_000, - ..auto - }; - assert_eq!(effective(&custom, &path), custom); - } - } - } + fn auto_is_the_recommended_quality_and_custom_is_left_alone() { + let auto = StreamSettings { + fps: 120, + ..Default::default() + }; + let s = effective(&auto); + assert_eq!( + (s.resolution, s.fps, s.bitrate_kbps), + (Resolution::Native, 120, 50_000) + ); + let custom = StreamSettings { + quality: Quality::Custom, + bitrate_kbps: 150_000, + ..auto + }; + assert_eq!(effective(&custom), custom); } #[test] diff --git a/crates/client/src/session.rs b/crates/client/src/session.rs index a048350..1bf8b18 100644 --- a/crates/client/src/session.rs +++ b/crates/client/src/session.rs @@ -666,7 +666,7 @@ fn run(c: &Connect, generation: u64) -> Result<()> { // carry. report(Step::Launching, "Measuring the path…".into()); let path = measure_path(&t.node_id, t.ip, &t.path); - let settings = path::effective(&c.settings, &path); + let settings = path::effective(&c.settings); tracing::info!( "path to {}: {} · {}", t.name, diff --git a/crates/client/src/settings.rs b/crates/client/src/settings.rs index 5523e2c..9c73fd9 100644 --- a/crates/client/src/settings.rs +++ b/crates/client/src/settings.rs @@ -65,7 +65,7 @@ pub fn stream_controls(ui: &mut egui::Ui, s: &mut StreamSettings, native: (u32, ui::setting_row(ui, "Video format", Some("HEVC gives sharper detail per megabit. Auto uses HEVC when supported; H.264 improves compatibility."), |ui| { ui::segmented(ui, &[(Codec::Auto, "Auto"), (Codec::Hevc, "HEVC"), (Codec::H264, "H.264")], &mut s.codec); }); - let effective = crate::path::effective(s, &crate::path::Path::default()); + let effective = crate::path::effective(s); let (w, h) = effective.resolution.pixels(native); ui.add_space(12.0); ui::status_pill( diff --git a/crates/client/src/stream.rs b/crates/client/src/stream.rs index aac08a2..ce33723 100644 --- a/crates/client/src/stream.rs +++ b/crates/client/src/stream.rs @@ -1133,7 +1133,6 @@ fn pointer_on_stream( in_video && !over_bar && !popup && !overlay && !over_overlay } -/// "Auto · Smooth" → "Auto", "Custom · Sharp" → "Sharp", else "Custom". /// Whether `host_version` is too old for `/v1/update`, with the release /// that an install through the stream would put on it. pub fn old_host( From 02a0fba02101eb6fe34404787e9abea7b6add8e1 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:13:47 -0700 Subject: [PATCH 13/52] Keep the adapter name out of double-quoted strings in setup The Wake-on-LAN step printed `Step "Enabling Wake-on-LAN on ''"`. The name was escaped for a single-quoted literal, but this string is double-quoted: a `"` in the adapter's name ended it, a syntax error that fails the entire elevated script, and `$x` or `$(...)` in the name was expanded or run. Both names are now assigned once as single-quoted literals and used through variables. Also drops a second, undocumented BROLINK_DUMP_SETUP hook that did what BROLINK_DUMP_SETUP_SCRIPT does, fixes two test helpers whose doc comments were swapped, and a stale task reference in migrate.rs. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 4 +++ crates/host/src/migrate.rs | 2 +- crates/host/src/setup.rs | 50 ++++++++++++++++++++++++++------------ 3 files changed, 39 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c50a29..c98ecc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,10 @@ GitHub release notes, so each version gets a heading of the form - `brolink-host.exe`'s version details (Explorer's Details tab) named the wrong licence, MIT; they now say GPL-3.0-or-later, the licence BroLink and moonlight-common-c are under. +- Windows setup no longer breaks on a network adapter whose name holds a + `"` or a `$`. The name was written into a double-quoted PowerShell + string, where a `"` ends the string (a syntax error that stops the whole + setup) and a `$` expands. It is now only ever a single-quoted literal. - Cancel stops a connection attempt straight away while the PC is not answering. The pairing check retried eight times regardless, which could hold the window on "Checking pairing…" for most of a minute after Cancel. diff --git a/crates/host/src/migrate.rs b/crates/host/src/migrate.rs index 83eec72..8ee5047 100644 --- a/crates/host/src/migrate.rs +++ b/crates/host/src/migrate.rs @@ -2,7 +2,7 @@ //! engine is migrated: copy → verify → start → prove → uninstall, never //! uninstall-first. Elevation is why this is script, not Rust. //! -//! Named list is the live Hermes inventory (task 14): required state is +//! What is copied is what a live MSI install held: required state is //! `sunshine_state.json` plus `credentials/cacert.pem` and `cakey.pem`. //! `apps.json` and `sunshine.conf` are best-effort. The log is regenerated. diff --git a/crates/host/src/setup.rs b/crates/host/src/setup.rs index 189b4aa..acf5320 100644 --- a/crates/host/src/setup.rs +++ b/crates/host/src/setup.rs @@ -382,15 +382,19 @@ if ($needFiles -or -not $svcUp) {{ "Step \"Wake-on-LAN: adapter unknown, skipped\"\n".to_string() } else { format!( - r#"Step "Enabling Wake-on-LAN on '{adapter}'" + r#"# The names are single-quoted literals held in variables. Spliced into +# a double-quoted string, a '$' in a name would expand and a '"' end it. +$wakeAdapter = '{adapter}' +$wakeDevice = '{desc}' +Step "Enabling Wake-on-LAN on '$wakeAdapter'" try {{ - Set-NetAdapterPowerManagement -Name '{adapter}' -WakeOnMagicPacket Enabled -ErrorAction Stop + Set-NetAdapterPowerManagement -Name $wakeAdapter -WakeOnMagicPacket Enabled -ErrorAction Stop }} catch {{ Write-Output " cmdlet failed ($_); the driver keywords below still apply" }} # The NDIS keywords are what the driver reads: magic packet from sleep, from # modern standby, and (Realtek's own keyword) from a full shutdown. ARP and # NS offload keep the card answering for the PC's address while it sleeps, # which is what lets a unicast wake packet reach it through a router. -$g = (Get-NetAdapter -Name '{adapter}' -ErrorAction SilentlyContinue).InterfaceGuid +$g = (Get-NetAdapter -Name $wakeAdapter -ErrorAction SilentlyContinue).InterfaceGuid $k = Get-ChildItem 'HKLM:\SYSTEM\CurrentControlSet\Control\Class\{{4d36e972-e325-11ce-bfc1-08002be10318}}' -ErrorAction SilentlyContinue | Where-Object {{ (Get-ItemProperty $_.PSPath -Name NetCfgInstanceId -ErrorAction SilentlyContinue).NetCfgInstanceId -eq $g }} | Select-Object -First 1 if ($k) {{ $changed = $false @@ -400,9 +404,9 @@ if ($k) {{ $changed = $true }} }} - if ($changed) {{ Restart-NetAdapter -Name '{adapter}' -ErrorAction SilentlyContinue }} + if ($changed) {{ Restart-NetAdapter -Name $wakeAdapter -ErrorAction SilentlyContinue }} }} else {{ Write-Output " no class key for the adapter; keywords unchanged" }} -try {{ powercfg /deviceenablewake '{desc}' | Out-Null }} catch {{ Write-Output " powercfg: $_" }} +try {{ powercfg /deviceenablewake $wakeDevice | Out-Null }} catch {{ Write-Output " powercfg: $_" }} "#, adapter = q(p.adapter), desc = q(p.adapter_description) @@ -815,8 +819,6 @@ mod tests { ); } - /// The script with its comments stripped: a "must not appear" check has - /// to be about what the script does, not about what it explains. /// Position of `needle` after the preamble's function definitions, so /// an ordering test reads the steps as they run, not the helpers. fn find_in_body(s: &str, needle: &str) -> Option { @@ -824,6 +826,8 @@ mod tests { s[at..].find(needle).map(|i| i + at) } + /// The script with its comments stripped: a "must not appear" check has + /// to be about what the script does, not about what it explains. fn code(s: &str) -> String { s.lines() .filter(|l| !l.trim_start().starts_with('#')) @@ -856,14 +860,29 @@ mod tests { }); assert!(s.contains("--creds 'brolink' 'p''w'"), "{s}"); assert!(!s.contains("Downloading the streaming engine")); - assert!(s.contains("Set-NetAdapterPowerManagement -Name 'Ethernet'")); - assert!(s.contains("Restart-NetAdapter -Name 'Ethernet'")); + assert!(s.contains("$wakeAdapter = 'Ethernet'"), "{s}"); + assert!(s.contains("Set-NetAdapterPowerManagement -Name $wakeAdapter")); + assert!(s.contains("Restart-NetAdapter -Name $wakeAdapter")); assert!(s.contains("'S5WakeOnLan'")); assert!(s.contains("HiberbootEnabled -Value 0")); assert!(s.contains("powercfg /change standby-timeout-ac 0")); assert!(s.contains("powercfg /change hibernate-timeout-ac 0")); assert!(s.contains("protocol=UDP localport=9 program='C:\\x\\brolink-host.exe'")); - assert!(s.contains("powercfg /deviceenablewake 'Realtek PCIe GbE'")); + assert!(s.contains("$wakeDevice = 'Realtek PCIe GbE'")); + assert!(s.contains("powercfg /deviceenablewake $wakeDevice")); + // A name only ever appears as a single-quoted literal: in a + // double-quoted string `$(...)` would run and a `"` end the string. + let named = script(&Plan { + adapter: r#"Eth "$(Stop-Computer)" 'x'"#, + adapter_description: "NIC $env:TEMP", + ..plan(&exe, false) + }); + assert!( + named.contains(r#"$wakeAdapter = 'Eth "$(Stop-Computer)" ''x'''"#), + "{named}" + ); + assert!(named.contains("$wakeDevice = 'NIC $env:TEMP'"), "{named}"); + assert_eq!(named.matches("Stop-Computer").count(), 1, "{named}"); assert!( s.contains("localport=47850 remoteip=100.64.0.0/10 program='C:\\x\\brolink-host.exe'") ); @@ -1022,10 +1041,6 @@ mod tests { fn the_virtual_display_learns_every_size_a_mac_can_ask_for() { let exe = PathBuf::from(r"C:\x\brolink-host.exe"); let s = script(&plan(&exe, false)); - // A copy to try on a real PC: BROLINK_DUMP_SETUP=/tmp/setup.ps1. - if let Ok(path) = std::env::var("BROLINK_DUMP_SETUP") { - std::fs::write(path, &s).expect("dump the script"); - } let body = code(&s); let step = find_in_body(&body, "Listing the sizes a Mac can ask for").expect("step"); let fast = find_in_body(&body, "Turning Fast Startup off").expect("fast startup"); @@ -1409,8 +1424,11 @@ system_tray = enabled assert!(wet.contains("Brand-Engine $dir"), "{wet}"); } - /// The seam the PowerShell syntax gate runs through: the generated script - /// is the real artefact, so it has to be obtainable off Windows. + /// The seam CI's PowerShell syntax check runs through (see + /// `.github/workflows/ci.yml`): the generated script is the real + /// artefact, so it has to be obtainable without running setup. + /// `BROLINK_DUMP_SETUP_SCRIPT=/tmp/setup.ps1` writes one; the other + /// `BROLINK_DUMP_*` variables pick the plan. #[test] fn the_generated_script_can_be_dumped_for_a_syntax_check() { let Ok(out) = std::env::var("BROLINK_DUMP_SETUP_SCRIPT") else { From dc5ee5451e30f08ec1d47726f89cf169b1c1679e Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:13:47 -0700 Subject: [PATCH 14/52] Parse every PowerShell script in CI, and test the whole workspace The Windows setup script is generated, so a quoting slip in Rust shows up only on a real PC. The Windows job now dumps it for every plan shape (fresh install, migration, each with and without dry run), with an exe path and adapter names that need quoting, and runs it, install-host.ps1 and take-over-engine.ps1 through Windows PowerShell's own parser. The macOS and Linux jobs and the release workflow listed four crates by name and so never ran brolink-ui's tests; they use --workspace now. The Linux job and the node Dockerfile stop installing libssl-dev and libpulse-dev, which nothing links (there is no openssl or libpulse crate in Cargo.lock). Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/ci.yml | 38 ++++++++++++++++++++++++++++++----- .github/workflows/release.yml | 2 +- deploy/node/Dockerfile | 4 ++-- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5572d4d..301e77e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,6 +24,34 @@ jobs: run: cargo clippy --workspace --all-targets -- -D warnings - name: test run: cargo test --workspace + # The setup script is generated, so only a dump of it can be parsed. + # Every plan shape, with names that need quoting, through the parser + # of the Windows PowerShell that runs it; plus the shipped scripts. + - name: PowerShell scripts parse + shell: powershell + run: | + $ErrorActionPreference = 'Stop' + $env:BROLINK_DUMP_EXE = "C:\Users\O'Brien `$lab\AppData\Local\BroLink\brolink-host.exe" + $env:BROLINK_DUMP_ADAPTER = "Ethernet 'Realtek' `"2`" `$(x)" + $env:BROLINK_DUMP_ADAPTER_DESC = "Realtek PCIe GbE (`"x`")" + $files = @('scripts\install-host.ps1', 'crates\host\windows\take-over-engine.ps1') + foreach ($migrate in '0', '1') { + foreach ($dry in '0', '1') { + $out = Join-Path $env:RUNNER_TEMP "setup-$migrate$dry.ps1" + $env:BROLINK_DUMP_SETUP_SCRIPT = $out + $env:BROLINK_DUMP_MIGRATE = $migrate + $env:BROLINK_DUMP_DRY_RUN = $dry + cargo test -q -p brolink-host --lib the_generated_script_can_be_dumped_for_a_syntax_check + if ($LASTEXITCODE -ne 0) { throw "dumping the setup script failed" } + $files += $out + } + } + foreach ($f in $files) { + $errors = $null + [System.Management.Automation.Language.Parser]::ParseFile((Resolve-Path $f), [ref]$null, [ref]$errors) | Out-Null + if ($errors.Count -gt 0) { $errors | Format-List | Out-String | Write-Host; throw "$f does not parse" } + Write-Host "parses: $f" + } - name: release build run: cargo build --workspace --release - uses: actions/upload-artifact@v4 @@ -42,9 +70,9 @@ jobs: components: clippy - uses: Swatinem/rust-cache@v2 - name: clippy - run: cargo clippy -p brolink-core -p brolink-stream -p brolink-client -p brolink-host --all-targets -- -D warnings + run: cargo clippy --workspace --all-targets -- -D warnings - name: test - run: cargo test -p brolink-core -p brolink-stream -p brolink-client -p brolink-host + run: cargo test --workspace - name: release app run: cargo build --release -p brolink-host --target aarch64-apple-darwin - name: bundle @@ -59,11 +87,11 @@ jobs: components: clippy - uses: Swatinem/rust-cache@v2 - name: native deps - run: sudo apt-get update && sudo apt-get install -y --no-install-recommends cmake clang nasm pkg-config libssl-dev libasound2-dev libpulse-dev libxkbcommon-dev libwayland-dev libx11-dev libxcursor-dev libxrandr-dev libxi-dev libx11-xcb-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev + run: sudo apt-get update && sudo apt-get install -y --no-install-recommends cmake clang nasm pkg-config libasound2-dev libxkbcommon-dev libwayland-dev libx11-dev libxcursor-dev libxrandr-dev libxi-dev libx11-xcb-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev - name: clippy - run: cargo clippy -p brolink-core -p brolink-stream -p brolink-client -p brolink-host --all-targets -- -D warnings + run: cargo clippy --workspace --all-targets -- -D warnings - name: test - run: cargo test -p brolink-core -p brolink-stream -p brolink-client -p brolink-host + run: cargo test --workspace - name: release host run: cargo build --release -p brolink-host diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 08e80f6..68ba400 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: - uses: Swatinem/rust-cache@v2 # Never ship a binary whose tests were not run. - name: test - run: cargo test -p brolink-core -p brolink-stream -p brolink-client -p brolink-host + run: cargo test --workspace - name: build app run: cargo build --release -p brolink-host --target aarch64-apple-darwin # bundle-macos.sh ad-hoc signs and verifies the bundle itself. diff --git a/deploy/node/Dockerfile b/deploy/node/Dockerfile index 483d863..24d7c72 100644 --- a/deploy/node/Dockerfile +++ b/deploy/node/Dockerfile @@ -8,8 +8,8 @@ FROM rust:1-bookworm AS build RUN apt-get update && apt-get install -y --no-install-recommends \ - build-essential cmake clang nasm pkg-config libssl-dev \ - libasound2-dev libpulse-dev \ + build-essential cmake clang nasm pkg-config \ + libasound2-dev \ libxkbcommon-dev libwayland-dev libx11-dev libxcursor-dev \ libxrandr-dev libxi-dev libx11-xcb-dev libxcb-render0-dev \ libxcb-shape0-dev libxcb-xfixes0-dev \ From e4f71efaf5d03cf90437a49a1ee99201ec63565a Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:15:58 -0700 Subject: [PATCH 15/52] Compile only the moonlight-common-c sources BroLink uses, and say why build.rs globbed every .c file under the vendored src/ and enet/, so it also built four files nothing links: ConnectionTester.c and SimpleStun.c (public helpers BroLink never calls), RecorderCallbacks.c (only for LC_DEBUG_RECORD_MODE) and ENet's compress.c (moonlight never enables compression). They are gone, and build.rs lists the sources it compiles, so a re-vendor has to decide about each new file. VERSION now records the upstream commits of moonlight-common-c and its ENet and nanors submodules, everything trimmed, and the two local patches with their reasons (the ENet control-channel timeout for relayed streams, and the 150 Mbps bitrate latch), plus how to re-vendor. Co-Authored-By: Claude Opus 5.5 (1M context) --- NOTICE | 4 +- crates/stream/build.rs | 60 +- third_party/moonlight-common-c/VERSION | 47 ++ .../moonlight-common-c/enet/compress.c | 654 ------------------ .../moonlight-common-c/src/ConnectionTester.c | 308 --------- .../src/RecorderCallbacks.c | 102 --- .../moonlight-common-c/src/SimpleStun.c | 194 ------ 7 files changed, 88 insertions(+), 1281 deletions(-) delete mode 100644 third_party/moonlight-common-c/enet/compress.c delete mode 100644 third_party/moonlight-common-c/src/ConnectionTester.c delete mode 100644 third_party/moonlight-common-c/src/RecorderCallbacks.c delete mode 100644 third_party/moonlight-common-c/src/SimpleStun.c diff --git a/NOTICE b/NOTICE index 74d9482..69ddc22 100644 --- a/NOTICE +++ b/NOTICE @@ -10,8 +10,8 @@ Compiled into the client (crates/stream): Includes ENet (MIT, Lee Salzman) and nanors (MIT, sleepybishop). Opus, BSD-3-Clause, Xiph.Org Foundation; statically linked through the audiopus_sys crate. https://opus-codec.org - OpenH264, BSD-2-Clause, Cisco Systems. Only the Windows development - client uses it, as a software H.264 decoder; the Mac uses VideoToolbox. + OpenH264, BSD-2-Clause, Cisco Systems. Only a Windows or Linux viewer + uses it, as a software H.264 decoder; the Mac uses VideoToolbox. It is built from source, so Cisco's binary patent grant does not cover it. https://github.com/cisco/openh264 diff --git a/crates/stream/build.rs b/crates/stream/build.rs index d3a8da2..e6dbed3 100644 --- a/crates/stream/build.rs +++ b/crates/stream/build.rs @@ -1,10 +1,46 @@ //! Compiles moonlight-common-c (with its ENet and Reed-Solomon dependencies) //! and the small C shim that adapts its callback structs to plain function //! pointers. Crypto is not compiled from C: `src/crypto.rs` provides the -//! `Plt*` functions the library expects. +//! `Plt*` functions the library expects. What is vendored, and why, is in +//! `third_party/moonlight-common-c/VERSION`. use std::path::Path; +/// Every C file the library needs, relative to the vendored root. Listed +/// rather than globbed, so a re-vendor that brings new files in has to +/// decide about each one. +const SOURCES: &[&str] = &[ + "src/AudioStream.c", + "src/ByteBuffer.c", + "src/Connection.c", + "src/ControlStream.c", + "src/FakeCallbacks.c", + "src/InputStream.c", + "src/LinkedBlockingQueue.c", + "src/Misc.c", + "src/Platform.c", + "src/PlatformSockets.c", + "src/RtpAudioQueue.c", + "src/RtpVideoQueue.c", + "src/RtspConnection.c", + "src/RtspParser.c", + "src/SdpGenerator.c", + "src/VideoDepacketizer.c", + "src/VideoStream.c", + "enet/callbacks.c", + "enet/host.c", + "enet/list.c", + "enet/packet.c", + "enet/peer.c", + "enet/protocol.c", + // Each is empty on the other platform. + "enet/unix.c", + "enet/win32.c", + "nanors/rs.c", + "nanors/deps/obl/oblas_common.c", + "nanors/deps/obl/oblas_lite.c", +]; + fn main() { let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../third_party/moonlight-common-c"); println!("cargo:rerun-if-changed=csrc"); @@ -27,16 +63,8 @@ fn main() { .define("NDEBUG", None) .define("HAS_SOCKLEN_T", "1") .warnings(false) - .opt_level(2); - for f in list(&root.join("src")) { - b.file(f); - } - for f in list(&root.join("enet")) { - b.file(f); - } - b.file(root.join("nanors/rs.c")) - .file(root.join("nanors/deps/obl/oblas_common.c")) - .file(root.join("nanors/deps/obl/oblas_lite.c")) + .opt_level(2) + .files(SOURCES.iter().map(|f| root.join(f))) .file("csrc/shim.c"); if target_os == "windows" { b.define("_CRT_SECURE_NO_WARNINGS", None); @@ -52,13 +80,3 @@ fn main() { } b.compile("moonlight-common-c"); } - -fn list(dir: &Path) -> Vec { - let mut v: Vec<_> = std::fs::read_dir(dir) - .unwrap_or_else(|e| panic!("{}: {e}", dir.display())) - .filter_map(|e| e.ok().map(|e| e.path())) - .filter(|p| p.extension().is_some_and(|x| x == "c")) - .collect(); - v.sort(); - v -} diff --git a/third_party/moonlight-common-c/VERSION b/third_party/moonlight-common-c/VERSION index 5819478..f8cf32b 100644 --- a/third_party/moonlight-common-c/VERSION +++ b/third_party/moonlight-common-c/VERSION @@ -1 +1,48 @@ 874ac9548f1bd6f095ef2b435c42cdde460e7821 2026-08-18 + +moonlight-common-c: the GameStream protocol engine BroLink's viewer links +(RTSP handshake, control and input channels, RTP video/audio reassembly with +FEC). crates/stream/build.rs compiles the files listed in its SOURCES array +and nothing else. GPL-3.0-or-later (LICENSE.txt); ENet and nanors keep their +own MIT licences (enet/LICENSE, nanors/LICENSE). + +Upstream + https://github.com/moonlight-stream/moonlight-common-c + 874ac9548f1bd6f095ef2b435c42cdde460e7821 (2026-08-18, + "Add LI_CTYPE_STEAM controller type") + enet submodule https://github.com/cgutman/enet + aca87840b57f045a1f7f9299e4b1b9b8e2a5e2f1 (v1.3.15-118-gaca8784) + nanors submodule https://github.com/sleepybishop/nanors + b1e3c22ca0cdc0bb83e3cd6ed1a2fc77869ed99a + +Trimmed (not present here) + - CMakeLists.txt, cmake/, README.md, and the submodules' build systems, + docs, tests and CI files: build.rs compiles the sources directly. + - src/PlatformCrypto.c: crates/stream/src/crypto.rs implements the same + Plt* functions in Rust, so no OpenSSL or mbedTLS is linked. + - src/ConnectionTester.c (LiTestClientConnectivity) and src/SimpleStun.c + (LiFindExternalAddressIP4): public helpers BroLink never calls. + Limelight.h still declares them; nothing may call them. + - src/RecorderCallbacks.c: only used when LC_DEBUG_RECORD_MODE is defined. + - enet/compress.c: ENet's range coder; moonlight never enables compression. + - nanors rs16*, oblas16*: the 16-bit field code; moonlight uses GF(2^8). + Everything else is byte-for-byte upstream except the two patches below. + +Local patches + 1. src/ControlStream.c: enet_peer_timeout(peer, 2, 10000, 10000) becomes + enet_peer_timeout(peer, 3, 15000, 30000). BroLink streams often cross + a Tailscale peer relay on a long path, where a burst of loss silences + the reliable control channel for seconds; upstream's 10 s limit then + declared the PC dead and ended long sessions with "Connection lost + (code -1)". A dead PC is still noticed within 30 s. + 2. src/SdpGenerator.c: the video bitrate latch is capped at 150000 kbps + instead of GFE's 100000. Sunshine has no 100 Mbps limit and BroLink's + bitrate setting goes to 150 Mbps. + +Re-vendoring + Check out the new upstream commit with submodules, copy src/, enet/*.c, + enet/include/, enet/LICENSE, nanors/rs.[ch], nanors/deps/obl/{oblas_common, + oblas_lite}.[ch], nanors/deps/obl/gf2_8_*.h and nanors/LICENSE, drop the + files listed above, re-apply both patches, update SOURCES in build.rs if + upstream added or removed files, and update the commits here. Then run + `cargo test -p brolink-stream` and a live stream_real test. diff --git a/third_party/moonlight-common-c/enet/compress.c b/third_party/moonlight-common-c/enet/compress.c deleted file mode 100644 index 784489a..0000000 --- a/third_party/moonlight-common-c/enet/compress.c +++ /dev/null @@ -1,654 +0,0 @@ -/** - @file compress.c - @brief An adaptive order-2 PPM range coder -*/ -#define ENET_BUILDING_LIB 1 -#include -#include "enet/enet.h" - -typedef struct _ENetSymbol -{ - /* binary indexed tree of symbols */ - enet_uint8 value; - enet_uint8 count; - enet_uint16 under; - enet_uint16 left, right; - - /* context defined by this symbol */ - enet_uint16 symbols; - enet_uint16 escapes; - enet_uint16 total; - enet_uint16 parent; -} ENetSymbol; - -/* adaptation constants tuned aggressively for small packet sizes rather than large file compression */ -enum -{ - ENET_RANGE_CODER_TOP = 1<<24, - ENET_RANGE_CODER_BOTTOM = 1<<16, - - ENET_CONTEXT_SYMBOL_DELTA = 3, - ENET_CONTEXT_SYMBOL_MINIMUM = 1, - ENET_CONTEXT_ESCAPE_MINIMUM = 1, - - ENET_SUBCONTEXT_ORDER = 2, - ENET_SUBCONTEXT_SYMBOL_DELTA = 2, - ENET_SUBCONTEXT_ESCAPE_DELTA = 5 -}; - -/* context exclusion roughly halves compression speed, so disable for now */ -#undef ENET_CONTEXT_EXCLUSION - -typedef struct _ENetRangeCoder -{ - /* only allocate enough symbols for reasonable MTUs, would need to be larger for large file compression */ - ENetSymbol symbols[4096]; -} ENetRangeCoder; - -void * -enet_range_coder_create (void) -{ - ENetRangeCoder * rangeCoder = (ENetRangeCoder *) enet_malloc (sizeof (ENetRangeCoder)); - if (rangeCoder == NULL) - return NULL; - - return rangeCoder; -} - -void -enet_range_coder_destroy (void * context) -{ - ENetRangeCoder * rangeCoder = (ENetRangeCoder *) context; - if (rangeCoder == NULL) - return; - - enet_free (rangeCoder); -} - -#define ENET_SYMBOL_CREATE(symbol, value_, count_) \ -{ \ - symbol = & rangeCoder -> symbols [nextSymbol ++]; \ - symbol -> value = value_; \ - symbol -> count = count_; \ - symbol -> under = count_; \ - symbol -> left = 0; \ - symbol -> right = 0; \ - symbol -> symbols = 0; \ - symbol -> escapes = 0; \ - symbol -> total = 0; \ - symbol -> parent = 0; \ -} - -#define ENET_CONTEXT_CREATE(context, escapes_, minimum) \ -{ \ - ENET_SYMBOL_CREATE (context, 0, 0); \ - (context) -> escapes = escapes_; \ - (context) -> total = escapes_ + 256*minimum; \ - (context) -> symbols = 0; \ -} - -static enet_uint16 -enet_symbol_rescale (ENetSymbol * symbol) -{ - enet_uint16 total = 0; - for (;;) - { - symbol -> count -= symbol->count >> 1; - symbol -> under = symbol -> count; - if (symbol -> left) - symbol -> under += enet_symbol_rescale (symbol + symbol -> left); - total += symbol -> under; - if (! symbol -> right) break; - symbol += symbol -> right; - } - return total; -} - -#define ENET_CONTEXT_RESCALE(context, minimum) \ -{ \ - (context) -> total = (context) -> symbols ? enet_symbol_rescale ((context) + (context) -> symbols) : 0; \ - (context) -> escapes -= (context) -> escapes >> 1; \ - (context) -> total += (context) -> escapes + 256*minimum; \ -} - -#define ENET_RANGE_CODER_OUTPUT(value) \ -{ \ - if (outData >= outEnd) \ - return 0; \ - * outData ++ = value; \ -} - -#define ENET_RANGE_CODER_ENCODE(under, count, total) \ -{ \ - encodeRange /= (total); \ - encodeLow += (under) * encodeRange; \ - encodeRange *= (count); \ - for (;;) \ - { \ - if((encodeLow ^ (encodeLow + encodeRange)) >= ENET_RANGE_CODER_TOP) \ - { \ - if(encodeRange >= ENET_RANGE_CODER_BOTTOM) break; \ - encodeRange = -encodeLow & (ENET_RANGE_CODER_BOTTOM - 1); \ - } \ - ENET_RANGE_CODER_OUTPUT (encodeLow >> 24); \ - encodeRange <<= 8; \ - encodeLow <<= 8; \ - } \ -} - -#define ENET_RANGE_CODER_FLUSH \ -{ \ - while (encodeLow) \ - { \ - ENET_RANGE_CODER_OUTPUT (encodeLow >> 24); \ - encodeLow <<= 8; \ - } \ -} - -#define ENET_RANGE_CODER_FREE_SYMBOLS \ -{ \ - if (nextSymbol >= sizeof (rangeCoder -> symbols) / sizeof (ENetSymbol) - ENET_SUBCONTEXT_ORDER ) \ - { \ - nextSymbol = 0; \ - ENET_CONTEXT_CREATE (root, ENET_CONTEXT_ESCAPE_MINIMUM, ENET_CONTEXT_SYMBOL_MINIMUM); \ - predicted = 0; \ - order = 0; \ - } \ -} - -#define ENET_CONTEXT_ENCODE(context, symbol_, value_, under_, count_, update, minimum) \ -{ \ - under_ = value*minimum; \ - count_ = minimum; \ - if (! (context) -> symbols) \ - { \ - ENET_SYMBOL_CREATE (symbol_, value_, update); \ - (context) -> symbols = symbol_ - (context); \ - } \ - else \ - { \ - ENetSymbol * node = (context) + (context) -> symbols; \ - for (;;) \ - { \ - if (value_ < node -> value) \ - { \ - node -> under += update; \ - if (node -> left) { node += node -> left; continue; } \ - ENET_SYMBOL_CREATE (symbol_, value_, update); \ - node -> left = symbol_ - node; \ - } \ - else \ - if (value_ > node -> value) \ - { \ - under_ += node -> under; \ - if (node -> right) { node += node -> right; continue; } \ - ENET_SYMBOL_CREATE (symbol_, value_, update); \ - node -> right = symbol_ - node; \ - } \ - else \ - { \ - count_ += node -> count; \ - under_ += node -> under - node -> count; \ - node -> under += update; \ - node -> count += update; \ - symbol_ = node; \ - } \ - break; \ - } \ - } \ -} - -#ifdef ENET_CONTEXT_EXCLUSION -static const ENetSymbol emptyContext = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - -#define ENET_CONTEXT_WALK(context, body) \ -{ \ - const ENetSymbol * node = (context) + (context) -> symbols; \ - const ENetSymbol * stack [256]; \ - size_t stackSize = 0; \ - while (node -> left) \ - { \ - stack [stackSize ++] = node; \ - node += node -> left; \ - } \ - for (;;) \ - { \ - body; \ - if (node -> right) \ - { \ - node += node -> right; \ - while (node -> left) \ - { \ - stack [stackSize ++] = node; \ - node += node -> left; \ - } \ - } \ - else \ - if (stackSize <= 0) \ - break; \ - else \ - node = stack [-- stackSize]; \ - } \ -} - -#define ENET_CONTEXT_ENCODE_EXCLUDE(context, value_, under, total, minimum) \ -ENET_CONTEXT_WALK(context, { \ - if (node -> value != value_) \ - { \ - enet_uint16 parentCount = rangeCoder -> symbols [node -> parent].count + minimum; \ - if (node -> value < value_) \ - under -= parentCount; \ - total -= parentCount; \ - } \ -}) -#endif - -size_t -enet_range_coder_compress (void * context, const ENetBuffer * inBuffers, size_t inBufferCount, size_t inLimit, enet_uint8 * outData, size_t outLimit) -{ - ENetRangeCoder * rangeCoder = (ENetRangeCoder *) context; - enet_uint8 * outStart = outData, * outEnd = & outData [outLimit]; - const enet_uint8 * inData, * inEnd; - enet_uint32 encodeLow = 0, encodeRange = ~0; - ENetSymbol * root; - enet_uint16 predicted = 0; - size_t order = 0, nextSymbol = 0; - - if (rangeCoder == NULL || inBufferCount <= 0 || inLimit <= 0) - return 0; - - inData = (const enet_uint8 *) inBuffers -> data; - inEnd = & inData [inBuffers -> dataLength]; - inBuffers ++; - inBufferCount --; - - ENET_CONTEXT_CREATE (root, ENET_CONTEXT_ESCAPE_MINIMUM, ENET_CONTEXT_SYMBOL_MINIMUM); - - for (;;) - { - ENetSymbol * subcontext, * symbol; -#ifdef ENET_CONTEXT_EXCLUSION - const ENetSymbol * childContext = & emptyContext; -#endif - enet_uint8 value; - enet_uint16 count, under, * parent = & predicted, total; - if (inData >= inEnd) - { - if (inBufferCount <= 0) - break; - inData = (const enet_uint8 *) inBuffers -> data; - inEnd = & inData [inBuffers -> dataLength]; - inBuffers ++; - inBufferCount --; - } - value = * inData ++; - - for (subcontext = & rangeCoder -> symbols [predicted]; - subcontext != root; -#ifdef ENET_CONTEXT_EXCLUSION - childContext = subcontext, -#endif - subcontext = & rangeCoder -> symbols [subcontext -> parent]) - { - ENET_CONTEXT_ENCODE (subcontext, symbol, value, under, count, ENET_SUBCONTEXT_SYMBOL_DELTA, 0); - * parent = symbol - rangeCoder -> symbols; - parent = & symbol -> parent; - total = subcontext -> total; -#ifdef ENET_CONTEXT_EXCLUSION - if (childContext -> total > ENET_SUBCONTEXT_SYMBOL_DELTA + ENET_SUBCONTEXT_ESCAPE_DELTA) - ENET_CONTEXT_ENCODE_EXCLUDE (childContext, value, under, total, 0); -#endif - if (count > 0) - { - ENET_RANGE_CODER_ENCODE (subcontext -> escapes + under, count, total); - } - else - { - if (subcontext -> escapes > 0 && subcontext -> escapes < total) - ENET_RANGE_CODER_ENCODE (0, subcontext -> escapes, total); - subcontext -> escapes += ENET_SUBCONTEXT_ESCAPE_DELTA; - subcontext -> total += ENET_SUBCONTEXT_ESCAPE_DELTA; - } - subcontext -> total += ENET_SUBCONTEXT_SYMBOL_DELTA; - if (count > 0xFF - 2*ENET_SUBCONTEXT_SYMBOL_DELTA || subcontext -> total > ENET_RANGE_CODER_BOTTOM - 0x100) - ENET_CONTEXT_RESCALE (subcontext, 0); - if (count > 0) goto nextInput; - } - - ENET_CONTEXT_ENCODE (root, symbol, value, under, count, ENET_CONTEXT_SYMBOL_DELTA, ENET_CONTEXT_SYMBOL_MINIMUM); - * parent = symbol - rangeCoder -> symbols; - parent = & symbol -> parent; - total = root -> total; -#ifdef ENET_CONTEXT_EXCLUSION - if (childContext -> total > ENET_SUBCONTEXT_SYMBOL_DELTA + ENET_SUBCONTEXT_ESCAPE_DELTA) - ENET_CONTEXT_ENCODE_EXCLUDE (childContext, value, under, total, ENET_CONTEXT_SYMBOL_MINIMUM); -#endif - ENET_RANGE_CODER_ENCODE (root -> escapes + under, count, total); - root -> total += ENET_CONTEXT_SYMBOL_DELTA; - if (count > 0xFF - 2*ENET_CONTEXT_SYMBOL_DELTA + ENET_CONTEXT_SYMBOL_MINIMUM || root -> total > ENET_RANGE_CODER_BOTTOM - 0x100) - ENET_CONTEXT_RESCALE (root, ENET_CONTEXT_SYMBOL_MINIMUM); - - nextInput: - if (order >= ENET_SUBCONTEXT_ORDER) - predicted = rangeCoder -> symbols [predicted].parent; - else - order ++; - ENET_RANGE_CODER_FREE_SYMBOLS; - } - - ENET_RANGE_CODER_FLUSH; - - return (size_t) (outData - outStart); -} - -#define ENET_RANGE_CODER_SEED \ -{ \ - if (inData < inEnd) decodeCode |= * inData ++ << 24; \ - if (inData < inEnd) decodeCode |= * inData ++ << 16; \ - if (inData < inEnd) decodeCode |= * inData ++ << 8; \ - if (inData < inEnd) decodeCode |= * inData ++; \ -} - -#define ENET_RANGE_CODER_READ(total) ((decodeCode - decodeLow) / (decodeRange /= (total))) - -#define ENET_RANGE_CODER_DECODE(under, count, total) \ -{ \ - decodeLow += (under) * decodeRange; \ - decodeRange *= (count); \ - for (;;) \ - { \ - if((decodeLow ^ (decodeLow + decodeRange)) >= ENET_RANGE_CODER_TOP) \ - { \ - if(decodeRange >= ENET_RANGE_CODER_BOTTOM) break; \ - decodeRange = -decodeLow & (ENET_RANGE_CODER_BOTTOM - 1); \ - } \ - decodeCode <<= 8; \ - if (inData < inEnd) \ - decodeCode |= * inData ++; \ - decodeRange <<= 8; \ - decodeLow <<= 8; \ - } \ -} - -#define ENET_CONTEXT_DECODE(context, symbol_, code, value_, under_, count_, update, minimum, createRoot, visitNode, createRight, createLeft) \ -{ \ - under_ = 0; \ - count_ = minimum; \ - if (! (context) -> symbols) \ - { \ - createRoot; \ - } \ - else \ - { \ - ENetSymbol * node = (context) + (context) -> symbols; \ - for (;;) \ - { \ - enet_uint16 after = under_ + node -> under + (node -> value + 1)*minimum, before = node -> count + minimum; \ - visitNode; \ - if (code >= after) \ - { \ - under_ += node -> under; \ - if (node -> right) { node += node -> right; continue; } \ - createRight; \ - } \ - else \ - if (code < after - before) \ - { \ - node -> under += update; \ - if (node -> left) { node += node -> left; continue; } \ - createLeft; \ - } \ - else \ - { \ - value_ = node -> value; \ - count_ += node -> count; \ - under_ = after - before; \ - node -> under += update; \ - node -> count += update; \ - symbol_ = node; \ - } \ - break; \ - } \ - } \ -} - -#define ENET_CONTEXT_TRY_DECODE(context, symbol_, code, value_, under_, count_, update, minimum, exclude) \ -ENET_CONTEXT_DECODE (context, symbol_, code, value_, under_, count_, update, minimum, return 0, exclude (node -> value, after, before), return 0, return 0) - -#define ENET_CONTEXT_ROOT_DECODE(context, symbol_, code, value_, under_, count_, update, minimum, exclude) \ -ENET_CONTEXT_DECODE (context, symbol_, code, value_, under_, count_, update, minimum, \ - { \ - value_ = code / minimum; \ - under_ = code - code%minimum; \ - ENET_SYMBOL_CREATE (symbol_, value_, update); \ - (context) -> symbols = symbol_ - (context); \ - }, \ - exclude (node -> value, after, before), \ - { \ - value_ = node->value + 1 + (code - after)/minimum; \ - under_ = code - (code - after)%minimum; \ - ENET_SYMBOL_CREATE (symbol_, value_, update); \ - node -> right = symbol_ - node; \ - }, \ - { \ - value_ = node->value - 1 - (after - before - code - 1)/minimum; \ - under_ = code - (after - before - code - 1)%minimum; \ - ENET_SYMBOL_CREATE (symbol_, value_, update); \ - node -> left = symbol_ - node; \ - }) \ - -#ifdef ENET_CONTEXT_EXCLUSION -typedef struct _ENetExclude -{ - enet_uint8 value; - enet_uint16 under; -} ENetExclude; - -#define ENET_CONTEXT_DECODE_EXCLUDE(context, total, minimum) \ -{ \ - enet_uint16 under = 0; \ - nextExclude = excludes; \ - ENET_CONTEXT_WALK (context, { \ - under += rangeCoder -> symbols [node -> parent].count + minimum; \ - nextExclude -> value = node -> value; \ - nextExclude -> under = under; \ - nextExclude ++; \ - }); \ - total -= under; \ -} - -#define ENET_CONTEXT_EXCLUDED(value_, after, before) \ -{ \ - size_t low = 0, high = nextExclude - excludes; \ - for(;;) \ - { \ - size_t mid = (low + high) >> 1; \ - const ENetExclude * exclude = & excludes [mid]; \ - if (value_ < exclude -> value) \ - { \ - if (low + 1 < high) \ - { \ - high = mid; \ - continue; \ - } \ - if (exclude > excludes) \ - after -= exclude [-1].under; \ - } \ - else \ - { \ - if (value_ > exclude -> value) \ - { \ - if (low + 1 < high) \ - { \ - low = mid; \ - continue; \ - } \ - } \ - else \ - before = 0; \ - after -= exclude -> under; \ - } \ - break; \ - } \ -} -#endif - -#define ENET_CONTEXT_NOT_EXCLUDED(value_, after, before) - -size_t -enet_range_coder_decompress (void * context, const enet_uint8 * inData, size_t inLimit, enet_uint8 * outData, size_t outLimit) -{ - ENetRangeCoder * rangeCoder = (ENetRangeCoder *) context; - enet_uint8 * outStart = outData, * outEnd = & outData [outLimit]; - const enet_uint8 * inEnd = & inData [inLimit]; - enet_uint32 decodeLow = 0, decodeCode = 0, decodeRange = ~0; - ENetSymbol * root; - enet_uint16 predicted = 0; - size_t order = 0, nextSymbol = 0; -#ifdef ENET_CONTEXT_EXCLUSION - ENetExclude excludes [256]; - ENetExclude * nextExclude = excludes; -#endif - - if (rangeCoder == NULL || inLimit <= 0) - return 0; - - ENET_CONTEXT_CREATE (root, ENET_CONTEXT_ESCAPE_MINIMUM, ENET_CONTEXT_SYMBOL_MINIMUM); - - ENET_RANGE_CODER_SEED; - - for (;;) - { - ENetSymbol * subcontext, * symbol, * patch; -#ifdef ENET_CONTEXT_EXCLUSION - const ENetSymbol * childContext = & emptyContext; -#endif - enet_uint8 value = 0; - enet_uint16 code, under, count, bottom, * parent = & predicted, total; - - for (subcontext = & rangeCoder -> symbols [predicted]; - subcontext != root; -#ifdef ENET_CONTEXT_EXCLUSION - childContext = subcontext, -#endif - subcontext = & rangeCoder -> symbols [subcontext -> parent]) - { - if (subcontext -> escapes <= 0) - continue; - total = subcontext -> total; -#ifdef ENET_CONTEXT_EXCLUSION - if (childContext -> total > 0) - ENET_CONTEXT_DECODE_EXCLUDE (childContext, total, 0); -#endif - if (subcontext -> escapes >= total) - continue; - code = ENET_RANGE_CODER_READ (total); - if (code < subcontext -> escapes) - { - ENET_RANGE_CODER_DECODE (0, subcontext -> escapes, total); - continue; - } - code -= subcontext -> escapes; -#ifdef ENET_CONTEXT_EXCLUSION - if (childContext -> total > 0) - { - ENET_CONTEXT_TRY_DECODE (subcontext, symbol, code, value, under, count, ENET_SUBCONTEXT_SYMBOL_DELTA, 0, ENET_CONTEXT_EXCLUDED); - } - else -#endif - { - ENET_CONTEXT_TRY_DECODE (subcontext, symbol, code, value, under, count, ENET_SUBCONTEXT_SYMBOL_DELTA, 0, ENET_CONTEXT_NOT_EXCLUDED); - } - bottom = symbol - rangeCoder -> symbols; - ENET_RANGE_CODER_DECODE (subcontext -> escapes + under, count, total); - subcontext -> total += ENET_SUBCONTEXT_SYMBOL_DELTA; - if (count > 0xFF - 2*ENET_SUBCONTEXT_SYMBOL_DELTA || subcontext -> total > ENET_RANGE_CODER_BOTTOM - 0x100) - ENET_CONTEXT_RESCALE (subcontext, 0); - goto patchContexts; - } - - total = root -> total; -#ifdef ENET_CONTEXT_EXCLUSION - if (childContext -> total > 0) - ENET_CONTEXT_DECODE_EXCLUDE (childContext, total, ENET_CONTEXT_SYMBOL_MINIMUM); -#endif - code = ENET_RANGE_CODER_READ (total); - if (code < root -> escapes) - { - ENET_RANGE_CODER_DECODE (0, root -> escapes, total); - break; - } - code -= root -> escapes; -#ifdef ENET_CONTEXT_EXCLUSION - if (childContext -> total > 0) - { - ENET_CONTEXT_ROOT_DECODE (root, symbol, code, value, under, count, ENET_CONTEXT_SYMBOL_DELTA, ENET_CONTEXT_SYMBOL_MINIMUM, ENET_CONTEXT_EXCLUDED); - } - else -#endif - { - ENET_CONTEXT_ROOT_DECODE (root, symbol, code, value, under, count, ENET_CONTEXT_SYMBOL_DELTA, ENET_CONTEXT_SYMBOL_MINIMUM, ENET_CONTEXT_NOT_EXCLUDED); - } - bottom = symbol - rangeCoder -> symbols; - ENET_RANGE_CODER_DECODE (root -> escapes + under, count, total); - root -> total += ENET_CONTEXT_SYMBOL_DELTA; - if (count > 0xFF - 2*ENET_CONTEXT_SYMBOL_DELTA + ENET_CONTEXT_SYMBOL_MINIMUM || root -> total > ENET_RANGE_CODER_BOTTOM - 0x100) - ENET_CONTEXT_RESCALE (root, ENET_CONTEXT_SYMBOL_MINIMUM); - - patchContexts: - for (patch = & rangeCoder -> symbols [predicted]; - patch != subcontext; - patch = & rangeCoder -> symbols [patch -> parent]) - { - ENET_CONTEXT_ENCODE (patch, symbol, value, under, count, ENET_SUBCONTEXT_SYMBOL_DELTA, 0); - * parent = symbol - rangeCoder -> symbols; - parent = & symbol -> parent; - if (count <= 0) - { - patch -> escapes += ENET_SUBCONTEXT_ESCAPE_DELTA; - patch -> total += ENET_SUBCONTEXT_ESCAPE_DELTA; - } - patch -> total += ENET_SUBCONTEXT_SYMBOL_DELTA; - if (count > 0xFF - 2*ENET_SUBCONTEXT_SYMBOL_DELTA || patch -> total > ENET_RANGE_CODER_BOTTOM - 0x100) - ENET_CONTEXT_RESCALE (patch, 0); - } - * parent = bottom; - - ENET_RANGE_CODER_OUTPUT (value); - - if (order >= ENET_SUBCONTEXT_ORDER) - predicted = rangeCoder -> symbols [predicted].parent; - else - order ++; - ENET_RANGE_CODER_FREE_SYMBOLS; - } - - return (size_t) (outData - outStart); -} - -/** @defgroup host ENet host functions - @{ -*/ - -/** Sets the packet compressor the host should use to the default range coder. - @param host host to enable the range coder for - @returns 0 on success, < 0 on failure -*/ -int -enet_host_compress_with_range_coder (ENetHost * host) -{ - ENetCompressor compressor; - memset (& compressor, 0, sizeof (compressor)); - compressor.context = enet_range_coder_create(); - if (compressor.context == NULL) - return -1; - compressor.compress = enet_range_coder_compress; - compressor.decompress = enet_range_coder_decompress; - compressor.destroy = enet_range_coder_destroy; - enet_host_compress (host, & compressor); - return 0; -} - -/** @} */ - - diff --git a/third_party/moonlight-common-c/src/ConnectionTester.c b/third_party/moonlight-common-c/src/ConnectionTester.c deleted file mode 100644 index d993834..0000000 --- a/third_party/moonlight-common-c/src/ConnectionTester.c +++ /dev/null @@ -1,308 +0,0 @@ -#include "Limelight-internal.h" - -#define TEST_PORT_TIMEOUT_SEC 3 - -#define VALID_PORT_FLAG_MASK (ML_PORT_FLAG_TCP_47984 | \ - ML_PORT_FLAG_TCP_47989 | \ - ML_PORT_FLAG_TCP_48010 | \ - ML_PORT_FLAG_UDP_47998 | \ - ML_PORT_FLAG_UDP_47999 | \ - ML_PORT_FLAG_UDP_48000 | \ - ML_PORT_FLAG_UDP_48010) - -#define PORT_FLAGS_MAX_COUNT 32 - -#define MTU_TEST_SIZE 1040 - -unsigned int LiGetPortFlagsFromStage(int stage) -{ - switch (stage) - { - case STAGE_RTSP_HANDSHAKE: - // GFE 3.22 requires a successful ping on 48000 to complete RTSP handshake - return ML_PORT_FLAG_TCP_48010 | ML_PORT_FLAG_UDP_48010 | ML_PORT_FLAG_UDP_48000; - - case STAGE_CONTROL_STREAM_START: - return ML_PORT_FLAG_UDP_47999; - - default: - return 0; - } -} - -unsigned int LiGetPortFlagsFromTerminationErrorCode(int errorCode) -{ - switch (errorCode) - { - case ML_ERROR_NO_VIDEO_TRAFFIC: - // Video is UDP 47998, but we'll also test UDP 48000 because - // we don't have an equivalent audio traffic error. - return ML_PORT_FLAG_UDP_47998 | ML_PORT_FLAG_UDP_48000; - - default: - return 0; - } -} - -int LiGetProtocolFromPortFlagIndex(int portFlagIndex) -{ - // The lower byte is reserved for TCP - return (portFlagIndex >= 8) ? IPPROTO_UDP : IPPROTO_TCP; -} - -unsigned short LiGetPortFromPortFlagIndex(int portFlagIndex) -{ - switch (portFlagIndex) - { - // TCP ports - case ML_PORT_INDEX_TCP_47984: - return 47984; - case ML_PORT_INDEX_TCP_47989: - return 47989; - case ML_PORT_INDEX_TCP_48010: - return 48010; - - // UDP ports - case ML_PORT_INDEX_UDP_47998: - return 47998; - case ML_PORT_INDEX_UDP_47999: - return 47999; - case ML_PORT_INDEX_UDP_48000: - return 48000; - case ML_PORT_INDEX_UDP_48010: - return 48010; - - default: - LC_ASSERT(false); - return 0; - } -} - -void LiStringifyPortFlags(unsigned int portFlags, const char* separator, char* outputBuffer, int outputBufferLength) -{ - // Initialize the output buffer to an empty string - outputBuffer[0] = 0; - - // If there is no separator specified, use an empty string - if (separator == NULL) { - separator = ""; - } - - int offset = 0; - for (int i = 0; i < PORT_FLAGS_MAX_COUNT; i++) { - if (portFlags & (1U << i)) { - const char* protoStr = LiGetProtocolFromPortFlagIndex(i) == IPPROTO_UDP ? "UDP" : "TCP"; - offset += snprintf(&outputBuffer[offset], outputBufferLength - offset, "%s%s %u", - offset != 0 ? separator : "", - protoStr, - LiGetPortFromPortFlagIndex(i)); - if (outputBufferLength - offset <= 0) { - // snprintf() will return the desired length if the buffer is too small, - // so it is possible for this calculation to be negative. - break; - } - } - } -} - -unsigned int LiTestClientConnectivity(const char* testServer, unsigned short referencePort, unsigned int testPortFlags) -{ - unsigned int failingPortFlags; - struct sockaddr_storage address; - SOCKADDR_LEN address_length; - int i; - int err; - SOCKET sockets[PORT_FLAGS_MAX_COUNT]; - - // Mask out invalid ports from the port flags - testPortFlags &= VALID_PORT_FLAG_MASK; - failingPortFlags = testPortFlags; - - // If no ports were specified, just return 0 - if (testPortFlags == 0) { - return 0; - } - - // Initialize sockets array to -1 - memset(sockets, 0xFF, sizeof(sockets)); - - err = initializePlatformSockets(); - if (err != 0) { - Limelog("Failed to initialize sockets: %d\n", err); - return ML_TEST_RESULT_INCONCLUSIVE; - } - - err = resolveHostName(testServer, AF_UNSPEC, TCP_PORT_FLAG_ALWAYS_TEST | referencePort, &address, &address_length); - if (err != 0) { - failingPortFlags = ML_TEST_RESULT_INCONCLUSIVE; - goto Exit; - } - - for (i = 0; i < PORT_FLAGS_MAX_COUNT; i++) { - if (testPortFlags & (1U << i)) { - sockets[i] = createSocket(address.ss_family, - LiGetProtocolFromPortFlagIndex(i) == IPPROTO_UDP ? SOCK_DGRAM : SOCK_STREAM, - LiGetProtocolFromPortFlagIndex(i), - true); - if (sockets[i] == INVALID_SOCKET) { - err = LastSocketFail(); - Limelog("Failed to create socket: %d\n", err); - failingPortFlags = ML_TEST_RESULT_INCONCLUSIVE; - goto Exit; - } - - SET_PORT((LC_SOCKADDR*)&address, LiGetPortFromPortFlagIndex(i)); - if (LiGetProtocolFromPortFlagIndex(i) == IPPROTO_TCP) { - // Initiate an asynchronous connection - err = connect(sockets[i], (struct sockaddr*)&address, address_length); - if (err < 0) { - err = (int)LastSocketError(); - if (err != EWOULDBLOCK && err != EAGAIN && err != EINPROGRESS) { - Limelog("Failed to start async connect to TCP %u: %d\n", LiGetPortFromPortFlagIndex(i), err); - - // Mask off this bit so we don't try to include it in pollSockets() below - testPortFlags &= ~(1U << i); - } - } - } - else { - const char buf[MTU_TEST_SIZE] = "moonlight-ctest"; - int j; - - // Send a few packets since UDP is unreliable - for (j = 0; j < 3; j++) { - err = sendto(sockets[i], buf, sizeof(buf), 0, (struct sockaddr*)&address, address_length); - if (err < 0) { - err = (int)LastSocketError(); - Limelog("Failed to send test packet to UDP %u: %d\n", LiGetPortFromPortFlagIndex(i), err); - - // Mask off this bit so we don't try to include it in pollSockets() below - testPortFlags &= ~(1U << i); - - break; - } - - PltSleepMs(50); - } - } - } - } - - // Continue to call pollSockets() until we have no more sockets to wait for, - // or our pollSockets() call times out. - while (testPortFlags != 0) { - int nfds; - struct pollfd pfds[PORT_FLAGS_MAX_COUNT]; - - nfds = 0; - - // Fill out our FD sets - for (i = 0; i < PORT_FLAGS_MAX_COUNT; i++) { - if (testPortFlags & (1U << i)) { - pfds[nfds].fd = sockets[i]; - - if (LiGetProtocolFromPortFlagIndex(i) == IPPROTO_UDP) { - // Watch for readability on UDP sockets - pfds[nfds].events = POLLIN; - } - else { - // Watch for writeability on TCP sockets - pfds[nfds].events = POLLOUT; - } - - nfds++; - } - } - - // Wait for the to complete or the timeout to elapse. - // NB: The timeout resets each time we get a valid response on a port, - // but that's probably fine. - err = pollSockets(pfds, nfds, TEST_PORT_TIMEOUT_SEC * 1000); - if (err < 0) { - // pollSockets() failed - err = LastSocketError(); - Limelog("pollSockets() failed: %d\n", err); - failingPortFlags = ML_TEST_RESULT_INCONCLUSIVE; - goto Exit; - } - else if (err == 0) { - // pollSockets() timed out - Limelog("Connection timed out after %d seconds\n", TEST_PORT_TIMEOUT_SEC); - break; - } - - // We know something was signalled. Now we just need to find out what. - for (i = 0; i < nfds; i++) { - if (pfds[i].revents != 0) { - int portIndex; - - // This socket was signalled. Figure out what port it was. - for (portIndex = 0; portIndex < PORT_FLAGS_MAX_COUNT; portIndex++) { - if (sockets[portIndex] == pfds[i].fd) { - LC_ASSERT(testPortFlags & (1U << portIndex)); - break; - } - } - - LC_ASSERT(portIndex != PORT_FLAGS_MAX_COUNT); - - if (LiGetProtocolFromPortFlagIndex(portIndex) == IPPROTO_UDP) { - char buf[MTU_TEST_SIZE]; - - // A UDP socket was signalled. This could be because we got - // a packet from the test server, or it could be because we - // received an ICMP error which will be given to us from - // recvfrom(). - testPortFlags &= ~(1U << portIndex); - - // Check if the socket can be successfully read now - err = recvfrom(sockets[portIndex], buf, sizeof(buf), 0, NULL, NULL); - if (err >= 0) { - // The UDP test was a success. - failingPortFlags &= ~(1U << portIndex); - - Limelog("UDP port %u test successful\n", LiGetPortFromPortFlagIndex(portIndex)); - } - else { - err = LastSocketError(); - Limelog("UDP port %u test failed: %d\n", LiGetPortFromPortFlagIndex(portIndex), err); - } - } - else { - // A TCP socket was signalled - SOCKADDR_LEN len = sizeof(err); - getsockopt(sockets[portIndex], SOL_SOCKET, SO_ERROR, (char*)&err, &len); - if (err != 0 || (pfds[i].revents & POLLERR)) { - // Get the error code - err = (err != 0) ? err : LastSocketFail(); - } - - // The TCP test has completed for this port - testPortFlags &= ~(1U << portIndex); - if (err == 0) { - // The TCP test was a success - failingPortFlags &= ~(1U << portIndex); - - Limelog("TCP port %u test successful\n", LiGetPortFromPortFlagIndex(portIndex)); - } - else { - Limelog("TCP port %u test failed: %d\n", LiGetPortFromPortFlagIndex(portIndex), err); - } - } - } - } - - // Next iteration, we'll remove the matching sockets from our FD set and - // call select() again to wait on the remaining sockets. - } - -Exit: - for (i = 0; i < PORT_FLAGS_MAX_COUNT; i++) { - if (sockets[i] != INVALID_SOCKET) { - closeSocket(sockets[i]); - } - } - - cleanupPlatformSockets(); - return failingPortFlags; -} diff --git a/third_party/moonlight-common-c/src/RecorderCallbacks.c b/third_party/moonlight-common-c/src/RecorderCallbacks.c deleted file mode 100644 index 840ac19..0000000 --- a/third_party/moonlight-common-c/src/RecorderCallbacks.c +++ /dev/null @@ -1,102 +0,0 @@ -#ifdef _WIN32 -// Don't warn for fopen() usage -#define _CRT_SECURE_NO_WARNINGS 1 -#endif - -#include "Limelight-internal.h" - -static FILE* videoFile; -static FILE* audioFile; - -static DECODER_RENDERER_CALLBACKS realDrCallbacks; -static AUDIO_RENDERER_CALLBACKS realArCallbacks; - -static int recDrSetup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) -{ - const char* path = context; - - if (path != NULL) { - videoFile = fopen(path, "wb"); - if (videoFile == NULL) { - return -1; - } - } - else { - Limelog("Video recording will not be enabled - file path not specified in drContext!\n"); - } - - return realDrCallbacks.setup(videoFormat, width, height, redrawRate, NULL, drFlags); -} - -static void recDrCleanup(void) -{ - if (videoFile != NULL) { - fclose(videoFile); - videoFile = NULL; - } - - realDrCallbacks.cleanup(); -} - -static int recDrSubmitDecodeUnit(PDECODE_UNIT decodeUnit) -{ - if (videoFile != NULL) { - PLENTRY entry = decodeUnit->bufferList; - while (entry != NULL) { - fwrite(entry->data, 1, entry->length, videoFile); - entry = entry->next; - } - } - - return realDrCallbacks.submitDecodeUnit(decodeUnit); -} - -static int recArInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, void* context, int arFlags) -{ - const char* path = context; - - if (path != NULL) { - audioFile = fopen(path, "wb"); - if (audioFile == NULL) { - return -1; - } - } - else { - Limelog("Audio recording will not be enabled - file path not specified in arContext!\n"); - } - - return realArCallbacks.init(audioConfiguration, opusConfig, NULL, arFlags); -} - -static void recArCleanup(void) -{ - if (audioFile != NULL) { - fclose(audioFile); - audioFile = NULL; - } - - realArCallbacks.cleanup(); -} - -static void recArDecodeAndPlaySample(char* sampleData, int sampleLength) -{ - if (audioFile != NULL) { - fwrite(sampleData, 1, sampleLength, audioFile); - } - - realArCallbacks.decodeAndPlaySample(sampleData, sampleLength); -} - -void setRecorderCallbacks(PDECODER_RENDERER_CALLBACKS drCallbacks, PAUDIO_RENDERER_CALLBACKS arCallbacks) -{ - realDrCallbacks = *drCallbacks; - realArCallbacks = *arCallbacks; - - drCallbacks->setup = recDrSetup; - drCallbacks->cleanup = recDrCleanup; - drCallbacks->submitDecodeUnit = recDrSubmitDecodeUnit; - - arCallbacks->init = recArInit; - arCallbacks->cleanup = recArCleanup; - arCallbacks->decodeAndPlaySample = recArDecodeAndPlaySample; -} diff --git a/third_party/moonlight-common-c/src/SimpleStun.c b/third_party/moonlight-common-c/src/SimpleStun.c deleted file mode 100644 index 64a93ef..0000000 --- a/third_party/moonlight-common-c/src/SimpleStun.c +++ /dev/null @@ -1,194 +0,0 @@ -#include "Limelight-internal.h" - -#define STUN_RECV_TIMEOUT_SEC 3 - -#define STUN_MESSAGE_BINDING_REQUEST 0x0001 -#define STUN_MESSAGE_BINDING_SUCCESS 0x0101 -#define STUN_MESSAGE_COOKIE 0x2112a442 - -#define STUN_ATTRIBUTE_MAPPED_ADDRESS 0x0001 -#define STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS 0x0020 - -#pragma pack(push, 1) - -typedef struct _STUN_ATTRIBUTE_HEADER { - unsigned short type; - unsigned short length; -} STUN_ATTRIBUTE_HEADER, *PSTUN_ATTRIBUTE_HEADER; - -typedef struct _STUN_MAPPED_IPV4_ADDRESS_ATTRIBUTE { - STUN_ATTRIBUTE_HEADER hdr; - unsigned char reserved; - unsigned char addressFamily; - unsigned short port; - unsigned int address; -} STUN_MAPPED_IPV4_ADDRESS_ATTRIBUTE, *PSTUN_MAPPED_IPV4_ADDRESS_ATTRIBUTE; - -typedef struct _STUN_MESSAGE { - unsigned short messageType; - unsigned short messageLength; - unsigned int magicCookie; - unsigned char transactionId[12]; -} STUN_MESSAGE, *PSTUN_MESSAGE; - -#pragma pack(pop) - -// This is extremely rudamentary STUN code simply for deriving the WAN IPv4 address when behind a NAT. -int LiFindExternalAddressIP4(const char* stunServer, unsigned short stunPort, unsigned int* wanAddr) -{ - SOCKET sock; - struct addrinfo* stunAddrs; - struct addrinfo hints; - char stunPortStr[6]; - int err; - STUN_MESSAGE reqMsg; - int i; - int bytesRead; - PSTUN_ATTRIBUTE_HEADER attribute; - PSTUN_MAPPED_IPV4_ADDRESS_ATTRIBUTE ipv4Attrib; - union { - STUN_MESSAGE hdr; - char buf[1024]; - } resp; - - sock = INVALID_SOCKET; - - err = initializePlatformSockets(); - if (err != 0) { - Limelog("Failed to initialize sockets: %d\n", err); - return err; - } - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_DGRAM; - hints.ai_protocol = IPPROTO_UDP; - hints.ai_flags = AI_ADDRCONFIG; - - snprintf(stunPortStr, sizeof(stunPortStr), "%u", stunPort); - err = getaddrinfo(stunServer, stunPortStr, &hints, &stunAddrs); - if (err != 0 || stunAddrs == NULL) { - Limelog("Failed to resolve STUN server: %d\n", err); - stunAddrs = NULL; - goto Exit; - } - - sock = bindUdpSocket(hints.ai_family, NULL, 0, 0, SOCK_QOS_TYPE_BEST_EFFORT); - if (sock == INVALID_SOCKET) { - err = LastSocketFail(); - Limelog("Failed to connect to STUN server: %d\n", err); - goto Exit; - } - - reqMsg.messageType = htons(STUN_MESSAGE_BINDING_REQUEST); - reqMsg.messageLength = 0; - reqMsg.magicCookie = htonl(STUN_MESSAGE_COOKIE); - PltGenerateRandomData(reqMsg.transactionId, sizeof(reqMsg.transactionId)); - - bytesRead = SOCKET_ERROR; - for (i = 0; i < STUN_RECV_TIMEOUT_SEC * 1000 / UDP_RECV_POLL_TIMEOUT_MS && bytesRead <= 0; i++) { - // Retransmit the request every second until the timeout elapses or we get a response - if (i % (1000 / UDP_RECV_POLL_TIMEOUT_MS) == 0) { - struct addrinfo *current; - - // Send a request to each resolved address but stop if we get a response - for (current = stunAddrs; current != NULL && bytesRead <= 0; current = current->ai_next) { - err = (int)sendto(sock, (char *)&reqMsg, sizeof(reqMsg), 0, current->ai_addr, (SOCKADDR_LEN)current->ai_addrlen); - if (err == SOCKET_ERROR) { - err = LastSocketFail(); - Limelog("Failed to send STUN binding request: %d\n", err); - continue; - } - - // Wait UDP_RECV_POLL_TIMEOUT_MS before moving on to the next server to - // avoid having to spam the other STUN servers if we find a working one. - bytesRead = recvUdpSocket(sock, resp.buf, sizeof(resp.buf), true); - } - } - else { - // This waits in UDP_RECV_POLL_TIMEOUT_MS increments - bytesRead = recvUdpSocket(sock, resp.buf, sizeof(resp.buf), true); - } - } - - if (bytesRead == 0) { - Limelog("No response from STUN server\n"); - err = -2; - goto Exit; - } - else if (bytesRead == SOCKET_ERROR) { - err = LastSocketFail(); - Limelog("Failed to read STUN binding response: %d\n", err); - goto Exit; - } - else if (bytesRead < (int)sizeof(resp.hdr)) { - Limelog("STUN message truncated: %d\n", bytesRead); - err = -3; - goto Exit; - } - else if (htonl(resp.hdr.magicCookie) != STUN_MESSAGE_COOKIE) { - Limelog("Bad STUN cookie value: %x\n", htonl(resp.hdr.magicCookie)); - err = -3; - goto Exit; - } - else if (memcmp(reqMsg.transactionId, resp.hdr.transactionId, sizeof(reqMsg.transactionId))) { - Limelog("STUN transaction ID mismatch\n"); - err = -3; - goto Exit; - } - else if (htons(resp.hdr.messageType) != STUN_MESSAGE_BINDING_SUCCESS) { - Limelog("STUN message type mismatch: %x\n", htons(resp.hdr.messageType)); - err = -4; - goto Exit; - } - - attribute = (PSTUN_ATTRIBUTE_HEADER)(&resp.hdr + 1); - bytesRead -= sizeof(resp.hdr); - while (bytesRead > (int)sizeof(*attribute)) { - if (bytesRead < (int)(sizeof(*attribute) + htons(attribute->length))) { - Limelog("STUN attribute out of bounds: %d\n", htons(attribute->length)); - err = -5; - goto Exit; - } - // Mask off the comprehension bit - else if ((htons(attribute->type) & 0x7FFF) != STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS) { - // Continue searching if this wasn't our address - bytesRead -= sizeof(*attribute) + htons(attribute->length); - attribute = (PSTUN_ATTRIBUTE_HEADER)(((char*)attribute) + sizeof(*attribute) + htons(attribute->length)); - continue; - } - - ipv4Attrib = (PSTUN_MAPPED_IPV4_ADDRESS_ATTRIBUTE)attribute; - if (htons(ipv4Attrib->hdr.length) != 8) { - Limelog("STUN address length mismatch: %d\n", htons(ipv4Attrib->hdr.length)); - err = -5; - goto Exit; - } - else if (ipv4Attrib->addressFamily != 1) { - Limelog("STUN address family mismatch: %x\n", ipv4Attrib->addressFamily); - err = -5; - goto Exit; - } - - // The address is XORed with the cookie - *wanAddr = ipv4Attrib->address ^ resp.hdr.magicCookie; - - err = 0; - goto Exit; - } - - Limelog("No XOR mapped address found in STUN response!\n"); - err = -6; - -Exit: - if (sock != INVALID_SOCKET) { - closeSocket(sock); - } - - if (stunAddrs != NULL) { - freeaddrinfo(stunAddrs); - } - - cleanupPlatformSockets(); - return err; -} From c19a220111cb2971cdccb94cdf1a4c89c23c9a56 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:17:07 -0700 Subject: [PATCH 16/52] Drop the stream crate's unused serde dependency brolink-stream declared serde but serialises nothing; its only JSON lives in brolink-core. Co-Authored-By: Claude Opus 5.5 (1M context) --- Cargo.lock | 1 - crates/stream/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ff76c22..2ae5ffe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -774,7 +774,6 @@ dependencies = [ "rcgen", "rsa", "rustls", - "serde", "sha2", "tracing", "x509-parser", diff --git a/crates/stream/Cargo.toml b/crates/stream/Cargo.toml index d890bd5..e8404e9 100644 --- a/crates/stream/Cargo.toml +++ b/crates/stream/Cargo.toml @@ -14,7 +14,6 @@ anyhow.workspace = true tracing.workspace = true rand.workspace = true parking_lot.workspace = true -serde.workspace = true # Pairing: the client identity is an RSA key and a self-signed certificate, # the PIN exchange is AES-128-ECB + SHA-256, and the paired HTTPS channel # presents that certificate to Sunshine. From af6933ee8df132b34caf6b398d1cb1a189d87d25 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:24:56 -0700 Subject: [PATCH 17/52] End the audio output thread when the player goes, not on a timer The thread that owns the cpal stream woke every 50 ms to look at a stop flag for the whole of every session, and kept the device open up to 50 ms after the player was dropped. It now blocks on a channel whose sender the player holds, so it wakes exactly once, at the end. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/stream/src/audio.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/crates/stream/src/audio.rs b/crates/stream/src/audio.rs index adcd454..12e68fd 100644 --- a/crates/stream/src/audio.rs +++ b/crates/stream/src/audio.rs @@ -9,7 +9,8 @@ use anyhow::{bail, Result}; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use parking_lot::Mutex; use std::collections::VecDeque; -use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::mpsc; use std::sync::Arc; /// Queued sound beyond this is cut back to [`KEEP_MS`]. @@ -39,7 +40,9 @@ pub struct Player { pcm: Vec, /// Interleaved samples at the stream's rate and channel count. queue: Arc>>, - stop: Arc, + /// Dropped with the player, which ends the output thread and with it + /// the device stream. + _output_thread: mpsc::Sender<()>, output: Arc>, /// Frames the output device has pulled, silence included: proof that /// sound is leaving. @@ -104,14 +107,14 @@ impl Player { bail!("Opus decoder failed ({err})"); } let queue: Arc>> = Arc::default(); - let stop = Arc::new(AtomicBool::new(false)); + let (stop, stopped) = mpsc::channel(); let output = Arc::new(Mutex::new(Output::Opening)); let pulled = Arc::new(AtomicU64::new(0)); output_thread( sample_rate, channels as u16, queue.clone(), - stop.clone(), + stopped, output.clone(), pulled.clone(), ); @@ -126,7 +129,7 @@ impl Player { // negotiated frame would refuse a longer packet outright. pcm: vec![0.0; max_frame * channels], queue, - stop, + _output_thread: stop, output, pulled, max_queued: per_ms * MAX_QUEUED_MS, @@ -219,7 +222,6 @@ fn phrase(output: &Output, packets: u64, lost: u64, pulled: u64) -> String { impl Drop for Player { fn drop(&mut self) { - self.stop.store(true, Ordering::Relaxed); unsafe { audiopus_sys::opus_multistream_decoder_destroy(self.decoder) }; } } @@ -233,7 +235,7 @@ fn output_thread( source_rate: u32, source_channels: u16, queue: Arc>>, - stop: Arc, + stop: mpsc::Receiver<()>, output: Arc>, pulled: Arc, ) { @@ -352,9 +354,8 @@ fn output_thread( rate, channels, }; - while !stop.load(Ordering::Relaxed) { - std::thread::sleep(std::time::Duration::from_millis(50)); - } + // Returns when the player, and with it the sender, is dropped. + let _ = stop.recv(); }) .expect("spawn audio thread"); } From f158641b6b6091b8073a30ae73cb715167500af4 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:24:56 -0700 Subject: [PATCH 18/52] Redraw an idle window once a second, and only on news from the service The lobby asked egui for a new frame every 400 ms, and the unified app's service poller asked for another after every one-second poll, whether or not anything had changed. Workers already repaint when discovery, a connection attempt or an update moves, so the lobby's own timer only has to age notices: it is now one second, and the poller repaints only when the status, error, paired clients or gamepad state it read differ from before. Measured on this Mac with the release viewer (brolink-client, lobby open and idle, 60 s after a 12 s warm-up): 0.58 s of CPU at 400 ms, 0.39 s at 1 s, from about 1.0% to 0.65% of a core. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/client/src/app.rs | 3 ++- crates/host/src/app.rs | 14 +++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/crates/client/src/app.rs b/crates/client/src/app.rs index 3995713..bdad3a4 100644 --- a/crates/client/src/app.rs +++ b/crates/client/src/app.rs @@ -502,7 +502,8 @@ impl eframe::App for ClientApp { return; } - ctx.request_repaint_after(Duration::from_millis(400)); + // Workers repaint when something changes; this only ages notices. + ctx.request_repaint_after(Duration::from_secs(1)); if self.tailscale_checked.elapsed() > Duration::from_secs(2) { self.tailscale_checked = Instant::now(); self.tailscale_ok = tailscale::cli().is_some(); diff --git a/crates/host/src/app.rs b/crates/host/src/app.rs index 21820de..d0927b0 100644 --- a/crates/host/src/app.rs +++ b/crates/host/src/app.rs @@ -697,6 +697,15 @@ fn spawn_poller(shared: Arc>, ctx: egui::Context) { "/v1/status", Duration::from_millis(800), ); + let seen = |s: &Shared| { + ( + s.status.clone(), + s.service_error.clone(), + s.clients.clone(), + s.gamepad_driver, + ) + }; + let before = seen(&shared.lock()); match r { Ok(st) => { failures = 0; @@ -760,7 +769,10 @@ fn spawn_poller(shared: Arc>, ctx: egui::Context) { } } } - ctx.request_repaint(); + // The window is drawn again only when there is something new. + if seen(&shared.lock()) != before { + ctx.request_repaint(); + } tick += 1; std::thread::sleep(Duration::from_secs(1)); } From 9dd9da773c7a1ff8bad48dc3a7d720ba99992428 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:26:32 -0700 Subject: [PATCH 19/52] Bring the docs up to date with what has shipped ROADMAP still listed clipboard sync under Later; it shipped in 3.1, and 3.1 to 4.0 had no section at all. README called the stream "BroLink's stream protocol" (it is GameStream, through moonlight-common-c and Sunshine), left out deploy/native, and now states the control API's account rule including tagged servers. MACOS.md still described a Windows-only machine list; WINDOWS.md's route table lacked /v1/display. CONTRIBUTING explains BROLINK_DATA_DIR, where the vendored library is described, and the stream_real variables for streaming from another machine. Co-Authored-By: Claude Opus 5.5 (1M context) --- CONTRIBUTING.md | 20 ++++++++++++++++++-- README.md | 14 ++++++++++---- docs/MACOS.md | 6 +++--- docs/ROADMAP.md | 17 ++++++++++++++++- docs/WINDOWS.md | 5 ++++- 5 files changed, 51 insertions(+), 11 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 49970ed..00d4542 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,12 +16,21 @@ cargo test --workspace example `BROLINK_SKIP_C=1 cargo check -p brolink-client --target aarch64-apple-darwin` from Windows after `rustup target add`. +`cargo test` and `cargo run` keep BroLink's settings, pairing identity and +logs in `target/brolink-data` (`.cargo/config.toml` sets +`BROLINK_DATA_DIR`), so neither touches an installed BroLink's files. To +point a development build at an installed one's data on purpose, set +`BROLINK_DATA_DIR` yourself, for example to `%LOCALAPPDATA%\BroLink` on +the development PC before `cargo run -p brolink-host -- --background`. + ## Where things live - `crates/stream`: everything that talks to Sunshine. `nvhttp` is the pairing and launch API, `session` drives moonlight-common-c through the C shim in `csrc/`, `video` and `audio` decode. It knows nothing about - windows or Tailscale. + windows or Tailscale. The vendored moonlight-common-c, what was trimmed + from it and its two local patches are described in + `third_party/moonlight-common-c/VERSION`. - `crates/client`: the viewer UI (a library plus `brolink-client`). `session.rs` is the path from a listed machine to a live stream; `stream.rs` is the toolbar and input while streaming; `video.rs` draws @@ -50,7 +59,8 @@ renders every screen to `target/ui-snapshots/*.png`. ## Tests against a real Sunshine With Sunshine running on the development PC and BroLink Host's service -started (`cargo run -p brolink-host -- --background`): +started (`cargo run -p brolink-host -- --background`, pointed at the +installed data directory as above so it has the engine login): ```bash cargo test -p brolink-stream pair_real -- --ignored --nocapture # five-phase pairing @@ -74,6 +84,12 @@ BROLINK_TEST_PC='Gaming-PC' cargo test -p brolink-client stream_snapshot -- --ig cargo test -p brolink-client decoded_frame_reaches -- --ignored ``` +`stream_real` streams from another machine too: `BROLINK_TEST_IP` is its +Tailscale address, `BROLINK_TEST_DIR` a directory holding copies of the files in the +Mac's `identity/` directory and the PC's certificate as `server.der`, and +`BROLINK_TEST_WIDTH`, `_HEIGHT`, `_FPS`, `_BITRATE_KBPS`, `_SECONDS` and +`BROLINK_TEST_HEVC=1` shape the stream. + `BROLINK_TEST_CODEC=h264` or `hevc` selects the codec. A black capture fails the live test by default; `BROLINK_TEST_EXPECT_BLACK=1` instead verifies the persistent black-picture diagnosis when testing a PC without an active diff --git a/README.md b/README.md index 7e5b597..02bf0a4 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,8 @@ cp deploy/node/env.example deploy/node/.env docker compose -f deploy/node/docker-compose.yml up -d --build ``` +A desktop can also run natively under systemd on the VPS, beside the +relay, as this project's own VPS does: [deploy/native/README.md](deploy/native/README.md). The packet relay (when two NATs cannot punch through) is a separate kit: [deploy/relay/RELAY.md](deploy/relay/RELAY.md). @@ -155,9 +157,12 @@ Ethernet is strongly preferred; most Wi-Fi adapters cannot wake a PC. - The host's control service listens on TCP 47850 and answers only loopback and Tailscale addresses that `tailscale whois` attributes to the - account the PC is signed in as. Everyone else gets a 403. The firewall - rule setup adds is scoped to `100.64.0.0/10`. -- The stream is BroLink's stream protocol over Tailscale (WireGuard), with + account the PC is signed in as. A tagged server (the relay VPS) belongs + to no account and answers the members of its own tailnet. Everyone else, + including machines shared in from other tailnets, gets a 403. The + firewall rule setup adds is scoped to `100.64.0.0/10`. +- The stream is the GameStream protocol (moonlight-common-c on the viewer, + Sunshine on the host), encrypted, inside Tailscale (WireGuard), with certificate pairing on top; BroLink pins the PC's certificate after the first pairing. - No BroLink account or password exists. The engine login BroLink @@ -194,8 +199,9 @@ crates/host node: control service, engine setup, unified window (view + shar crates/client viewer UI: machine list, wake, pair, stream window and toolbar crates/ui theme and widgets shared by both windows deploy/node/ Docker kit: virtual desktop + engine + BroLink on a VPS +deploy/native/ systemd user units for the same desktop without Docker deploy/relay/ Docker kit: Tailscale peer relay -third_party/ moonlight-common-c (GPL-3.0), vendored +third_party/ moonlight-common-c (GPL-3.0), vendored; VERSION says what and why docs/ platform notes scripts/ installers and the macOS bundle ``` diff --git a/docs/MACOS.md b/docs/MACOS.md index e03a36c..18439ff 100644 --- a/docs/MACOS.md +++ b/docs/MACOS.md @@ -69,9 +69,9 @@ xcrun stapler staple dist/BroLink.app 1. If Tailscale is missing or signed out, the top card says so and **Get Tailscale** opens the download page. -2. **Your PCs** lists the Windows machines on your Tailscale account. Each - line says whether it is ready, asleep, or missing something on the PC - side. +2. **Your machines** lists every other machine on your Tailscale account + (Windows, macOS and Linux alike). Each line says whether it is ready, + asleep, or missing something on its side. 3. Click **Connect**. The first time with a PC, BroLink pairs with it: it picks a random PIN, sends it to BroLink Host over Tailscale, and the host enters it for you. You see the PIN but never need it. If there is no diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 11f7677..311e8d0 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -25,12 +25,27 @@ Moonlight and launched it; BroLink itself did wake, pairing and power. - [x] Both Sunshine PIN APIs (with and without pairing ids) - [x] GPL-3.0-or-later +## v3.1 – v3.3 + +- [x] Clipboard sync both ways while streaming (text, up to 32 KB) +- [x] Automatic updates: the Mac fetches releases from GitHub and pushes + the new host to every PC over the control API +- [x] Direct or relayed path shown per PC, with the reason for a relay +- [x] A peer relay you run yourself (`deploy/relay/`) +- [x] Match screen: the stream is the viewing machine's own resolution + +## v4.0 + +- [x] One app on every OS: each machine lists the others and can share + its own desktop (Windows, macOS, Linux, a VPS) +- [x] `deploy/node/` Docker kit and `deploy/native/` units for a VPS desktop +- [x] The chosen bitrate is the one the PC's encoder targets + ## Later - [ ] AV1 decode on M3 and newer (moonlight-common-c negotiates it; the VideoToolbox path is HEVC and H.264 today) - [ ] Gamepads on the Mac (GameController framework to Sunshine's virtual pad) -- [ ] Clipboard sync between Mac and PC - [ ] Signed Windows and macOS binaries (Authenticode, Developer ID and notarization) - [ ] Menu-bar presence on the Mac, tray icon on Windows diff --git a/docs/WINDOWS.md b/docs/WINDOWS.md index a0c51cb..97f4cff 100644 --- a/docs/WINDOWS.md +++ b/docs/WINDOWS.md @@ -84,10 +84,13 @@ requests, all JSON: | `GET /v1/clipboard` | The clipboard as text, with Windows' clipboard sequence number | | `POST /v1/clipboard {"text"}` | Replaces the clipboard, so a ⌘V on the Mac pastes the Mac's text | | `POST /v1/update` | A new `brolink-host.exe`; see Updates | +| `GET /v1/display` | Why a capture may be black: monitors, the desktop's sampled brightness, HDR state, the engine's capture settings | +| `POST /v1/display {"advanced_color"}` | Turns the HDR (advanced colour) desktop off or on | A request is answered only if it comes from loopback or from a Tailscale address that `tailscale whois` attributes to the account this PC is signed -in as. Anything else is refused with a 403 before any action. +in as; a machine shared in from another tailnet is refused too. Anything +else gets a 403 before any action. The service also listens on UDP 9. A magic packet for this PC's MAC that arrives while it is awake is logged and reported in the status, which is From 949a670de2869b353bafcb6d21e59a8ad7ae5624 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:26:58 -0700 Subject: [PATCH 20/52] Restart the Mac's background service when a script installs the app install-macos.sh and install-macos-release.sh replaced /Applications/BroLink.app but left the launchd job running the old binary: replacing a bundle does not stop a process, and the agent's KeepAlive restarts only after a failure. The Mac's sharing service then answered on the previous version until the next login, and every manual install needed a `launchctl kickstart`. Both scripts now kickstart the agent when launchd has it loaded. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 4 ++++ scripts/install-macos-release.sh | 7 +++++++ scripts/install-macos.sh | 7 +++++++ 3 files changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c98ecc2..9446d5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,10 @@ GitHub release notes, so each version gets a heading of the form `"` or a `$`. The name was written into a double-quoted PowerShell string, where a `"` ends the string (a syntax error that stops the whole setup) and a `$` expands. It is now only ever a single-quoted literal. +- The macOS install scripts restart the background service after putting + the new app in place. It kept running the old version until the next + login, so a Mac that shares its desktop served other machines from the + previous release. - Cancel stops a connection attempt straight away while the PC is not answering. The pairing check retried eight times regardless, which could hold the window on "Checking pairing…" for most of a minute after Cancel. diff --git a/scripts/install-macos-release.sh b/scripts/install-macos-release.sh index 35c86b1..be21274 100644 --- a/scripts/install-macos-release.sh +++ b/scripts/install-macos-release.sh @@ -82,5 +82,12 @@ if [[ -d "$DEST" ]]; then else mv "$STAGE" "$DEST" fi +# The background service keeps running the old binary until it restarts: +# replacing the bundle does not stop it, and launchd restarts it only after +# a failure. Restart it through launchd so it runs the new one now. +AGENT="gui/$(id -u)/dev.brolink.node" +if launchctl print "$AGENT" >/dev/null 2>&1; then + launchctl kickstart -k "$AGENT" && echo "restarted the BroLink background service" +fi echo "installed $DEST" open "$DEST" diff --git a/scripts/install-macos.sh b/scripts/install-macos.sh index 36f27b6..ccb3d0c 100755 --- a/scripts/install-macos.sh +++ b/scripts/install-macos.sh @@ -21,5 +21,12 @@ LSREGISTER="/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchS "$LSREGISTER" -u "$APP" 2>/dev/null || true "$LSREGISTER" -f /Applications/BroLink.app 2>/dev/null || true rm -rf "$APP" +# The background service keeps running the old binary until it restarts: +# replacing the bundle does not stop it, and launchd restarts it only after +# a failure. Restart it through launchd so it runs the new one now. +AGENT="gui/$(id -u)/dev.brolink.node" +if launchctl print "$AGENT" >/dev/null 2>&1; then + launchctl kickstart -k "$AGENT" && echo "restarted the BroLink background service" +fi echo "installed /Applications/BroLink.app" echo "open it; it lists every machine on your Tailscale account, and can share this Mac." From afc685736e744247aa368d84df6fece614f91e24 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:28:31 -0700 Subject: [PATCH 21/52] Check for a black picture in vector-sized steps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every decoded frame is checked for being entirely black (the stream's black-picture diagnosis). The check walked the planes byte by byte with a short-circuiting all(), which cannot be vectorised, so a frame that really was black, the case the check exists for, cost the decoder thread 2.5 ms at 3024x1964: 15% of a core at 60 fps, on the latency path. Each row is now reduced to its maximum (and minimum, for chroma), which the compiler vectorises, still stopping at the first row with a visible pixel. Release build, 3024x1964 NV12, 200 runs: a black frame 2.47 ms -> 143 µs, a frame with a picture near the top 8.1 µs -> 0.34 µs. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/stream/src/video.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/crates/stream/src/video.rs b/crates/stream/src/video.rs index ef44e2b..1cc57dd 100644 --- a/crates/stream/src/video.rs +++ b/crates/stream/src/video.rs @@ -66,18 +66,23 @@ impl Frame { return false; } let black = if self.full_range { 1 } else { 17 }; + // Whole-row reductions rather than a short-circuiting `all`: the + // compiler turns a max or min over a row into vector instructions, + // and the scan still stops at the first row with a visible pixel. + // A black frame is every row, sixty times a second. + let max = |row: &[u8]| row.iter().fold(0, |m, &v| m.max(v)); + let min = |row: &[u8]| row.iter().fold(u8::MAX, |m, &v| m.min(v)); self.y .chunks(self.y_stride) .take(height) - .all(|row| row[..width].iter().all(|&v| v <= black)) + .all(|row| max(&row[..width]) <= black) && self .uv .chunks(self.uv_stride) .take(chroma_height) .all(|row| { - row[..chroma_width] - .iter() - .all(|&v| (127..=129).contains(&v)) + let row = &row[..chroma_width]; + min(row) >= 127 && max(row) <= 129 }) } } From 356f458e1bbf0463befa5b3a694596217eb4eeba Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:29:37 -0700 Subject: [PATCH 22/52] Render each icon size once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The unified window renders the 64-pixel logo for the client's header, again for the host panel's, and on Windows and Linux once more for the window icon, all on the UI thread before the first frame. Each render is a Lanczos resize of the 1024-pixel logo, about 6.6 ms in a release build. Rendered sizes are now kept, so every render after the first of a size costs a 16 KB copy (3.5 µs). Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/core/src/icon.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/core/src/icon.rs b/crates/core/src/icon.rs index 4fe8121..ceefe7b 100644 --- a/crates/core/src/icon.rs +++ b/crates/core/src/icon.rs @@ -3,7 +3,8 @@ //! build script, which includes this file by path, so it must not refer to //! anything else in this crate. -use std::sync::OnceLock; +use std::collections::BTreeMap; +use std::sync::{Mutex, OnceLock}; use image::{Rgba, RgbaImage}; @@ -64,6 +65,14 @@ fn tile_bounds(im: &RgbaImage) -> (u32, u32, u32, u32) { /// square and applies the rounded app-icon shape itself. Pre-padding left /// a square plate around the mark. pub fn render(size: u32) -> Vec { + // Each window's header and the window icon ask for the same size at + // startup, before the first frame; the resize is the slow part. + static RENDERED: Mutex>> = Mutex::new(BTreeMap::new()); + let mut rendered = RENDERED.lock().unwrap_or_else(|e| e.into_inner()); + rendered.entry(size).or_insert_with(|| draw(size)).clone() +} + +fn draw(size: u32) -> Vec { if size == 0 { return Vec::new(); } From 107f486e7424983b6949131124dd0f6322f44483 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:30:52 -0700 Subject: [PATCH 23/52] Bundle only the unified app bundle-macos.sh fell back to the viewer-only brolink-client binary when no brolink-host was built, which produced a BroLink.app that could not share its machine. Nothing builds the bundle that way; it now asks for the host build instead. Co-Authored-By: Claude Opus 5.5 (1M context) --- scripts/bundle-macos.sh | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/scripts/bundle-macos.sh b/scripts/bundle-macos.sh index 69388cc..187a097 100755 --- a/scripts/bundle-macos.sh +++ b/scripts/bundle-macos.sh @@ -1,9 +1,8 @@ #!/usr/bin/env bash set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" -# Unified node binary first (views and shares); viewer-only as a fallback. -# When both a cross-compiled and a native build exist, the newer one is the -# one that was just built. +# The unified node binary (views and shares). When both a cross-compiled +# and a native build exist, the newer one is the one that was just built. newest() { local pick="" for f in "$@"; do @@ -16,12 +15,6 @@ newest() { } BIN="$(newest "$ROOT/target/aarch64-apple-darwin/release/brolink-host" "$ROOT/target/release/brolink-host")" if [[ -z "$BIN" ]]; then - BIN="$(newest "$ROOT/target/aarch64-apple-darwin/release/brolink-client" "$ROOT/target/release/brolink-client")" -fi -if [[ -z "$BIN" ]]; then - BIN="$ROOT/target/release/brolink-host" -fi -if [[ ! -f "$BIN" ]]; then echo "build the app first: cargo build --release -p brolink-host" >&2 exit 1 fi From 66d4aa88b87b687204982da6e2c55a6a665ca922 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:31:17 -0700 Subject: [PATCH 24/52] Compile the clipboard sequence helper only where it is used Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/host/src/clipboard.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/host/src/clipboard.rs b/crates/host/src/clipboard.rs index abc52c1..8e1cb1a 100644 --- a/crates/host/src/clipboard.rs +++ b/crates/host/src/clipboard.rs @@ -153,7 +153,7 @@ pub fn write(text: &str) -> Result<()> { /// stands in for one: it changes whenever different text is copied, which /// is all a reader needs to tell a new copy from one it has seen. A fixed /// `seq` made every copy on a Mac or Linux desktop look like the last. -#[cfg_attr(windows, allow(dead_code))] +#[cfg(any(not(windows), test))] fn text_seq(text: &str) -> u64 { use std::hash::{Hash, Hasher}; let mut h = std::collections::hash_map::DefaultHasher::new(); From 68a190f44ef2fd6174ab782fe684bf9b01a315e5 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:32:12 -0700 Subject: [PATCH 25/52] Redesign the window around one design system The window read as three apps glued together: violet pills and cards in the viewer, a host panel the unified window never showed, and egui's own menus in the stream toolbar. Every screen now takes its colours, type, spacing, radii and elevation from crates/ui, in the product site's black, white and hairlines, set in Geist and Geist Mono. - crates/ui is a real design system: surface levels, three text strengths, one primary (white), status tones, the logo violet only for focus and progress, a 4-point spacing scale, a type scale, three radii, two elevations. Buttons come in five kinds; menus, tabs, switches, sliders, notices, tags and keycaps are drawn here, report themselves to accesskit and show a focus ring. Contrast is tested. - The unified window has tabs: Machines, Sharing and Settings. Sharing is the host's page (setup, status, paired devices, options, log), which the unified window used to leave unreachable. - The machine list, connection progress, failures, empty and Tailscale states, Settings and the stream overlay are rebuilt on it, with shorter, honest copy that names the right kind of machine. - The idle window no longer repaints on a timer, the Tailscale probe and the host's network calls are off the UI thread, and the host poller repaints only when something changed. - The snapshot harness builds every screen headless with default settings (it used to read the real client.toml) and renders each state at 640x420, 1280x800 and 1920x1200, at 1x and 2x; unignored tests check every state fits with no overlapping controls. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 48 + NOTICE | 8 +- crates/client/src/app.rs | 2516 +++++++++++------ crates/client/src/path.rs | 28 +- crates/client/src/settings.rs | 188 +- crates/client/src/share.rs | 22 +- crates/client/src/stream.rs | 1274 +++++---- crates/host/src/app.rs | 1413 +++++---- crates/host/src/product.rs | 97 +- crates/ui/assets/Geist-Medium.ttf | Bin 0 -> 127660 bytes crates/ui/assets/Geist-Regular.ttf | Bin 0 -> 126048 bytes crates/ui/assets/Geist-SemiBold.ttf | Bin 0 -> 127872 bytes crates/ui/assets/GeistMono-Medium.ttf | Bin 0 -> 149328 bytes crates/ui/assets/GeistMono-Regular.ttf | Bin 0 -> 148516 bytes crates/ui/assets/Inter-Medium.ttf | Bin 417300 -> 0 bytes crates/ui/assets/Inter-Regular.ttf | Bin 411640 -> 0 bytes crates/ui/assets/Inter-SemiBold.ttf | Bin 419744 -> 0 bytes .../{LICENSE-Inter.txt => LICENSE-Geist.txt} | 11 +- crates/ui/src/lib.rs | 18 +- crates/ui/src/theme.rs | 565 ++-- crates/ui/src/widgets.rs | 1893 ++++++++++--- 21 files changed, 5486 insertions(+), 2595 deletions(-) create mode 100644 crates/ui/assets/Geist-Medium.ttf create mode 100644 crates/ui/assets/Geist-Regular.ttf create mode 100644 crates/ui/assets/Geist-SemiBold.ttf create mode 100644 crates/ui/assets/GeistMono-Medium.ttf create mode 100644 crates/ui/assets/GeistMono-Regular.ttf delete mode 100644 crates/ui/assets/Inter-Medium.ttf delete mode 100644 crates/ui/assets/Inter-Regular.ttf delete mode 100644 crates/ui/assets/Inter-SemiBold.ttf rename crates/ui/assets/{LICENSE-Inter.txt => LICENSE-Geist.txt} (96%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b8f0f2..540aac3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,54 @@ The release workflow publishes the section that matches the tag as the GitHub release notes, so each version gets a heading of the form `## X.Y.Z (date)`. +## Unreleased + +- The window is redesigned around one design system, in the product + site's black, white and hairlines, set in Geist and Geist Mono. Colour + now means something: green for ready, amber for needs attention, red + for broken, and the logo's violet only for keyboard focus and + progress. Every text colour meets WCAG AA on every surface it sits on; + the old footer and captions did not. +- Tabs across the top: **Machines**, **Sharing** and **Settings** + (Command-1, 2 and Command-comma on a Mac; Ctrl elsewhere; Escape goes + back). The Sharing tab is new in the unified app: this machine's setup, + what it reports (Tailscale, streaming engine, network, Wake-on-LAN, + controllers), the devices paired with it, its options and its log. + Until now the unified window showed only a one-line summary and a + setup button; paired devices, the log and Stop service were + unreachable. +- The machine list shows each machine's state as a dot and one line, + with **Connect** (or **Wake and connect**) and a menu for Wake, Test + wake, Sleep, Restart, Shut down and Copy Tailscale address. Buttons + stay put and dim while a connection is in progress instead of + disappearing. This machine leads the list and opens its Sharing tab. +- Connecting says what is happening and what to expect; pairing shows + the PIN in boxes. A failure is titled "Couldn't connect to …" or + "… disconnected", with **Try again**, and a lighter profile is offered + only when the stream itself failed. The empty list, "looking", + Tailscale missing and Tailscale stopped each say what to do. +- Settings is grouped (Stream, Window and input, Power, Updates, Relay, + About) and the quality profile is one control: Recommended, Smooth, + Balanced, Sharp or Custom. Mouse capture is now a setting as well as a + toolbar choice. "Command acts as Ctrl" shows only on a Mac, and "Keep + this PC awake" only on Windows, where it does something. +- In a stream, the toolbar's menus look and behave the same, open + towards the window's middle, and fold into one **More** menu on narrow + windows. The host key is shown as keycaps (control option on a Mac, + Ctrl Alt elsewhere) when a stream starts and on the toolbar. The + connecting screen has a **Cancel** button, so leaving no longer needs + the host key. Stream settings and Stream performance are proper + panels with a close button; Escape closes Stream settings. +- A relayed path is amber, not red: it works, and Connection details + says how to get a direct one. +- The idle window no longer repaints on a timer (it redrew 2.5 times a + second forever), and the Tailscale check that could start a process + every two seconds on the UI thread now runs on its own thread. + Unpairing and installing the controller driver no longer block the + window. +- The app is about 570 KB smaller: Geist's five cuts replace Inter's + three. + ## 4.0.2 (2026-09-22) - `panel.log` and `service.log` no longer grow without end. Both were diff --git a/NOTICE b/NOTICE index 74d9482..7f549d2 100644 --- a/NOTICE +++ b/NOTICE @@ -34,5 +34,9 @@ Used when present, never redistributed: Fonts: - Inter, SIL Open Font License 1.1, Rasmus Andersson and the Inter Project - Authors. crates/ui/assets/LICENSE-Inter.txt https://github.com/rsms/inter + Geist and Geist Mono, SIL Open Font License 1.1, The Geist Project + Authors (Vercel). Regular, Medium and SemiBold of Geist and Regular + and Medium of Geist Mono, unmodified, v1.7.2. + crates/ui/assets/LICENSE-Geist.txt https://github.com/vercel/geist-font + egui's fallback fonts: Hack (MIT), Ubuntu Light (Ubuntu Font Licence), + Noto Emoji (OFL 1.1) and emoji-icon-font (MIT), through epaint. diff --git a/crates/client/src/app.rs b/crates/client/src/app.rs index a3192b6..0f416b6 100644 --- a/crates/client/src/app.rs +++ b/crates/client/src/app.rs @@ -1,5 +1,5 @@ -//! The window: a list of PCs with a Connect button each, settings, and the -//! stream screen once connected. +//! The window: the machines on this tailnet, this machine's Sharing page, +//! settings, and the stream once connected. use crate::config::ClientConfig; #[cfg(test)] @@ -15,28 +15,42 @@ use crate::update; use brolink_core::api::PowerAction; use brolink_core::tailscale; use brolink_stream::Event; -use brolink_ui::{self as ui, Tone, PALETTE as P}; +use brolink_ui::{self as ui, column, space, Icon, Tone, PALETTE as P}; use eframe::egui; use parking_lot::Mutex; use semver::Version; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::{Duration, Instant}; -const COLUMN_WIDTH: f32 = 860.0; - -const RELAY_NONE: &str = "No relay on this network. Streams use Tailscale's default relay if a direct path is not available. You can run your own relay with the deploy kit."; -const RELAY_READY: &str = "Using your network relay when a direct path is not available."; -const RELAY_OFFLINE: &str = "A relay is on this network but it is offline. Streams use Tailscale's default relay until it comes back."; -const RELAY_CHECKING: &str = "A relay is on this network. BroLink could not tell whether this device may use it — that is not a denial. Peer relay needs Tailscale 1.86 or later on every device."; -const RELAY_UNAVAILABLE: &str = "A relay is on this network but it is not available to this device yet. That is not a denial. Confirm the relay is configured, and paste this grant into the tailnet policy if it is missing."; +const RELAY_NONE: &str = "When two machines can't reach each other directly, Tailscale's relays carry the stream. For a shorter detour, run your own relay with BroLink's deploy kit."; +const RELAY_READY: &str = + "Your relay carries streams between machines that can't connect directly."; +const RELAY_OFFLINE: &str = "Your relay is offline. Until it is back, Tailscale's relays carry streams between machines that can't connect directly."; +const RELAY_CHECKING: &str = "A relay is on this tailnet, but BroLink couldn't confirm that this device may use it. That does not mean access is denied. Peer relays need Tailscale 1.86 or later on every device."; +const RELAY_UNAVAILABLE: &str = "A relay is on this tailnet but isn't available to this device yet. That does not mean access is denied: check that the relay is configured, and add this grant to the tailnet policy if it is missing."; #[cfg(test)] const RELAY_UNGRANTED: &str = "A relay node is online but this device is not granted access."; const RELAY_GRANT: &str = "{\n \"src\": [\"autogroup:member\"],\n \"dst\": [\"tag:relay\"],\n \"app\": {\n \"tailscale.com/cap/relay\": []\n }\n}"; const RELAY_DOCS: &str = "https://tailscale.com/docs/features/peer-relay"; +const DOWNLOAD_URL: &str = "https://github.com/MrBeldum/brolink/releases/latest"; +const SOURCE_URL: &str = "https://github.com/MrBeldum/brolink"; + +/// How long a one-line result stays on the machine list. +const NOTICE_FOR: Duration = Duration::from_secs(12); /// A one-line result, filled in by a worker thread. type Notice = Arc>>; +/// The window's pages, one per tab. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Page { + Machines, + /// This machine's sharing: setup, status, paired devices. + Sharing, + Settings, +} + /// Give the window the stream's proportions, keeping its width, so the /// picture fills it with no bar on any side. Only in a window: full screen /// is the screen's shape, which Match screen already is. @@ -58,6 +72,16 @@ fn window_size_for(current: egui::Vec2, aspect: f32) -> egui::Vec2 { egui::Vec2::new(width, (width / aspect).round().max(420.0)) } +/// "Command-," on a Mac, "Ctrl+," elsewhere: how a window shortcut is +/// written in a tooltip. +fn chord(key: &str) -> String { + if cfg!(target_os = "macos") { + format!("Command-{key}") + } else { + format!("Ctrl+{key}") + } +} + pub struct ClientApp { cfg: ClientConfig, dirty: bool, @@ -65,11 +89,11 @@ pub struct ClientApp { progress: Arc>, live: Arc>>, view: stream::View, - tailscale_ok: bool, - tailscale_checked: Instant, + /// Tailscale's CLI is on this machine. Probed off the UI thread: when it + /// is missing, looking for it runs a process. + tailscale_ok: Arc, brand: ui::Brand, - settings_open: bool, - notices_open: bool, + page: Page, fullscreen: bool, /// A restart or shutdown waits for a second click. confirm: Option<(String, PowerAction)>, @@ -84,6 +108,11 @@ pub struct ClientApp { /// Connect again as soon as the current stream has stopped. reconnect: Option, restart_capture: bool, + /// The last attempt reached the picture before it ended. + streamed: bool, + /// The last attempt failed in the stream itself (not while waking, + /// pairing or launching), where a lighter profile can help. + stream_failed: bool, /// What the PC said about its black picture, and whether a fix is on /// its way. Asked for once per stream, by the thread that answers. video_help: Arc>>, @@ -95,13 +124,15 @@ pub struct ClientApp { display_change: Option<((u32, u32), Instant)>, /// Present when this process also shares this machine. pub local: Option, + share_page: Option>, } impl ClientApp { pub fn new(cc: &eframe::CreationContext<'_>) -> Self { let discovery = Arc::new(Mutex::new(Discovery::default())); session::spawn_discovery(discovery.clone(), cc.egui_ctx.clone()); - let app = Self::with_shared(cc, discovery.clone(), Arc::default(), true); + let app = Self::with_shared(cc, discovery.clone(), Arc::default(), ClientConfig::load()); + spawn_tailscale_probe(app.tailscale_ok.clone(), cc.egui_ctx.clone()); update::spawn( app.updates.clone(), discovery, @@ -116,24 +147,22 @@ impl ClientApp { cc: &eframe::CreationContext<'_>, discovery: Arc>, progress: Arc>, - probe_tools: bool, + cfg: ClientConfig, ) -> Self { ui::apply(&cc.egui_ctx); if let Some(rs) = &cc.wgpu_render_state { crate::video::install(rs); } Self { - cfg: ClientConfig::load(), + cfg, dirty: false, discovery, progress, live: Arc::default(), view: stream::View::default(), - tailscale_ok: !probe_tools || tailscale::cli().is_some(), - tailscale_checked: Instant::now(), + tailscale_ok: Arc::new(AtomicBool::new(true)), brand: ui::Brand::new(&cc.egui_ctx), - settings_open: false, - notices_open: false, + page: Page::Machines, fullscreen: false, confirm: None, notice: None, @@ -144,21 +173,51 @@ impl ClientApp { last_pc: None, reconnect: None, restart_capture: false, + streamed: false, + stream_failed: false, video_help: Arc::default(), asked_about_video: false, handover: None, display_at_connect: (0, 0), display_change: None, local: None, + share_page: None, } } - /// Show "This machine" in the lobby and let the unified app run setup. - pub fn with_local(mut self, local: share::Slot) -> Self { + /// A window with no background threads and the given settings, for + /// rendering screens in tests (this crate's and the host's). + #[doc(hidden)] + pub fn headless( + cc: &eframe::CreationContext<'_>, + discovery: Discovery, + progress: Progress, + cfg: ClientConfig, + ) -> Self { + Self::with_shared( + cc, + Arc::new(Mutex::new(discovery)), + Arc::new(Mutex::new(progress)), + cfg, + ) + } + + /// Show this machine in the machine list and its Sharing page as a tab. + pub fn with_local(mut self, local: share::Slot, page: Box) -> Self { self.local = Some(local); + self.share_page = Some(page); self } + /// Switch tabs, as a click on one would. + pub fn open_page(&mut self, page: Page) { + self.page = if page == Page::Sharing && self.share_page.is_none() { + Page::Machines + } else { + page + }; + } + fn commit(&mut self) { if self.dirty { self.dirty = false; @@ -166,7 +225,7 @@ impl ClientApp { tracing::warn!("could not save settings: {e:#}"); self.notice = Some(( Tone::Danger, - format!("Could not save settings: {e}"), + format!("Couldn't save settings: {e}. They apply until BroLink quits."), Instant::now(), )); } @@ -190,6 +249,9 @@ impl ClientApp { }; self.ended_seen = false; self.offer_sleep = None; + self.streamed = false; + self.stream_failed = false; + self.confirm = None; self.last_pc = Some(pc.clone()); self.asked_about_video = false; *self.video_help.lock() = None; @@ -270,7 +332,7 @@ impl ClientApp { ) } } - Err(e) => (Tone::Danger, format!("Could not turn HDR off: {e}")), + Err(e) => (Tone::Danger, format!("Couldn't turn HDR off: {e}")), }; if let Some(h) = help.lock().as_mut() { h.busy = false; @@ -305,12 +367,12 @@ impl ClientApp { self.notify_later(move || match session::wake_test(&pc) { Ok(true) => ( Tone::Success, - format!("{} received the wake packet. Waking it from here will work.", pc.name), + format!("{} received the wake packet, so waking it from this network works.", pc.name), ), Ok(false) => ( - Tone::Danger, + Tone::Warning, format!( - "The wake packet did not reach {} from this network. Its router would have to forward UDP 9 to it.", + "The wake packet didn't reach {} from this network. Waking it from here needs its router to forward UDP port 9 to it.", pc.name ), ), @@ -347,6 +409,7 @@ impl ClientApp { Event::Connected => { prog.step = Step::Streaming; prog.since = Instant::now(); + self.streamed = true; if self.cfg.stream.fullscreen { fullscreen = Some(true); } else { @@ -355,9 +418,9 @@ impl ClientApp { self.view.stream_started(ctx, live, self.cfg.capture_mouse); if let Some(problem) = host_audio_problem(&self.discovery, &live.node_id) { self.view.toast( - Tone::Danger, + Tone::Warning, format!( - "{} has no sound to send: the PC reports “{problem}”. See the host window.", + "{} has no sound to send: it reports “{problem}”. Its Sharing page in BroLink says more.", live.pc ), ); @@ -365,21 +428,25 @@ impl ClientApp { } Event::Failed { stage, code } => { let cancelled = prog.cancel; + self.stream_failed = !cancelled; prog.step = Step::Ended { - error: (!cancelled) - .then_some(format!("Connecting failed at {stage} (code {code}).")), + error: (!cancelled).then_some(format!( + "The stream didn't start: the {stage} step failed (code {code}). The machine answered, so this is usually the network between you." + )), }; } Event::Terminated { code, message } => { let cancelled = prog.cancel; + let error = code != 0 && !cancelled; + self.stream_failed = error; prog.step = Step::Ended { - error: (code != 0 && !cancelled).then_some(message), + error: error.then_some(message), }; } Event::Poor(p) => self.view.set_poor(p), Event::NoAudio(e) => self .view - .toast(Tone::Danger, format!("No sound on this Mac: {e}.")), + .toast(Tone::Warning, format!("No sound on this machine: {e}.")), } } // A stop we asked for ends without a Terminated event. @@ -407,6 +474,55 @@ impl ClientApp { self.pending_notice = None; } } + + fn tailscale_installed(&self) -> bool { + self.tailscale_ok.load(Ordering::Relaxed) + } + + /// Window shortcuts: Command-1/2/3 (Ctrl elsewhere) switch tabs, + /// Command-, opens Settings, Escape goes back to the machine list or + /// drops a pending question. + fn shortcuts(&mut self, ctx: &egui::Context) { + use egui::{Key, KeyboardShortcut, Modifiers}; + let shortcut = |key| KeyboardShortcut::new(Modifiers::COMMAND, key); + let busy = ctx.memory(|m| m.any_popup_open()) || ctx.wants_keyboard_input(); + let (settings, one, two, three, escape) = ctx.input_mut(|i| { + ( + i.consume_shortcut(&shortcut(Key::Comma)), + i.consume_shortcut(&shortcut(Key::Num1)), + i.consume_shortcut(&shortcut(Key::Num2)), + i.consume_shortcut(&shortcut(Key::Num3)), + !busy && i.key_pressed(Key::Escape), + ) + }); + let has_sharing = self.share_page.is_some(); + if settings || three || (two && !has_sharing) { + self.page = Page::Settings; + } else if two { + self.page = Page::Sharing; + } else if one { + self.page = Page::Machines; + } + if escape { + if self.confirm.is_some() { + self.confirm = None; + } else { + self.page = Page::Machines; + } + } + } +} + +/// Look for Tailscale's CLI every few seconds on a thread of its own, and +/// repaint when the answer changes. +fn spawn_tailscale_probe(ok: Arc, ctx: egui::Context) { + std::thread::spawn(move || loop { + let found = tailscale::cli().is_some(); + if ok.swap(found, Ordering::Relaxed) != found { + ctx.request_repaint(); + } + std::thread::sleep(Duration::from_secs(if found { 30 } else { 3 })); + }); } impl eframe::App for ClientApp { @@ -439,6 +555,7 @@ impl eframe::App for ClientApp { cfg: &self.cfg, fullscreen: self.fullscreen, path: pc_now.map(|p| p.path.clone()), + os: pc_now.map(|p| p.os.clone()).unwrap_or_default(), old_host, handover: self.handover.as_ref().map(|h| h.status(&l.pc)), video_help: help, @@ -502,16 +619,15 @@ impl eframe::App for ClientApp { return; } - ctx.request_repaint_after(Duration::from_millis(400)); - if self.tailscale_checked.elapsed() > Duration::from_secs(2) { - self.tailscale_checked = Instant::now(); - self.tailscale_ok = tailscale::cli().is_some(); - } + // Nothing here repaints on a timer when idle: discovery, the + // updater, the local service and the connect worker each ask for a + // repaint when what they know changes. Only the clocks below do. let disc = self.discovery.lock().clone(); let prog = self.progress.lock().clone(); if let Some(pc) = self.reconnect.take() { if prog.active() { self.reconnect = Some(pc); + ctx.request_repaint_after(Duration::from_millis(250)); } else { // Fresh details if discovery has them; the saved ones otherwise. let fresh = disc @@ -538,290 +654,407 @@ impl eframe::App for ClientApp { } } if let Some((_, _, at)) = &self.notice { - if at.elapsed() > Duration::from_secs(12) { - self.notice = None; + match NOTICE_FOR.checked_sub(at.elapsed()) { + Some(left) => ctx.request_repaint_after(left), + None => self.notice = None, } } + if self.pending_notice.is_some() { + ctx.request_repaint_after(Duration::from_millis(250)); + } + self.shortcuts(ctx); ui::top_bar(ctx, "top", |ui| { - let (label, tone) = if !self.tailscale_ok || disc.error.is_some() { - ("Tailscale off", Tone::Danger) - } else if prog.active() { - ("Connecting", Tone::Accent) - } else { - ("Ready", Tone::Success) - }; - self.brand.header(ui, "BroLink", |ui| { - if ui::ghost_button( - ui, - if self.settings_open { - "Close settings" - } else { - "Settings" - }, - ) - .clicked() - { - self.settings_open = !self.settings_open; - } - ui::status_pill(ui, label, tone); + self.brand.lockup(ui, "BroLink"); + ui.add_space(space::XL); + let mut pages = vec![(Page::Machines, "Machines", chord("1"))]; + if self.share_page.is_some() { + pages.push((Page::Sharing, "Sharing", chord("2"))); + } + pages.push((Page::Settings, "Settings", chord(","))); + ui.scope(|ui| { + ui::tabs(ui, &pages, &mut self.page); + }); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + let (tone, label) = if !self.tailscale_installed() { + (Tone::Danger, "Tailscale not installed") + } else if disc.error.is_some() { + (Tone::Danger, "Tailscale off") + } else if prog.active() { + (Tone::Accent, "Connecting") + } else if disc.refreshed.is_none() { + (Tone::Neutral, "Looking for machines") + } else { + (Tone::Success, "Tailscale on") + }; + ui::status_text(ui, tone, label); }); }); ui::bottom_bar(ctx, "bottom", |ui| { - // The button first, so a long login is cut rather than pushing - // it out of the window. - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - let mut line = format!("v{}", env!("CARGO_PKG_VERSION")); - if !disc.login.is_empty() { - line.push_str(&format!(" · Tailscale as {}", disc.login)); - } - ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { - ui.add(egui::Label::new(line).truncate()); - }); - }); + let mut line = format!("BroLink {}", env!("CARGO_PKG_VERSION")); + if !disc.login.is_empty() { + line.push_str(&format!(" · {}", disc.login)); + } + if let Some(v) = self.updates.lock().ready.clone() { + line.push_str(&format!(" · {v} installs after this session")); + } + ui.add(egui::Label::new(line).truncate()); }); egui::CentralPanel::default() .frame(egui::Frame::new().fill(P.bg)) .show(ctx, |ui| { - egui::ScrollArea::vertical().show(ui, |ui| { - ui.add_space(20.0); - ui::content_column(ui, COLUMN_WIDTH, |ui| { - ui.spacing_mut().item_spacing.y = 14.0; - self.tailscale_card(ui, &disc); - if let Some((tone, text, _)) = &self.notice { - ui::notice(ui, *tone, text); - } - if let Some((tone, text)) = self.updates.lock().notice.clone() { - ui::notice(ui, tone, &text); - } - if prog.active() { - self.session_card(ui, &prog); - } else if let Step::Ended { error } = &prog.step { - self.ended_card(ui, &prog, error.as_deref()); - } - if self.settings_open { - self.settings_card(ui, ctx, &prog); - self.relay_card(ui, &disc); - } else { - ui.add_space(8.0); - ui.label(egui::RichText::new("Your workspace, anywhere.").font(ui::theme::semibold(30.0)).color(P.text)); - ui::caption(ui, "Choose a machine to open its desktop. Your display and quality settings follow you."); - ui.add_space(14.0); - self.pcs_card(ui, ctx, &disc, &prog); - ui::titled_card(ui, "Next session", None, |ui| { - let settings = path::effective(&self.cfg.stream, &path::Path::default()); - let (w, h) = settings.resolution.pixels(Self::native_pixels(ctx)); - ui.horizontal_wrapped(|ui| { - ui::status_pill(ui, &format!("{w} × {h}"), Tone::Info); - ui::status_pill(ui, &format!("{} fps", settings.fps), Tone::Neutral); - ui::status_pill(ui, &format!("{} Mbps target", settings.bitrate_kbps / 1000), Tone::Neutral); - if ui::ghost_button(ui, "Configure stream").clicked() { self.settings_open = true; } - }); - }); - if !path_warnings(&disc).is_empty() { - egui::CollapsingHeader::new("Connection details").show(ui, |ui| self.path_notices(ui, &disc)); + egui::ScrollArea::vertical() + .id_salt(("page", self.page as u8)) + .show(ui, |ui| { + ui.add_space(space::XL); + let width = match self.page { + Page::Machines => column::WIDE, + _ => column::NARROW, + }; + ui::content_column(ui, width, |ui| { + ui.spacing_mut().item_spacing.y = space::LG; + match self.page { + Page::Machines => self.machines_page(ui, ctx, &disc, &prog), + Page::Sharing => match self.share_page.as_mut() { + Some(page) => page.show(ui), + None => self.page = Page::Machines, + }, + Page::Settings => self.settings_page(ui, ctx, &disc, &prog), } - self.key_expiry_notices(ui, &disc); - } - ui.add_space(10.0); + ui.add_space(space::XL); + }); }); - }); }); self.commit(); } } impl ClientApp { - fn tailscale_card(&mut self, ui: &mut egui::Ui, disc: &Discovery) { - let problem = if !self.tailscale_ok { - Some("Tailscale is not installed.".to_string()) - } else { - disc.error.as_ref().map(|e| format!("Tailscale: {e}.")) + // ----------------------------------------------------------------------- + // Machines + // ----------------------------------------------------------------------- + + fn machines_page( + &mut self, + ui: &mut egui::Ui, + ctx: &egui::Context, + disc: &Discovery, + prog: &Progress, + ) { + let online = disc.pcs.iter().filter(|p| p.online).count(); + let summary = match disc.pcs.len() { + 0 => String::new(), + 1 => format!("1 machine · {online} online"), + n => format!("{n} machines · {online} online"), }; - let Some(problem) = problem else { return }; - ui::toned_card(ui, Tone::Danger, |ui| { - ui::heading( + ui::page_header(ui, "Machines", None, |ui| { + ui::small_print(ui, summary); + }); + self.tailscale_banner(ui, disc); + if let Some((tone, text, _)) = self.notice.clone() { + ui::notice(ui, tone, &text); + } + if let Some((tone, text)) = self.updates.lock().notice.clone() { + ui::notice(ui, tone, &text); + } + if prog.active() { + self.session_card(ui, prog); + } else if let Step::Ended { error } = &prog.step { + self.ended_card(ui, prog, error.as_deref()); + } + self.confirm_banner(ui, disc); + self.machine_list(ui, ctx, disc, prog); + self.next_stream(ui, ctx); + self.details(ui, disc); + } + + fn tailscale_banner(&mut self, ui: &mut egui::Ui, disc: &Discovery) { + if !self.tailscale_installed() { + ui::banner( ui, - "Tailscale is needed", - Some("It connects this machine to the others on your account from anywhere and confirms they are yours."), + Tone::Danger, + "Tailscale isn't installed", + Some("BroLink finds your machines and connects to them through Tailscale. Install it and sign in with the account your other machines use; they appear here within seconds."), + |ui| { + if ui::primary_button(ui, "Get Tailscale").clicked() { + let url = if cfg!(windows) { + "https://tailscale.com/download/windows" + } else if cfg!(target_os = "linux") { + "https://tailscale.com/download/linux" + } else { + "https://tailscale.com/download/mac" + }; + ui.ctx().open_url(egui::OpenUrl::new_tab(url)); + } + }, ); - ui::setting_row(ui, "Tailscale", Some(&problem), |ui| { - if ui::primary_button(ui, "Get Tailscale").clicked() { - let url = if cfg!(windows) { - "https://tailscale.com/download/windows" - } else if cfg!(target_os = "linux") { - "https://tailscale.com/download/linux" - } else { - "https://tailscale.com/download/mac" - }; - ui.ctx().open_url(egui::OpenUrl::new_tab(url)); - } - }); - }); + } else if let Some(e) = &disc.error { + ui::banner( + ui, + Tone::Danger, + "Tailscale isn't connected", + Some(&format!( + "{}. Open Tailscale and sign in with the account your other machines use. Machines seen before are listed, but can't be reached until then.", + sentence(e) + )), + |_| {}, + ); + } } - fn this_machine_row(&mut self, ui: &mut egui::Ui) { - let Some(slot) = self.local.clone() else { + fn confirm_banner(&mut self, ui: &mut egui::Ui, disc: &Discovery) { + let Some((name, action)) = self.confirm.clone() else { return; }; + let pc = disc.pcs.iter().find(|p| p.name == name).cloned(); + ui::banner( + ui, + Tone::Danger, + &format!("{} {name}?", action.label()), + Some("Programs there close without asking, and anything unsaved is lost."), + |ui| { + if ui::destructive_button(ui, action.label()).clicked() { + if let Some(ip) = pc.as_ref().and_then(|p| p.ip) { + self.power(ip, &name, action); + } + self.confirm = None; + } + if ui::ghost_button(ui, "Cancel").clicked() { + self.confirm = None; + } + }, + ); + } + + fn machine_list( + &mut self, + ui: &mut egui::Ui, + ctx: &egui::Context, + disc: &Discovery, + prog: &Progress, + ) { + ui::group(ui, |ui| { + let mut first = true; + if let Some(slot) = self.local.clone() { + if self.share_page.is_some() { + self.this_machine_row(ui, &slot); + first = false; + } + } + if disc.pcs.is_empty() { + if !first { + ui::row_separator(ui); + } + self.empty_list(ui, disc); + return; + } + for pc in &disc.pcs { + if !first { + ui::row_separator(ui); + } + first = false; + let (tone, detail) = describe(pc); + ui::list_row(ui, Some(tone), &pc.name, &detail, |ui| { + self.row_menu(ui, pc); + if pc.can_stream() && !pc.remembered { + let label = if pc.online { + "Connect" + } else { + "Wake and connect" + }; + let clicked = ui + .add_enabled_ui(!prog.active(), |ui| { + ui::secondary_button(ui, label) + .on_disabled_hover_text("Another connection is in progress.") + }) + .inner + .clicked(); + if clicked { + self.connect(ctx, pc); + } + } + }); + } + }); + } + + fn empty_list(&mut self, ui: &mut egui::Ui, disc: &Discovery) { + ui.add_space(space::LG); + if disc.error.is_some() || !self.tailscale_installed() { + ui::muted(ui, "Your machines appear here once Tailscale is connected."); + } else if disc.refreshed.is_none() { + ui::empty_state(ui, "Looking for machines on your tailnet…", true); + } else { + ui::strong(ui, "No other machines yet"); + ui::caption( + ui, + "Install BroLink on another computer and sign in to Tailscale there with the same account. It appears here within a few seconds.", + ); + ui.add_space(space::XS); + if ui::link(ui, "Download BroLink").clicked() { + ui.ctx().open_url(egui::OpenUrl::new_tab(DOWNLOAD_URL)); + } + } + ui.add_space(space::LG); + } + + /// The first row of the list: this machine, and where its sharing is. + fn this_machine_row(&mut self, ui: &mut egui::Ui, slot: &share::Slot) { let g = slot.lock(); let running = g.setup_running; - let result = g.setup_result.clone(); let status = g.status.clone(); drop(g); - ui::row_separator(ui); + let noun = this_noun(status.as_ref().map(|s| s.os.as_str()).unwrap_or_default()); let name = status .as_ref() - .map(|s| s.name.as_str()) + .map(|s| s.name.clone()) .filter(|n| !n.is_empty()) - .unwrap_or("This machine"); - let detail = match &status { - Some(s) if s.streamer.running && s.streamer.api_ok => { + .unwrap_or_else(|| noun.to_string()); + let shared = status + .as_ref() + .is_some_and(|s| s.streamer.running && s.streamer.api_ok); + let (tone, detail) = match &status { + None => ( + Tone::Neutral, + "Starting BroLink's background service…".to_string(), + ), + Some(_) if running => (Tone::Accent, format!("{noun} · Setting up sharing…")), + Some(s) if shared && s.setup.is_empty() => ( + Tone::Success, format!( - "Sharing · {}{}", - os_label(&s.os), + "{noun} · Shared{}", s.tailscale_ip .as_deref() - .map(|ip| format!(" · {ip}")) + .map(|ip| format!(" at {ip}")) .unwrap_or_default() - ) - } - Some(s) if s.streamer.installed => { - format!( - "{} · streaming engine installed, not sharing yet", - os_label(&s.os) - ) - } - Some(s) => format!("{} · set up sharing so others can connect", os_label(&s.os)), - None => "Starting the local BroLink service…".into(), + ), + ), + Some(_) if shared => ( + Tone::Warning, + format!("{noun} · Shared; setup needs attention"), + ), + Some(s) if s.streamer.installed => ( + Tone::Warning, + format!("{noun} · Not shared: the streaming engine isn't running"), + ), + Some(_) => ( + Tone::Neutral, + format!("{noun} · Not shared, so others can't connect here"), + ), }; - ui::list_row(ui, name, &detail, |ui| { - if running { - ui::caption(ui, "Setting up…"); - } else if ui::primary_button( - ui, - if status - .as_ref() - .is_some_and(|s| s.streamer.running && s.streamer.api_ok) - { - "Repair sharing" - } else { - "Share this machine" - }, - ) - .clicked() - { - slot.lock().want_setup = true; + ui::list_row(ui, Some(tone), &name, &detail, |ui| { + let label = if shared { "Manage" } else { "Set up sharing" }; + if ui::secondary_button(ui, label).clicked() { + self.page = Page::Sharing; } }); - if let Some(Err(e)) = result.as_ref() { - ui::notice(ui, Tone::Danger, e); - } else if let Some(Ok(())) = result.as_ref() { - ui::notice(ui, Tone::Success, "Sharing is set up on this machine."); - } - if let Some(s) = &status { - if !s.setup.is_empty() { - for item in s.setup.iter().take(3) { - ui::dot_label(ui, Tone::Accent, item); - } - } - } - ui::row_separator(ui); } - fn pcs_card( - &mut self, - ui: &mut egui::Ui, - ctx: &egui::Context, - disc: &Discovery, - prog: &Progress, - ) { - ui::titled_card( - ui, - "Your machines", - Some("Every machine reachable on your tailnet. Connect opens its desktop when BroLink is sharing there."), - |ui| { - self.this_machine_row(ui); - if disc.pcs.is_empty() { - let text = if disc.error.is_some() || !self.tailscale_ok { - "Sign in to Tailscale to see your machines." - } else if disc.refreshed.is_none() { - "Looking for machines…" - } else { - "No other machines on this tailnet yet. Install BroLink on each one and sign in to the same Tailscale account." - }; - ui::empty_state(ui, text, disc.refreshed.is_none() && disc.error.is_none()); + /// The row's "…" menu: wake, power, copy the address. + fn row_menu(&mut self, ui: &mut egui::Ui, pc: &Pc) { + let live = !pc.remembered; + let wake = live && !pc.online && pc.can_wake(); + let test_wake = live && pc.online && pc.host.is_some() && pc.can_wake(); + let power = live && pc.online && pc.power_allowed(); + if !(wake || test_wake || power || pc.ip.is_some()) { + return; + } + ui::icon_menu(ui, Icon::More, &format!("More for {}", pc.name), |ui| { + if wake && ui::menu_item(ui, "Wake").clicked() { + self.notice = Some(match session::wake_only(pc) { + Ok(_) => ( + Tone::Neutral, + format!( + "Wake packets sent to {}. It can take a minute to come online.", + pc.name + ), + Instant::now(), + ), + Err(e) => (Tone::Danger, format!("{}: {e}", pc.name), Instant::now()), + }); + } + if test_wake && ui::menu_item(ui, "Test waking it from this network").clicked() { + self.test_wake(pc); + } + if power { + if wake || test_wake { + ui::menu_separator(ui); } - let pcs = disc.pcs.clone(); - for (i, pc) in pcs.iter().enumerate() { - if i > 0 { - ui::row_separator(ui); + if ui::menu_item(ui, "Sleep").clicked() { + if let Some(ip) = pc.ip { + self.power(ip, &pc.name, PowerAction::Sleep); } - let detail = describe(pc); - ui::list_row(ui, &pc.name, &detail, |ui| { - let busy = prog.active(); - if pc.can_stream() - && !pc.remembered - && !busy - && ui::primary_button(ui, "Connect").clicked() - { - self.connect(ctx, pc); - } - if !pc.online - && pc.can_wake() - && !busy - && ui::ghost_button(ui, "Wake").clicked() - { - self.notice = Some(match session::wake_only(pc) { - Ok(n) => ( - Tone::Info, - format!("Sent {n} wake packets to {}.", pc.name), - Instant::now(), - ), - Err(e) => (Tone::Danger, e.to_string(), Instant::now()), - }); - } - if pc.power_allowed() { - self.power_menu(ui, pc); - } - }); } - if let Some((name, action)) = self.confirm.clone() { - ui::notice( - ui, - Tone::Danger, - &format!( - "{} {name}? Programs are closed without asking; anything unsaved is lost.", - action.label() - ), - ); - ui.horizontal(|ui| { - if ui::toned_button(ui, action.label(), Tone::Danger).clicked() { - if let Some(pc) = pcs.iter().find(|p| p.name == name) { - if let Some(ip) = pc.ip { - self.power(ip, &pc.name, action); - } - } - self.confirm = None; - } - if ui::ghost_button(ui, "Cancel").clicked() { - self.confirm = None; - } - }); + if ui::menu_item(ui, "Restart…").clicked() { + self.confirm = Some((pc.name.clone(), PowerAction::Restart)); } - }, - ); + if ui::menu_item(ui, "Shut down…").clicked() { + self.confirm = Some((pc.name.clone(), PowerAction::Shutdown)); + } + } + if let Some(ip) = pc.ip { + if wake || test_wake || power { + ui::menu_separator(ui); + } + if ui::menu_item(ui, "Copy Tailscale address").clicked() { + ui.ctx().copy_text(ip.to_string()); + self.notice = Some(( + Tone::Neutral, + format!("Copied {ip}, {}'s Tailscale address.", pc.name), + Instant::now(), + )); + } + } + }); + } + + /// What the next stream asks for, and a way to change it. + fn next_stream(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) { + let s = path::effective(&self.cfg.stream, &path::Path::default()); + let (w, h) = s.resolution.pixels(Self::native_pixels(ctx)); + ui.horizontal_wrapped(|ui| { + ui.spacing_mut().item_spacing = egui::vec2(space::SM, space::SM); + ui::section_label(ui, "Next stream"); + ui.add_space(space::XS); + ui::tag(ui, None, &format!("{w} × {h}")); + ui::tag(ui, None, &format!("{} fps", s.fps)); + ui::tag(ui, None, &format!("{} Mbps", s.bitrate_kbps / 1000)); + ui.add_space(space::XS); + if ui::link(ui, "Change").clicked() { + self.page = Page::Settings; + } + }); } - /// Why a PC is relayed, a PC whose host encodes in software, and a PC - /// whose host is too old to update itself: each gets one line under - /// the list, with what to do about it. - fn path_notices(&self, ui: &mut egui::Ui, disc: &Discovery) { - for text in path_warnings(disc) { - ui::notice(ui, Tone::Accent, &text); + /// Key expiry that is about to cut a machine off, then everything else + /// worth knowing about the paths, folded away. + fn details(&self, ui: &mut egui::Ui, disc: &Discovery) { + let mut later = Vec::new(); + for text in key_expiry_warnings(disc) { + let urgent = text.contains("expired") || text.contains(" days") && days_in(&text) <= 30; + if urgent { + ui::notice(ui, Tone::Danger, &text); + } else { + later.push((Tone::Neutral, text)); + } + } + let mut items: Vec<(Tone, String)> = path_warnings(disc) + .into_iter() + .map(|t| (Tone::Warning, t)) + .collect(); + items.extend(later); + if items.is_empty() { + return; } + let title = format!("Connection details ({})", items.len()); + ui::collapsible(ui, "lobby-details", &title, false, |ui| { + ui.spacing_mut().item_spacing.y = space::SM; + for (tone, text) in &items { + ui::notice(ui, *tone, text); + } + }); } /// Start installing the newest BroLink Host on the PC through the @@ -837,7 +1070,7 @@ impl ClientApp { let Some(mac_ip) = disc.self_ip else { self.view.toast( Tone::Danger, - "Tailscale on this Mac has no address to serve from.", + "Tailscale on this machine has no address to serve the update from.", ); return; }; @@ -865,7 +1098,7 @@ impl ClientApp { ctx.clone(), )); self.view.toast( - Tone::Info, + Tone::Neutral, format!("Installing BroLink Host on {name} through the stream…"), ); } @@ -895,306 +1128,369 @@ impl ClientApp { } } - /// A Tailscale node key that expires is the one thing that can take a - /// far-away PC off the tailnet with nobody there to sign it back in. - fn key_expiry_notices(&self, ui: &mut egui::Ui, disc: &Discovery) { - for text in key_expiry_warnings(disc) { - let urgent = text.contains("expired") || text.contains(" days") && days_in(&text) <= 30; - ui::notice(ui, if urgent { Tone::Danger } else { Tone::Info }, &text); + fn session_card(&mut self, ui: &mut egui::Ui, prog: &Progress) { + if prog.updating { + ui::banner( + ui, + Tone::Accent, + "Installing a BroLink update", + Some("BroLink restarts itself in a moment. New connections wait until it has."), + |_| {}, + ); + return; } - } - - fn power_menu(&mut self, ui: &mut egui::Ui, pc: &Pc) { - ui::menu_button(ui, "PC", |ui| { - if ui.button("Sleep").clicked() { - if let Some(ip) = pc.ip { - self.power(ip, &pc.name, PowerAction::Sleep); - } - ui.close_menu(); - } - if ui.button("Restart…").clicked() { - self.confirm = Some((pc.name.clone(), PowerAction::Restart)); - ui.close_menu(); - } - if pc.can_wake() && ui.button("Test wake").clicked() { - self.test_wake(pc); - ui.close_menu(); + let pc = &prog.pc; + let (title, explain) = match &prog.step { + Step::Waking => ( + format!("Waking {pc}"), + "BroLink sends wake packets every few seconds. A sleeping machine can take up to two minutes to come back.", + ), + Step::Waiting => ( + format!("Waiting for {pc}"), + "It is awake; its streaming engine is starting.", + ), + Step::Pairing { .. } => ( + format!("Pairing with {pc}"), + "This happens once per machine. BroLink on that machine enters this PIN by itself; nothing needs typing unless it says otherwise below.", + ), + _ => (format!("Connecting to {pc}"), ""), + }; + ui::toned_card(ui, Tone::Accent, |ui| { + ui::heading(ui, &title, None); + if let Step::Pairing { pin } = &prog.step { + ui::display_digits(ui, pin); + ui.add_space(space::XS); } - if ui.button("Shut down…").clicked() { - self.confirm = Some((pc.name.clone(), PowerAction::Shutdown)); - ui.close_menu(); + if !explain.is_empty() { + ui::caption(ui, explain); } - }); - } - - fn session_card(&mut self, ui: &mut egui::Ui, prog: &Progress) { - ui::toned_card(ui, Tone::Accent, |ui| { - let title = match &prog.step { - Step::Waking => "Waking the PC", - Step::Waiting => "Waiting for the PC", - Step::Pairing { .. } => "Pairing", - Step::Launching | Step::Connecting | Step::Streaming => "Connecting", - _ => "", - }; - ui::heading(ui, title, None); match &prog.step { - Step::Pairing { pin } => { - ui.label(format!( - "First time with {}. BroLink Host on the PC enters this PIN for you.", - prog.pc - )); - ui::display_digits(ui, pin); - if !prog.detail.is_empty() { - ui::notice(ui, Tone::Accent, &prog.detail); - } + Step::Pairing { .. } if !prog.detail.is_empty() => { + ui::notice(ui, Tone::Warning, &prog.detail); } - _ => ui::empty_state(ui, &prog.detail, true), + Step::Pairing { .. } => {} + _ => ui::empty_state( + ui, + if prog.detail.is_empty() { + "Starting…" + } else { + &prog.detail + }, + true, + ), + } + ui.add_space(space::XS); + if ui::secondary_button(ui, "Cancel").clicked() { + self.disconnect(); } - ui.horizontal(|ui| { - if ui::danger_button(ui, "Cancel").clicked() { - self.disconnect(); - } - }); }); } fn ended_card(&mut self, ui: &mut egui::Ui, prog: &Progress, error: Option<&str>) { - if error.is_none() && self.offer_sleep.is_none() { - return; - } - let tone = if error.is_some() { - Tone::Danger - } else { - Tone::Neutral - }; - ui::toned_card(ui, tone, |ui| { - match error { - Some(e) => { - ui::heading(ui, &format!("{} disconnected", prog.pc), None); - ui.label(e); - } - None => ui::heading( - ui, - "Session ended", - Some(&format!("Leave {} on, or put it to sleep?", prog.pc)), - ), - } - ui.horizontal(|ui| { - if let Some(pc) = self.offer_sleep.clone() { - if ui::primary_button(ui, "Sleep the PC").clicked() { - if let Some(ip) = pc.ip { - self.power(ip, &pc.name, PowerAction::Sleep); - } - self.offer_sleep = None; + let pc = prog.pc.clone(); + if let Some(e) = error { + let title = if self.streamed { + format!("{pc} disconnected") + } else { + format!("Couldn't connect to {pc}") + }; + let lighter = self.stream_failed + && self + .cfg + .stream + .preset() + .is_none_or(|p| p != crate::config::Preset::Smooth); + ui::banner(ui, Tone::Danger, &title, Some(e), |ui| { + if let Some(last) = self.last_pc.clone() { + if ui::primary_button(ui, "Try again").clicked() { + self.reconnect = Some(last.clone()); self.progress.lock().step = Step::Idle; } - } - if let (Some(_), Some(pc)) = (error, self.last_pc.clone()) { // The lighter profile is the likeliest to hold if the - // network, not the PC, ended the last one. - if ui::primary_button(ui, "Try again at Smooth (1080p · 20 Mbps)").clicked() { + // network, not the machine, ended the last one. + if lighter + && ui::secondary_button(ui, "Try at 1080p, 20 Mbps") + .on_hover_text("Switches Settings to the Smooth profile.") + .clicked() + { self.cfg.stream.apply_preset(crate::config::Preset::Smooth); self.dirty = true; - self.reconnect = Some(pc.clone()); - self.progress.lock().step = Step::Idle; - } - if ui::ghost_button(ui, "Try again").clicked() { - self.reconnect = Some(pc); + self.reconnect = Some(last); self.progress.lock().step = Step::Idle; } } - let label = if error.is_some() { - "Dismiss" - } else { - "Leave it on" - }; - if ui::ghost_button(ui, label).clicked() { + if ui::ghost_button(ui, "Dismiss").clicked() { self.offer_sleep = None; self.progress.lock().step = Step::Idle; } }); - }); - } - - fn settings_card(&mut self, ui: &mut egui::Ui, ctx: &egui::Context, prog: &Progress) { - ui::titled_card( - ui, - "Settings", - Some("Applied the next time you connect."), - |ui| { - let native = Self::native_pixels(ctx); - if crate::settings::stream_controls(ui, &mut self.cfg.stream, native) { - self.dirty = true; - } - ui::row_separator(ui); - let s = &mut self.cfg.stream; - if ui::toggle_row( - ui, - &mut s.fullscreen, - "Full screen", - Some("Otherwise the stream fills this window."), - ) { - self.dirty = true; - } - ui::row_separator(ui); - ui::setting_row( - ui, - "App", - Some("What the PC starts. “Desktop” is the whole PC."), - |ui| { - let mut app = s.app.clone(); - let mut changed = false; - if prog.apps.is_empty() { - changed = ui - .add(egui::TextEdit::singleline(&mut app).desired_width(160.0)) - .changed(); - } else { - egui::ComboBox::from_id_salt("app_pick") - .selected_text(app.clone()) - .show_ui(ui, |ui| { - for a in &prog.apps { - if ui.selectable_value(&mut app, a.clone(), a).clicked() { - changed = true; - } - } - }); - } - if changed && !app.trim().is_empty() { - s.app = app; - self.dirty = true; + } else if let Some(target) = self.offer_sleep.clone() { + ui::banner( + ui, + Tone::Neutral, + &format!("Session with {pc} ended"), + Some("Put it to sleep, or leave it on to connect again from anywhere. Asleep, it can only be woken from its own network."), + |ui| { + if ui::primary_button(ui, &format!("Sleep {}", target.name)).clicked() { + if let Some(ip) = target.ip { + self.power(ip, &target.name, PowerAction::Sleep); } - }, - ); - ui::row_separator(ui); - if ui::toggle_row( + self.offer_sleep = None; + self.progress.lock().step = Step::Idle; + } + if ui::ghost_button(ui, "Leave it on").clicked() { + self.offer_sleep = None; + self.progress.lock().step = Step::Idle; + } + }, + ); + } + } + + // ----------------------------------------------------------------------- + // Settings + // ----------------------------------------------------------------------- + + fn settings_page( + &mut self, + ui: &mut egui::Ui, + ctx: &egui::Context, + disc: &Discovery, + prog: &Progress, + ) { + ui::page_header( + ui, + "Settings", + Some("Stream changes apply the next time you connect."), + |_| {}, + ); + let native = Self::native_pixels(ctx); + ui::section(ui, "Stream", |ui| { + if crate::settings::stream_controls(ui, &mut self.cfg.stream, native) { + self.dirty = true; + } + }); + let (_, host_key) = stream::host_key(); + ui::section(ui, "Window and input", |ui| { + if ui::toggle_row( ui, - &mut self.cfg.cmd_is_ctrl, - "Command key acts as Ctrl", - Some("So ⌘C, ⌘V and ⌘Z do what you expect on the PC. Off makes it the Windows key."), + &mut self.cfg.stream.fullscreen, + "Open streams full screen", + Some("Otherwise the stream fills this window."), ) { self.dirty = true; } + ui::row_separator(ui); + if ui::toggle_row( + ui, + &mut self.cfg.capture_mouse, + "Capture the mouse", + Some(&format!( + "A click on the picture hides this cursor and sends raw movement, which games need. {host_key} releases it." + )), + ) { + self.dirty = true; + } + if cfg!(target_os = "macos") { ui::row_separator(ui); if ui::toggle_row( ui, - &mut self.cfg.sleep_prompt, - "Offer to sleep the PC after each session", - Some("Asleep, Tailscale is off. This Mac can only wake the PC from that PC's own network, not from elsewhere."), - ) { - self.dirty = true; - } - ui::row_separator(ui); - if ui::toggle_row( - ui, - &mut self.cfg.auto_update, - "Keep BroLink and your machines up to date", - Some("Checks GitHub every few hours, installs new versions of this app, and sends host updates to your other machines over Tailscale."), + &mut self.cfg.cmd_is_ctrl, + "Command acts as Ctrl", + Some("So Command-C, V and Z copy, paste and undo on Windows. Off, Command is the Windows key."), ) { self.dirty = true; } - if let Some(slot) = self.local.clone() { - ui::row_separator(ui); - ui::caption(ui, "THIS MACHINE"); - let g = slot.lock(); - let mut power = g.power_allowed; - let mut stay = g.stay_awake; - let mut auto = g.autostart; - drop(g); - if ui::toggle_row( - ui, - &mut power, - "Let others sleep, restart, or shut down this machine", - Some("Devices allowed by your Tailscale access rules can ask."), - ) { - slot.lock().want_power = Some(power); - } - ui::row_separator(ui); - if ui::toggle_row( - ui, - &mut stay, - "Keep this machine awake while plugged in", - Some("Tailscale only works while the machine is on."), - ) { - slot.lock().want_stay_awake = Some(stay); + } + ui::row_separator(ui); + let s = &mut self.cfg.stream; + ui::setting_row( + ui, + "App to open", + Some("What the other machine starts. Desktop is its whole screen."), + |ui| { + let mut app = s.app.clone(); + let mut changed = false; + if prog.apps.is_empty() { + changed = ui + .add( + egui::TextEdit::singleline(&mut app) + .desired_width(160.0) + .margin(egui::Margin::symmetric(8, 7)), + ) + .on_hover_text("The list fills in after the first connection.") + .changed(); + } else { + ui::select(ui, "app_pick", app.clone(), 160.0, |ui| { + for a in &prog.apps { + if ui.selectable_value(&mut app, a.clone(), a).clicked() { + changed = true; + } + } + }); } - ui::row_separator(ui); - if ui::toggle_row( - ui, - &mut auto, - "Start BroLink when you log in", - Some( - "So others can connect after a restart without anyone at the keyboard.", - ), - ) { - slot.lock().want_autostart = Some(auto); + if changed && !app.trim().is_empty() { + s.app = app; + self.dirty = true; } + }, + ); + }); + ui::section(ui, "Power", |ui| { + if ui::toggle_row( + ui, + &mut self.cfg.sleep_prompt, + "Offer to sleep a machine after a session", + Some("Asleep, Tailscale is off there too, so it can only be woken from its own network."), + ) { + self.dirty = true; + } + }); + self.updates_section(ui); + self.relay_section(ui, disc); + self.about_section(ui); + } + + fn updates_section(&mut self, ui: &mut egui::Ui) { + ui::section(ui, "Updates", |ui| { + if ui::toggle_row( + ui, + &mut self.cfg.auto_update, + "Keep BroLink up to date", + Some("Checks GitHub every few hours, installs new versions of this app, and sends updates to your other machines over Tailscale."), + ) { + self.dirty = true; + } + ui::row_separator(ui); + let (message, checked) = { + let st = self.updates.lock(); + (st.message.clone(), st.checked) + }; + let hint = if !self.cfg.auto_update { + "Off. This app and your other machines stay on their current versions.".to_string() + } else if message.is_empty() { + format!("Waiting for the first check · {}", update::ago(checked)) + } else { + format!("{message} · {}", update::ago(checked)) + }; + ui::setting_row(ui, "Status", Some(&hint), |ui| { + let clicked = ui + .add_enabled_ui(self.cfg.auto_update, |ui| { + ui::secondary_button(ui, "Check now") + }) + .inner + .clicked(); + if clicked { + self.updates.lock().check_now = true; } - let (message, checked) = { - let st = self.updates.lock(); - (st.message.clone(), st.checked) - }; - let hint = format!( - "{} · {}", - if message.is_empty() { - "Waiting for the first check." - } else { - &message - }, - update::ago(checked) - ); - ui::setting_row(ui, "Updates", Some(&hint), |ui| { - if ui::ghost_button(ui, "Check now").clicked() { - self.updates.lock().check_now = true; - } - }); - ui::row_separator(ui); - ui::open_source_row(ui, &mut self.notices_open); - }, - ); + }); + }); } - fn relay_card(&mut self, ui: &mut egui::Ui, disc: &Discovery) { + fn relay_section(&mut self, ui: &mut egui::Ui, disc: &Discovery) { let state = relay_state(disc); - ui::titled_card(ui, "Relay", Some(state.sentence()), |ui| match &state { - RelayState::Ready { name, ip } => { - let detail = match ip { - Some(ip) => format!("{name} · {ip}"), - None => name.clone(), - }; - ui::caption(ui, detail); - } - RelayState::Unavailable { name } => { - ui::caption( - ui, - format!( - "{name} is on the tailnet. This grant is what Tailscale needs if the relay is yours:" - ), - ); + ui::section(ui, "Relay", |ui| { + ui.add_space(space::MD); + ui.spacing_mut().item_spacing.y = space::SM; + let (tone, title) = state.title(); + ui::status_text(ui, tone, &title); + ui::caption(ui, state.sentence()); + if let RelayState::Unavailable { .. } = &state { ui::well(ui, |ui| { - ui.label(egui::RichText::new(RELAY_GRANT).monospace().color(P.muted)); + ui.label( + egui::RichText::new(RELAY_GRANT) + .font(ui::theme::mono(ui::theme::text::MONO)) + .color(P.text_secondary), + ); }); - ui.horizontal(|ui| { - if ui::ghost_button(ui, "Copy grant").clicked() { + } + ui.horizontal_wrapped(|ui| { + ui.spacing_mut().item_spacing.x = space::LG; + if let RelayState::Unavailable { .. } = &state { + if ui::secondary_button(ui, "Copy grant").clicked() { ui.ctx().copy_text(RELAY_GRANT.to_string()); - self.notice = - Some((Tone::Info, "Copied the grant.".into(), Instant::now())); + self.notice = Some(( + Tone::Neutral, + "Copied the relay grant.".into(), + Instant::now(), + )); } - if ui::ghost_button(ui, "How it works").clicked() { - ui.ctx().open_url(egui::OpenUrl::new_tab(RELAY_DOCS)); - } - }); - } - RelayState::None | RelayState::Offline { .. } | RelayState::Checking { .. } => { - ui.horizontal(|ui| { - if ui::ghost_button(ui, "How it works").clicked() { - ui.ctx().open_url(egui::OpenUrl::new_tab(RELAY_DOCS)); + } + if ui::link(ui, "How peer relays work").clicked() { + ui.ctx().open_url(egui::OpenUrl::new_tab(RELAY_DOCS)); + } + }); + ui.add_space(space::XS); + }); + } + + fn about_section(&mut self, ui: &mut egui::Ui) { + ui::section(ui, "About", |ui| { + ui::setting_row( + ui, + &format!("BroLink {}", env!("CARGO_PKG_VERSION")), + Some("Free software under the GNU GPL, version 3 or later. It builds on Moonlight, Sunshine, Opus and the Geist typeface."), + |ui| { + if ui::link(ui, "Source code").clicked() { + ui.ctx().open_url(egui::OpenUrl::new_tab(SOURCE_URL)); } + }, + ); + ui::row_separator(ui); + ui.add_space(space::XS); + ui::disclosure(ui, "about-notices", "Open-source notices", false, |ui| { + ui::well(ui, |ui| { + egui::ScrollArea::vertical() + .id_salt("open_source_notices") + .max_height(280.0) + .show(ui, |ui| { + ui.add( + egui::Label::new( + egui::RichText::new(ui::NOTICES) + .font(ui::theme::mono(11.5)) + .color(P.text_secondary), + ) + .wrap(), + ); + }); }); - } + }); + ui.add_space(space::SM); }); } } +/// "Tailscale is stopped" → "Tailscale is stopped", with a capital and +/// without a trailing full stop, for building a sentence around it. +fn sentence(s: &str) -> String { + let s = s.trim().trim_end_matches('.'); + let mut c = s.chars(); + match c.next() { + Some(f) => f.to_uppercase().collect::() + c.as_str(), + None => String::new(), + } +} + +/// "This Mac", "This PC": what to call the machine BroLink runs on. +fn this_noun(os: &str) -> &'static str { + let os = if os.is_empty() { + if cfg!(windows) { + "windows" + } else if cfg!(target_os = "macos") { + "macOS" + } else { + "linux" + } + } else { + os + }; + match os_label(os) { + "Windows" => "This PC", + "macOS" => "This Mac", + _ => "This machine", + } +} + #[derive(Debug, Clone, PartialEq, Eq)] enum RelayState { None, @@ -1223,6 +1519,26 @@ impl RelayState { Self::Ready { .. } => RELAY_READY, } } + + /// The status line over the sentence. + fn title(&self) -> (Tone, String) { + match self { + Self::None => (Tone::Neutral, "No relay of your own".into()), + Self::Offline { name } => (Tone::Warning, format!("{name} is offline")), + Self::Checking { name } => (Tone::Neutral, format!("Couldn't check {name}")), + Self::Unavailable { name } => ( + Tone::Warning, + format!("{name} isn't available to this device"), + ), + Self::Ready { name, ip } => ( + Tone::Success, + match ip { + Some(ip) => format!("Using {name} ({ip})"), + None => format!("Using {name}"), + }, + ), + } + } } fn ip_listed(servers: &[String], ip: Option) -> bool { @@ -1268,30 +1584,30 @@ fn relay_state(disc: &Discovery) -> RelayState { } } -/// One line per machine whose Tailscale key expires, this Mac included. +/// One line per machine whose Tailscale key expires, this one included. fn key_expiry_warnings(disc: &Discovery) -> Vec { let mut out = Vec::new(); for pc in &disc.pcs { if let Some(d) = pc.key_expiry_days { out.push(if d <= 0 { format!( - "{}'s Tailscale key has expired: it is off the tailnet until someone signs Tailscale in at the PC.", + "{}'s Tailscale key has expired. It is off the tailnet until someone signs in to Tailscale on it.", pc.name ) } else { format!( - "{}'s Tailscale key expires in {d} days. In the Tailscale admin console (login.tailscale.com/admin/machines), open {} and choose Disable key expiry; otherwise it drops off the tailnet and needs a sign-in at the PC.", - pc.name, pc.name + "{}'s Tailscale key expires in {d} days. Turn off key expiry for it in the Tailscale admin console (login.tailscale.com/admin/machines), or it will need a sign-in on that machine.", + pc.name ) }); } } if let Some(d) = disc.self_key_days { out.push(if d <= 0 { - "This machine's Tailscale key has expired; sign in to Tailscale again.".to_string() + "This machine's Tailscale key has expired. Sign in to Tailscale again.".to_string() } else { format!( - "This machine's Tailscale key expires in {d} days; disable key expiry for it in the admin console as well." + "This machine's Tailscale key expires in {d} days. Turn off key expiry for it in the admin console too." ) }); } @@ -1307,7 +1623,7 @@ fn days_in(text: &str) -> i64 { .unwrap_or(i64::MAX) } -/// The second line under a PC's name. Kept short: it is cut, not wrapped. +/// "Windows", "macOS", "Linux". fn os_label(os: &str) -> &'static str { let n = tailscale::Node { os: os.to_string(), @@ -1316,24 +1632,32 @@ fn os_label(os: &str) -> &'static str { n.os_label() } -fn describe(pc: &Pc) -> String { - let ip = pc.ip.map(|ip| ip.to_string()).unwrap_or_default(); +/// A machine's status dot and the line under its name. Kept short: the +/// line is cut, not wrapped, and shows whole on hover. +fn describe(pc: &Pc) -> (Tone, String) { let os = os_label(&pc.os); + let at = pc.ip.map(|ip| format!(" · {ip}")).unwrap_or_default(); if pc.remembered { let seen = pc.known.as_ref().and_then(|k| k.last_seen_unix); - return match seen { - Some(t) => format!( - "{os} · last seen {} · Tailscale is off here", - brolink_core::dates::ymd(t) - ), - None => format!("{os} · Tailscale is off here"), - }; + return ( + Tone::Neutral, + match seen { + Some(t) => format!("{os} · Last seen {}", brolink_core::dates::ymd(t)), + None => format!("{os} · Not seen yet"), + }, + ); } if !pc.online { return if pc.can_wake() { - format!("{os} · asleep or off · Connect wakes it") + ( + Tone::Neutral, + format!("{os} · Asleep or off · BroLink can wake it"), + ) } else { - format!("{os} · offline · turn it on once with BroLink running") + ( + Tone::Neutral, + format!("{os} · Offline · Turn it on to connect"), + ) }; } let path = if pc.path.direct.is_some() { @@ -1342,11 +1666,14 @@ fn describe(pc: &Pc) -> String { String::new() }; match (&pc.host, pc.sunshine) { - (Some(h), true) if h.setup.is_empty() => format!("{os} · Ready · {ip}{path}"), - (Some(_), true) => format!("{os} · Ready · {ip} · still needs setup"), - (Some(_), false) => format!("{os} · online · {ip} · not sharing yet"), - (None, true) => format!("{os} · online · {ip}{path} · no BroLink control"), - (None, false) => format!("{os} · online · {ip} · install BroLink to share"), + (Some(h), true) if h.setup.is_empty() => (Tone::Success, format!("{os}{at}{path}")), + (Some(_), true) => ( + Tone::Warning, + format!("{os}{at} · Sharing needs attention there"), + ), + (Some(_), false) => (Tone::Neutral, format!("{os}{at} · Not shared yet")), + (None, true) => (Tone::Success, format!("{os}{at}{path} · Without BroLink")), + (None, false) => (Tone::Neutral, format!("{os}{at} · BroLink isn't installed")), } } @@ -1375,13 +1702,13 @@ fn path_warnings(disc: &Discovery) -> Vec { if let Some(h) = &pc.host { if h.streamer.encoder == "software" { out.push(format!( - "{} encodes video in software: no GPU encoder there, so BroLink uses every CPU core. A still desktop should still hold the bitrate you set.", + "{} encodes video in software: it has no GPU encoder BroLink can use, so every CPU core does the work. A still desktop holds the bitrate you set; fast motion may drop frames.", pc.name )); } if !h.streamer.audio_problem.is_empty() { out.push(format!( - "{} has no sound to send: the PC reports “{}”. A PC with no monitor or speakers has no audio device to capture; give it a virtual one (Steam's Streaming Speakers, or VB-CABLE) and pick it as the PC's audio sink.", + "{} has no sound to send: it reports “{}”. A machine with no speakers or monitor has no audio device to capture; add a virtual one (Steam Streaming Speakers or VB-CABLE) and make it the default output.", pc.name, h.streamer.audio_problem )); } @@ -1414,7 +1741,7 @@ mod tests { }; assert_eq!( describe(&pc), - "Unknown · last seen 2026-09-07 · Tailscale is off here" + (Tone::Neutral, "Unknown · Last seen 2026-09-07".to_string()) ); let disc = Discovery { pcs: vec![ @@ -1453,19 +1780,28 @@ mod tests { ip: Some("203.0.113.10".parse().unwrap()), ..Default::default() }; - assert!(describe(&pc).contains("offline")); + let line = |pc: &Pc| describe(pc).1; + assert!(line(&pc).contains("Offline")); pc.known = Some(KnownPc { mac: Some("02:00:00:00:00:01".into()), ..Default::default() }); - assert!(describe(&pc).contains("asleep")); + assert!(line(&pc).contains("Asleep")); + assert!(line(&pc).contains("can wake it")); pc.online = true; - assert!(describe(&pc).contains("install BroLink to share")); + assert!(line(&pc).contains("BroLink isn't installed")); + assert_eq!(describe(&pc).0, Tone::Neutral); pc.sunshine = true; - assert!(describe(&pc).contains("no BroLink control")); - assert!(describe(&pc).len() < 80, "{}", describe(&pc)); + assert!(line(&pc).contains("Without BroLink")); + assert!(line(&pc).len() < 80, "{}", line(&pc)); pc.host = Some(brolink_core::api::Status::default()); - assert_eq!(describe(&pc), "Unknown · Ready · 203.0.113.10"); + assert_eq!( + describe(&pc), + (Tone::Success, "Unknown · 203.0.113.10".to_string()) + ); + pc.host.as_mut().unwrap().setup = vec!["The streaming engine is not installed.".into()]; + assert_eq!(describe(&pc).0, Tone::Warning, "set up there, not ready"); + pc.host.as_mut().unwrap().setup.clear(); pc.path = crate::path::Path { direct: Some(false), relay: "tok".into(), @@ -1473,8 +1809,8 @@ mod tests { ..Default::default() }; assert_eq!( - describe(&pc), - "Unknown · Ready · 203.0.113.10 · Relayed via Tokyo · 210 ms" + line(&pc), + "Unknown · 203.0.113.10 · Relayed via Tokyo · 210 ms" ); } @@ -1602,7 +1938,7 @@ mod tests { assert!( relay_state(&disc_with(online.clone(), PeerRelayServers::Unknown)) .sentence() - .contains("not a denial") + .contains("does not mean access is denied") ); assert!( relay_state(&disc_with(online.clone(), PeerRelayServers::Unknown)) @@ -1623,7 +1959,7 @@ mod tests { "Known([]) is not ACL denial" ); assert!( - empty.sentence().contains("not a denial"), + empty.sentence().contains("does not mean access is denied"), "{}", empty.sentence() ); @@ -1703,123 +2039,327 @@ mod tests { } } -/// `cargo test -p brolink-client snapshots -- --ignored` writes PNGs of each -/// screen to `target/ui-snapshots/`. +/// Screens rendered headlessly. The unignored tests check that every state +/// fits the minimum window with no control overlapping another; the +/// ignored ones write PNGs for review: +/// +/// ```text +/// cargo test -p brolink-client -p brolink-host snapshots -- --ignored +/// ``` +/// +/// Files land in `target/ui-snapshots/` as +/// `client--x@x.png`. Nothing here reads or +/// writes the real settings: the window is built with defaults and never +/// saves. #[cfg(test)] -mod snapshots { +pub(crate) mod snapshots { use super::*; use crate::config::KnownPc; use crate::session::Relay; - use brolink_core::api::Status; - - fn out_dir() -> std::path::PathBuf { + use brolink_core::api::{Status, Streamer}; + use egui_kittest::kittest::Queryable; + + pub const MIN: egui::Vec2 = egui::vec2(640.0, 420.0); + pub const TYPICAL: egui::Vec2 = egui::vec2(1280.0, 800.0); + pub const LARGE: egui::Vec2 = egui::vec2(1920.0, 1200.0); + /// Every size at both scales. + pub const MATRIX: [(egui::Vec2, f32); 6] = [ + (MIN, 1.0), + (MIN, 2.0), + (TYPICAL, 1.0), + (TYPICAL, 2.0), + (LARGE, 1.0), + (LARGE, 2.0), + ]; + /// The smallest and the usual window, for secondary states. + pub const PAIR: [(egui::Vec2, f32); 2] = [(MIN, 2.0), (TYPICAL, 2.0)]; + + pub fn out_dir() -> std::path::PathBuf { let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../target/ui-snapshots"); std::fs::create_dir_all(&dir).expect("create snapshot dir"); dir } - fn save(img: image::RgbaImage, name: &str) { + pub fn save(img: image::RgbaImage, name: &str) { let path = out_dir().join(name); img.save(&path).expect("write png"); eprintln!("wrote {}", path.display()); } - fn pcs() -> Discovery { - let ready = Pc { - node_id: "n1".into(), - name: "Gaming-PC".into(), - ip: Some("100.64.0.10".parse().unwrap()), + /// `client-lobby-1280x800@2x.png` + pub fn file_name(prefix: &str, state: &str, size: egui::Vec2, ppp: f32) -> String { + format!( + "{prefix}-{state}-{}x{}@{}x.png", + size.x as u32, size.y as u32, ppp as u32 + ) + } + + fn a_status(name: &str, os: &str) -> Status { + Status { + app: "brolink".into(), + version: env!("CARGO_PKG_VERSION").into(), + name: name.into(), + os: os.into(), + power_allowed: true, + streamer: Streamer { + kind: "BroLink".into(), + installed: true, + running: true, + api_ok: true, + encoder: "nvenc".into(), + audio_problem: String::new(), + }, + ..Default::default() + } + } + + fn ready_pc(id: &str, name: &str, os: &str, ip: &str, rtt: u32, direct: bool) -> Pc { + Pc { + node_id: id.into(), + name: name.into(), + os: os.into(), + ip: Some(ip.parse().unwrap()), online: true, - host: Some(Status { - app: "brolink".into(), - version: "3.0.0".into(), - power_allowed: true, - streamer: brolink_core::api::Streamer { - encoder: "software".into(), - ..Default::default() - }, - ..Default::default() - }), + host: Some(a_status(name, os)), path: crate::path::Path { - direct: Some(false), - relay: "tok".into(), - rtt_ms: Some(210), + direct: Some(direct), + relay: if direct { String::new() } else { "tok".into() }, + rtt_ms: Some(rtt), ..Default::default() }, sunshine: true, known: Some(KnownPc { - name: "Gaming-PC".into(), + name: name.into(), mac: Some("02:00:00:00:00:01".into()), - lan_ip: Some("192.168.1.10".into()), ..Default::default() }), - key_expiry_days: Some(176), ..Default::default() - }; - let asleep = Pc { - node_id: "n2".into(), - name: "Office".into(), + } + } + + fn asleep_pc(id: &str, name: &str) -> Pc { + Pc { + node_id: id.into(), + name: name.into(), + os: "windows".into(), ip: Some("100.64.0.30".parse().unwrap()), known: Some(KnownPc { mac: Some("aa:bb:cc:dd:ee:01".into()), ..Default::default() }), ..Default::default() - }; - let bare = Pc { + } + } + + fn found(pcs: Vec) -> Discovery { + Discovery { + login: "user@example.com".into(), + pcs, + refreshed: Some(Instant::now()), + ..Default::default() + } + } + + /// The three machines most screens show: one ready and relayed, one + /// asleep, one that streams without BroLink. + pub fn pcs() -> Discovery { + let mut gaming = ready_pc("n1", "Gaming-PC", "windows", "100.64.0.10", 210, false); + gaming.key_expiry_days = Some(176); + if let Some(h) = gaming.host.as_mut() { + h.version = "3.0.0".into(); + h.streamer.encoder = "software".into(); + } + let den = Pc { node_id: "n3".into(), name: "Den".into(), + os: "linux".into(), ip: Some("100.64.0.31".parse().unwrap()), online: true, sunshine: true, ..Default::default() }; + let mut d = found(vec![gaming, asleep_pc("n2", "Office"), den]); + d.self_nat = Some(brolink_core::api::NatReport { + udp: true, + ipv4: true, + ipv6: false, + hard: Some(false), + portmap: false, + derp: "tok".into(), + }); + d + } + + fn one() -> Discovery { + found(vec![ready_pc( + "n1", + "Studio", + "macOS", + "100.64.0.12", + 14, + true, + )]) + } + + /// A dozen machines in every state, some with long names. + fn many() -> Discovery { + let mut list = vec![ + ready_pc("a", "Gaming-PC", "windows", "100.64.0.10", 18, true), + ready_pc("b", "Studio", "macOS", "100.64.0.12", 6, true), + ready_pc( + "c", + "render-node-with-a-very-long-hostname-eu-central", + "linux", + "100.64.0.13", + 96, + true, + ), + ready_pc("d", "vps-sanjose", "linux", "100.64.0.14", 160, false), + asleep_pc("e", "Office"), + asleep_pc("f", "Living-room-PC"), + ]; + let mut setup = ready_pc("g", "Laptop", "windows", "100.64.0.16", 30, true); + setup.host.as_mut().unwrap().setup = vec!["The streaming engine is not installed.".into()]; + list.push(setup); + let mut not_shared = ready_pc("h", "Workstation", "linux", "100.64.0.17", 22, true); + not_shared.sunshine = false; + not_shared.host.as_mut().unwrap().streamer = Streamer::default(); + list.push(not_shared); + list.push(Pc { + node_id: "i".into(), + name: "phone".into(), + os: "iOS".into(), + ip: Some("100.64.0.18".parse().unwrap()), + online: true, + ..Default::default() + }); + list.push(Pc { + node_id: "j".into(), + name: "Old-Tower".into(), + os: "windows".into(), + ip: Some("100.64.0.19".parse().unwrap()), + ..Default::default() + }); + list.push(Pc { + node_id: "k".into(), + name: "Media-Server".into(), + os: "linux".into(), + ip: Some("100.64.0.20".parse().unwrap()), + online: true, + sunshine: true, + ..Default::default() + }); + let mut d = found(list); + d.login = "someone.with.a.long.address@example-mail-provider.com".into(); + d + } + + fn remembered() -> Discovery { + let pc = |id: &str, name: &str, seen| Pc { + node_id: id.into(), + name: name.into(), + os: "windows".into(), + ip: Some("100.64.0.10".parse().unwrap()), + remembered: true, + known: Some(KnownPc { + name: name.into(), + last_seen_unix: seen, + ..Default::default() + }), + ..Default::default() + }; Discovery { - error: None, - login: "user@example.com".into(), - pcs: vec![ready, asleep, bare], + error: Some("Tailscale is stopped".into()), + pcs: vec![ + pc("n1", "Gaming-PC", Some(1_788_739_200)), + pc("n2", "Office", None), + ], refreshed: Some(Instant::now()), - self_nat: Some(brolink_core::api::NatReport { - udp: true, - ipv4: true, - ipv6: false, - hard: Some(false), - portmap: false, - derp: "tok".into(), - }), ..Default::default() } } - fn build( - disc: Discovery, - prog: Progress, - settings: bool, - ) -> egui_kittest::Harness<'static, ClientApp> { - build_sized(disc, prog, settings, egui::vec2(640.0, 1100.0), 2.0) + fn relay_disc(online: bool, servers: PeerRelayServers) -> Discovery { + Discovery { + relays: vec![Relay { + name: "relay-sj".into(), + ip: Some("100.64.0.40".parse().unwrap()), + online, + }], + peer_relay_servers: servers, + ..found(vec![]) + } } - fn build_sized( - disc: Discovery, - prog: Progress, - settings: bool, - size: egui::Vec2, - ppp: f32, - ) -> egui_kittest::Harness<'static, ClientApp> { - build_with(disc, prog, settings, size, ppp, true) + /// A stand-in for the host's Sharing page. + struct Placeholder; + impl share::SharePage for Placeholder { + fn show(&mut self, ui: &mut egui::Ui) { + ui::page_header(ui, "This machine", None, |_| {}); + ui::muted(ui, "The host crate draws this page."); + } + } + + /// Everything a screen needs besides the window size. + pub struct Setup { + pub disc: Discovery, + pub prog: Progress, + pub page: Page, + pub tailscale: bool, + pub cfg: ClientConfig, + pub local: Option, + pub tweak: fn(&mut ClientApp), + } + + impl Setup { + pub fn new(disc: Discovery) -> Self { + Self { + disc, + prog: Progress::default(), + page: Page::Machines, + tailscale: true, + cfg: ClientConfig::default(), + local: None, + tweak: |_| {}, + } + } + + fn page(mut self, page: Page) -> Self { + self.page = page; + self + } + + fn step(mut self, pc: &str, step: Step, detail: &str) -> Self { + self.prog = Progress { + pc: pc.into(), + step, + detail: detail.into(), + ..Default::default() + }; + self + } + + fn tweak(mut self, f: fn(&mut ClientApp)) -> Self { + self.tweak = f; + self + } + + fn local(mut self, status: Option) -> Self { + self.local = Some(share::LocalShare { + status, + ..Default::default() + }); + self + } } - fn build_with( - disc: Discovery, - prog: Progress, - settings: bool, + pub fn build( + setup: Setup, size: egui::Vec2, ppp: f32, gpu: bool, ) -> egui_kittest::Harness<'static, ClientApp> { - let disc = Arc::new(Mutex::new(disc)); - let prog = Arc::new(Mutex::new(prog)); let mut builder = egui_kittest::Harness::builder() .with_size(size) .with_pixels_per_point(ppp) @@ -1827,9 +2367,23 @@ mod snapshots { if gpu { builder = builder.wgpu(); } + let Setup { + disc, + prog, + page, + tailscale, + cfg, + local, + tweak, + } = setup; let mut harness = builder.build_eframe(move |cc| { - let mut app = ClientApp::with_shared(cc, disc, prog, false); - app.settings_open = settings; + let mut app = ClientApp::headless(cc, disc, prog, cfg); + if let Some(local) = local { + app = app.with_local(Arc::new(Mutex::new(local)), Box::new(Placeholder)); + } + app.tailscale_ok.store(tailscale, Ordering::Relaxed); + app.open_page(page); + tweak(&mut app); app }); harness.run_steps(3); @@ -1837,9 +2391,9 @@ mod snapshots { } /// Every widget sits inside the window, and no two controls overlap. - fn assert_fits(h: &egui_kittest::Harness<'_, ClientApp>, width: f32) { + pub fn assert_fits(h: &egui_kittest::Harness<'_, S>, width: f32) { use egui::accesskit::Role; - use egui_kittest::kittest::{By, Queryable}; + use egui_kittest::kittest::By; let mut controls = Vec::new(); for node in h.query_all(By::new().predicate(|_| true)) { let Some(b) = node.raw_bounds() else { continue }; @@ -1851,7 +2405,7 @@ mod snapshots { ); if matches!( node.role(), - Role::Button | Role::CheckBox | Role::RadioButton | Role::ComboBox + Role::Button | Role::CheckBox | Role::RadioButton | Role::ComboBox | Role::Link ) { controls.push(( text, @@ -1874,70 +2428,337 @@ mod snapshots { } } - const MIN_WINDOW: egui::Vec2 = egui::vec2(640.0, 420.0); + /// Every named state, for the fit checks and the PNGs. + fn states() -> Vec<(&'static str, Setup)> { + let failed = |pc: &str, e: &str| { + Setup::new(pcs()).step( + pc, + Step::Ended { + error: Some(e.into()), + }, + "", + ) + }; + vec![ + ("lobby", Setup::new(pcs()).local(Some(a_status("MacBook-Pro", "macOS")))), + ("lobby-one", Setup::new(one())), + ( + "lobby-many", + Setup::new(many()).local(Some({ + let mut s = a_status("MacBook-Pro", "macOS"); + s.streamer = Streamer::default(); + s + })), + ), + ("lobby-looking", Setup::new(Discovery::default())), + ("lobby-empty", Setup::new(found(vec![]))), + ( + "lobby-no-tailscale", + Setup { + tailscale: false, + ..Setup::new(Discovery::default()) + }, + ), + ("lobby-tailscale-off", Setup::new(remembered())), + ( + "lobby-service-starting", + Setup::new(one()).local(None), + ), + ( + "waking", + Setup::new(pcs()).step("Office", Step::Waking, "Waking Office… 12s"), + ), + ( + "waiting", + Setup::new(pcs()).step("Office", Step::Waiting, "Waiting for Office… 4s"), + ), + ( + "connecting", + Setup::new(pcs()).step("Gaming-PC", Step::Launching, "Measuring the path…"), + ), + ( + "pairing", + Setup::new(pcs()).step( + "Gaming-PC", + Step::Pairing { pin: "4821".into() }, + "", + ), + ), + ( + "pairing-stuck", + Setup::new(pcs()).step( + "Gaming-PC", + Step::Pairing { pin: "4821".into() }, + "BroLink on Gaming-PC isn't answering. Open BroLink there and choose Share this machine, then try again.", + ), + ), + ( + "failed", + failed("Office", "Office did not wake up. A wake packet only reaches it from its own network, or through a router that forwards UDP 9 to it."), + ), + ( + "disconnected", + failed("Gaming-PC", "The connection to the PC was lost.").tweak(|app| { + app.streamed = true; + app.stream_failed = true; + app.last_pc = Some(pcs().pcs[0].clone()); + }), + ), + ( + "ended-offer-sleep", + Setup::new(pcs()) + .step("Gaming-PC", Step::Ended { error: None }, "") + .tweak(|app| app.offer_sleep = Some(pcs().pcs[0].clone())), + ), + ( + "confirm-restart", + Setup::new(pcs()).tweak(|app| { + app.confirm = Some(("Gaming-PC".into(), PowerAction::Restart)) + }), + ), + ( + "notice-and-update", + Setup::new(pcs()).tweak(|app| { + app.notice = Some(( + Tone::Success, + "Gaming-PC received the wake packet, so waking it from this network works.".into(), + Instant::now(), + )); + let mut u = app.updates.lock(); + u.ready = Some(Version::new(4, 1, 0)); + u.notice = Some(( + Tone::Info, + "BroLink 4.1.0 is downloaded and installs when no stream is running." + .into(), + )); + }), + ), + ( + "updating", + Setup::new(pcs()).tweak(|app| app.progress.lock().updating = true), + ), + ( + "key-expiring", + Setup::new({ + let mut d = pcs(); + d.pcs[0].key_expiry_days = Some(6); + d.self_key_days = Some(90); + d + }) + ), + ("settings", Setup::new(pcs()).page(Page::Settings)), + ( + "settings-custom", + Setup::new(pcs()).page(Page::Settings).tweak(|app| { + app.cfg.stream.apply_preset(crate::config::Preset::Sharp); + app.cfg.stream.fps = 120; + app.cfg.auto_update = false; + }), + ), + ( + "relay-unavailable", + Setup::new(relay_disc(true, PeerRelayServers::Known(vec![]))).page(Page::Settings), + ), + ("sharing", Setup::new(pcs()).local(Some(a_status("MacBook-Pro", "macOS"))).page(Page::Sharing)), + ] + } + + #[test] + fn every_state_fits_the_minimum_window() { + for (name, setup) in states() { + let h = build(setup, MIN, 1.0, false); + // A name in the assertion would be nicer; kittest panics + // inside, so say which state first. + eprintln!("checking {name}"); + assert_fits(&h, MIN.x); + } + } + + #[test] + fn every_state_fits_a_large_window() { + for (name, setup) in states() { + eprintln!("checking {name}"); + let h = build(setup, LARGE, 1.0, false); + assert_fits(&h, LARGE.x); + } + } #[test] - fn lobby_fits_the_minimum_window() { - use egui_kittest::kittest::Queryable; - let h = build_with(pcs(), Progress::default(), false, MIN_WINDOW, 1.0, false); - assert_fits(&h, 640.0); - assert_eq!(h.query_all_by_label("Connect").count(), 3); + fn the_lobby_lists_machines_with_their_actions() { + let h = build(Setup::new(pcs()), MIN, 1.0, false); + assert_eq!(h.query_all_by_label("Connect").count(), 2); + assert_eq!(h.query_all_by_label("Wake and connect").count(), 1); + assert!(h.query_by_label("More for Gaming-PC").is_some()); + assert!(h.query_by_label("Settings").is_some()); let mut disc = pcs(); disc.login = "someone.with.a.long.name@example-mail-provider.com".into(); - let h = build_with(disc, Progress::default(), false, MIN_WINDOW, 1.0, false); + let h = build(Setup::new(disc), MIN, 1.0, false); assert_fits(&h, 640.0); - assert!(h.query_by_label("Settings").is_some()); } #[test] - fn settings_fit_the_minimum_window() { - use egui_kittest::kittest::Queryable; - let h = build_with(pcs(), Progress::default(), true, MIN_WINDOW, 1.0, false); - assert_fits(&h, 640.0); - assert!(h.query_by_label("Close settings").is_some()); + fn the_row_menu_is_a_popup_with_the_machines_actions() { + let mut h = build(Setup::new(pcs()), TYPICAL, 1.0, false); + h.get_by_label("More for Gaming-PC").click(); + h.run_steps(2); + assert!(h.ctx.memory(|m| m.any_popup_open())); + for item in ["Sleep", "Restart…", "Shut down…", "Copy Tailscale address"] { + assert!(h.query_by_label(item).is_some(), "{item}"); + } + h.get_by_label("Restart…").click(); + h.run_steps(2); + assert!( + !h.ctx.memory(|m| m.any_popup_open()), + "an item closes the menu" + ); + assert_eq!( + h.state().confirm, + Some(("Gaming-PC".into(), PowerAction::Restart)) + ); + assert!( + h.query_by_label("Restart").is_some(), + "the confirming button" + ); + } + + #[test] + fn this_machine_leads_the_list_and_opens_sharing() { + let mut h = build( + Setup::new(pcs()).local(Some(a_status("MacBook-Pro", "macOS"))), + MIN, + 1.0, + false, + ); + assert!(h.query_by_label("MacBook-Pro").is_some()); + h.get_by_label("Manage").click(); + h.run_steps(2); + assert_eq!(h.state().page, Page::Sharing); + assert!(h + .query_by_label("The host crate draws this page.") + .is_some()); } #[test] - fn open_source_row_shows_the_bundled_notices() { - use egui_kittest::kittest::Queryable; - let mut h = build_with(pcs(), Progress::default(), true, MIN_WINDOW, 1.0, false); - assert!(h.query_by_label("Open source").is_some()); + fn the_standalone_viewer_has_no_sharing_tab() { + let h = build(Setup::new(pcs()), MIN, 1.0, false); + assert!(h.query_by_label("Sharing").is_none()); + let h = build( + Setup::new(pcs()).local(Some(a_status("MacBook-Pro", "macOS"))), + MIN, + 1.0, + false, + ); + assert!(h.query_by_label("Sharing").is_some()); + } + + #[test] + fn tabs_switch_pages() { + let mut h = build(Setup::new(pcs()), MIN, 1.0, false); + assert_eq!(h.state().page, Page::Machines); + assert!(h.query_by_label("How peer relays work").is_none()); + h.get_by_label("Settings").click(); + h.run_steps(3); + assert_eq!(h.state().page, Page::Settings); + assert!(h.query_by_label("How peer relays work").is_some()); + h.get_by_label("Machines").click(); + h.run_steps(3); + assert_eq!(h.state().page, Page::Machines); + } + + #[test] + fn keyboard_shortcuts_switch_pages() { + let mut h = build(Setup::new(pcs()), MIN, 1.0, false); + h.press_key_modifiers(egui::Modifiers::COMMAND, egui::Key::Comma); + h.run_steps(2); + assert_eq!(h.state().page, Page::Settings); + h.press_key(egui::Key::Escape); + h.run_steps(2); + assert_eq!(h.state().page, Page::Machines); + h.press_key_modifiers(egui::Modifiers::COMMAND, egui::Key::Num2); + h.run_steps(2); + assert_eq!( + h.state().page, + Page::Settings, + "with no Sharing tab, the second shortcut is Settings" + ); + } + + #[test] + fn open_source_notices_unfold_in_settings() { + let mut h = build( + Setup::new(pcs()).page(Page::Settings), + egui::vec2(640.0, 3000.0), + 1.0, + false, + ); assert!(h.query_by_label_contains("moonlight-common-c").is_none()); - h.get_by_label("Show notices").click(); + h.get_by_label("Open-source notices").click(); h.run_steps(3); - assert!(h.state().notices_open); assert!(h.query_by_label_contains("moonlight-common-c").is_some()); - assert!(h.query_by_label("Hide notices").is_some()); + assert!( + h.query_by_label_contains("Geist Mono, SIL Open Font License") + .is_some(), + "the typeface's licence is listed" + ); assert_fits(&h, 640.0); } #[test] - fn an_empty_lobby_fits_the_minimum_window() { - let disc = Discovery { - login: "user@example.com".into(), - refreshed: Some(Instant::now()), - ..Default::default() + fn a_failure_offers_a_retry_and_a_lighter_profile_only_for_stream_failures() { + let failed = |streamed: bool| { + let mut setup = Setup::new(pcs()).step( + "Gaming-PC", + Step::Ended { + error: Some("The connection to the PC was lost.".into()), + }, + "", + ); + setup.tweak = if streamed { + |app| { + app.streamed = true; + app.stream_failed = true; + app.last_pc = Some(pcs().pcs[0].clone()); + } + } else { + |app| app.last_pc = Some(pcs().pcs[0].clone()) + }; + build(setup, MIN, 1.0, false) }; - let h = build_with(disc, Progress::default(), false, MIN_WINDOW, 1.0, false); - assert_fits(&h, 640.0); + let h = failed(true); + assert!(h.query_by_label("Gaming-PC disconnected").is_some()); + assert!(h.query_by_label("Try again").is_some()); + assert!(h.query_by_label("Try at 1080p, 20 Mbps").is_some()); + let h = failed(false); + assert!(h.query_by_label("Couldn't connect to Gaming-PC").is_some()); + assert!(h.query_by_label("Try at 1080p, 20 Mbps").is_none()); } #[test] - fn settings_opens_and_closes_as_a_bool_toggled_view() { - use egui_kittest::kittest::Queryable; - let mut h = build_with(pcs(), Progress::default(), false, MIN_WINDOW, 1.0, false); - assert!(!h.state().settings_open); - assert!(h.query_by_label("How it works").is_none()); - h.get_by_label("Settings").click(); - h.run_steps(3); - assert!(h.state().settings_open); - assert!(h.query_by_label("Close settings").is_some()); - assert!(h.query_by_label("How it works").is_some()); - h.get_by_label("Close settings").click(); - h.run_steps(3); - assert!(!h.state().settings_open); - assert!(h.query_by_label("Settings").is_some()); - assert!(h.query_by_label("How it works").is_none()); + fn tagged_relay_nodes_stay_out_of_the_pc_list_and_in_discovery() { + let disc = discovery_from_status(TAGGED_RELAY_STATUS); + assert_eq!( + disc.pcs.iter().map(|p| p.name.as_str()).collect::>(), + ["Gaming-PC", "tagged-pc"] + ); + assert_eq!( + disc.relays + .iter() + .map(|r| r.name.as_str()) + .collect::>(), + ["relay-pc", "sj-instance"] + ); + let h = build(Setup::new(disc.clone()), MIN, 1.0, false); + assert_fits(&h, 640.0); + assert!(h.query_by_label("Gaming-PC").is_some()); + assert!(h.query_by_label("tagged-pc").is_some()); + assert!(h.query_by_label("sj-instance").is_none()); + assert!(h.query_by_label("relay-pc").is_none()); + assert_eq!(h.query_all_by_label("Connect").count(), 2); + + let h = build(Setup::new(disc).page(Page::Settings), MIN, 1.0, false); + assert_fits(&h, 640.0); + assert!(h.query_by_label("sj-instance").is_none()); + assert!(h.query_by_label("relay-pc").is_none()); } const TAGGED_RELAY_STATUS: &str = r#"{"BackendState":"Running","Peer":{ @@ -1983,66 +2804,9 @@ mod snapshots { } #[test] - fn tagged_relay_nodes_stay_out_of_the_pc_list_and_in_discovery() { - use egui_kittest::kittest::Queryable; - let disc = discovery_from_status(TAGGED_RELAY_STATUS); - assert_eq!( - disc.pcs.iter().map(|p| p.name.as_str()).collect::>(), - ["Gaming-PC", "tagged-pc"] - ); - assert_eq!( - disc.relays - .iter() - .map(|r| r.name.as_str()) - .collect::>(), - ["relay-pc", "sj-instance"] - ); - - let h = build_with( - disc.clone(), - Progress::default(), - false, - MIN_WINDOW, - 1.0, - false, - ); - assert_fits(&h, 640.0); - assert!(h.query_by_label("Gaming-PC").is_some()); - assert!(h.query_by_label("tagged-pc").is_some()); - assert!(h.query_by_label("sj-instance").is_none()); - assert!(h.query_by_label("relay-pc").is_none()); - assert_eq!(h.query_all_by_label("Connect").count(), 2); - - let h = build_with(disc, Progress::default(), true, MIN_WINDOW, 1.0, false); - assert_fits(&h, 640.0); - assert!(h.query_by_label("How it works").is_some()); - assert!(h.query_by_label("sj-instance").is_none()); - assert!(h.query_by_label("relay-pc").is_none()); - } - - fn relay_disc(online: bool, servers: PeerRelayServers) -> Discovery { - Discovery { - login: "user@example.com".into(), - refreshed: Some(Instant::now()), - relays: vec![Relay { - name: "relay-sj".into(), - ip: Some("100.64.0.40".parse().unwrap()), - online, - }], - peer_relay_servers: servers, - ..Default::default() - } - } - - #[test] - fn relay_card_fits_the_minimum_window_in_every_state() { - use egui_kittest::kittest::Queryable; + fn relay_section_fits_the_minimum_window_in_every_state() { let states = [ - Discovery { - login: "user@example.com".into(), - refreshed: Some(Instant::now()), - ..Default::default() - }, + found(vec![]), relay_disc(false, PeerRelayServers::Unknown), relay_disc(true, PeerRelayServers::Unknown), relay_disc(true, PeerRelayServers::Known(vec![])), @@ -2050,227 +2814,137 @@ mod snapshots { relay_disc(true, PeerRelayServers::Known(vec!["100.64.0.40".into()])), ]; for disc in states { - let h = build_with(disc, Progress::default(), true, MIN_WINDOW, 1.0, false); + let h = build(Setup::new(disc).page(Page::Settings), MIN, 1.0, false); assert_fits(&h, 640.0); } - let h = build_with( - relay_disc(true, PeerRelayServers::Known(vec![])), - Progress::default(), - true, - MIN_WINDOW, + let tall = egui::vec2(640.0, 3000.0); + let h = build( + Setup::new(relay_disc(true, PeerRelayServers::Known(vec![]))).page(Page::Settings), + tall, 1.0, false, ); assert!(h.query_by_label("Copy grant").is_some()); - assert!(h.query_by_label("How it works").is_some()); - let h = build_with( - Discovery { - login: "user@example.com".into(), - refreshed: Some(Instant::now()), - ..Default::default() - }, - Progress::default(), - true, - MIN_WINDOW, + assert!(h.query_by_label("How peer relays work").is_some()); + let h = build( + Setup::new(found(vec![])).page(Page::Settings), + tall, 1.0, false, ); assert!(h.query_by_label("Copy grant").is_none()); - assert!(h.query_by_label("How it works").is_some()); - let h = build_with( - relay_disc(true, PeerRelayServers::Known(vec!["100.64.0.40".into()])), - Progress::default(), - true, - MIN_WINDOW, + assert!(h.query_by_label("How peer relays work").is_some()); + let h = build( + Setup::new(relay_disc( + true, + PeerRelayServers::Known(vec!["100.64.0.40".into()]), + )) + .page(Page::Settings), + tall, 1.0, false, ); assert!(h.query_by_label("Copy grant").is_none()); + assert!(h.query_by_label("Using relay-sj (100.64.0.40)").is_some()); } - /// The stream screen with its toolbar, before any picture has arrived: - /// the session points at an address that never answers. + /// The window never repaints on a timer while nothing is happening. #[test] - #[ignore = "renders with a GPU; run on demand to review the UI"] - fn stream_toolbar() { - let mut h = build_sized( - pcs(), - Progress::default(), - false, - egui::vec2(1400.0, 860.0), - 1.0, - ); - let ctx = h.ctx.clone(); - let (tx, rx) = std::sync::mpsc::channel(); - let frames = Arc::new(brolink_stream::FrameSlot::default()); - let path = crate::path::Path { - direct: Some(false), - relay: "tok".into(), - rtt_ms: Some(210), - ..Default::default() - }; - let settings = crate::path::effective(&crate::config::StreamSettings::default(), &path); - let session = brolink_stream::Session::start( - brolink_stream::session::Server { - address: "10.255.255.1".into(), - app_version: "7.1.431.-1".into(), - gfe_version: "3.23.0.74".into(), - rtsp_url: "rtsp://10.255.255.1:48010".into(), - codec_mode_support: 1, - }, - brolink_stream::Settings { - width: 1920, - height: 1080, - fps: 30, - bitrate_kbps: 4000, - hevc: true, - remote: true, - }, - [0; 16], - [0; 16], - frames.clone(), - tx, - || {}, - ); - let ip: std::net::Ipv4Addr = "100.64.0.10".parse().unwrap(); - let input = session.input(); - let live = Live { - pc: "Gaming-PC".into(), - node_id: "n1".into(), - ip, - session, - input, - frames, - events: rx, - started: Instant::now(), - codec: "HEVC", - requested: (1920, 1080, 30), - settings, - path, - clipboard: crate::clipboard::Sync::spawn(ip, ctx), - }; - *h.state().live.lock() = Some(live); - h.state().progress.lock().step = Step::Streaming; - h.run_steps(3); - save(h.render().unwrap(), "client-stream-toolbar.png"); - h.state_mut().disconnect(); + fn an_idle_window_asks_for_no_repaint() { + let mut h = build(Setup::new(pcs()), TYPICAL, 1.0, false); + h.run_steps(4); + let delay = h + .output() + .viewport_output + .get(&egui::ViewportId::ROOT) + .map(|v| v.repaint_delay) + .unwrap_or(Duration::MAX); + assert_eq!(delay, Duration::MAX, "the idle lobby keeps repainting"); } #[test] #[ignore = "renders with a GPU; run on demand to review the UI"] - fn lobby() { - let mut h = build(pcs(), Progress::default(), false); - save(h.render().unwrap(), "client-lobby.png"); - let mut h = build(pcs(), Progress::default(), true); - save(h.render().unwrap(), "client-settings.png"); - let mut h = build_sized(pcs(), Progress::default(), false, MIN_WINDOW, 2.0); - save(h.render().unwrap(), "client-lobby-640x420.png"); - } - - #[test] - #[ignore = "renders with a GPU; run on demand to review the UI"] - fn pairing_and_waking() { - let mut p = Progress { - pc: "Gaming-PC".into(), - step: Step::Pairing { pin: "4821".into() }, - ..Default::default() - }; - let mut h = build(pcs(), p.clone(), false); - save(h.render().unwrap(), "client-pairing.png"); - p.step = Step::Waking; - p.detail = "Waking Gaming-PC… 12s".into(); - let mut h = build(pcs(), p, false); - save(h.render().unwrap(), "client-waking.png"); + fn snapshots_every_state() { + let key = [ + "lobby", + "lobby-many", + "lobby-no-tailscale", + "pairing", + "failed", + "settings", + ]; + for (name, _) in states() { + let sizes: &[(egui::Vec2, f32)] = if key.contains(&name) { &MATRIX } else { &PAIR }; + for &(size, ppp) in sizes { + let setup = states().into_iter().find(|(n, _)| *n == name).unwrap().1; + let mut h = build(setup, size, ppp, true); + save(h.render().unwrap(), &file_name("client", name, size, ppp)); + } + } } + /// Long pages whole, to review what scrolls. #[test] #[ignore = "renders with a GPU; run on demand to review the UI"] - fn failed_and_empty() { - let p = Progress { - pc: "Office".into(), - step: Step::Ended { - error: Some("Office did not wake up. A wake packet only reaches it from its own network, or through a router that forwards UDP 9 to it.".into()), - }, - ..Default::default() - }; - let mut h = build(pcs(), p, false); - save(h.render().unwrap(), "client-failed.png"); + fn snapshots_full_pages() { + for (name, setup, h) in [ + ("settings", Setup::new(pcs()).page(Page::Settings), 2300.0), + ( + "relay-unavailable", + Setup::new(relay_disc(true, PeerRelayServers::Known(vec![]))).page(Page::Settings), + 2400.0, + ), + ("lobby-many", Setup::new(many()), 1100.0), + ( + "key-expiring", + Setup::new({ + let mut d = pcs(); + d.pcs[0].key_expiry_days = Some(6); + d.self_key_days = Some(90); + d + }), + 1000.0, + ), + ] { + let size = egui::vec2(1280.0, h); + let mut harness = build(setup, size, 1.0, true); + save( + harness.render().unwrap(), + &format!("client-{name}-full.png"), + ); + } let mut h = build( - Discovery { - error: Some("Tailscale is stopped".into()), - refreshed: Some(Instant::now()), - ..Default::default() - }, - Progress::default(), - false, - ); - save(h.render().unwrap(), "client-no-tailscale.png"); - } - - #[test] - #[ignore = "renders with a GPU; run on demand to review the UI"] - fn open_source_notices() { - use egui_kittest::kittest::Queryable; - let mut h = build_sized( - pcs(), - Progress::default(), - true, - egui::vec2(1024.0, 2400.0), + Setup::new(pcs()).page(Page::Settings), + egui::vec2(1280.0, 3000.0), 1.0, + true, ); - h.get_by_label("Show notices").click(); + h.get_by_label("Open-source notices").click(); + h.run_steps(3); + save(h.render().unwrap(), "client-open-source-full.png"); + let mut h = build(Setup::new(pcs()), TYPICAL, 2.0, true); + h.get_by_label("More for Gaming-PC").click(); h.run_steps(3); - save(h.render().unwrap(), "client-open-source.png"); + save(h.render().unwrap(), "client-row-menu-1280x800@2x.png"); + let mut h = build(Setup::new(pcs()), TYPICAL, 2.0, true); + h.get_by_label_contains("Connection details").click(); + h.run_steps(3); + save(h.render().unwrap(), "client-details-open-1280x800@2x.png"); } + /// The stream screen as the window shows it, before any picture has + /// arrived: the session points at an address that never answers. #[test] #[ignore = "renders with a GPU; run on demand to review the UI"] - fn relay_card_states() { - let none = Discovery { - login: "user@example.com".into(), - refreshed: Some(Instant::now()), - ..Default::default() - }; - let mut h = build(none, Progress::default(), true); - save(h.render().unwrap(), "client-relay-none.png"); - let mut h = build( - relay_disc(false, PeerRelayServers::Unknown), - Progress::default(), - true, - ); - save(h.render().unwrap(), "client-relay-offline.png"); - let mut h = build( - relay_disc(true, PeerRelayServers::Unknown), - Progress::default(), - true, - ); - save(h.render().unwrap(), "client-relay-checking.png"); - let mut h = build( - relay_disc(true, PeerRelayServers::Known(vec![])), - Progress::default(), - true, - ); - save(h.render().unwrap(), "client-relay-unavailable.png"); - let mut h = build( - relay_disc(true, PeerRelayServers::Known(vec!["100.64.0.99".into()])), - Progress::default(), - true, - ); - save(h.render().unwrap(), "client-relay-mismatch.png"); - let mut h = build( - relay_disc(true, PeerRelayServers::Known(vec!["100.64.0.40".into()])), - Progress::default(), - true, - ); - save(h.render().unwrap(), "client-relay-ready.png"); - let mut h = build_sized( - relay_disc(true, PeerRelayServers::Known(vec![])), - Progress::default(), - true, - MIN_WINDOW, - 2.0, - ); - save(h.render().unwrap(), "client-relay-unavailable-640x420.png"); + fn snapshots_stream_connecting() { + let mut h = build(Setup::new(pcs()), egui::vec2(1400.0, 860.0), 1.0, true); + let ctx = h.ctx.clone(); + let live = crate::stream::tests::dummy_live(&ctx); + *h.state().live.lock() = Some(live); + h.state().progress.lock().step = Step::Streaming; + h.run_steps(3); + save(h.render().unwrap(), "client-stream-connecting.png"); + h.state_mut().disconnect(); } } @@ -2316,7 +2990,7 @@ mod live_snapshot { .build_eframe({ let (disc, prog) = (disc.clone(), prog.clone()); move |cc| { - let mut app = ClientApp::with_shared(cc, disc, prog, false); + let mut app = ClientApp::with_shared(cc, disc, prog, ClientConfig::load()); app.cfg.stream.fullscreen = false; if std::env::var_os("BROLINK_TEST_PC").is_none() { app.cfg.stream.resolution = Resolution::P1080; diff --git a/crates/client/src/path.rs b/crates/client/src/path.rs index eba5cfe..dae7007 100644 --- a/crates/client/src/path.rs +++ b/crates/client/src/path.rs @@ -71,14 +71,16 @@ impl Path { } } - /// A DERP detour is the one thing worth red; a relay of your own is a - /// detour too, but one sized to the round trip, so it gets the colour a - /// long direct path gets. + /// A DERP detour is the one thing worth a warning: it works, but it is + /// shared and often fixable (see [`explain`]). A relay of your own is a + /// detour chosen on purpose, and a long direct path is just distance, + /// so both are plain; only a short direct path is green. Nothing about + /// a path is red: red means broken. pub fn tone(&self) -> Tone { match (path_kind(self), self.rtt_ms) { - (Some(PathKind::Derp), _) => Tone::Danger, - (Some(PathKind::PeerRelay), _) => Tone::Accent, - (Some(PathKind::Direct), Some(ms)) if ms >= 80 => Tone::Accent, + (Some(PathKind::Derp), _) => Tone::Warning, + (Some(PathKind::PeerRelay), _) => Tone::Neutral, + (Some(PathKind::Direct), Some(ms)) if ms >= 80 => Tone::Neutral, (Some(PathKind::Direct), _) => Tone::Success, (None, _) => Tone::Neutral, } @@ -197,7 +199,11 @@ mod tests { ..Default::default() }; assert_eq!(p.label(), "Relayed via Tokyo · 210 ms"); - assert_eq!(p.tone(), Tone::Danger); + assert_eq!( + p.tone(), + Tone::Warning, + "a detour worth fixing, not a failure" + ); let p = Path { direct: Some(true), relay: "tok".into(), @@ -213,8 +219,8 @@ mod tests { }; assert_eq!( p.tone(), - Tone::Accent, - "a long round trip is worth a colour" + Tone::Neutral, + "a long round trip is distance, not a fault" ); assert_eq!(Path::default().label(), "Path unknown"); assert_eq!(Path::default().tone(), Tone::Neutral); @@ -237,7 +243,7 @@ mod tests { rtt_ms: Some(60), }; assert_eq!(p.label(), "Via your relay · 60 ms"); - assert_eq!(p.tone(), Tone::Accent, "a detour, not an alarm"); + assert_eq!(p.tone(), Tone::Neutral, "a detour chosen on purpose"); assert!(!p.label().contains("Tokyo")); // Given: the relay is named before any traffic has flowed. @@ -246,7 +252,7 @@ mod tests { ..Default::default() }; assert_eq!(early.label(), "Via your relay"); - assert_eq!(early.tone(), Tone::Accent); + assert_eq!(early.tone(), Tone::Neutral); // Given: a direct path holds while Tailscale still names the relay. let direct = Path { diff --git a/crates/client/src/settings.rs b/crates/client/src/settings.rs index 5523e2c..c1cd046 100644 --- a/crates/client/src/settings.rs +++ b/crates/client/src/settings.rs @@ -1,81 +1,163 @@ -//! Shared controls for the lobby and the live session. +//! The stream controls, shared by Settings and the in-stream panel so the +//! two cannot drift apart. Rows only: the caller supplies the group. + use crate::config::{Codec, Preset, Quality, Resolution, StreamSettings}; -use brolink_ui::{self as ui, Tone}; +use brolink_ui::{self as ui, space}; + +/// The one-line choice at the top: a named profile, or the values as set. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Profile { + Recommended, + Preset(Preset), + Custom, +} + +impl Profile { + fn of(s: &StreamSettings) -> Self { + match (s.quality, s.preset()) { + (Quality::Auto, _) => Profile::Recommended, + (Quality::Custom, Some(p)) => Profile::Preset(p), + (Quality::Custom, None) => Profile::Custom, + } + } +} +/// Edit `s` in place; true when anything changed. pub fn stream_controls(ui: &mut egui::Ui, s: &mut StreamSettings, native: (u32, u32)) -> bool { let before = s.clone(); - ui::caption(ui, "QUICK PROFILES"); - ui.horizontal_wrapped(|ui| { - for preset in Preset::ALL { - if ui::ghost_button(ui, preset.label()) - .on_hover_text(preset.describe()) - .clicked() - { - s.apply_preset(preset); + let mut profile = Profile::of(s); + let options = [ + (Profile::Recommended, "Recommended"), + (Profile::Preset(Preset::Smooth), Preset::Smooth.label()), + (Profile::Preset(Preset::Balanced), Preset::Balanced.label()), + (Profile::Preset(Preset::Sharp), Preset::Sharp.label()), + (Profile::Custom, "Custom"), + ]; + let hint = match profile { + Profile::Recommended => "This display's own size at 50 Mbps. A relay or a long round trip never lowers the frame rate or the bitrate.".to_string(), + Profile::Preset(p) => format!("{}.", p.describe()), + Profile::Custom => "Your own resolution, frame rate and bitrate.".to_string(), + }; + ui::setting_block(ui, "Quality", Some(&hint), |ui| { + if ui::segmented(ui, &options, &mut profile) { + match profile { + Profile::Recommended => { + s.quality = Quality::Auto; + s.resolution = Resolution::Native; + } + Profile::Preset(p) => s.apply_preset(p), + Profile::Custom => { + // Start from what Recommended was asking for, so + // nothing jumps. + if s.quality == Quality::Auto { + s.resolution = Resolution::Native; + s.bitrate_kbps = 50_000; + } + s.quality = Quality::Custom; + } } } - if ui::ghost_button(ui, "Reset").clicked() { - *s = StreamSettings { - app: s.app.clone(), - fullscreen: s.fullscreen, - ..Default::default() - }; - } - }); - ui::row_separator(ui); - ui::setting_row(ui, "Picture quality", Some("Recommended starts at 50 Mbps. A relay or a long round trip never lowers your frame rate or caps your bitrate."), |ui| { - if ui::segmented(ui, &[(Quality::Auto, "Recommended"), (Quality::Custom, "Manual")], &mut s.quality) && s.quality == Quality::Auto { s.resolution = Resolution::Native; } }); ui::row_separator(ui); - ui::setting_row(ui, "Resolution", Some("Match screen is this display’s own pixel size, so the picture fills it exactly. 1080p, 1440p and 4K are the standard 16:9 sizes; on a display of another shape they leave a bar above and below."), |ui| { - let mut resolution = s.resolution; - egui::ComboBox::from_id_salt("stream-resolution").selected_text(resolution.describe(native)).show_ui(ui, |ui| { - for r in [Resolution::Native, Resolution::P1080, Resolution::P1440, Resolution::P2160] { ui.selectable_value(&mut resolution, r, r.describe(native)); } - }); - if resolution != s.resolution { s.resolution = resolution; s.quality = Quality::Custom; } - }); + ui::setting_row( + ui, + "Resolution", + Some("Match screen fills this display exactly. The 16:9 sizes leave bars on a display of another shape."), + |ui| { + let mut resolution = s.resolution; + ui::select(ui, "stream-resolution", resolution.describe(native), 200.0, |ui| { + for r in [ + Resolution::Native, + Resolution::P1080, + Resolution::P1440, + Resolution::P2160, + ] { + ui.selectable_value(&mut resolution, r, r.describe(native)); + } + }); + if resolution != s.resolution { + s.resolution = resolution; + s.quality = Quality::Custom; + } + }, + ); ui::row_separator(ui); - ui::setting_row(ui, "Frame rate", Some("60 fps is a good starting point. Higher rates need a matching host display and more bandwidth."), |ui| { - egui::ComboBox::from_id_salt("stream-fps").selected_text(format!("{} fps", s.fps)).show_ui(ui, |ui| { - for fps in [30, 60, 90, 120, 144, 165, 240] { ui.selectable_value(&mut s.fps, fps, format!("{fps} fps")); } - }); - }); + ui::setting_row( + ui, + "Frame rate", + Some("Above 60 needs a display there that runs that fast."), + |ui| { + ui::select(ui, "stream-fps", format!("{} fps", s.fps), 120.0, |ui| { + for fps in [30, 60, 90, 120, 144, 165, 240] { + ui.selectable_value(&mut s.fps, fps, format!("{fps} fps")); + } + }); + }, + ); ui::row_separator(ui); ui::setting_row( ui, - "Bitrate target", - Some("The PC's encoder aims at exactly this rate (CBR) while the picture is changing; a still screen uses less."), + "Bitrate", + Some("The encoder aims at this while the picture moves; a still screen uses less."), |ui| { let mut mbps = if s.quality == Quality::Auto { 50 } else { s.bitrate_kbps / 1000 }; - ui.spacing_mut().slider_width = ui.available_width().min(200.0); - if ui - .add(egui::Slider::new(&mut mbps, 2..=150).suffix(" Mbps")) - .changed() - { + // Right to left: the value first, then the track before it. + ui::slider_value(ui, &format!("{mbps} Mbps")); + let w = (ui.available_width() - space::LG).clamp(96.0, 180.0); + if ui::slider(ui, &mut mbps, 2..=150, w, "Bitrate").changed() { s.quality = Quality::Custom; s.bitrate_kbps = mbps * 1000; } }, ); ui::row_separator(ui); - ui::setting_row(ui, "Video format", Some("HEVC gives sharper detail per megabit. Auto uses HEVC when supported; H.264 improves compatibility."), |ui| { - ui::segmented(ui, &[(Codec::Auto, "Auto"), (Codec::Hevc, "HEVC"), (Codec::H264, "H.264")], &mut s.codec); - }); - let effective = crate::path::effective(s, &crate::path::Path::default()); - let (w, h) = effective.resolution.pixels(native); - ui.add_space(12.0); - ui::status_pill( + ui::setting_row( ui, - &format!( - "{w} × {h} · {} fps · {} Mbps target", - effective.fps, - effective.bitrate_kbps / 1000 - ), - Tone::Info, + "Codec", + Some("HEVC is sharper for the same bitrate. Auto uses it when both ends can."), + |ui| { + ui::segmented( + ui, + &[ + (Codec::Auto, "Auto"), + (Codec::Hevc, "HEVC"), + (Codec::H264, "H.264"), + ], + &mut s.codec, + ); + }, ); + ui::row_separator(ui); + let effective = crate::path::effective(s, &crate::path::Path::default()); + let (w, h) = effective.resolution.pixels(native); + ui.add_space(space::MD); + ui.horizontal_wrapped(|ui| { + ui.spacing_mut().item_spacing.x = space::SM; + ui::section_label(ui, "Next stream"); + ui.add_space(space::XS); + ui::tag(ui, None, &format!("{w} × {h}")); + ui::tag(ui, None, &format!("{} fps", effective.fps)); + ui::tag(ui, None, &format!("{} Mbps", effective.bitrate_kbps / 1000)); + }); + ui.add_space(space::MD); *s != before } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn the_profile_follows_the_values() { + let mut s = StreamSettings::default(); + assert_eq!(Profile::of(&s), Profile::Recommended); + s.apply_preset(Preset::Smooth); + assert_eq!(Profile::of(&s), Profile::Preset(Preset::Smooth)); + s.fps = 144; + assert_eq!(Profile::of(&s), Profile::Custom); + } +} diff --git a/crates/client/src/share.rs b/crates/client/src/share.rs index 06e02c7..0bb4a45 100644 --- a/crates/client/src/share.rs +++ b/crates/client/src/share.rs @@ -1,6 +1,7 @@ -//! This machine's sharing side, filled by the unified app from the local -//! control service. The lobby shows it so every BroLink install is both a -//! viewer and a host. +//! This machine's sharing side, as the unified window sees it. The host +//! crate fills [`LocalShare`] from the local control service and draws the +//! Sharing page through [`SharePage`]; the viewer only shows a summary row +//! in the machine list and hosts the page in its window. use brolink_core::api::Status; use parking_lot::Mutex; @@ -11,15 +12,12 @@ pub struct LocalShare { pub status: Option, pub setup_running: bool, pub setup_result: Option>, - /// The window asked to run setup; the unified app clears this after it - /// starts the platform setup. - pub want_setup: bool, - pub power_allowed: bool, - pub stay_awake: bool, - pub autostart: bool, - pub want_power: Option, - pub want_stay_awake: Option, - pub want_autostart: Option, } pub type Slot = Arc>; + +/// The Sharing page: setup, what this machine reports, paired devices, +/// options and diagnostics. Drawn by whoever shares this machine. +pub trait SharePage { + fn show(&mut self, ui: &mut egui::Ui); +} diff --git a/crates/client/src/stream.rs b/crates/client/src/stream.rs index 49de0a4..6f17128 100644 --- a/crates/client/src/stream.rs +++ b/crates/client/src/stream.rs @@ -1,11 +1,14 @@ -//! The stream screen: the PC's picture filling the window, and nothing -//! else until Ctrl+Alt is pressed. That frees the mouse and drops a toolbar -//! over the top of the picture, the way a hypervisor's host key does; a -//! click on the picture (or Ctrl+Alt again) hides it and captures the -//! mouse back. Short notices (clipboard, connection, an install in -//! progress) appear under the toolbar's place and fade. +//! The stream screen: the other machine's picture filling the window, and +//! nothing else until the host key (Ctrl+Alt; Control-Option on a Mac) is +//! pressed. That frees the mouse and drops a toolbar over the top of the +//! picture, the way a hypervisor's host key does; a click on the picture +//! (or the host key again) hides it and captures the mouse back. Short +//! notices (clipboard, connection, an install in progress) appear under +//! the toolbar's place and go after a few seconds. //! -//! allow: SIZE_OK — one stream view; later UI tasks own any split. +//! Nothing here costs a frame while the toolbar is away: the bar, its +//! menus and the panels are only laid out while they show, and every +//! timed notice asks for a bounded, slow repaint only while it is up. use crate::clipboard; use crate::config::{ClientConfig, StreamSettings}; @@ -14,20 +17,25 @@ use crate::path::Path; use crate::session::Live; use crate::video; use brolink_core::api::PowerAction; -use brolink_ui::{self as ui, Tone, PALETTE as P}; +use brolink_ui::{self as ui, size, space, theme, Kv, Tone, PALETTE as P}; use egui::{ - Align, Color32, CursorIcon, Event, Frame, Id, Layout, Margin, Pos2, Rect, RichText, Vec2, - ViewportCommand, + Align, Align2, Color32, CursorIcon, Event, Frame, Id, Layout, Margin, Pos2, Rect, RichText, + Vec2, ViewportCommand, }; use semver::Version; use std::time::{Duration, Instant}; -const BAR: f32 = 40.0; -/// Below this width the right-hand controls collapse into a real egui More -/// menu so they cannot overlap Disconnect or the PC name. -const OVERFLOW_BELOW: f32 = 1200.0; +/// Below this width the toolbar's menus fold into one More menu, so they +/// cannot overlap Disconnect or the machine's name. +const OVERFLOW_BELOW: f32 = 1040.0; +/// Below this width the toolbar drops the host-key hint. +const HINT_BELOW: f32 = 1200.0; +/// Below this width the toolbar drops the stream facts after the name. +const FACTS_BELOW: f32 = 1560.0; const STATUS: f32 = 24.0; const TOAST_FOR: Duration = Duration::from_secs(5); +/// How long the host-key hint stays up when a stream starts. +const HINT_FOR: Duration = Duration::from_secs(6); /// Each side of an overlay frame (its inner margin) and the picture it /// leaves visible beyond that. const OVERLAY_PAD: f32 = 12.0; @@ -38,6 +46,28 @@ fn overlay_width(max: f32, screen_w: f32) -> f32 { max.min(screen_w - 4.0 * OVERLAY_PAD).max(0.0) } +/// The host key as printed on this keyboard, and as written in a sentence. +pub fn host_key() -> (&'static [&'static str], &'static str) { + if cfg!(target_os = "macos") { + (&["control", "option"], "Control-Option") + } else { + (&["Ctrl", "Alt"], "Ctrl+Alt") + } +} + +/// What to call the other machine in the toolbar: its power menu reads +/// "PC" for Windows, as the lobby's messages expect. +fn machine_noun(os: &str) -> &'static str { + let os = os.to_ascii_lowercase(); + if os.starts_with("windows") { + "PC" + } else if os.starts_with("macos") || os.starts_with("darwin") { + "Mac" + } else { + "Machine" + } +} + #[derive(Debug, Clone, PartialEq)] pub enum Action { Disconnect, @@ -61,6 +91,8 @@ pub struct Env<'a> { pub fullscreen: bool, /// The path as discovery sees it now; fresher than `live.path`. pub path: Option, + /// The other machine's Tailscale OS string, for naming its menu. + pub os: String, /// The PC's host version when it cannot take updates over the network, /// and the release that could be installed through the stream. pub old_host: Option<(Version, Option)>, @@ -80,7 +112,7 @@ pub struct View { /// The mouse is captured: hidden, held in place, raw movement sent. captured: bool, grabbed: bool, - /// Ctrl+Alt dropped the toolbar over the picture. + /// The host key dropped the toolbar over the picture. bar_shown: bool, stats: bool, settings: Option, @@ -94,6 +126,8 @@ pub struct View { poor: bool, poor_hinted: bool, toasts: Vec, + /// When the stream started, for the host-key hint. + hint_since: Option, /// Pastes seen through `clipboard.pastes_done()`. pastes_seen: u32, /// Ctrl+Alt was down when capture last toggled; ignore further edges @@ -113,12 +147,13 @@ impl Default for View { held: Held::default(), scroll: (0.0, 0.0), motion: (0.0, 0.0), - bar_h: BAR, + bar_h: size::TOOLBAR, confirm: None, confirm_install: false, poor: false, poor_hinted: false, toasts: Vec::new(), + hint_since: None, pastes_seen: 0, capture_chord_held: false, } @@ -129,9 +164,10 @@ impl View { pub fn set_poor(&mut self, poor: bool) { if poor && !self.poor && !self.poor_hinted { self.poor_hinted = true; + let (_, keys) = host_key(); self.toast( - Tone::Danger, - "Video is arriving unevenly. Open Stats to check loss and decode time, or lower the bitrate in Stream settings.", + Tone::Warning, + format!("Video is arriving unevenly. {keys} opens the toolbar: Stats shows loss and decode time, and Stream settings can lower the bitrate."), ); } self.poor = poor; @@ -164,6 +200,7 @@ impl View { self.poor = false; self.poor_hinted = false; self.toasts.clear(); + self.hint_since = None; self.capture_chord_held = false; } @@ -202,19 +239,20 @@ impl View { }; // The picture fills the window, in its own proportions; the - // toolbar, when Ctrl+Alt has asked for it, lies over the top of it. - // A menu or a question keeps it there until it is answered. + // toolbar, when the host key has asked for it, lies over the top of + // it. A menu or a question keeps it there until it is answered. let video = fit(screen, vw / vh); let bottom_gap = screen.bottom() - video.bottom(); let popup = ctx.memory(|m| m.any_popup_open()); let bar_visible = self.bar_shown || popup || self.confirm.is_some() || self.confirm_install; let mut bar_rect = bar_visible .then(|| Rect::from_min_size(screen.min, Vec2::new(screen.width(), self.bar_h))); + let waiting = live.frames.seq() == 0; egui::CentralPanel::default() .frame(Frame::new().fill(Color32::BLACK)) .show(ctx, |ui| { - if live.frames.seq() > 0 { + if !waiting { ui.painter().add(egui::Shape::Callback( egui_wgpu::Callback::new_paint_callback( video, @@ -223,40 +261,18 @@ impl View { }, ), )); - } else { - ui.painter().rect_filled(video, 0.0, P.bg); - let c = video.center(); - ui.put( - Rect::from_center_size(c, Vec2::new(300.0, 60.0)), - ui::spinner(22.0, P.accent), - ); - ui.painter().text( - c + Vec2::new(0.0, 36.0), - egui::Align2::CENTER_CENTER, - if live.session.connected() { - format!("Waiting for video from {}…", live.pc) - } else { - format!("Connecting to {}…", live.pc) - }, - egui::FontId::proportional(15.0), - P.muted, - ); - ui.painter().text( - c + Vec2::new(0.0, 60.0), - egui::Align2::CENTER_CENTER, - format!("{} · {}", live.path.label(), live.settings.describe()), - egui::FontId::proportional(12.5), - P.faint, - ); } if bottom_gap >= STATUS || self.stats { self.status_line(ui, screen, video, env, &stats, bottom_gap >= STATUS); } }); + if waiting { + self.waiting(ctx, screen, env, &mut actions); + } if let Some(rect) = bar_rect { let measured = self.toolbar(ctx, rect, env, &mut actions); - self.bar_h = measured.height().max(BAR); + self.bar_h = measured.height().max(size::TOOLBAR); bar_rect = Some(Rect::from_min_size( screen.min, Vec2::new(screen.width(), self.bar_h), @@ -279,83 +295,165 @@ impl View { actions } + /// Before the first picture: what is happening, and a way out that does + /// not need the host key. + fn waiting( + &mut self, + ctx: &egui::Context, + screen: Rect, + env: &Env<'_>, + actions: &mut Vec, + ) { + let live = env.live; + let w = overlay_width(420.0, screen.width()); + egui::Area::new(Id::new("stream-waiting")) + .order(egui::Order::Middle) + .anchor(Align2::CENTER_CENTER, Vec2::ZERO) + .show(ctx, |ui| { + ui.set_width(w); + ui.vertical_centered(|ui| { + ui.spacing_mut().item_spacing.y = space::SM; + ui.add(ui::spinner(20.0, P.accent)); + ui.add_space(space::XS); + ui.label( + RichText::new(if live.session.connected() { + format!("Waiting for the first picture from {}", live.pc) + } else { + format!("Connecting to {}", live.pc) + }) + .font(theme::medium(theme::text::BODY + 1.0)) + .color(P.text), + ); + let path = env.path.as_ref().unwrap_or(&live.path); + ui.label( + RichText::new(format!("{} · {}", path.label(), live.settings.describe())) + .font(theme::mono(theme::text::MONO - 0.5)) + .color(P.text_tertiary), + ); + ui.add_space(space::SM); + if ui::secondary_button(ui, "Cancel").clicked() { + actions.push(Action::Disconnect); + } + }); + }); + } + fn settings_panel(&mut self, ctx: &egui::Context, env: &Env<'_>, actions: &mut Vec) { + let _ = env; let Some(settings) = self.settings.as_mut() else { return; }; - let mut open = true; let mut apply = false; - let mut cancel = false; + let mut close = false; + let screen = ctx.screen_rect(); + let w = overlay_width(520.0, screen.width()) - 2.0 * space::LG; egui::Window::new("Stream settings") - .open(&mut open) + .title_bar(false) + .frame(ui::panel_frame()) .collapsible(false) .resizable(false) - .default_width(520.0) - .max_width((ctx.screen_rect().width() - 48.0).max(280.0)) - .anchor(egui::Align2::CENTER_CENTER, Vec2::ZERO) + .default_width(w) + .max_width(w) + .anchor(Align2::CENTER_CENTER, Vec2::ZERO) .show(ctx, |ui| { + ui.set_width(w); + close = ui::panel_header(ui, "Stream settings"); egui::ScrollArea::vertical() - .max_height((ctx.screen_rect().height() - 150.0).max(120.0)) + .max_height((screen.height() - 190.0).max(120.0)) .show(ui, |ui| { + ui.spacing_mut().item_spacing.y = 0.0; crate::settings::stream_controls( ui, settings, crate::app::ClientApp::native_pixels(ctx), ); }); - ui::row_separator(ui); - ui::caption(ui, "Applying reconnects the stream with these settings."); - ui.horizontal(|ui| { + ui.add_space(space::MD); + ui.horizontal_wrapped(|ui| { + ui.spacing_mut().item_spacing.x = space::SM; apply = ui::primary_button(ui, "Apply and reconnect").clicked(); - cancel = ui::ghost_button(ui, "Cancel").clicked(); + if ui::ghost_button(ui, "Cancel").clicked() { + close = true; + } }); + ui::small_print( + ui, + "Applying restarts the stream with these settings, in a second or two.", + ); }); + if ctx.input(|i| i.key_pressed(egui::Key::Escape)) && !ctx.memory(|m| m.any_popup_open()) { + close = true; + } if apply { actions.push(Action::ApplySettings(settings.clone())); } - if !open || apply || cancel { + if close || apply { self.settings = None; } - let _ = env; } fn diagnostics(&mut self, ctx: &egui::Context, env: &Env<'_>, stats: &brolink_stream::Stats) { let live = env.live; - egui::Window::new("Stream performance").open(&mut self.stats) - .resizable(false).collapsible(false).default_pos(Pos2::new(16.0, 60.0)).default_width(330.0) + let mut close = false; + egui::Window::new("Stream performance") + .title_bar(false) + .frame(ui::panel_frame()) + .resizable(false) + .collapsible(false) + .default_pos(Pos2::new(16.0, self.bar_h + 16.0)) + .default_width(340.0) + .max_width(340.0) .show(ctx, |ui| { + ui.set_width(340.0); + close = ui::panel_header(ui, "Stream performance"); let path = env.path.as_ref().unwrap_or(&live.path); - ui::status_pill(ui, &path.label(), path.tone()); - ui::caption(ui, live.quality_label()); - ui.add_space(8.0); - egui::Grid::new("stream-metrics").num_columns(2).spacing([18.0, 10.0]).show(ui, |ui| { - for (name, value) in [ - ("Stream resolution", format!("{} × {}", stats.width, stats.height)), - ("Frames decoded", format!("{:.1} / {} fps", stats.fps, live.requested.2)), - ("Video received", format!("{:.2} Mbps", stats.mbps)), - ("Bitrate target", format!("{} Mbps", live.settings.bitrate_kbps / 1000)), - ("Network round trip", format!("{} ± {} ms", stats.rtt_ms, stats.rtt_var_ms)), - ("Packet loss", format!("{:.2}%", stats.loss_pct)), - ("Host processing", format!("{:.2} ms", stats.host_ms)), - ("Frame assembly", format!("{:.2} ms", stats.assembly_ms)), - ("Decoder queue", format!("{:.2} ms", stats.queue_ms)), - ("Decode time", format!("{:.2} ms", stats.decode_ms)), - ("Decoder", stats.decoder.to_string()), - ] { - ui::caption(ui, name); - ui.label(RichText::new(value).color(P.text)); - ui.end_row(); - } + ui.horizontal_wrapped(|ui| { + ui::tag(ui, Some(path.tone()), &path.label()); + ui::tag(ui, None, &profile_label(&live.settings)); }); - ui.add_space(8.0); - ui::caption(ui, "The PC's encoder aims at the target (CBR) and reaches it when the picture is changing; a still screen needs fewer bits. Received falling well short during motion, or swinging, means the path is dropping packets."); - if !stats.audio.is_empty() { ui::caption(ui, &stats.audio); } - if ui::ghost_button(ui, "Copy diagnostics").clicked() { + ui.add_space(space::SM); + let rows = [ + Kv::new("Resolution", format!("{} × {}", stats.width, stats.height)), + Kv::new( + "Frame rate", + format!("{:.1} of {} fps", stats.fps, live.requested.2), + ), + Kv::new( + "Bitrate", + format!( + "{:.1} of {} Mbps", + stats.mbps, + live.settings.bitrate_kbps / 1000 + ), + ), + Kv::new( + "Round trip", + format!("{} ± {} ms", stats.rtt_ms, stats.rtt_var_ms), + ), + Kv::new("Packet loss", format!("{:.2}%", stats.loss_pct)), + Kv::new("Encode on host", format!("{:.2} ms", stats.host_ms)), + Kv::new("Frame assembly", format!("{:.2} ms", stats.assembly_ms)), + Kv::new("Decoder queue", format!("{:.2} ms", stats.queue_ms)), + Kv::new("Decode", format!("{:.2} ms", stats.decode_ms)), + Kv::new("Decoder", stats.decoder.to_string()), + ] + .map(Kv::mono); + ui::kv_grid(ui, &rows); + ui.add_space(space::SM); + ui::small_print(ui, "The encoder aims at the bitrate while the picture changes; a still screen needs less. Received bitrate falling well short during motion, or swinging, means packets are being lost on the way."); + if !stats.audio.is_empty() { + ui::small_print(ui, &stats.audio); + } + ui.add_space(space::XS); + if ui::secondary_button(ui, "Copy diagnostics").clicked() { ctx.copy_text(format!("BroLink {}\n{}\nRequested: {} × {}, {} fps, {} Mbps\n{stats:#?}", env!("CARGO_PKG_VERSION"), path.label(), live.requested.0, live.requested.1, live.requested.2, live.settings.bitrate_kbps / 1000)); } }); + if close { + self.stats = false; + } } fn toolbar( @@ -368,9 +466,7 @@ impl View { let live = env.live; let cfg = env.cfg; let overflow = rect.width() < OVERFLOW_BELOW; - let frame = ui::overlay_frame() - .corner_radius(0) - .inner_margin(Margin::symmetric(12, 0)); + let facts = rect.width() >= FACTS_BELOW; let path = env.path.clone().unwrap_or_else(|| live.path.clone()); let bar = egui::Area::new(Id::new("stream-toolbar")) .fixed_pos(rect.min) @@ -379,99 +475,113 @@ impl View { .show(ctx, |ui| { ui.set_min_width(rect.width()); ui.set_max_width(rect.width()); - ui.set_min_height(BAR); - frame.show(ui, |ui| { - ui.set_min_height(BAR); - ui.horizontal_centered(|ui| { - ui.spacing_mut().item_spacing.x = 8.0; - let s = live.session.stats(); - let tone = if !live.session.connected() { - Tone::Accent - } else if self.poor || s.video_problem.is_some() { - Tone::Danger - } else { - Tone::Success - }; - ui.label(RichText::new("●").size(9.0).color(tone.color())); - ui.label( - RichText::new(&live.pc) - .font(brolink_ui::theme::medium(13.5)) - .color(P.text), - ); - if !overflow { - let (w, h) = if s.width > 0 { - (s.width, s.height) + Frame::new() + .fill(P.overlay) + .inner_margin(Margin::symmetric(space::MD as i8, 0)) + .show(ui, |ui| { + ui.set_min_height(size::TOOLBAR); + ui.spacing_mut().interact_size.y = size::CONTROL_SM; + ui.horizontal_centered(|ui| { + ui.spacing_mut().item_spacing.x = space::SM; + let s = live.session.stats(); + let tone = if !live.session.connected() { + Tone::Accent + } else if self.poor || s.video_problem.is_some() { + Tone::Warning } else { - (live.requested.0, live.requested.1) + Tone::Success }; - ui::caption( - ui, - format!("{w}×{h} · {} fps · {}", live.requested.2, live.codec), + ui::status_dot(ui, tone); + ui.add( + egui::Label::new( + RichText::new(&live.pc) + .font(theme::medium(theme::text::BODY)) + .color(P.text), + ) + .truncate(), ); - ui.add_space(2.0); - let pill = ui::status_pill(ui, &path.label(), path.tone()); - if path.relayed() { - pill.on_hover_text( - "This is the route currently in use. A relay does not impose a bitrate limit in BroLink.", + if facts { + let (w, h) = if s.width > 0 { + (s.width, s.height) + } else { + (live.requested.0, live.requested.1) + }; + ui.label( + RichText::new(format!( + "{w}×{h} · {} fps · {}", + live.requested.2, live.codec + )) + .font(theme::mono(theme::text::MONO - 0.5)) + .color(P.text_tertiary), ); - } else if path.direct == Some(true) { - pill.on_hover_text("Packets go straight to the PC."); } - ui::caption(ui, "Ctrl+Alt hides this bar"); - } - ui.with_layout(Layout::right_to_left(Align::Center), |ui| { - if ui::danger_button(ui, "Disconnect").clicked() { - actions.push(Action::Disconnect); - } - if ui::ghost_button(ui, "Stream settings").clicked() { - self.held.release_all(&live.input); - self.set_captured(ctx, false); - self.settings = Some(cfg.stream.clone()); + if !overflow { + let tag = ui::tag(ui, Some(path.tone()), &path.label()); + if path.relayed() { + tag.on_hover_text("The route in use right now. A relay adds delay; BroLink never lowers the bitrate for it."); + } else if path.direct == Some(true) { + tag.on_hover_text("Packets go straight to the machine."); + } } - if overflow { - self.more_menu(ui, ctx, env, actions); - } else { - self.pc_menu(ui, env, actions); - self.fullscreen_button(ui, env, actions); - self.stats_button(ui); - self.keys_menu(ui, live, cfg, actions); - self.mouse_menu(ui, ctx, live, cfg, actions); + if rect.width() >= HINT_BELOW { + ui.add_space(space::XS); + let (keys, _) = host_key(); + ui::shortcut(ui, keys, "hides this bar"); } + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + ui.spacing_mut().item_spacing.x = space::XS; + if ui::danger_button(ui, "Disconnect").clicked() { + actions.push(Action::Disconnect); + } + toolbar_rule(ui); + if ui::ghost_button(ui, "Stream settings").clicked() { + self.held.release_all(&live.input); + self.set_captured(ctx, false); + self.settings = Some(cfg.stream.clone()); + } + if overflow { + self.more_menu(ui, ctx, env, actions); + } else { + self.power_menu(ui, env, actions); + self.fullscreen_button(ui, env, actions); + self.stats_button(ui); + self.keys_menu(ui, live, cfg, actions); + self.mouse_menu(ui, ctx, live, cfg, actions); + } + }); }); }); - }); + let r = ui.min_rect(); + ui.painter().hline( + r.x_range(), + r.bottom() - 0.5, + theme::stroke(1.0, Color32::from_white_alpha(28)), + ); }); if let Some(action) = self.confirm { - let w = overlay_width(440.0, rect.width()); - egui::Area::new(Id::new("stream-confirm")) - .fixed_pos(Pos2::new( - rect.center().x - w / 2.0 - OVERLAY_PAD, - rect.bottom() + 8.0, - )) - .order(egui::Order::Foreground) - .show(ctx, |ui| { - ui::overlay_frame().show(ui, |ui| { - ui.set_width(w); - ui.label( - RichText::new(format!( - "{} {}? Programs are closed without asking; anything unsaved is lost.", - action.label(), - live.pc - )) - .color(P.text), - ); - ui.horizontal(|ui| { - if ui::toned_button(ui, action.label(), Tone::Danger).clicked() { - actions.push(Action::Power(action)); - self.confirm = None; - } - if ui::ghost_button(ui, "Cancel").clicked() { - self.confirm = None; - } - }); - }); + let w = overlay_width(420.0, rect.width()); + question(ctx, "stream-confirm", rect, w, |ui| { + ui.label( + RichText::new(format!("{} {}?", action.label(), live.pc)) + .font(theme::semibold(theme::text::TITLE)) + .color(P.text), + ); + ui::muted( + ui, + "The stream ends first. Programs there close without asking, and anything unsaved is lost.", + ); + ui.add_space(space::XS); + ui.horizontal(|ui| { + if ui::destructive_button(ui, action.label()).clicked() { + actions.push(Action::Power(action)); + self.confirm = None; + } + if ui::ghost_button(ui, "Cancel").clicked() { + self.confirm = None; + } }); + }); } if self.confirm_install { let (running, latest) = env @@ -479,43 +589,42 @@ impl View { .clone() .map(|(r, l)| (r.to_string(), l.map(|v| v.to_string()))) .unwrap_or_default(); - let w = overlay_width(500.0, rect.width()); - egui::Area::new(Id::new("stream-install")) - .fixed_pos(Pos2::new( - rect.center().x - w / 2.0 - OVERLAY_PAD, - rect.bottom() + 8.0, - )) - .order(egui::Order::Foreground) - .show(ctx, |ui| { - ui::overlay_frame().show(ui, |ui| { - ui.set_width(w); - ui.label( - RichText::new(format!("Install BroLink Host {} on {}", latest.as_deref().unwrap_or("(latest)"), live.pc)) - .font(brolink_ui::theme::medium(14.0)) - .color(P.text), - ); - ui.label( - RichText::new(format!( - "{} runs {running}, which cannot take updates over the network. This Mac will press Win+R on the PC, type one line that fetches the new version from this Mac over Tailscale, and press Enter. A PowerShell window appears on the PC, swaps the file in and restarts the service; the stream is not interrupted. The PC's desktop must be unlocked and in front.", - live.pc - )) - .color(P.muted), - ); - ui.horizontal(|ui| { - if ui::primary_button(ui, "Install through the stream").clicked() { - actions.push(Action::InstallHost); - self.confirm_install = false; - } - if ui::ghost_button(ui, "Cancel").clicked() { - self.confirm_install = false; - } - }); - }); + let w = overlay_width(480.0, rect.width()); + question(ctx, "stream-install", rect, w, |ui| { + ui.label( + RichText::new(format!( + "Install BroLink Host {} on {}?", + latest.as_deref().unwrap_or("(latest)"), + live.pc + )) + .font(theme::semibold(theme::text::TITLE)) + .color(P.text), + ); + ui::muted( + ui, + format!( + "{} runs {running}, which can't take updates over the network. BroLink presses Win+R on it, types one line that fetches the new version from this machine over Tailscale, and presses Enter. A PowerShell window opens there, swaps the file and restarts the service; the stream stays up.", + live.pc + ), + ); + ui::small_print(ui, "Its desktop must be unlocked and in front."); + ui.add_space(space::XS); + ui.horizontal(|ui| { + if ui::primary_button(ui, "Install through the stream").clicked() { + actions.push(Action::InstallHost); + self.confirm_install = false; + } + if ui::ghost_button(ui, "Cancel").clicked() { + self.confirm_install = false; + } }); + }); } bar.response.rect } + /// The narrow toolbar's one menu: everything but Stream settings and + /// Disconnect, in labelled groups, scrolling if the window is short. fn more_menu( &mut self, ui: &mut egui::Ui, @@ -525,39 +634,26 @@ impl View { ) { let live = env.live; let cfg = env.cfg; - let response = ui::ghost_button(ui, "More"); - let popup_id = Id::new("stream-more"); - if response.clicked() { - ui.memory_mut(|m| m.toggle_popup(popup_id)); - } - egui::popup::popup_below_widget( - ui, - popup_id, - &response, - egui::popup::PopupCloseBehavior::CloseOnClickOutside, - |ui| { - ui.set_min_width(180.0); - self.mouse_menu(ui, ctx, live, cfg, actions); - self.keys_menu(ui, live, cfg, actions); - if ui - .button(if self.stats { "Hide stats" } else { "Stats" }) - .clicked() - { - self.stats = !self.stats; - ui.memory_mut(|m| m.close_popup()); - } - let fs = if env.fullscreen { - "Exit full screen" - } else { - "Full screen" - }; - if ui.button(fs).clicked() { - actions.push(Action::Fullscreen(!env.fullscreen)); - ui.memory_mut(|m| m.close_popup()); - } - self.pc_menu(ui, env, actions); - }, - ); + let max_h = (ctx.screen_rect().height() - self.bar_h - space::LG).max(120.0); + ui::menu_button(ui, "More", |ui| { + egui::ScrollArea::vertical() + .max_height(max_h) + .show(ui, |ui| { + ui::menu_label(ui, "View"); + if ui::menu_choice(ui, "Full screen", env.fullscreen).clicked() { + actions.push(Action::Fullscreen(!env.fullscreen)); + } + if ui::menu_choice(ui, "Stream performance", self.stats).clicked() { + self.stats = !self.stats; + } + ui::menu_label(ui, "Mouse"); + self.mouse_items(ui, ctx, live, cfg, actions); + ui::menu_label(ui, "Send keys"); + self.key_items(ui, live, cfg, actions); + ui::menu_label(ui, machine_noun(&env.os)); + self.power_items(ui, env, actions); + }); + }); } fn fullscreen_button(&self, ui: &mut egui::Ui, env: &Env<'_>, actions: &mut Vec) { @@ -577,44 +673,41 @@ impl View { fn stats_button(&mut self, ui: &mut egui::Ui) { if ui::ghost_button(ui, if self.stats { "Hide stats" } else { "Stats" }) - .on_hover_text( - "Show received and target bitrate, frame rate, network delay and decoder performance", - ) + .on_hover_text("Frame rate, bitrate, round trip, packet loss and decode time") .clicked() { self.stats = !self.stats; } } - fn pc_menu(&mut self, ui: &mut egui::Ui, env: &Env<'_>, actions: &mut Vec) { - ui::menu_button(ui, "PC", |ui| { - if ui.button("Sleep").clicked() { - actions.push(Action::Power(PowerAction::Sleep)); - ui.close_menu(); - } - if ui.button("Restart…").clicked() { - self.confirm = Some(PowerAction::Restart); - ui.close_menu(); - } - if ui.button("Shut down…").clicked() { - self.confirm = Some(PowerAction::Shutdown); - ui.close_menu(); - } - if let Some((running, _)) = &env.old_host { - ui.separator(); - let label = format!("Update BroLink Host… (runs {running})"); - if ui - .add_enabled(env.handover.is_none(), egui::Button::new(label)) - .on_hover_text("Installs the newest BroLink Host on the PC through this stream, so future updates arrive by themselves.") - .clicked() - { - self.confirm_install = true; - ui.close_menu(); - } - } + fn power_menu(&mut self, ui: &mut egui::Ui, env: &Env<'_>, actions: &mut Vec) { + ui::menu_button(ui, machine_noun(&env.os), |ui| { + self.power_items(ui, env, actions) }); } + fn power_items(&mut self, ui: &mut egui::Ui, env: &Env<'_>, actions: &mut Vec) { + if ui::menu_item(ui, "Sleep").clicked() { + actions.push(Action::Power(PowerAction::Sleep)); + } + if ui::menu_item(ui, "Restart…").clicked() { + self.confirm = Some(PowerAction::Restart); + } + if ui::menu_item(ui, "Shut down…").clicked() { + self.confirm = Some(PowerAction::Shutdown); + } + if let Some((running, _)) = &env.old_host { + ui::menu_separator(ui); + let label = format!("Update BroLink Host… (runs {running})"); + if ui::menu_item_enabled(ui, env.handover.is_none(), &label) + .on_hover_text("Installs the newest BroLink Host through this stream, so later updates arrive by themselves.") + .clicked() + { + self.confirm_install = true; + } + } + } + fn keys_menu( &mut self, ui: &mut egui::Ui, @@ -623,99 +716,108 @@ impl View { actions: &mut Vec, ) { ui::menu_button(ui, "Keys", |ui| { - let input = &live.input; - for (label, keys) in [ - ( - "Ctrl+Alt+Del", - &[input::VK_CONTROL, input::VK_MENU, input::VK_DELETE][..], - ), - ("Windows key", &[input::VK_LWIN][..]), - ("Alt+Tab", &[input::VK_MENU, input::VK_TAB][..]), - ("Esc", &[input::VK_ESCAPE][..]), - ("Print Screen", &[input::VK_SNAPSHOT][..]), - ] { - if ui.button(label).clicked() { - self.held.chord(input, keys); - ui.close_menu(); - } - } - ui.separator(); - let cmd = if cfg.cmd_is_ctrl { - "● ⌘ acts as Ctrl (⌘C, ⌘V, ⌘Z work)" - } else { - "○ ⌘ acts as Ctrl (now the Windows key)" - }; - if ui - .button(cmd) - .on_hover_text("Click to switch what the Command key does on the PC") - .clicked() - { - actions.push(Action::ToggleCmd); - ui.close_menu(); - } - ui.separator(); - ui::caption( + ui::menu_label(ui, "Send to the other machine"); + self.key_items(ui, live, cfg, actions); + let (_, keys) = host_key(); + ui::menu_note( ui, - "Everything else goes to the PC as pressed. Ctrl+Alt toggles mouse capture.", + &format!("Everything else you type goes there as pressed. {keys} shows or hides this bar."), ); if live.clipboard.unsupported() { - ui::caption( + ui::menu_note( ui, - "Clipboard sync needs BroLink Host 3.1 on the PC (PC → Update BroLink Host).", + "Clipboard sync needs BroLink Host 3.1 or later there (PC menu → Update BroLink Host).", ); } else { - ui::caption( + ui::menu_note( ui, - "⌘C on the PC copies to this Mac; ⌘V pastes this Mac's text.", + "Copy there and paste here, or the other way round: the clipboard follows you.", ); } }); } - fn mouse_menu( + fn key_items( &mut self, ui: &mut egui::Ui, - ctx: &egui::Context, live: &Live, cfg: &ClientConfig, actions: &mut Vec, ) { - let label = if cfg.capture_mouse { - "Mouse: captured" - } else { - "Mouse: free" - }; - ui::menu_button(ui, label, |ui| { - let mark = |on: bool| if on { "● " } else { " " }; - if ui - .button(format!( - "{}Captured · raw movement, which games read. A click on the picture captures; Ctrl+Alt frees", - mark(cfg.capture_mouse) - )) - .clicked() - { - if !cfg.capture_mouse { - actions.push(Action::MouseCapture(true)); - } - ui.close_menu(); + let input = &live.input; + for (label, keys) in [ + ( + "Ctrl+Alt+Delete", + &[input::VK_CONTROL, input::VK_MENU, input::VK_DELETE][..], + ), + ("Windows key", &[input::VK_LWIN][..]), + ("Alt+Tab", &[input::VK_MENU, input::VK_TAB][..]), + ("Escape", &[input::VK_ESCAPE][..]), + ("Print Screen", &[input::VK_SNAPSHOT][..]), + ] { + if ui::menu_item(ui, label).clicked() { + self.held.chord(input, keys); } - if ui - .button(format!( - "{}Free · the Mac cursor's position is sent, 1:1 on the PC", - mark(!cfg.capture_mouse) - )) + } + if cfg!(target_os = "macos") { + ui::menu_separator(ui); + if ui::menu_choice(ui, "Command acts as Ctrl", cfg.cmd_is_ctrl) + .on_hover_text("On: Command-C, V and Z copy, paste and undo there. Off: Command is the Windows key.") .clicked() { - if cfg.capture_mouse { - actions.push(Action::MouseCapture(false)); - self.release(ctx, live); - } - ui.close_menu(); + actions.push(Action::ToggleCmd); } + } + } + + fn mouse_menu( + &mut self, + ui: &mut egui::Ui, + ctx: &egui::Context, + live: &Live, + cfg: &ClientConfig, + actions: &mut Vec, + ) { + ui::menu_button(ui, "Mouse", |ui| { + self.mouse_items(ui, ctx, live, cfg, actions); + let (_, keys) = host_key(); + ui::menu_note( + ui, + &format!( + "Captured: a click on the picture takes the mouse and {keys} gives it back." + ), + ); }); } - /// Short notices under the toolbar: clipboard, connection, an install. + fn mouse_items( + &mut self, + ui: &mut egui::Ui, + ctx: &egui::Context, + live: &Live, + cfg: &ClientConfig, + actions: &mut Vec, + ) { + if ui::menu_choice(ui, "Captured, for games (raw movement)", cfg.capture_mouse).clicked() + && !cfg.capture_mouse + { + actions.push(Action::MouseCapture(true)); + } + if ui::menu_choice( + ui, + "Free, for desktops (follows this cursor)", + !cfg.capture_mouse, + ) + .clicked() + && cfg.capture_mouse + { + actions.push(Action::MouseCapture(false)); + self.release(ctx, live); + } + } + + /// Short notices under the toolbar: the host-key hint, clipboard, + /// connection, an install. fn toasts( &mut self, ctx: &egui::Context, @@ -726,40 +828,63 @@ impl View { actions: &mut Vec, ) { self.toasts.retain(|t| t.at.elapsed() < TOAST_FOR); + let hint = self + .hint_since + .is_some_and(|t| t.elapsed() < HINT_FOR && !self.bar_shown); + if !hint { + self.hint_since = None; + } let mut lines: Vec<(Tone, String)> = self .toasts .iter() .map(|t| (t.tone, t.text.clone())) .collect(); if let Some(h) = &env.handover { - lines.push((Tone::Info, h.clone())); + lines.push((Tone::Neutral, h.clone())); } if let Some(n) = env.live.clipboard.note() { lines.push((Tone::Neutral, n)); } - if lines.is_empty() && video_problem.is_none() { + if lines.is_empty() && video_problem.is_none() && !hint { return; } + // Slow and bounded: enough to take a notice down on time. ctx.request_repaint_after(Duration::from_millis(500)); - let top = bar.map(|b| b.bottom()).unwrap_or(screen.top()) + 10.0; - let w = overlay_width(496.0, screen.width()); + let top = bar.map(|b| b.bottom()).unwrap_or(screen.top()) + space::MD; + let w = overlay_width(480.0, screen.width()); egui::Area::new(Id::new("stream-toasts")) .fixed_pos(Pos2::new(screen.center().x - w / 2.0 - OVERLAY_PAD, top)) .order(egui::Order::Foreground) .interactable(video_problem.is_some()) .show(ctx, |ui| { ui.set_width(w + 2.0 * OVERLAY_PAD); + ui.spacing_mut().item_spacing.y = space::SM; + if hint { + ui::overlay_frame().show(ui, |ui| { + ui.set_width(w); + let (keys, _) = host_key(); + ui::shortcut(ui, keys, "releases the mouse and shows the toolbar"); + }); + } if let Some(problem) = video_problem { ui::overlay_frame().show(ui, |ui| { ui.set_width(w); - ui.label(RichText::new(problem).color(P.text)); - // The PC's own answer, once it has given one. + ui.spacing_mut().item_spacing.y = space::XS; + ui.add( + egui::Label::new( + RichText::new(problem) + .font(theme::medium(theme::text::BODY)) + .color(P.text), + ) + .wrap(), + ); + // The machine's own answer, once it has given one. if let Some(help) = env.video_help.as_ref() { - ui.add_space(6.0); - ui.label(RichText::new(&help.message).color(P.muted)); + ui::muted(ui, &help.message); } + ui.add_space(space::XS); ui.horizontal(|ui| { - if ui::ghost_button(ui, "Restart stream").clicked() { + if ui::secondary_button(ui, "Restart stream").clicked() { actions.push(Action::RestartStream); } let Some(help) = env.video_help.as_ref() else { @@ -769,15 +894,9 @@ impl View { return; } if help.busy { - ui.label( - RichText::new(format!("Turning {} off…", help.mode)) - .color(P.muted), - ); - } else if ui::ghost_button( - ui, - &format!("Turn off {} on the PC", help.mode), - ) - .clicked() + ui::empty_state(ui, &format!("Turning {} off…", help.mode), true); + } else if ui::ghost_button(ui, &format!("Turn off {} there", help.mode)) + .clicked() { actions.push(Action::TurnOffHdr); } @@ -787,10 +906,7 @@ impl View { for (tone, text) in lines { ui::overlay_frame().show(ui, |ui| { ui.set_width(w); - ui.horizontal_wrapped(|ui| { - ui.label(RichText::new("●").size(9.0).color(tone.color())); - ui.label(RichText::new(text).color(P.text)); - }); + ui::dot_label(ui, tone, &text); }); } }); @@ -807,41 +923,43 @@ impl View { ) { let live = env.live; let secs = live.started.elapsed().as_secs(); - let mut text = format!( - "{:02}:{:02}:{:02}", + let text = format!( + "{:02}:{:02}:{:02} {:.0}/{} fps {:.1}/{} Mbps {} ms", secs / 3600, (secs / 60) % 60, - secs % 60 - ); - text.push_str(&format!( - " {:.0} / {} fps {:.1} / {} Mbps {} ms RTT", + secs % 60, stats.fps, live.requested.2, stats.mbps, live.settings.bitrate_kbps / 1000, stats.rtt_ms - )); - let font = egui::FontId::monospace(12.0); + ); + let font = theme::mono(theme::text::LABEL + 0.5); if in_gap { let y = video.bottom() + (screen.bottom() - video.bottom()) / 2.0; ui.painter().text( - Pos2::new(screen.left() + 14.0, y), - egui::Align2::LEFT_CENTER, + Pos2::new(screen.left() + space::MD, y), + Align2::LEFT_CENTER, text, font, - P.faint, + P.text_tertiary, ); } else { - let galley = ui.painter().layout_no_wrap(text, font, P.text); - let size = galley.size() + Vec2::new(20.0, 10.0); + let galley = ui.painter().layout_no_wrap(text, font, P.text_secondary); + let size = galley.size() + Vec2::new(2.0 * space::SM, space::SM); let rect = Rect::from_min_size( - Pos2::new(screen.left() + 12.0, screen.bottom() - size.y - 12.0), + Pos2::new( + screen.left() + space::MD, + screen.bottom() - size.y - space::MD, + ), size, ); - ui.painter() - .rect_filled(rect, 6.0, Color32::from_black_alpha(190)); - ui.painter() - .galley(rect.min + Vec2::new(10.0, 5.0), galley, P.text); + ui.painter().rect_filled(rect, theme::radius::SM, P.overlay); + ui.painter().galley( + rect.min + Vec2::new(space::SM, space::XS), + galley, + P.text_secondary, + ); } } @@ -863,6 +981,7 @@ impl View { /// drops the toolbar; from the toolbar it hides it and, when capture /// is on, takes the mouse back. fn host_key(&mut self, ctx: &egui::Context, live: &Live, cfg: &ClientConfig) { + self.hint_since = None; if self.captured || !self.bar_shown { self.release(ctx, live); self.bar_shown = true; @@ -874,8 +993,8 @@ impl View { } } - /// The stream has just connected: take the mouse if the pointer is - /// here already, and say once how to get it back. + /// The stream has just connected: take the mouse if the window has + /// focus, and say once how to get it back. pub fn stream_started(&mut self, ctx: &egui::Context, live: &Live, capture: bool) { // Capture as soon as the stream connects, whenever the window has // focus, without waiting for the pointer to be over the picture. A @@ -888,14 +1007,7 @@ impl View { if capture && focused { self.grab(ctx, live); } - self.toast( - Tone::Info, - if capture { - "Ctrl+Alt frees the mouse and shows the toolbar; a click on the picture captures it again." - } else { - "Ctrl+Alt shows and hides the toolbar." - }, - ); + self.hint_since = Some(Instant::now()); } /// Forward this frame's keyboard and mouse events to the PC. @@ -1133,7 +1245,6 @@ fn pointer_on_stream( in_video && !over_bar && !popup && !overlay && !over_overlay } -/// "Auto · Smooth" → "Auto", "Custom · Sharp" → "Sharp", else "Custom". /// Whether `host_version` is too old for `/v1/update`, with the release /// that an install through the stream would put on it. pub fn old_host( @@ -1157,11 +1268,48 @@ pub fn fit(area: Rect, aspect: f32) -> Rect { Rect::from_center_size(area.center(), Vec2::new(w.floor(), h.floor())) } +/// The quality profile a stream runs at, named as Settings names it. +fn profile_label(s: &StreamSettings) -> String { + match (s.quality, s.preset()) { + (crate::config::Quality::Auto, _) => "Recommended".into(), + (_, Some(p)) => p.label().into(), + _ => format!("Custom · {}", s.describe()), + } +} + +/// A question that floats under the toolbar, centred: restart, install. +fn question(ctx: &egui::Context, id: &str, bar: Rect, w: f32, add: impl FnOnce(&mut egui::Ui)) { + egui::Area::new(Id::new(id)) + .fixed_pos(Pos2::new( + bar.center().x - w / 2.0 - OVERLAY_PAD, + bar.bottom() + space::SM, + )) + .order(egui::Order::Foreground) + .show(ctx, |ui| { + ui::overlay_frame().show(ui, |ui| { + ui.set_width(w); + ui.spacing_mut().item_spacing.y = space::SM; + add(ui) + }); + }); +} + +/// A short vertical hairline between groups of toolbar controls. +fn toolbar_rule(ui: &mut egui::Ui) { + let (rect, _) = ui.allocate_exact_size(Vec2::new(1.0, 16.0), egui::Sense::hover()); + ui.painter().vline( + rect.center().x, + rect.y_range(), + theme::stroke(1.0, Color32::from_white_alpha(36)), + ); +} + #[cfg(test)] -mod tests { +pub(crate) mod tests { use super::*; use crate::config::ClientConfig; use crate::session::Live; + use egui_kittest::kittest::Queryable; #[test] fn fit_letterboxes_a_16_9_stream_on_a_16_10_screen() { @@ -1217,11 +1365,29 @@ mod tests { ); } - struct Fixture { - view: View, - live: Option, - cfg: ClientConfig, - fullscreen: bool, + #[test] + fn the_power_menu_is_named_for_the_machine() { + assert_eq!(machine_noun("windows"), "PC"); + assert_eq!(machine_noun("macOS"), "Mac"); + assert_eq!(machine_noun("linux"), "Machine"); + assert_eq!(machine_noun(""), "Machine"); + } + + /// What the window tells the view, besides the stream itself. + #[derive(Default, Clone)] + pub(crate) struct Extra { + pub os: String, + pub old_host: Option<(Version, Option)>, + pub handover: Option, + pub video_help: Option, + } + + pub(crate) struct Fixture { + pub view: View, + pub live: Option, + pub cfg: ClientConfig, + pub fullscreen: bool, + pub extra: Extra, applied: bool, } @@ -1233,7 +1399,9 @@ mod tests { } } - fn dummy_live(ctx: &egui::Context) -> Live { + /// A session to an address that never answers, so the view shows its + /// toolbar and overlays with no picture behind them. + pub(crate) fn dummy_live(ctx: &egui::Context) -> Live { let (tx, rx) = std::sync::mpsc::channel(); let frames = std::sync::Arc::new(brolink_stream::FrameSlot::default()); let path = crate::path::Path { @@ -1254,8 +1422,8 @@ mod tests { brolink_stream::Settings { width: 1920, height: 1080, - fps: 30, - bitrate_kbps: 4000, + fps: 60, + bitrate_kbps: 50_000, hevc: true, remote: true, }, @@ -1277,7 +1445,7 @@ mod tests { events: rx, started: Instant::now(), codec: "HEVC", - requested: (1920, 1080, 30), + requested: (1920, 1080, 60), settings, path, clipboard: crate::clipboard::Sync::spawn(ip, ctx.clone()), @@ -1298,6 +1466,7 @@ mod tests { live, cfg, fullscreen, + extra, .. } = f; let env = Env { @@ -1305,14 +1474,15 @@ mod tests { cfg, fullscreen: *fullscreen, path: None, - old_host: None, - handover: None, - video_help: None, + os: extra.os.clone(), + old_host: extra.old_host.clone(), + handover: extra.handover.clone(), + video_help: extra.video_help.clone(), }; view.show(ctx, &env); } - /// A view with the toolbar dropped, as Ctrl+Alt leaves it. + /// A view with the toolbar dropped, as the host key leaves it. fn view_with_bar() -> View { View { bar_shown: true, @@ -1320,25 +1490,51 @@ mod tests { } } - fn harness(size: Vec2) -> egui_kittest::Harness<'static, Fixture> { - egui_kittest::Harness::builder() + pub(crate) fn fixture(view: View, extra: Extra) -> Fixture { + Fixture { + view, + live: None, + cfg: ClientConfig::default(), + fullscreen: false, + extra, + applied: false, + } + } + + fn harness_with( + size: Vec2, + ppp: f32, + gpu: bool, + f: Fixture, + ) -> egui_kittest::Harness<'static, Fixture> { + let mut b = egui_kittest::Harness::builder() .with_size(size) - .with_pixels_per_point(1.0) - .with_max_steps(2) - .build_state( - paint, - Fixture { - view: view_with_bar(), - live: None, - cfg: ClientConfig::default(), - fullscreen: false, - applied: false, + .with_pixels_per_point(ppp) + .with_max_steps(2); + if gpu { + b = b.wgpu(); + } + let mut h = b.build_state(paint, f); + h.run_steps(3); + h + } + + fn harness(size: Vec2) -> egui_kittest::Harness<'static, Fixture> { + harness_with( + size, + 1.0, + false, + fixture( + view_with_bar(), + Extra { + os: "windows".into(), + ..Default::default() }, - ) + ), + ) } fn bar_control_rects(h: &egui_kittest::Harness<'_, Fixture>) -> Vec<(String, Rect)> { - use egui_kittest::kittest::Queryable; let names = [ "Disconnect", "More", @@ -1353,9 +1549,9 @@ mod tests { ]; let mut out = Vec::new(); for name in names { - for node in h.query_all_by_label_contains(name) { + for node in h.query_all_by_label(name) { let Some(b) = node.raw_bounds() else { continue }; - if b.y0 > f64::from(BAR + 8.0) { + if b.y0 > f64::from(size::TOOLBAR + 8.0) { continue; } out.push(( @@ -1386,15 +1582,13 @@ mod tests { #[test] fn toolbar_at_640_does_not_overlap_and_keeps_disconnect() { - use egui_kittest::kittest::Queryable; - let mut h = harness(Vec2::new(640.0, 420.0)); - h.run_steps(2); + let h = harness(Vec2::new(640.0, 420.0)); assert!( h.query_by_label("Disconnect").is_some(), "Disconnect must stay reachable" ); assert!( - h.query_by_label_contains("More").is_some(), + h.query_by_label("More").is_some(), "narrow width must overflow into More" ); assert!( @@ -1404,17 +1598,16 @@ mod tests { let rects = bar_control_rects(&h); assert_no_overlap(&rects); assert!( - h.state().view.bar_h >= BAR, + h.state().view.bar_h >= size::TOOLBAR, "bar_rect is measured, got {}", h.state().view.bar_h ); + crate::app::snapshots::assert_fits(&h, 640.0); } #[test] fn more_is_a_real_egui_popup_and_consumes_clicks() { - use egui_kittest::kittest::Queryable; let mut h = harness(Vec2::new(640.0, 420.0)); - h.run_steps(2); assert!( !h.ctx.memory(|m| m.any_popup_open()), "closed More is not a popup" @@ -1425,86 +1618,203 @@ mod tests { h.ctx.memory(|m| m.any_popup_open()), "More must use egui::popup so any_popup_open is true; a Background Area would fail this" ); - assert!( - h.query_by_label("Stream settings").is_some() - || h.query_by_label_contains("Keys").is_some() - || h.query_by_label("Stats").is_some(), - "More lists the overflowed controls" - ); + for item in ["Ctrl+Alt+Delete", "Stream performance", "Sleep"] { + assert!(h.query_by_label(item).is_some(), "More lists {item}"); + } let popup_open = h.ctx.memory(|m| m.any_popup_open()); assert!(!pointer_on_stream(true, false, popup_open, false, false)); - if let Some(stats) = h.query_by_label("Stats") { - stats.simulate_click(); - h.run_steps(2); - assert!( - h.state().view.stats, - "click inside More is consumed by the popup, not forwarded" - ); - } + h.get_by_label("Stream performance").simulate_click(); + h.run_steps(2); + assert!( + h.state().view.stats, + "click inside More is consumed by the popup, not forwarded" + ); } #[test] fn toolbar_at_normal_width_keeps_controls_on_the_bar() { - use egui_kittest::kittest::Queryable; - let mut h = harness(Vec2::new(1400.0, 860.0)); - h.run_steps(2); + let h = harness(Vec2::new(1400.0, 860.0)); assert!(h.query_by_label("Disconnect").is_some()); - assert!(h.query_by_label_contains("More").is_none()); + assert!(h.query_by_label("More").is_none()); assert!(h.query_by_label("Stream settings").is_some()); - assert!(h.query_by_label_contains("Keys").is_some()); + assert!(h.query_by_label("Keys").is_some()); + assert!( + h.query_by_label("PC").is_some(), + "a Windows machine's menu reads PC" + ); assert_no_overlap(&bar_control_rects(&h)); + crate::app::snapshots::assert_fits(&h, 1400.0); } - fn save_snapshot(img: image::RgbaImage, name: &str) { - let dir = - std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../target/ui-snapshots"); - std::fs::create_dir_all(&dir).expect("create snapshot dir"); - let path = dir.join(name); - img.save(&path).expect("write png"); - eprintln!("wrote {}", path.display()); + #[test] + fn the_toolbar_fits_every_width_between_the_minimum_and_full_hd() { + for w in [640.0, 800.0, 1039.0, 1040.0, 1100.0, 1299.0, 1300.0, 1920.0] { + let h = harness(Vec2::new(w, 600.0)); + assert_no_overlap(&bar_control_rects(&h)); + crate::app::snapshots::assert_fits(&h, w); + } } #[test] - #[ignore = "renders with a GPU; run on demand to review the UI"] - fn stream_toolbar_snapshot_min_width() { - let mut h = egui_kittest::Harness::builder() - .wgpu() - .with_size(Vec2::new(640.0, 420.0)) - .with_pixels_per_point(1.0) - .with_max_steps(2) - .build_state( - paint, - Fixture { - view: view_with_bar(), - live: None, - cfg: ClientConfig::default(), - fullscreen: false, - applied: false, - }, - ); - h.run_steps(3); - save_snapshot(h.render().unwrap(), "client-stream-toolbar-640.png"); + fn a_hidden_toolbar_puts_nothing_over_the_picture() { + let h = harness_with( + Vec2::new(1280.0, 800.0), + 1.0, + false, + fixture(View::default(), Extra::default()), + ); + assert!(h.query_by_label("Disconnect").is_none()); + assert!(h.query_by_label("Stream settings").is_none()); + } + + #[test] + fn the_settings_panel_applies_or_cancels() { + let mut view = view_with_bar(); + view.settings = Some(ClientConfig::default().stream); + let mut h = harness_with( + Vec2::new(640.0, 420.0), + 1.0, + false, + fixture(view, Extra::default()), + ); + assert!(h.query_by_label("Apply and reconnect").is_some()); + crate::app::snapshots::assert_fits(&h, 640.0); + h.get_by_label("Close Stream settings").click(); + h.run_steps(2); + assert!(h.state().view.settings.is_none()); } #[test] #[ignore = "renders with a GPU; run on demand to review the UI"] - fn stream_toolbar_snapshot_normal_width() { - let mut h = egui_kittest::Harness::builder() - .wgpu() - .with_size(Vec2::new(1400.0, 860.0)) - .with_pixels_per_point(1.0) - .with_max_steps(2) - .build_state( - paint, - Fixture { - view: view_with_bar(), - live: None, - cfg: ClientConfig::default(), - fullscreen: false, - applied: false, - }, + fn snapshots_stream_overlay() { + use crate::app::snapshots::{file_name, save, MATRIX, PAIR}; + let windows = || Extra { + os: "windows".into(), + ..Default::default() + }; + for &(size, ppp) in &MATRIX { + let mut h = harness_with(size, ppp, true, fixture(view_with_bar(), windows())); + save( + h.render().unwrap(), + &file_name("stream", "toolbar", size, ppp), ); - h.run_steps(3); - save_snapshot(h.render().unwrap(), "client-stream-toolbar.png"); + } + let shots: Vec<(&str, View, Extra, Option<&str>)> = vec![ + ("waiting", View::default(), windows(), None), + ( + "hint", + View { + hint_since: Some(Instant::now()), + ..View::default() + }, + windows(), + None, + ), + ("more-open", view_with_bar(), windows(), Some("More")), + ("keys-open", view_with_bar(), windows(), Some("Keys")), + ("mouse-open", view_with_bar(), windows(), Some("Mouse")), + ( + "power-open", + view_with_bar(), + Extra { + old_host: Some((Version::new(3, 0, 0), Some(Version::new(4, 0, 2)))), + ..windows() + }, + Some("PC"), + ), + ( + "confirm-restart", + View { + confirm: Some(PowerAction::Restart), + ..view_with_bar() + }, + windows(), + None, + ), + ( + "install-host", + View { + confirm_install: true, + ..view_with_bar() + }, + Extra { + old_host: Some((Version::new(3, 0, 0), Some(Version::new(4, 0, 2)))), + ..windows() + }, + None, + ), + ( + "settings", + View { + settings: Some(ClientConfig::default().stream), + ..View::default() + }, + windows(), + None, + ), + ( + "stats", + View { + stats: true, + ..view_with_bar() + }, + windows(), + None, + ), + ( + "toasts", + { + let mut v = View::default(); + v.toast(Tone::Warning, "Gaming-PC has no sound to send: it reports “Unable to initialize audio capture”. Its Sharing page in BroLink says more."); + v.set_poor(true); + v + }, + Extra { + handover: Some("Installing BroLink Host 4.0.2 on Gaming-PC: waiting for it to fetch the file…".into()), + ..windows() + }, + None, + ), + ]; + for (name, view, extra, open) in shots { + let sizes: &[(Vec2, f32)] = if name == "more-open" { + &[ + (Vec2::new(640.0, 420.0), 2.0), + (Vec2::new(960.0, 640.0), 2.0), + ] + } else if name == "settings" { + &PAIR + } else { + &[(Vec2::new(1280.0, 800.0), 2.0)] + }; + for &(size, ppp) in sizes { + let view = View { + settings: view.settings.clone(), + stats: view.stats, + bar_shown: view.bar_shown, + confirm: view.confirm, + confirm_install: view.confirm_install, + hint_since: view.hint_since, + toasts: view + .toasts + .iter() + .map(|t| Toast { + tone: t.tone, + text: t.text.clone(), + at: t.at, + }) + .collect(), + poor: view.poor, + poor_hinted: view.poor_hinted, + ..View::default() + }; + let mut h = harness_with(size, ppp, true, fixture(view, extra.clone())); + if let Some(label) = open { + h.get_by_label(label).simulate_click(); + h.run_steps(3); + } + save(h.render().unwrap(), &file_name("stream", name, size, ppp)); + } + } } } diff --git a/crates/host/src/app.rs b/crates/host/src/app.rs index 21820de..43d0094 100644 --- a/crates/host/src/app.rs +++ b/crates/host/src/app.rs @@ -1,4 +1,7 @@ -//! The host control panel: a status window with one setup button. +//! The Sharing page: setup for sharing this machine, what it reports, the +//! devices paired with it, its options and diagnostics. The unified window +//! shows it as a tab (see `product.rs`); background threads keep +//! [`Shared`] current and the page reads it once per frame. use crate::config::{self, HostConfig}; use crate::setup; @@ -6,7 +9,7 @@ use crate::streamer::Api; use brolink_core::api::Status; use brolink_core::http; use brolink_core::CONTROL_PORT; -use brolink_ui::{self as ui, Tone, PALETTE as P}; +use brolink_ui::{self as ui, space, Kv, Tone}; use eframe::egui; use parking_lot::Mutex; use semver::Version; @@ -14,7 +17,8 @@ use std::cmp::Ordering; use std::sync::Arc; use std::time::{Duration, Instant}; -const COLUMN_WIDTH: f32 = 680.0; +/// How long the result of an action (an unpair, a driver install) stays. +const NOTE_FOR: Duration = Duration::from_secs(10); /// What the background threads know, read by the UI every frame. #[derive(Default)] @@ -26,6 +30,53 @@ pub struct Shared { pub setup_running: bool, pub setup_result: Option>, pub setup_log: Vec, + /// Moves whenever anything above changes, so a reader can skip copying + /// what it already has. + pub rev: u64, +} + +impl Shared { + fn touch(&mut self) { + self.rev = self.rev.wrapping_add(1); + } +} + +/// A one-line result from a worker thread. +type Note = Arc>>; + +/// Which OS the page describes. Compiled in for the running app; set by +/// tests to render the other platforms' pages. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Os { + Windows, + Mac, + Linux, +} + +impl Os { + pub const HERE: Os = if cfg!(windows) { + Os::Windows + } else if cfg!(target_os = "macos") { + Os::Mac + } else { + Os::Linux + }; + + fn noun(self) -> &'static str { + match self { + Os::Windows => "PC", + Os::Mac => "Mac", + Os::Linux => "machine", + } + } + + fn tailscale_url(self) -> &'static str { + match self { + Os::Windows => "https://tailscale.com/download/windows", + Os::Mac => "https://tailscale.com/download/mac", + Os::Linux => "https://tailscale.com/download/linux", + } + } } pub struct HostApp { @@ -33,121 +84,132 @@ pub struct HostApp { cfg: HostConfig, autostart: bool, dirty: bool, - brand: ui::Brand, confirm_unpair: Option, - notices_open: bool, - settings_open: bool, busy_since: Option, /// The engine archive sits beside the exe, so setup needs no download. bundled_engine: bool, + /// Actions reach the system: setup runs, devices unpair, the service + /// stops. Off when the page is only rendered, in tests. + live: bool, + os: Os, + note: Option<(Tone, String, Instant)>, + pending: Option, } impl HostApp { pub fn new(cc: &eframe::CreationContext<'_>) -> Self { let shared = Arc::new(Mutex::new(Shared::default())); spawn_poller(shared.clone(), cc.egui_ctx.clone()); - Self::with_shared(cc, shared) + Self { + cfg: HostConfig::load(), + autostart: setup::starts_with_windows(), + bundled_engine: std::env::current_exe().is_ok_and(|e| setup::bundled_engine(&e)), + live: true, + ..Self::headless(shared, Os::HERE) + } } - fn with_shared(cc: &eframe::CreationContext<'_>, shared: Arc>) -> Self { - ui::apply(&cc.egui_ctx); + /// A page that only draws: default settings, no threads, and buttons + /// that change what it shows but never touch the system. + #[doc(hidden)] + pub fn headless(shared: Arc>, os: Os) -> Self { Self { shared, - cfg: HostConfig::load(), - autostart: setup::starts_with_windows(), + cfg: HostConfig::default(), + autostart: true, dirty: false, - brand: ui::Brand::new(&cc.egui_ctx), confirm_unpair: None, - notices_open: false, - settings_open: false, busy_since: None, - bundled_engine: std::env::current_exe().is_ok_and(|e| setup::bundled_engine(&e)), + bundled_engine: os == Os::Windows, + live: false, + os, + note: None, + pending: None, } } fn commit(&mut self) { if self.dirty { self.dirty = false; + if !self.live { + return; + } if let Err(e) = self.cfg.save() { tracing::warn!("could not save host config: {e:#}"); + self.note = Some(( + Tone::Danger, + format!("Couldn't save this setting: {e}."), + Instant::now(), + )); } } } - fn api(&self) -> Option> { - self.cfg.has_creds().then_some(Api { - user: &self.cfg.sunshine_user, - pass: &self.cfg.sunshine_pass, - }) - } - pub(crate) fn shared(&self) -> parking_lot::MutexGuard<'_, Shared> { self.shared.lock() } - pub(crate) fn request_setup(&mut self) { - let status = self.shared.lock().status.clone(); - if let Some(s) = status { - self.start_setup(&s); - } else { - crate::service::ensure_service_running(); - self.shared.lock().setup_result = Some(Err( - "The sharing service is starting. Wait for its status, then try setup again." - .into(), - )); - } - } - - pub(crate) fn apply_share_toggles( + /// Run `job` off the UI thread and show what it returns under the + /// page's header. + fn later( &mut self, - power: Option, - stay_awake: Option, - autostart: Option, + ctx: &egui::Context, + job: impl FnOnce() -> (Tone, String) + Send + 'static, ) { - if let Some(v) = power { - self.cfg.power_allowed = v; - self.dirty = true; - } - if let Some(v) = stay_awake { - self.cfg.stay_awake = v; - self.dirty = true; + let note: Note = Arc::default(); + let out = note.clone(); + let ctx = ctx.clone(); + std::thread::spawn(move || { + *out.lock() = Some(job()); + ctx.request_repaint(); + }); + self.pending = Some(note); + } + + fn poll_note(&mut self, ctx: &egui::Context) { + if let Some(done) = self.pending.as_ref().and_then(|n| n.lock().take()) { + self.note = Some((done.0, done.1, Instant::now())); + self.pending = None; } - if let Some(v) = autostart { - if let Ok(exe) = std::env::current_exe() { - if crate::setup::set_start_with_windows(v, &exe).is_ok() { - self.autostart = v; - self.cfg.start_with_windows = v; - self.dirty = true; - } + if let Some((_, _, at)) = &self.note { + match NOTE_FOR.checked_sub(at.elapsed()) { + Some(left) => ctx.request_repaint_after(left), + None => self.note = None, } } - self.commit(); } - pub(crate) fn publish_share(&self, local: &brolink_client::share::Slot) { - let mut g = local.lock(); - g.power_allowed = self.cfg.power_allowed; - g.stay_awake = self.cfg.stay_awake; - g.autostart = self.autostart; + /// The engine's login, for a worker thread. + fn creds(&self) -> Option<(String, String)> { + self.cfg.has_creds().then(|| { + ( + self.cfg.sunshine_user.clone(), + self.cfg.sunshine_pass.clone(), + ) + }) } /// Generate a Sunshine login if there is none, save it, and run the - /// elevated script on a thread. + /// setup on a thread. fn start_setup(&mut self, status: &Status) { let migrate = crate::migrate::uses_old_engine(&status.streamer.kind); - if !self.cfg.has_creds() && !migrate { - self.cfg.sunshine_user = "brolink".into(); - self.cfg.sunshine_pass = config::random_password(); - if let Err(e) = self.cfg.save() { - tracing::warn!("could not save engine login: {e:#}"); - } - } let shared = self.shared.clone(); { let mut s = shared.lock(); s.setup_running = true; s.setup_result = None; s.setup_log.clear(); + s.touch(); + } + if !self.live { + return; + } + if !self.cfg.has_creds() && !migrate { + self.cfg.sunshine_user = "brolink".into(); + self.cfg.sunshine_pass = config::random_password(); + if let Err(e) = self.cfg.save() { + tracing::warn!("could not save engine login: {e:#}"); + } } let cfg = self.cfg.clone(); let install_engine = !status.streamer.installed || migrate || !status.streamer.running; @@ -168,8 +230,8 @@ impl HostApp { adapter_description: &desc, }) .map_err(|e| e.to_string())?; - // The service is what the Mac needs at logon; turn it on - // once setup has succeeded rather than asking. + // The service is what other machines need at logon; turn + // it on once setup has succeeded rather than asking. let _ = setup::set_start_with_windows(true, &exe); Ok(()) }); @@ -177,13 +239,95 @@ impl HostApp { s.setup_running = false; s.setup_log = read_setup_log(); s.setup_result = Some(result); + s.touch(); }); } + + fn set_autostart(&mut self, on: bool) { + if !self.live { + self.autostart = on; + return; + } + let result = std::env::current_exe() + .map_err(anyhow::Error::from) + .and_then(|exe| setup::set_start_with_windows(on, &exe)); + match result { + Ok(()) => { + self.autostart = on; + self.cfg.start_with_windows = on; + self.dirty = true; + } + Err(e) => { + tracing::warn!("autostart: {e:#}"); + self.note = Some(( + Tone::Danger, + format!("Couldn't change whether BroLink starts at login: {e}."), + Instant::now(), + )); + } + } + } + + fn stop_service(&mut self) { + if self.live { + // Off the UI thread: the service may take a moment to answer. + std::thread::spawn(|| { + let _ = http::request( + ("127.0.0.1", CONTROL_PORT), + "POST", + "/v1/quit", + None, + Duration::from_secs(2), + ); + }); + } + let mut s = self.shared.lock(); + s.status = None; + s.service_error = Some(STOPPED.into()); + s.touch(); + } + + fn start_service(&mut self) { + { + let mut s = self.shared.lock(); + s.service_error = None; + s.touch(); + } + if self.live { + std::thread::spawn(crate::service::ensure_service_running); + } + } + + fn open_log_folder(&mut self) { + if !self.live { + return; + } + let Ok(dir) = brolink_core::config::data_dir() else { + return; + }; + let opener = match self.os { + Os::Windows => "explorer", + Os::Mac => "open", + Os::Linux => "xdg-open", + }; + if let Err(e) = std::process::Command::new(opener).arg(&dir).spawn() { + self.note = Some(( + Tone::Danger, + format!("Couldn't open {}: {e}.", dir.display()), + Instant::now(), + )); + } + } } -impl eframe::App for HostApp { - fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - ctx.request_repaint_after(Duration::from_millis(500)); +/// What the service error says after the user stopped it from here. +const STOPPED: &str = "Stopped from this window."; + +impl HostApp { + /// Draw the page into `ui`, a column the window has already laid out. + pub fn page(&mut self, ui: &mut egui::Ui) { + let ctx = ui.ctx().clone(); + self.poll_note(&ctx); let shared = self.shared.lock(); let status = shared.status.clone(); let service_error = shared.service_error.clone(); @@ -195,101 +339,90 @@ impl eframe::App for HostApp { drop(shared); if setup_running { self.busy_since.get_or_insert_with(Instant::now); + // The seconds counter under the spinner. + ctx.request_repaint_after(Duration::from_secs(1)); } else { self.busy_since = None; } - ui::top_bar(ctx, "top", |ui| { - let (label, tone) = pill(status.as_ref(), service_error.as_deref()); - self.brand.header(ui, "BroLink Host", |ui| { - if ui::ghost_button( - ui, - if self.settings_open { - "Overview" - } else { - "Settings" - }, - ) - .clicked() - { - self.settings_open = !self.settings_open; - } - ui::status_pill(ui, label, tone); - }); + let Some(s) = status else { + self.no_service(ui, service_error.as_deref()); + return; + }; + let (label, tone) = pill(Some(&s), None); + ui::page_header(ui, &s.name, Some(&self.summary(&s)), |ui| { + ui::status_text(ui, tone, label); }); + if let Some((tone, text, _)) = self.note.clone() { + ui::notice(ui, tone, &text); + } + if s.version != env!("CARGO_PKG_VERSION") { + ui::notice( + ui, + Tone::Neutral, + &format!( + "The background service runs BroLink {} and this window {}; BroLink is restarting the older one.", + s.version, + env!("CARGO_PKG_VERSION") + ), + ); + } + self.setup_card(ui, &s, setup_running, setup_result.as_ref(), &setup_log); + self.overview(ui, &s, gamepad); + self.paired(ui, &clients); + self.options(ui); + self.diagnostics(ui, &s, setup_running); + self.commit(); + } - ui::bottom_bar(ctx, "bottom", |ui| { - ui.horizontal(|ui| { - ui.label(format!("v{}", env!("CARGO_PKG_VERSION"))); - ui.label("·"); - ui.label(format!("control port TCP {CONTROL_PORT}")); - if let Some(s) = &status { - if s.version != env!("CARGO_PKG_VERSION") { - ui.label("·"); - ui.colored_label( - P.accent, - format!("service is v{}, restarting it", s.version), - ); + /// One sentence under the machine's name: can others connect? + fn summary(&self, s: &Status) -> String { + let noun = self.os.noun(); + match pill(Some(s), None).0 { + "Tailscale off" => format!( + "Tailscale isn't running on this {noun}, so no other machine can reach it." + ), + "Needs setup" => format!( + "Other machines can see this {noun} but can't connect until setup finishes." + ), + _ => format!( + "Shared. Any machine signed in to your Tailscale account can connect to this {noun}." + ), + } + } + + fn no_service(&mut self, ui: &mut egui::Ui, error: Option<&str>) { + ui::page_header(ui, "Sharing", None, |_| {}); + if error == Some(STOPPED) { + ui::banner( + ui, + Tone::Neutral, + "The background service is stopped", + Some(&format!( + "Other machines can't connect to this {} until it runs again. It also starts the next time this window opens.", + self.os.noun() + )), + |ui| { + if ui::primary_button(ui, "Start it now").clicked() { + self.start_service(); } + }, + ); + } else { + ui::card(ui, |ui| { + ui::heading(ui, "Starting the background service", None); + ui::empty_state( + ui, + "It shares this machine and answers the others. This takes a second or two.", + true, + ); + if let Some(e) = error { + ui::small_print(ui, format!("Last attempt: {e}")); } }); - }); - - egui::CentralPanel::default() - .frame(egui::Frame::new().fill(P.bg)) - .show(ctx, |ui| { - egui::ScrollArea::vertical().show(ui, |ui| { - ui.add_space(20.0); - ui::content_column(ui, COLUMN_WIDTH, |ui| { - ui.spacing_mut().item_spacing.y = 14.0; - match &status { - Some(s) => { - self.setup_card( - ui, - s, - setup_running, - setup_result.as_ref(), - &setup_log, - ); - if self.settings_open { - self.settings_card(ui); - ui::titled_card(ui, "Repair streaming", Some("Reapply display matching and remove stale bitrate limits. Setup preserves paired Macs."), |ui| { - if ui.add_enabled(!setup_running, egui::Button::new("Repair setup")).clicked() { self.start_setup(s); } - }); - } else { - ui.label(egui::RichText::new(&s.name).font(ui::theme::semibold(28.0)).color(P.text)); - ui::caption(ui, "Your desktop is available to every BroLink on this Tailscale account."); - self.pc_card(ui, s, gamepad); - self.paired_card(ui, &clients); - } - ui::collapsible(ui, "host-diagnostics", "Diagnostics", false, |ui| { - ui::log_view(ui, "host_log", &s.log, 200.0); - if ui::ghost_button(ui, "Copy log").clicked() { ui.ctx().copy_text(s.log.join("\n")); } - }); - } - None => { - ui::toned_card(ui, Tone::Neutral, |ui| { - ui::heading(ui, "Starting the background service", None); - ui::empty_state( - ui, - service_error - .as_deref() - .unwrap_or("This takes a second or two."), - true, - ); - }); - } - } - ui.add_space(10.0); - }); - }); - }); - - self.commit(); + } } -} -impl HostApp { fn setup_card( &mut self, ui: &mut egui::Ui, @@ -305,307 +438,468 @@ impl HostApp { .filter(|l| !l.starts_with("Tailscale")) .collect(); let tailscale_item = s.setup.iter().find(|l| l.starts_with("Tailscale")); - if admin_items.is_empty() && tailscale_item.is_none() && result.is_none() && !running { + let done = admin_items.is_empty() && tailscale_item.is_none(); + if done && result.is_none() && !running { return; } - let tone = if admin_items.is_empty() && tailscale_item.is_none() { + let migrate = crate::migrate::uses_old_engine(&s.streamer.kind); + let tone = if done && !matches!(result, Some(Err(_))) { Tone::Success + } else if matches!(result, Some(Err(_))) { + Tone::Danger } else { - Tone::Accent + Tone::Warning }; + let (title, how) = match self.os { + Os::Windows => ( + if migrate { + "Update the streaming engine" + } else { + "Share this PC" + }, + "One administrator prompt does everything listed here. Nothing else needs configuring.", + ), + Os::Mac => ( + "Share this Mac", + "BroLink installs the streaming engine for you. The first time someone connects, macOS asks once for Screen Recording.", + ), + Os::Linux => ( + "Share this machine", + "BroLink installs the streaming engine for this user and keeps it running.", + ), + }; + let title = if done { "Sharing is set up" } else { title }; ui::toned_card(ui, tone, |ui| { - ui::heading( - ui, - "Set up this PC", - Some("One administrator prompt does everything below. Nothing else needs configuring."), - ); + ui.spacing_mut().item_spacing.y = space::SM; + ui::heading(ui, title, (!done).then_some(how)); if let Some(t) = tailscale_item { - ui::notice(ui, Tone::Danger, t); - ui.horizontal(|ui| { - if ui::ghost_button(ui, "Get Tailscale").clicked() { - ui.ctx().open_url(egui::OpenUrl::new_tab( - "https://tailscale.com/download/windows", - )); - } - ui::caption(ui, "Sign in with the same account as your Mac."); - }); + let why = t + .trim_start_matches("Tailscale:") + .trim() + .trim_end_matches("Install it and sign in.") + .trim() + .trim_end_matches('.'); + ui::dot_label( + ui, + Tone::Danger, + &format!( + "{}. Install Tailscale and sign in with the account your other machines use.", + sentence_case(why) + ), + ); + if ui::link(ui, "Get Tailscale").clicked() { + ui.ctx() + .open_url(egui::OpenUrl::new_tab(self.os.tailscale_url())); + } } for item in &admin_items { - ui::dot_label(ui, Tone::Accent, item); + ui::dot_label(ui, Tone::Warning, item); } - if admin_items.is_empty() && tailscale_item.is_none() { - ui::dot_label(ui, Tone::Success, "Everything is in place."); - } - ui.add_space(4.0); - ui.horizontal_wrapped(|ui| { - if running { - ui::empty_state( - ui, - &format!( - "Waiting for the administrator prompt and the setup steps… {}s", - self.busy_since.map(|t| t.elapsed().as_secs()).unwrap_or(0) - ), - true, - ); - } else { - let migrate = crate::migrate::uses_old_engine(&s.streamer.kind); + ui.add_space(space::XS); + if running { + let secs = self.busy_since.map(|t| t.elapsed().as_secs()).unwrap_or(0); + let what = match self.os { + Os::Windows => "Waiting for the administrator prompt and the setup steps", + _ => "Installing and starting the streaming engine", + }; + ui::empty_state(ui, &format!("{what}… {secs} s"), true); + } else if !done || result.is_some() { + ui.horizontal_wrapped(|ui| { + ui.spacing_mut().item_spacing.x = space::MD; let label = if migrate { - "Update this PC" + "Update the engine" } else if admin_items.is_empty() { "Run setup again" + } else if self.os == Os::Windows { + "Set up as administrator" + } else { + "Set up sharing" + }; + let primary = !admin_items.is_empty() || migrate; + let clicked = if primary { + ui::primary_button(ui, label).clicked() } else { - "Set up this PC (administrator)" + ui::secondary_button(ui, label).clicked() }; - if ui::primary_button(ui, label).clicked() { + if clicked { self.start_setup(s); } - if migrate { - ui::caption( - ui, - "One administrator prompt. Keeps this PC's pairing and web login.", - ); + let note = if migrate { + Some("Keeps this PC's pairings and web login.") } else if !s.streamer.installed { - ui::caption( - ui, - if self.bundled_engine { - "Installs the streaming engine that ships with BroLink." - } else { - "Downloads the streaming engine and installs it." - }, - ); + Some(match (self.os, self.bundled_engine) { + (Os::Windows, true) => "Installs the engine that ships with BroLink.", + _ => "Downloads the streaming engine and installs it.", + }) + } else { + None + }; + if let Some(n) = note { + ui::small_print(ui, n); } - } - }); + }); + } match result { Some(Ok(())) => { ui::notice( ui, Tone::Success, - "Setup finished. The service re-checks everything within a few seconds.", + "Setup finished. The list above clears as the service re-checks, within a few seconds.", ); } Some(Err(e)) => { - ui::notice(ui, Tone::Danger, e); + let friendly = setup_error(e, self.os); + ui::banner( + ui, + Tone::Danger, + "Setup didn't finish", + Some(&friendly), + |ui| { + if !friendly.contains(e.trim_end_matches('.')) { + ui.add( + egui::Label::new( + egui::RichText::new(e) + .font(ui::theme::mono(ui::theme::text::MONO - 1.0)) + .color(ui::PALETTE.text_tertiary), + ) + .wrap(), + ); + } + }, + ); } None => {} } if !log.is_empty() { - ui::collapsible( + ui::disclosure( ui, "setup_log", "Setup log", - result.as_ref().is_some_and(|r| r.is_err()), + matches!(result, Some(Err(_))), |ui| { - ui::log_view(ui, "setup_log_view", log, 160.0); + ui::log_view(ui, "setup_log_view", log, 180.0); }, ); } }); } - fn pc_card(&mut self, ui: &mut egui::Ui, s: &Status, gamepad: Option) { - ui::titled_card(ui, "This PC", None, |ui| { - let tailscale = match (&s.tailscale_ip, &s.tailscale_login) { - (Some(ip), Some(login)) => format!("{ip} · signed in as {login}"), - (Some(ip), None) => ip.clone(), - _ => "not running".into(), + fn overview(&mut self, ui: &mut egui::Ui, s: &Status, gamepad: Option) { + let noun = self.os.noun(); + let (ts_tone, tailscale) = match (&s.tailscale_ip, &s.tailscale_login) { + (Some(ip), Some(login)) => (Tone::Success, format!("{ip} · {login}")), + (Some(ip), None) => (Tone::Success, ip.clone()), + _ => (Tone::Danger, "Not running".into()), + }; + let (engine_tone, engine) = if !s.streamer.installed { + (Tone::Warning, "Not installed".to_string()) + } else if !s.streamer.running { + (Tone::Warning, "Installed, not running".to_string()) + } else if !s.streamer.api_ok { + ( + Tone::Warning, + "Running, but BroLink can't sign in to it".to_string(), + ) + } else { + let encoder = match s.streamer.encoder.as_str() { + "" => String::new(), + "software" => { + " · Software encoding: no GPU encoder worked, so streams use the CPU".into() + } + "nvenc" => " · NVIDIA encoder".into(), + "amdvce" => " · AMD encoder".into(), + "quicksync" => " · Intel encoder".into(), + "videotoolbox" => " · Apple encoder".into(), + e => format!(" · {e} encoder"), }; - let streamer = if !s.streamer.installed { - "not installed".to_string() + let tone = if s.streamer.encoder == "software" { + Tone::Warning } else { - format!( - "{} · {}{}{}{}", - s.streamer.kind, - if s.streamer.running { - "running" - } else { - "not running" - }, - if s.streamer.api_ok { - " · BroLink logged in" - } else { - "" - }, - match s.streamer.encoder.as_str() { - "" => String::new(), - "software" => - " · software encoder (no GPU encoder worked: streams will be slow)" - .into(), - e => format!(" · {e} encoder"), - }, - if s.streamer.audio_problem.is_empty() { - String::new() - } else { - format!(" · no sound: {}", s.streamer.audio_problem) - } - ) - }; - let network = match &s.nat { - Some(n) => crate::service::describe_nat(n) - .trim_start_matches("network: ") - .to_string(), - None => "checking…".into(), + Tone::Success }; - let mut wake = match (&s.mac, s.wake_ready) { - (Some(mac), Some(true)) => format!("ready · {} · {mac}", s.wake_adapter), - (Some(mac), Some(false)) => format!("off · {} · {mac}", s.wake_adapter), - (Some(mac), None) => format!("unknown · {} · {mac}", s.wake_adapter), - (None, _) => "no wired adapter found".into(), + (tone, format!("Running{encoder}")) + }; + let network = match &s.nat { + Some(n) => network_sentence(n, noun), + None => "Checking…".into(), + }; + let mut rows = vec![ + Kv::new("Tailscale", tailscale).tone(ts_tone), + Kv::new("Streaming engine", engine).tone(engine_tone), + ]; + if !s.streamer.audio_problem.is_empty() { + rows.push( + Kv::new( + "Sound", + format!( + "None to send: “{}”. Give this {noun} an audio output, even a virtual one.", + s.streamer.audio_problem + ), + ) + .tone(Tone::Warning), + ); + } + rows.push(Kv::new("Network", network)); + if self.os == Os::Windows { + let (tone, mut wake) = match (&s.mac, s.wake_ready) { + (Some(mac), Some(true)) => { + (Tone::Success, format!("Ready · {} · {mac}", s.wake_adapter)) + } + (Some(mac), Some(false)) => { + (Tone::Warning, format!("Off · {} · {mac}", s.wake_adapter)) + } + (Some(mac), None) => ( + Tone::Neutral, + format!("Unknown · {} · {mac}", s.wake_adapter), + ), + (None, _) => (Tone::Neutral, "No wired adapter found".into()), }; if s.fast_startup == Some(true) { - wake.push_str(" · Fast Startup on"); + wake.push_str(" · Fast Startup is on"); } if let Some(age) = s.wake_packet_age_secs { - wake.push_str(&format!(" · packet received {age}s ago")); + wake.push_str(&format!(" · Last wake packet {age} s ago")); } - let gamepad_text = match gamepad { - Some(true) => "virtual controller driver installed", - Some(false) => "virtual controller driver missing", - None => "unknown until the streaming engine is running", + rows.push(Kv::new("Wake-on-LAN", wake).tone(tone)); + } + rows.push(Kv::new( + "LAN address", + s.lan_ip.clone().unwrap_or_else(|| "None".into()), + )); + if self.os == Os::Windows { + let (tone, text) = match gamepad { + Some(true) => (Tone::Success, "Virtual controller driver installed"), + Some(false) => (Tone::Warning, "Virtual controller driver missing"), + None => (Tone::Neutral, "Known once the streaming engine runs"), }; - ui::kv_grid( - ui, - &[ - ("Name", s.name.clone()), - ("Tailscale", tailscale), - ("Network", network), - ("Streaming", streamer), - ("Wake-on-LAN", wake), + rows.push(Kv::new("Game controllers", text).tone(tone)); + } + let heading = format!("This {noun}"); + ui::section(ui, &heading, |ui| { + ui.add_space(space::MD); + ui::kv_grid(ui, &rows); + if self.os == Os::Windows && gamepad == Some(false) { + ui.add_space(space::SM); + if ui::secondary_button(ui, "Install controller driver").clicked() { + self.install_gamepad_driver(ui.ctx()); + } + } + ui.add_space(space::MD); + }); + } + + fn install_gamepad_driver(&mut self, ctx: &egui::Context) { + let Some((user, pass)) = self.creds().filter(|_| self.live) else { + return; + }; + self.later(ctx, move || { + let api = Api { + user: &user, + pass: &pass, + }; + match api.install_gamepad_driver() { + Ok(()) => ( + Tone::Success, + "The controller driver is installing. Windows may ask to confirm.".into(), + ), + Err(e) => { + tracing::warn!("ViGEmBus install: {e:#}"); ( - "LAN address", - s.lan_ip.clone().unwrap_or_else(|| "—".into()), - ), - ("Gamepads", gamepad_text.into()), - ], - ); - ui.add_space(4.0); - ui.horizontal(|ui| { - if gamepad == Some(false) - && ui::ghost_button(ui, "Install controller driver").clicked() - { - if let Some(api) = self.api() { - if let Err(e) = api.install_gamepad_driver() { - tracing::warn!("ViGEmBus install: {e:#}"); - } - } + Tone::Danger, + format!("Couldn't install the controller driver: {e}."), + ) } - }); + } }); } - fn paired_card(&mut self, ui: &mut egui::Ui, clients: &[(String, String)]) { - ui::titled_card( - ui, - "Paired Macs", - Some("A Mac pairs itself the first time it connects; nothing to type here."), - |ui| { - if clients.is_empty() { - ui::empty_state(ui, "No Mac has paired yet.", false); + fn paired(&mut self, ui: &mut egui::Ui, clients: &[(String, String)]) { + ui::section(ui, "Paired devices", |ui| { + if clients.is_empty() { + ui.add_space(space::MD); + ui::muted(ui, "None yet. A machine pairs itself the first time it connects; nothing needs typing here."); + ui.add_space(space::MD); + return; + } + for (i, (name, uuid)) in clients.iter().enumerate() { + if i > 0 { + ui::row_separator(ui); } - for (i, (name, uuid)) in clients.iter().enumerate() { - if i > 0 { - ui::row_separator(ui); - } - ui::list_row(ui, name, &uuid[..uuid.len().min(8)], |ui| { - if self.confirm_unpair.as_deref() == Some(uuid) { - if ui::toned_button(ui, "Unpair", Tone::Danger).clicked() { - if let Some(api) = self.api() { - if let Err(e) = api.unpair(uuid) { - tracing::warn!("unpair: {e:#}"); - } - } - self.confirm_unpair = None; - } - if ui::ghost_button(ui, "Keep").clicked() { - self.confirm_unpair = None; - } - } else if ui::danger_button(ui, "Unpair…").clicked() { - self.confirm_unpair = Some(uuid.clone()); + let id = format!("ID {}", &uuid[..uuid.len().min(8)]); + ui::list_row(ui, None, name, &id, |ui| { + if self.confirm_unpair.as_deref() == Some(uuid) { + if ui::destructive_button(ui, "Unpair").clicked() { + self.unpair(ui.ctx(), name, uuid); + self.confirm_unpair = None; + } + if ui::ghost_button(ui, "Keep").clicked() { + self.confirm_unpair = None; } - }); + } else if ui::danger_button(ui, "Unpair…") + .on_hover_text("It pairs again by itself the next time it connects.") + .clicked() + { + self.confirm_unpair = Some(uuid.clone()); + } + }); + } + }); + } + + fn unpair(&mut self, ctx: &egui::Context, name: &str, uuid: &str) { + let Some((user, pass)) = self.creds().filter(|_| self.live) else { + return; + }; + let (name, uuid) = (name.to_string(), uuid.to_string()); + self.later(ctx, move || { + let api = Api { + user: &user, + pass: &pass, + }; + match api.unpair(&uuid) { + Ok(()) => ( + Tone::Success, + format!("Unpaired {name}. It pairs again the next time it connects."), + ), + Err(e) => { + tracing::warn!("unpair: {e:#}"); + (Tone::Danger, format!("Couldn't unpair {name}: {e}.")) } - }, - ); + } + }); } - fn settings_card(&mut self, ui: &mut egui::Ui) { - ui::titled_card(ui, "Settings", None, |ui| { + fn options(&mut self, ui: &mut egui::Ui) { + let noun = self.os.noun(); + ui::section(ui, "Options", |ui| { if ui::toggle_row( ui, &mut self.cfg.power_allowed, - "Let a paired Mac sleep, restart, or shut down this PC", - Some("Devices allowed by your Tailscale access rules can ask. Waking a sleeping PC requires access to its local network."), + &format!("Let other machines sleep, restart or shut down this {noun}"), + Some(&format!("Any machine your Tailscale access rules allow can ask. Waking this {noun} works only from its own network.")), ) { self.dirty = true; } + if self.os == Os::Windows { + ui::row_separator(ui); + if ui::toggle_row( + ui, + &mut self.cfg.stay_awake, + "Keep this PC awake while plugged in", + Some("Asleep, Tailscale is off too, so machines on other networks can't wake it. Sleep from the Start menu or another machine still works."), + ) { + self.dirty = true; + } + } ui::row_separator(ui); let mut auto = self.autostart; if ui::toggle_row( ui, - &mut self.cfg.stay_awake, - "Keep this PC awake while plugged in", - Some("Tailscale only works while the PC is on. Asleep, a Mac on another network cannot wake it. Sleep from the Mac or the Start menu still works."), + &mut auto, + "Start BroLink when you log in", + Some(&format!( + "So others can connect after this {noun} restarts, with nobody at the keyboard." + )), ) { - self.dirty = true; + self.set_autostart(auto); } + }); + } + + fn diagnostics(&mut self, ui: &mut egui::Ui, s: &Status, setup_running: bool) { + ui::section(ui, "Diagnostics", |ui| { + ui.add_space(space::XS); + ui::disclosure(ui, "host-diagnostics", "Service log", false, |ui| { + ui::log_view(ui, "host_log", &s.log, 220.0); + ui.add_space(space::XS); + if ui::secondary_button(ui, "Copy log").clicked() { + ui.ctx().copy_text(s.log.join("\n")); + } + }); ui::row_separator(ui); - if ui::toggle_row( + ui::setting_row( ui, - &mut auto, - "Start the background service with Windows", - Some("On by default, so the Mac can reach this PC after every restart without anyone at the keyboard."), - ) { - if let Ok(exe) = std::env::current_exe() { - match setup::set_start_with_windows(auto, &exe) { - Ok(()) => { - self.autostart = auto; - self.cfg.start_with_windows = auto; - self.dirty = true; - } - Err(e) => tracing::warn!("autostart: {e:#}"), + "Repair sharing", + Some("Runs setup again: reapplies display matching and clears stale bitrate limits. Paired devices stay paired."), + |ui| { + let clicked = ui + .add_enabled_ui(!setup_running, |ui| { + ui::secondary_button(ui, "Run setup again") + }) + .inner + .clicked(); + if clicked { + self.start_setup(s); } - } - } + }, + ); ui::row_separator(ui); ui::setting_row( ui, - "Updates", - Some("New versions of BroLink Host arrive from your Mac over Tailscale and install by themselves; nothing to do here."), + "Log files", + Some(&format!( + "Control port TCP {CONTROL_PORT}. The service and window logs roll over at 4 MB." + )), |ui| { - ui::muted(ui, format!("v{}", env!("CARGO_PKG_VERSION"))); + if ui::secondary_button(ui, "Open folder").clicked() { + self.open_log_folder(); + } }, ); ui::row_separator(ui); - ui::open_source_row(ui, &mut self.notices_open); - ui::row_separator(ui); - ui.horizontal(|ui| { - if ui::danger_button(ui, "Stop the background service").clicked() { - // Off the UI thread: the service may take a moment to - // answer, and the window must not freeze meanwhile. - std::thread::spawn(|| { - let _ = http::request( - ("127.0.0.1", CONTROL_PORT), - "POST", - "/v1/quit", - None, - Duration::from_secs(2), - ); - }); - let mut s = self.shared.lock(); - s.status = None; - s.service_error = - Some("Stopped. It starts again next time this window opens.".into()); - } - if ui::ghost_button(ui, "Open log folder").clicked() { - if let Ok(dir) = brolink_core::config::data_dir() { - let _ = std::process::Command::new("explorer").arg(dir).spawn(); + ui::setting_row( + ui, + "Background service", + Some("Stopping it stops sharing until this window opens again."), + |ui| { + if ui::danger_button(ui, "Stop service").clicked() { + self.stop_service(); } - } - }); + }, + ); }); } } +/// What this machine's NAT means for the others, in a sentence that names +/// the right kind of machine (the service log's version says "a Mac"). +fn network_sentence(n: &brolink_core::api::NatReport, noun: &str) -> String { + let city = brolink_core::tailscale::derp_city(&n.derp); + if !n.udp { + format!("UDP is blocked here, so other machines reach this {noun} only through a Tailscale relay.") + } else if n.hard == Some(true) && !n.portmap { + format!("Hard NAT without UPnP: machines on other networks reach this {noun} through the {city} relay, unless the router gets UPnP or a forwarded UDP port.") + } else if n.hard == Some(true) { + "Hard NAT, but the router maps ports, so direct connections should work.".into() + } else if n.hard == Some(false) { + format!("Easy NAT: direct connections should work. Nearest relay: {city}.") + } else { + format!("NAT type unknown. Nearest relay: {city}.") + } +} + +/// A setup failure in words: what happened, then what to do. +fn setup_error(e: &str, os: Os) -> String { + let lower = e.to_lowercase(); + if os == Os::Windows && lower.contains("declined") { + return "The administrator prompt was declined, or setup stopped part way. Try again and choose Yes; the setup log below shows the last step reached.".into(); + } + format!( + "{}. The setup log below shows the last step reached; run setup again once it is fixed.", + sentence_case(e.trim_end_matches('.')) + ) +} + +/// Capitalise the first letter. +fn sentence_case(s: &str) -> String { + let mut c = s.chars(); + match c.next() { + Some(f) => f.to_uppercase().collect::() + c.as_str(), + None => String::new(), + } +} + +/// The page's status in two words, and its tone. fn pill(status: Option<&Status>, err: Option<&str>) -> (&'static str, Tone) { match status { None if err.is_some() => ("Service stopped", Tone::Neutral), @@ -613,8 +907,8 @@ fn pill(status: Option<&Status>, err: Option<&str>) -> (&'static str, Tone) { Some(s) if s.setup.iter().any(|l| l.starts_with("Tailscale")) => { ("Tailscale off", Tone::Danger) } - Some(s) if !s.setup.is_empty() => ("Needs setup", Tone::Accent), - Some(_) => ("Ready", Tone::Success), + Some(s) if !s.setup.is_empty() => ("Needs setup", Tone::Warning), + Some(_) => ("Shared", Tone::Success), } } @@ -685,13 +979,17 @@ fn relaunch_this_exe() -> bool { } /// Poll the service every second; refresh Sunshine's client list now and -/// then; restart the service after an update. +/// then; restart the service after an update. The window repaints only +/// when an answer differs from the last. fn spawn_poller(shared: Arc>, ctx: egui::Context) { std::thread::spawn(move || { let mut failures = 0u32; let mut tick = 0u64; let mut restarted_service = false; loop { + // Repaint only when something the page shows has changed: an + // idle window draws nothing. + let mut changed = false; let r: Result = http::get_json( ("127.0.0.1", CONTROL_PORT), "/v1/status", @@ -731,28 +1029,28 @@ fn spawn_poller(shared: Arc>, ctx: egui::Context) { let clients = api.clients(); let gamepad = api.gamepad_driver(); let mut s = shared.lock(); + changed |= s.clients != clients || s.gamepad_driver != gamepad; s.clients = clients; s.gamepad_driver = gamepad; } let mut s = shared.lock(); + changed |= s.status.as_ref() != Some(&st); s.status = Some(st); - if s.service_error - .as_deref() - .is_some_and(|e| !e.starts_with("Stopped")) - { + if s.service_error.as_deref().is_some_and(|e| e != STOPPED) { s.service_error = None; + changed = true; } } Err(e) => { failures += 1; let mut s = shared.lock(); - let stopped_by_user = s - .service_error - .as_deref() - .is_some_and(|e| e.starts_with("Stopped")); + let stopped_by_user = s.service_error.as_deref() == Some(STOPPED); + changed |= s.status.is_some(); s.status = None; if !stopped_by_user { - s.service_error = Some(format!("{e}")); + let e = format!("{e}"); + changed |= s.service_error.as_deref() != Some(e.as_str()); + s.service_error = Some(e); } drop(s); if failures % 4 == 2 && !stopped_by_user { @@ -760,7 +1058,10 @@ fn spawn_poller(shared: Arc>, ctx: egui::Context) { } } } - ctx.request_repaint(); + if changed { + shared.lock().touch(); + ctx.request_repaint(); + } tick += 1; std::thread::sleep(Duration::from_secs(1)); } @@ -803,24 +1104,47 @@ mod tests { fn pill_reflects_setup_state() { assert_eq!(pill(None, None).0, "Starting"); let mut s = Status::default(); - assert_eq!(pill(Some(&s), None).0, "Ready"); + assert_eq!(pill(Some(&s), None), ("Shared", Tone::Success)); s.setup .push("The streaming engine is not installed.".into()); - assert_eq!(pill(Some(&s), None).0, "Needs setup"); + assert_eq!(pill(Some(&s), None), ("Needs setup", Tone::Warning)); s.setup.push("Tailscale: not running".into()); - assert_eq!(pill(Some(&s), None).0, "Tailscale off"); + assert_eq!(pill(Some(&s), None), ("Tailscale off", Tone::Danger)); + } + + #[test] + fn a_declined_prompt_says_what_to_do() { + let e = setup_error( + "the administrator prompt was declined or setup failed", + Os::Windows, + ); + assert!(e.contains("choose Yes"), "{e}"); + assert!(!e.contains("setup failed"), "no echo of the raw error: {e}"); + let e = setup_error("curl: (6) Could not resolve host", Os::Mac); + assert!(e.starts_with("Curl"), "{e}"); + assert!(e.contains("run setup again"), "{e}"); } } -/// Render the panel to PNGs for review without a PC: +/// The Sharing page inside the unified window, rendered without a PC: /// /// ```text /// cargo test -p brolink-host snapshots -- --ignored /// ``` +/// +/// Nothing here reads or writes real settings or reaches the service: the +/// window and the page are both built headless. #[cfg(test)] mod snapshots { use super::*; + use crate::product::NodeApp; + use brolink_client::app::Page; use brolink_core::api::Streamer; + use egui_kittest::kittest::Queryable; + + const MIN: egui::Vec2 = egui::vec2(640.0, 420.0); + const TYPICAL: egui::Vec2 = egui::vec2(1280.0, 800.0); + const LARGE: egui::Vec2 = egui::vec2(1920.0, 1200.0); fn out_dir() -> std::path::PathBuf { let dir = @@ -835,12 +1159,16 @@ mod snapshots { eprintln!("wrote {}", path.display()); } - fn ready_status() -> Status { + fn ready_status(os: &str) -> Status { Status { app: "brolink".into(), version: env!("CARGO_PKG_VERSION").into(), - name: "GAMING-PC".into(), - os: "windows".into(), + name: if os == "windows" { + "GAMING-PC".into() + } else { + "MacBook-Pro".into() + }, + os: os.into(), tailscale_ip: Some("100.64.0.10".into()), tailscale_login: Some("user@example.com".into()), lan_ip: Some("192.168.1.10".into()), @@ -855,7 +1183,11 @@ mod snapshots { installed: true, running: true, api_ok: true, - encoder: "nvenc".into(), + encoder: if os == "windows" { + "nvenc".into() + } else { + "videotoolbox".into() + }, audio_problem: String::new(), }, power_allowed: true, @@ -869,42 +1201,30 @@ mod snapshots { }), setup: vec![], log: vec![ - "BroLink Host 3.0.1 listening on TCP 47850".into(), + "BroLink 4.0.2 listening on TCP 47850".into(), "listening for wake packets on UDP 9".into(), "Tailscale up as user@example.com (100.64.0.10)".into(), - "BroLink is running and BroLink is logged in".into(), + "the streaming engine is running and BroLink is signed in".into(), "Wake-on-LAN ready on Ethernet (02:00:00:00:00:01)".into(), - "example-mac (user@example.com) asked".into(), - "paired \"Example Mac\"".into(), + "paired \"MacBook-Pro\"".into(), ], } } - fn build(shared: Shared) -> egui_kittest::Harness<'static, HostApp> { - build_sized(shared, egui::vec2(720.0, 1500.0), 2.0, true) - } - - fn build_sized( - shared: Shared, - size: egui::Vec2, - ppp: f32, - gpu: bool, - ) -> egui_kittest::Harness<'static, HostApp> { - let shared = Arc::new(Mutex::new(shared)); - let mut builder = egui_kittest::Harness::builder() - .with_size(size) - .with_pixels_per_point(ppp) - .with_max_steps(8); - if gpu { - builder = builder.wgpu(); + fn ready() -> Shared { + Shared { + status: Some(ready_status("windows")), + clients: vec![ + ("MacBook-Pro".into(), "0000000000000001".into()), + ("Studio".into(), "0000000000000002".into()), + ], + gamepad_driver: Some(false), + ..Default::default() } - let mut harness = builder.build_eframe(move |cc| HostApp::with_shared(cc, shared)); - harness.run_steps(3); - harness } - fn needs_setup_shared() -> Shared { - let mut st = ready_status(); + fn needs_setup() -> Shared { + let mut st = ready_status("windows"); st.streamer = Streamer::default(); st.wake_ready = Some(false); st.fast_startup = Some(true); @@ -920,10 +1240,108 @@ mod snapshots { } } + fn failed() -> Shared { + let mut st = ready_status("windows"); + st.tailscale_ip = None; + st.tailscale_login = None; + st.streamer = Streamer::default(); + st.setup = vec![ + "Tailscale: Tailscale is not installed. Install it and sign in.".into(), + "The streaming engine is not installed.".into(), + ]; + Shared { + status: Some(st), + setup_result: Some(Err("the administrator prompt was declined or setup failed (see C:\\Users\\Example User\\AppData\\Local\\BroLink\\setup.log)".into())), + setup_log: vec![ + "[14:02:11] BroLink setup started".into(), + "[14:02:11] Downloading the streaming engine".into(), + " engine: curl: (6) Could not resolve host: api.github.com".into(), + ], + ..Default::default() + } + } + + fn states() -> Vec<(&'static str, Os, Shared)> { + vec![ + ("ready", Os::Windows, ready()), + ("setup", Os::Windows, needs_setup()), + ( + "setup-running", + Os::Windows, + Shared { + setup_running: true, + ..needs_setup() + }, + ), + ("setup-failed", Os::Windows, failed()), + ( + "setup-done", + Os::Windows, + Shared { + setup_result: Some(Ok(())), + ..ready() + }, + ), + ( + "mac-ready", + Os::Mac, + Shared { + status: Some(ready_status("macOS")), + ..Default::default() + }, + ), + ( + "mac-setup", + Os::Mac, + Shared { + status: Some({ + let mut s = ready_status("macOS"); + s.streamer = Streamer::default(); + s.setup = vec!["The streaming engine is not installed.".into()]; + s + }), + ..Default::default() + }, + ), + ("starting", Os::Windows, Shared::default()), + ( + "stopped", + Os::Windows, + Shared { + service_error: Some(STOPPED.into()), + ..Default::default() + }, + ), + ] + } + + fn build( + shared: Shared, + os: Os, + size: egui::Vec2, + ppp: f32, + gpu: bool, + ) -> egui_kittest::Harness<'static, NodeApp> { + let mut builder = egui_kittest::Harness::builder() + .with_size(size) + .with_pixels_per_point(ppp) + .with_max_steps(8); + if gpu { + builder = builder.wgpu(); + } + let mut harness = builder.build_eframe(move |cc| { + let mut app = NodeApp::headless(cc, Default::default(), shared, os); + app.client_mut().open_page(Page::Sharing); + app + }); + harness.run_steps(3); + harness + } + /// Every widget sits inside the window, and no two controls overlap. - fn assert_fits(h: &egui_kittest::Harness<'_, HostApp>, width: f32) { + fn assert_fits(h: &egui_kittest::Harness<'_, NodeApp>, width: f32) { use egui::accesskit::Role; - use egui_kittest::kittest::{By, Queryable}; + use egui_kittest::kittest::By; let mut controls = Vec::new(); for node in h.query_all(By::new().predicate(|_| true)) { let Some(b) = node.raw_bounds() else { continue }; @@ -935,7 +1353,7 @@ mod snapshots { ); if matches!( node.role(), - Role::Button | Role::CheckBox | Role::RadioButton | Role::ComboBox + Role::Button | Role::CheckBox | Role::RadioButton | Role::ComboBox | Role::Link ) { controls.push(( text, @@ -958,73 +1376,120 @@ mod snapshots { } } - fn ready_shared() -> Shared { - Shared { - status: Some(ready_status()), - clients: vec![ - ("Example Mac".into(), "0000000000000001".into()), - ("Second Example Mac".into(), "0000000000000002".into()), - ], - gamepad_driver: Some(false), - ..Default::default() - } - } - #[test] - fn ready_panel_fits_the_minimum_window() { - let h = build_sized(ready_shared(), egui::vec2(560.0, 600.0), 1.0, false); - assert_fits(&h, 560.0); + fn every_sharing_state_fits_the_minimum_and_a_large_window() { + for (name, os, shared) in states() { + eprintln!("checking {name}"); + let h = build(shared, os, MIN, 1.0, false); + assert_fits(&h, MIN.x); + } + for (name, os, shared) in states() { + eprintln!("checking {name}"); + let h = build(shared, os, LARGE, 1.0, false); + assert_fits(&h, LARGE.x); + } } #[test] - fn setup_panel_fits_the_minimum_window() { - let h = build_sized(needs_setup_shared(), egui::vec2(560.0, 600.0), 1.0, false); - assert_fits(&h, 560.0); + fn the_page_is_a_tab_of_the_window_and_the_machine_leads_the_list() { + let mut h = build(ready(), Os::Windows, MIN, 1.0, false); + assert!(h.query_by_label("GAMING-PC").is_some()); + h.get_by_label("Machines").click(); + h.run_steps(3); + assert!( + h.query_by_label("GAMING-PC").is_some(), + "this PC leads the list" + ); + assert!(h.query_by_label("Manage").is_some()); } #[test] - #[ignore = "renders with a GPU; run on demand to review the UI"] - fn ready() { - let mut h = build(Shared { - status: Some(ready_status()), - clients: vec![ - ("Example Mac".into(), "0000000000000001".into()), - ("Second Example Mac".into(), "0000000000000002".into()), - ], - gamepad_driver: Some(true), - ..Default::default() - }); - save(h.render().unwrap(), "host-ready.png"); - let mut h = build_sized(ready_shared(), egui::vec2(560.0, 600.0), 2.0, true); - save(h.render().unwrap(), "host-ready-560.png"); + fn windows_only_rows_stay_off_a_mac() { + let tall = egui::vec2(1280.0, 2400.0); + let h = build(ready(), Os::Windows, tall, 1.0, false); + assert!(h.query_by_label("Wake-on-LAN").is_some()); + assert!(h + .query_all_by_label("Keep this PC awake while plugged in") + .next() + .is_some()); + let h = build( + Shared { + status: Some(ready_status("macOS")), + ..Default::default() + }, + Os::Mac, + tall, + 1.0, + false, + ); + assert!(h.query_by_label("Wake-on-LAN").is_none()); + assert!(h.query_by_label("Game controllers").is_none()); + assert!( + h.query_by_label_contains("Keep this").is_none(), + "keeping awake is Windows-only; the switch would do nothing on a Mac" + ); } #[test] - #[ignore = "renders with a GPU; run on demand to review the UI"] - fn needs_setup() { - let mut h = build(needs_setup_shared()); - save(h.render().unwrap(), "host-setup.png"); - let mut h = build_sized(needs_setup_shared(), egui::vec2(560.0, 600.0), 2.0, true); - save(h.render().unwrap(), "host-setup-560.png"); + fn unpair_asks_first_and_a_headless_page_touches_nothing() { + let tall = egui::vec2(1280.0, 2400.0); + let mut h = build(ready(), Os::Windows, tall, 1.0, false); + h.get_all_by_label("Unpair…").next().unwrap().click(); + h.run_steps(3); + assert!( + h.query_by_label("Unpair").is_some(), + "the confirming button" + ); + assert!(h.query_by_label("Keep").is_some()); + h.get_by_label("Run setup again").click(); + h.run_steps(3); + assert!( + h.query_by_label_contains("Waiting for the administrator prompt") + .is_some(), + "setup shows progress; headless, nothing ran" + ); } #[test] #[ignore = "renders with a GPU; run on demand to review the UI"] - fn setup_failed_no_tailscale() { - let mut st = ready_status(); - st.tailscale_ip = None; - st.tailscale_login = None; - st.setup = vec!["Tailscale: Tailscale is not installed. Install it and sign in.".into()]; - let mut h = build(Shared { - status: Some(st), - setup_result: Some(Err("the administrator prompt was declined or setup failed (see C:\\Users\\Example User\\AppData\\Local\\BroLink\\setup.log)".into())), - setup_log: vec![ - "[14:02:11] BroLink setup started".into(), - "[14:02:11] Downloading the streaming engine".into(), - " engine: curl: (6) Could not resolve host: api.github.com".into(), - ], - ..Default::default() - }); - save(h.render().unwrap(), "host-setup-failed.png"); + fn snapshots_sharing() { + let key = ["ready", "setup", "setup-failed"]; + for (name, os, _) in states() { + let sizes: Vec<(egui::Vec2, f32)> = if key.contains(&name) { + vec![ + (MIN, 1.0), + (MIN, 2.0), + (TYPICAL, 1.0), + (TYPICAL, 2.0), + (LARGE, 1.0), + (LARGE, 2.0), + ] + } else { + vec![(MIN, 2.0), (TYPICAL, 2.0)] + }; + for (size, ppp) in sizes { + let shared = states().into_iter().find(|(n, ..)| *n == name).unwrap().2; + let mut h = build(shared, os, size, ppp, true); + save( + h.render().unwrap(), + &format!( + "sharing-{name}-{}x{}@{}x.png", + size.x as u32, size.y as u32, ppp as u32 + ), + ); + } + } + for (name, os, shared) in states() { + let mut h = build(shared, os, egui::vec2(1280.0, 1800.0), 1.0, true); + save(h.render().unwrap(), &format!("sharing-{name}-full.png")); + } + let mut h = build(ready(), Os::Windows, egui::vec2(1280.0, 1800.0), 1.0, true); + h.get_by_label("Service log").click(); + h.run_steps(3); + save(h.render().unwrap(), "sharing-log-open-full.png"); + let mut h = build(ready(), Os::Windows, TYPICAL, 2.0, true); + h.get_by_label("Machines").click(); + h.run_steps(3); + save(h.render().unwrap(), "sharing-lobby-row-1280x800@2x.png"); } } diff --git a/crates/host/src/product.rs b/crates/host/src/product.rs index 6df904f..41d4219 100644 --- a/crates/host/src/product.rs +++ b/crates/host/src/product.rs @@ -1,53 +1,96 @@ -//! The one BroLink window: a machine list to connect from, and sharing of -//! this machine so others can connect in. +//! The one BroLink window: the machine list to connect from, this +//! machine's Sharing page, and settings. The viewer (`brolink-client`) +//! draws the window; the Sharing page is drawn by [`HostApp`] through +//! [`SharePage`], and a summary of it is handed over for the machine list. -use crate::app::HostApp; +use crate::app::{HostApp, Os, Shared}; use brolink_client::app::ClientApp; -use brolink_client::share::{LocalShare, Slot}; +use brolink_client::share::{LocalShare, SharePage, Slot}; use eframe::egui; use parking_lot::Mutex; +use std::cell::RefCell; +use std::rc::Rc; use std::sync::Arc; pub struct NodeApp { client: ClientApp, - host: HostApp, + host: Rc>, local: Slot, + /// The host state last copied into `local`; copied again only when it + /// moves, so a stream does not pay for it every frame. + synced: Option, +} + +/// The Sharing page, as the viewer's window hosts it. +struct HostPage(Rc>); + +impl SharePage for HostPage { + fn show(&mut self, ui: &mut egui::Ui) { + self.0.borrow_mut().page(ui); + } } impl NodeApp { pub fn new(cc: &eframe::CreationContext<'_>) -> Self { + let client = ClientApp::new(cc); + let host = HostApp::new(cc); + Self::assemble(client, host) + } + + fn assemble(client: ClientApp, host: HostApp) -> Self { + let host = Rc::new(RefCell::new(host)); let local: Slot = Arc::new(Mutex::new(LocalShare::default())); - let client = ClientApp::new(cc).with_local(local.clone()); + let client = client.with_local(local.clone(), Box::new(HostPage(host.clone()))); Self { client, - host: HostApp::new(cc), + host, local, + synced: None, + } + } + + /// The whole window with no threads, no settings read and no actions + /// that reach the system, for rendering in tests. + #[doc(hidden)] + pub fn headless( + cc: &eframe::CreationContext<'_>, + discovery: brolink_client::session::Discovery, + shared: Shared, + os: Os, + ) -> Self { + let client = ClientApp::headless( + cc, + discovery, + Default::default(), + brolink_client::config::ClientConfig::default(), + ); + let host = HostApp::headless(Arc::new(Mutex::new(shared)), os); + Self::assemble(client, host) + } + + pub fn client_mut(&mut self) -> &mut ClientApp { + &mut self.client + } + + /// Copy what the machine list shows about this machine, when it has + /// changed since the last copy. + fn sync(&mut self) { + let host = self.host.borrow(); + let shared = host.shared(); + if self.synced == Some(shared.rev) { + return; } + self.synced = Some(shared.rev); + let mut g = self.local.lock(); + g.status = shared.status.clone(); + g.setup_running = shared.setup_running; + g.setup_result = shared.setup_result.clone(); } } impl eframe::App for NodeApp { fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { - { - let shared = self.host.shared(); - let mut g = self.local.lock(); - g.status = shared.status.clone(); - g.setup_running = shared.setup_running; - g.setup_result = shared.setup_result.clone(); - let want = g.want_setup; - g.want_setup = false; - let want_power = g.want_power.take(); - let want_stay = g.want_stay_awake.take(); - let want_auto = g.want_autostart.take(); - drop(g); - drop(shared); - if want { - self.host.request_setup(); - } - self.host - .apply_share_toggles(want_power, want_stay, want_auto); - self.host.publish_share(&self.local); - } + self.sync(); self.client.update(ctx, frame); } } diff --git a/crates/ui/assets/Geist-Medium.ttf b/crates/ui/assets/Geist-Medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..96cb22f8bb85577f3d56c497ee82913883da0fbd GIT binary patch literal 127660 zcmcG%31HR5wLd=d{oXD6cC(O#aBprllAC=GNeEd8kPs3GAps28fDpsJh=_<(5fN9G z3IbYGTuSi<7r?5uYI)ZB)cSlrpS4uc=d+et>a*5bx%qw0%=d0tsPF&pLnq(wojEge z=FFKhXU?3NFXN1{aQx9ROMPwK^c6o@XJP!-X2y6=ee;}o{eOM!6yv*}Vr*V){k-|r z)sybHmhs=d#hCf=IrFmf%74H5Gr&HIQo4FqcCR^5s+?mitd=p;_TEh!EY|P{qfZqjp%gUu2x|cus@=J_=xfQ>&S9Pyk`iuF$eVXx3;A4ndvwHo8&|`nUgyKIy z@mH@|w{(r}7xzXomWuYWcNx)&zy7a}1m9V{B&7T=EJXbbG3>t{TQ-lLpVs)V8ZH?Q z^h?wMM`mCO#o{0Gseg3-Cu4qH!zGtX{1Rn8nMi-tHwDa_Y#pevfa#Gl9nag5bbD|h z`i3)|(!#$&3R89~?TBxXPu|J$_*5#x_3uYa8E)pET<7}THn*mcy~r$oS0oO)DsHea z_Crcn%+y=V!u(NYnli-9|Mc)A3!^gCk5RURwJE=3_A$frNRuHH&rqcszyJS+_fXfo zE7VE2e1-5jU|wf|^yD$DOhbOy zLewpY`~5iKPr&2A41t(kRJJXjupBP?+`HQN+X_C z|D3=Nvto>s^l`&=NSgz^<}iaYXZQ<<%$W!cG7SF_&tNT%;uVBk&OmcaCU~A#0s$XC z{GE3|KQ$=b!+)eFkJUn(`(3YY6!lS^qXV`5{~)NpLb*i$s}MkIz8m<+P=RN$G6Csh zg&x2if~=?TfcC_@G7IHgDKsN}tT0xd92TWkj1K6tY-KWwx?)IU`HELy-%2_Q+D>7~ z%0D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

_e`PJ@7?Hro{A8GiEbw#-HCsja8vnjlV+| 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 literal 0 HcmV?d00001 diff --git a/crates/ui/assets/Geist-Regular.ttf b/crates/ui/assets/Geist-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..4da1b3a6dba4c809bea130138138754650e5a87e GIT binary patch literal 126048 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�yj3@rDuD5@3s_u9~XZY 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 literal 0 HcmV?d00001 diff --git a/crates/ui/assets/GeistMono-Regular.ttf b/crates/ui/assets/GeistMono-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..50c9d5a667bc397a1495b0bb45a52bef4184661c GIT binary patch literal 148516 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

qKjwHQ!ob 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�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 z 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 diff --git a/crates/ui/assets/Inter-SemiBold.ttf b/crates/ui/assets/Inter-SemiBold.ttf deleted file mode 100644 index 47f8ab1d68144fc004b310d65d95180d502b449b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 419744 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�# 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 diff --git a/crates/ui/assets/LICENSE-Inter.txt b/crates/ui/assets/LICENSE-Geist.txt similarity index 96% rename from crates/ui/assets/LICENSE-Inter.txt rename to crates/ui/assets/LICENSE-Geist.txt index 9b2ca37..04e95fc 100644 --- a/crates/ui/assets/LICENSE-Inter.txt +++ b/crates/ui/assets/LICENSE-Geist.txt @@ -1,8 +1,9 @@ -Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter) +Copyright 2024 The Geist Project Authors (https://github.com/vercel/geist-font) This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL +https://openfontlicense.org + ----------------------------------------------------------- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 @@ -17,7 +18,7 @@ with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, +fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The @@ -43,7 +44,7 @@ new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. -PERMISSION AND CONDITIONS +PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font @@ -89,4 +90,4 @@ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. +OTHER DEALINGS IN THE FONT SOFTWARE. \ No newline at end of file diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs index 5453497..e18f4ad 100644 --- a/crates/ui/src/lib.rs +++ b/crates/ui/src/lib.rs @@ -1,14 +1,18 @@ -//! The look shared by the BroLink host and client. +//! BroLink's design system: the tokens and components every window and +//! overlay is built from. //! -//! Both apps are small egui programs aimed at people who are not developers. +//! The apps are small egui programs aimed at people who are not developers. //! egui's defaults read as a debugging tool, so this crate owns everything -//! that makes the two windows look like one product: the palette, the -//! bundled typeface, the widget styling, and a handful of composite widgets -//! (cards, list rows, status pills, the stream overlay) the screens are built -//! from. Keeping it here means the host and client cannot drift apart. +//! that makes the viewer, the Sharing page and the stream overlay look like +//! one product, and like the product site: the palette, the type scale on +//! Geist and Geist Mono, the spacing grid, radii and elevation +//! ([`theme`]), and the components screens are assembled from ([`widgets`]): +//! cards, grouped lists and rows, buttons in five kinds, menus, tabs, +//! switches, notices, tags, keycaps. Screens use these and nothing inline, +//! so the parts cannot drift apart. pub mod theme; pub mod widgets; -pub use theme::{apply, Palette, PALETTE}; +pub use theme::{apply, column, radius, size, space, Palette, PALETTE}; pub use widgets::*; diff --git a/crates/ui/src/theme.rs b/crates/ui/src/theme.rs index bcf6115..49e45ad 100644 --- a/crates/ui/src/theme.rs +++ b/crates/ui/src/theme.rs @@ -1,94 +1,215 @@ -//! Palette, typography and widget styling. +//! Design tokens: colour, type, spacing, radii and elevation, and the egui +//! style built from them. //! -//! One dark theme, deliberately: the client spends its life next to a black -//! video surface, and the host is a status window people glance at. Everything -//! is derived from the handful of colours in [`Palette`], so a retune is a -//! change to one struct. +//! One dark theme, deliberately. The client spends its life next to a black +//! video surface and the product site (bardbro.com) is black, white and +//! hairlines, so the app is too: neutral surfaces a step apart, one text +//! colour in three strengths, white for the primary action and for "on", +//! and colour only where it carries meaning — green, amber and red for +//! status, and the logo's violet for keyboard focus, selection and +//! progress. Screens take every colour, size and distance from here; a +//! retune is a change to this file. use egui::{ - epaint::Shadow, Color32, CornerRadius, FontData, FontDefinitions, FontFamily, FontId, - FontTweak, Margin, Stroke, TextStyle, Vec2, Visuals, + Color32, CornerRadius, FontData, FontDefinitions, FontFamily, FontId, FontTweak, Margin, + Stroke, TextStyle, Vec2, Visuals, }; use std::sync::Arc; -/// The colours the two apps are built from. +// --------------------------------------------------------------------------- +// Colour +// --------------------------------------------------------------------------- + +/// Every colour the two windows and the stream overlay use. #[derive(Debug, Clone, Copy)] pub struct Palette { - /// Window background. + /// Window background (elevation 0). pub bg: Color32, - /// Cards and panels that sit on the background. + /// Grouped lists, cards and panels on the background (elevation 1). pub surface: Color32, - /// Inputs and buttons that sit on a card. + /// Controls on a surface: buttons, fields, the off track of a switch. pub raised: Color32, - /// The same, hovered. + /// A raised control under the pointer, and a hovered list row. pub raised_hover: Color32, - /// Hairlines around cards and controls. + /// A raised control being pressed. + pub pressed: Color32, + /// Sunken areas: logs and blocks of code. + pub well: Color32, + /// Floating panels over the stream picture. + pub overlay: Color32, + /// Separators between rows and around groups. + pub hairline: Color32, + /// Outlines of controls. pub border: Color32, - /// Hairlines that should read a little louder (hovered controls). + /// Outlines of hovered controls. pub border_strong: Color32, - /// Sunken areas: text boxes, the log. - pub well: Color32, - /// Primary text. + /// Primary text. 16:1 on `bg`. pub text: Color32, - /// Secondary text: hints, captions, metadata. - pub muted: Color32, - /// Tertiary text: disabled labels, placeholders. - pub faint: Color32, - /// Brand accent. Foreground colour: links, warnings, PIN digits, status - /// pills and selection. Readable at 4.5:1 on both `bg` and `surface`. + /// Secondary text: descriptions, metadata. 7:1 on `surface`. + pub text_secondary: Color32, + /// Tertiary text: labels, captions, small print. At least 4.5:1 on + /// every surface up to `raised_hover`. + pub text_tertiary: Color32, + /// Disabled labels. Exempt from contrast minimums; never the only cue. + pub text_disabled: Color32, + /// The primary button's fill and a switch that is on. + pub primary: Color32, + pub primary_hover: Color32, + pub primary_pressed: Color32, + /// Text and marks drawn on `primary`. + pub on_primary: Color32, + /// The logo's violet, lightened to read on the dark surfaces. Keyboard + /// focus, text selection, progress. Never a fill behind text. pub accent: Color32, - /// The accent as a solid fill (the primary button, the selected segment). - /// White text on this fill holds 4.5:1 in idle, hover and pressed states. - pub accent_fill: Color32, - /// Secondary brand colour from the logo (pink). Decorative only — never - /// carries text: white on this fill is 2.87:1. - pub accent2: Color32, - /// Text drawn on top of the accent fill. - pub on_accent: Color32, + /// Healthy, online, ready. pub success: Color32, + /// Needs attention; still works. + pub warning: Color32, + /// Broken, failed, destructive. Text on dark surfaces. pub danger: Color32, - pub info: Color32, + /// Fill behind white text on a destructive button. + pub danger_fill: Color32, } -/// Logo-derived dark palette (`logo.webp`: violet / pink / cyan on near-black). -/// -/// Locked tokens: bg `#07080c`, surface `#101219`, accent2 `#f05fd6`, -/// info `#22d3ee`, on_accent white. The logo violet is split into two roles: -/// `accent` (`#8d5ef6`, the `#8b5cf6` logo violet lightened) is foreground -/// text and reads 4.5:1 on both `bg` and `surface`; `accent_fill` (`#8257f6`, -/// the logo violet darkened) is the solid button fill whose white text holds -/// 4.5:1 in every state. One violet cannot do both at 4.5:1 — the luminance -/// windows are disjoint, so the roles are separate tokens. +/// The palette. Contrast ratios are checked by the tests at the bottom. pub const PALETTE: Palette = Palette { - bg: Color32::from_rgb(7, 8, 12), // #07080c - surface: Color32::from_rgb(16, 18, 25), // #101219 - raised: Color32::from_rgb(25, 28, 38), - raised_hover: Color32::from_rgb(34, 38, 50), - border: Color32::from_rgb(30, 34, 46), - border_strong: Color32::from_rgb(54, 58, 78), - well: Color32::from_rgb(5, 6, 10), - text: Color32::from_rgb(232, 236, 242), - muted: Color32::from_rgb(143, 154, 172), - faint: Color32::from_rgb(92, 102, 118), - accent: Color32::from_rgb(141, 94, 246), // #8d5ef6 - accent_fill: Color32::from_rgb(130, 87, 246), // #8257f6 - accent2: Color32::from_rgb(240, 95, 214), // #f05fd6 - on_accent: Color32::WHITE, - success: Color32::from_rgb(63, 185, 80), - danger: Color32::from_rgb(248, 81, 73), - info: Color32::from_rgb(34, 211, 238), // #22d3ee + bg: Color32::from_rgb(0x0a, 0x0a, 0x0a), + surface: Color32::from_rgb(0x11, 0x11, 0x11), + raised: Color32::from_rgb(0x1a, 0x1a, 0x1a), + raised_hover: Color32::from_rgb(0x24, 0x24, 0x24), + pressed: Color32::from_rgb(0x2e, 0x2e, 0x2e), + well: Color32::from_rgb(0x06, 0x06, 0x06), + overlay: Color32::from_rgba_premultiplied(0x08, 0x08, 0x08, 0xeb), + hairline: Color32::from_rgb(0x24, 0x24, 0x24), + border: Color32::from_rgb(0x36, 0x36, 0x36), + border_strong: Color32::from_rgb(0x4a, 0x4a, 0x4a), + text: Color32::from_rgb(0xed, 0xed, 0xed), + text_secondary: Color32::from_rgb(0xa1, 0xa1, 0xa1), + text_tertiary: Color32::from_rgb(0x8c, 0x8c, 0x8c), + text_disabled: Color32::from_rgb(0x5e, 0x5e, 0x5e), + primary: Color32::from_rgb(0xed, 0xed, 0xed), + primary_hover: Color32::from_rgb(0xd4, 0xd4, 0xd4), + primary_pressed: Color32::from_rgb(0xbd, 0xbd, 0xbd), + on_primary: Color32::from_rgb(0x0a, 0x0a, 0x0a), + accent: Color32::from_rgb(0xa7, 0x8b, 0xfa), + success: Color32::from_rgb(0x4c, 0xc3, 0x8a), + warning: Color32::from_rgb(0xf0, 0xb2, 0x49), + danger: Color32::from_rgb(0xff, 0x63, 0x69), + danger_fill: Color32::from_rgb(0xcd, 0x2b, 0x31), }; -/// Font families registered by [`apply`], for text that needs a specific -/// weight. Everything else goes through [`FontFamily::Proportional`], which -/// resolves to Inter Regular. -pub const MEDIUM: &str = "Inter-Medium"; -pub const SEMIBOLD: &str = "Inter-SemiBold"; +// --------------------------------------------------------------------------- +// Spacing, sizes, radii +// --------------------------------------------------------------------------- -/// Named text styles beyond egui's built-ins. -pub const TITLE: &str = "title"; -pub const DISPLAY: &str = "display"; -pub const CAPTION: &str = "caption"; +/// The spacing scale, on a 4-point grid. Layout uses these and nothing in +/// between. +pub mod space { + pub const XXS: f32 = 2.0; + pub const XS: f32 = 4.0; + pub const SM: f32 = 8.0; + pub const MD: f32 = 12.0; + pub const LG: f32 = 16.0; + pub const XL: f32 = 24.0; + pub const XXL: f32 = 32.0; + pub const XXXL: f32 = 48.0; +} + +/// Fixed sizes of recurring elements. +pub mod size { + use egui::Vec2; + /// Height of a button, field or select. + pub const CONTROL: f32 = 32.0; + /// Height of a compact control (the stream toolbar, menus). + pub const CONTROL_SM: f32 = 28.0; + /// The smallest target a pointer is asked to hit (WCAG 2.5.8). + pub const HIT_MIN: f32 = 24.0; + /// Height of the window's top bar. + pub const TOP_BAR: f32 = 48.0; + /// Height of the stream toolbar. + pub const TOOLBAR: f32 = 44.0; + /// Minimum height of a row in a list or a settings group. + pub const ROW: f32 = 44.0; + /// Diameter of a status dot. + pub const DOT: f32 = 8.0; + /// Width of the switch control. + pub const SWITCH: Vec2 = Vec2::new(36.0, 20.0); +} + +/// Corner radii. Small and consistent: controls are nearly square, as on +/// the product site; only floating panels round off more. +pub mod radius { + /// Buttons, fields, keycaps, tags. + pub const SM: u8 = 4; + /// Cards, grouped lists, notices, menus. + pub const MD: u8 = 6; + /// Floating panels over the stream. + pub const LG: u8 = 10; +} + +/// Maximum widths of the content column, per kind of page. +pub mod column { + /// Lists: the machine list. + pub const WIDE: f32 = 880.0; + /// Forms: settings, sharing. + pub const NARROW: f32 = 720.0; +} + +/// Shadows, by how far a layer floats above the window. +pub mod elevation { + use egui::{epaint::Shadow, Color32}; + /// Menus, dropdowns, tooltips. + pub const POPUP: Shadow = Shadow { + offset: [0, 8], + blur: 24, + spread: 0, + color: Color32::from_black_alpha(140), + }; + /// Panels floating over the stream picture. + pub const OVERLAY: Shadow = Shadow { + offset: [0, 12], + blur: 32, + spread: 0, + color: Color32::from_black_alpha(160), + }; +} + +// --------------------------------------------------------------------------- +// Type +// --------------------------------------------------------------------------- + +/// Font families registered by [`apply`]. [`FontFamily::Proportional`] is +/// Geist Regular and [`FontFamily::Monospace`] is Geist Mono Regular; these +/// name the heavier cuts. +pub const MEDIUM: &str = "Geist-Medium"; +pub const SEMIBOLD: &str = "Geist-SemiBold"; +pub const MONO_MEDIUM: &str = "GeistMono-Medium"; + +/// The type scale, in points. Every piece of text uses one of these. +pub mod text { + /// A page's title: "Machines", "Settings", a machine's name. + pub const PAGE_TITLE: f32 = 22.0; + /// A card or dialog title. + pub const TITLE: f32 = 15.0; + /// Running text and controls. + pub const BODY: f32 = 14.0; + /// Descriptions under a label, metadata, small print. + pub const CAPTION: f32 = 12.5; + /// Mono uppercase section labels, as on the product site. + pub const LABEL: f32 = 11.0; + /// Mono data: addresses, versions, rates, logs. + pub const MONO: f32 = 12.5; + /// The pairing PIN. + pub const DISPLAY: f32 = 32.0; + /// Tracking of the mono uppercase labels (0.06 em at 11 pt). + pub const LABEL_TRACKING: f32 = 0.66; + /// Tracking of page titles (-0.02 em). + pub const TITLE_TRACKING: f32 = -0.44; +} + +pub fn regular(size: f32) -> FontId { + FontId::proportional(size) +} pub fn medium(size: f32) -> FontId { FontId::new(size, FontFamily::Name(MEDIUM.into())) @@ -98,12 +219,20 @@ pub fn semibold(size: f32) -> FontId { FontId::new(size, FontFamily::Name(SEMIBOLD.into())) } -pub fn title() -> TextStyle { - TextStyle::Name(TITLE.into()) +pub fn mono(size: f32) -> FontId { + FontId::monospace(size) +} + +pub fn mono_medium(size: f32) -> FontId { + FontId::new(size, FontFamily::Name(MONO_MEDIUM.into())) } -pub fn display() -> TextStyle { - TextStyle::Name(DISPLAY.into()) +/// Named text styles beyond egui's built-ins. +pub const TITLE: &str = "title"; +pub const CAPTION: &str = "caption"; + +pub fn title() -> TextStyle { + TextStyle::Name(TITLE.into()) } pub fn caption() -> TextStyle { @@ -116,104 +245,119 @@ pub fn stroke(width: f32, color: Color32) -> Stroke { Stroke::new(width, color) } -/// Corner radius used for cards and other large containers. -pub const RADIUS_LG: u8 = 12; -/// Corner radius used for controls. -pub const RADIUS: u8 = 8; - /// Install the fonts and style on a context. Call once at startup. pub fn apply(ctx: &egui::Context) { ctx.set_fonts(fonts()); let mut style = (*ctx.style()).clone(); style.visuals = visuals(); style.text_styles = [ - (TextStyle::Small, FontId::proportional(12.0)), - (TextStyle::Body, FontId::proportional(14.0)), - (TextStyle::Button, medium(14.0)), - (TextStyle::Heading, semibold(20.0)), - (TextStyle::Monospace, FontId::monospace(13.0)), - (title(), semibold(15.0)), - (display(), semibold(40.0)), - (caption(), FontId::proportional(12.5)), + (TextStyle::Small, regular(text::CAPTION)), + (TextStyle::Body, regular(text::BODY)), + (TextStyle::Button, medium(text::BODY)), + (TextStyle::Heading, semibold(text::PAGE_TITLE)), + (TextStyle::Monospace, mono(text::MONO)), + (title(), semibold(text::TITLE)), + (caption(), regular(text::CAPTION)), ] .into(); let sp = &mut style.spacing; - sp.item_spacing = Vec2::new(8.0, 8.0); - sp.button_padding = Vec2::new(14.0, 7.0); - sp.interact_size = Vec2::new(40.0, 30.0); - sp.indent = 18.0; - sp.slider_width = 220.0; - sp.slider_rail_height = 6.0; - sp.text_edit_width = 320.0; - sp.combo_width = 140.0; + sp.item_spacing = Vec2::new(space::SM, space::SM); + sp.button_padding = Vec2::new(space::MD, 6.0); + sp.interact_size = Vec2::new(40.0, size::CONTROL); + sp.indent = space::LG; + sp.slider_width = 200.0; + sp.slider_rail_height = 4.0; + sp.text_edit_width = 240.0; + sp.combo_width = 160.0; + sp.combo_height = 320.0; sp.icon_width = 16.0; sp.icon_width_inner = 10.0; sp.icon_spacing = 6.0; - sp.window_margin = Margin::same(14); - sp.menu_margin = Margin::same(8); - sp.tooltip_width = 360.0; - sp.scroll.bar_width = 8.0; + sp.menu_width = 220.0; + sp.menu_spacing = space::XS; + sp.window_margin = Margin::same(space::LG as i8); + sp.menu_margin = Margin::same(space::XS as i8); + sp.tooltip_width = 320.0; + sp.scroll.bar_width = 6.0; sp.scroll.floating = true; - sp.scroll.bar_inner_margin = 4.0; + sp.scroll.bar_inner_margin = space::XS; style.url_in_tooltip = true; + // Popups, menus and tooltips appear and go at once: a fade would ask + // for repaints the stream view does not want. + style.animation_time = 0.1; ctx.set_style(style); } +fn font(bytes: &'static [u8]) -> Arc { + // Geist sits a hair high on egui's line box; nudge it so text centres + // in buttons and rows. + let mut data = FontData::from_static(bytes); + data.tweak = FontTweak { + y_offset_factor: 0.04, + ..Default::default() + }; + Arc::new(data) +} + fn fonts() -> FontDefinitions { let mut fonts = FontDefinitions::default(); - // Inter is bundled so the two apps look the same on every machine; the - // egui defaults stay in the family lists as fallbacks for symbols and - // emoji Inter does not carry. - fonts.font_data.insert( - "Inter".into(), - Arc::new(tweaked(FontData::from_static(include_bytes!( - "../assets/Inter-Regular.ttf" - )))), + // Geist and Geist Mono are bundled so every machine draws the same + // letters as the product site. egui's own fonts stay at the end of each + // family as fallbacks for glyphs Geist does not carry. + let add = |fonts: &mut FontDefinitions, name: &str, bytes: &'static [u8]| { + fonts.font_data.insert(name.into(), font(bytes)); + }; + add( + &mut fonts, + "Geist", + include_bytes!("../assets/Geist-Regular.ttf"), + ); + add( + &mut fonts, + MEDIUM, + include_bytes!("../assets/Geist-Medium.ttf"), + ); + add( + &mut fonts, + SEMIBOLD, + include_bytes!("../assets/Geist-SemiBold.ttf"), ); - fonts.font_data.insert( - MEDIUM.into(), - Arc::new(tweaked(FontData::from_static(include_bytes!( - "../assets/Inter-Medium.ttf" - )))), + add( + &mut fonts, + "GeistMono", + include_bytes!("../assets/GeistMono-Regular.ttf"), ); - fonts.font_data.insert( - SEMIBOLD.into(), - Arc::new(tweaked(FontData::from_static(include_bytes!( - "../assets/Inter-SemiBold.ttf" - )))), + add( + &mut fonts, + MONO_MEDIUM, + include_bytes!("../assets/GeistMono-Medium.ttf"), ); - let fallbacks: Vec = fonts - .families - .get(&FontFamily::Proportional) - .cloned() - .unwrap_or_default(); - let with = |name: &str| { + let fallbacks = |family: FontFamily| -> Vec { + fonts.families.get(&family).cloned().unwrap_or_default() + }; + let sans = fallbacks(FontFamily::Proportional); + let monos = fallbacks(FontFamily::Monospace); + let with = |name: &str, rest: &[String]| { let mut v = vec![name.to_string()]; - v.extend(fallbacks.iter().cloned()); + v.extend(rest.iter().cloned()); v }; - fonts - .families - .insert(FontFamily::Proportional, with("Inter")); - fonts - .families - .insert(FontFamily::Name(MEDIUM.into()), with(MEDIUM)); - fonts - .families - .insert(FontFamily::Name(SEMIBOLD.into()), with(SEMIBOLD)); + let families = [ + (FontFamily::Proportional, with("Geist", &sans)), + (FontFamily::Name(MEDIUM.into()), with(MEDIUM, &sans)), + (FontFamily::Name(SEMIBOLD.into()), with(SEMIBOLD, &sans)), + (FontFamily::Monospace, with("GeistMono", &monos)), + ( + FontFamily::Name(MONO_MEDIUM.into()), + with(MONO_MEDIUM, &monos), + ), + ]; + for (family, list) in families { + fonts.families.insert(family, list); + } fonts } -/// Inter sits a touch high on its line at egui's default metrics; nudge it -/// so text centres in buttons and pills. -fn tweaked(mut data: FontData) -> FontData { - data.tweak = FontTweak { - y_offset_factor: 0.02, - ..Default::default() - }; - data -} - fn visuals() -> Visuals { let p = PALETTE; let mut v = Visuals::dark(); @@ -221,25 +365,15 @@ fn visuals() -> Visuals { v.panel_fill = p.bg; v.window_fill = p.surface; v.window_stroke = stroke(1.0, p.border); - v.window_corner_radius = CornerRadius::same(RADIUS_LG); - v.window_shadow = Shadow { - offset: [0, 8], - blur: 24, - spread: 0, - color: Color32::from_black_alpha(120), - }; - v.popup_shadow = Shadow { - offset: [0, 6], - blur: 18, - spread: 0, - color: Color32::from_black_alpha(110), - }; - v.menu_corner_radius = CornerRadius::same(10); + v.window_corner_radius = CornerRadius::same(radius::LG); + v.window_shadow = elevation::OVERLAY; + v.popup_shadow = elevation::POPUP; + v.menu_corner_radius = CornerRadius::same(radius::MD); v.extreme_bg_color = p.well; v.faint_bg_color = p.raised; v.code_bg_color = p.well; - v.hyperlink_color = p.accent; - v.warn_fg_color = p.accent; + v.hyperlink_color = p.text; + v.warn_fg_color = p.warning; v.error_fg_color = p.danger; v.button_frame = true; v.collapsing_header_frame = false; @@ -247,21 +381,23 @@ fn visuals() -> Visuals { v.striped = false; v.slider_trailing_fill = true; v.handle_shape = egui::style::HandleShape::Circle; - v.selection.bg_fill = p.accent.gamma_multiply(0.35); + v.selection.bg_fill = p.accent.gamma_multiply(0.30); v.selection.stroke = stroke(1.0, p.accent); v.text_cursor.stroke = stroke(2.0, p.accent); + v.interact_cursor = Some(egui::CursorIcon::PointingHand); + v.clip_rect_margin = 0.0; let w = &mut v.widgets; - let r = CornerRadius::same(RADIUS); + let r = CornerRadius::same(radius::SM); w.noninteractive.bg_fill = p.surface; w.noninteractive.weak_bg_fill = p.surface; - w.noninteractive.bg_stroke = stroke(1.0, p.border); - w.noninteractive.fg_stroke = stroke(1.0, p.text); + w.noninteractive.bg_stroke = stroke(1.0, p.hairline); + w.noninteractive.fg_stroke = stroke(1.0, p.text_secondary); w.noninteractive.corner_radius = r; w.inactive.bg_fill = p.raised; w.inactive.weak_bg_fill = p.raised; - w.inactive.bg_stroke = stroke(1.0, p.border_strong.gamma_multiply(0.6)); + w.inactive.bg_stroke = stroke(1.0, p.border); w.inactive.fg_stroke = stroke(1.0, p.text); w.inactive.corner_radius = r; w.inactive.expansion = 0.0; @@ -269,14 +405,16 @@ fn visuals() -> Visuals { w.hovered.bg_fill = p.raised_hover; w.hovered.weak_bg_fill = p.raised_hover; w.hovered.bg_stroke = stroke(1.0, p.border_strong); - w.hovered.fg_stroke = stroke(1.5, p.text); + w.hovered.fg_stroke = stroke(1.0, p.text); w.hovered.corner_radius = r; w.hovered.expansion = 0.0; - w.active.bg_fill = p.border_strong; - w.active.weak_bg_fill = p.border_strong; - w.active.bg_stroke = stroke(1.0, p.border_strong); - w.active.fg_stroke = stroke(1.5, p.text); + // egui draws a focused built-in widget (select, slider, menu item) with + // the active visuals, so this outline is also their focus ring. + w.active.bg_fill = p.pressed; + w.active.weak_bg_fill = p.pressed; + w.active.bg_stroke = stroke(1.5, p.accent); + w.active.fg_stroke = stroke(1.0, p.text); w.active.corner_radius = r; w.active.expansion = 0.0; @@ -288,10 +426,8 @@ fn visuals() -> Visuals { v } -#[cfg(test)] -mod tests { - use super::*; - +/// WCAG 2 contrast ratio between two opaque colours. +pub fn contrast(fg: Color32, bg: Color32) -> f64 { fn lin(c: u8) -> f64 { let s = f64::from(c) / 255.0; if s <= 0.04045 { @@ -300,42 +436,75 @@ mod tests { ((s + 0.055) / 1.055).powf(2.4) } } + let lum = |c: Color32| 0.2126 * lin(c.r()) + 0.7152 * lin(c.g()) + 0.0722 * lin(c.b()); + let (a, b) = (lum(fg), lum(bg)); + let (hi, lo) = if a > b { (a, b) } else { (b, a) }; + (hi + 0.05) / (lo + 0.05) +} - fn lum(c: Color32) -> f64 { - 0.2126 * lin(c.r()) + 0.7152 * lin(c.g()) + 0.0722 * lin(c.b()) - } +#[cfg(test)] +mod tests { + use super::*; - fn contrast(fg: Color32, bg: Color32) -> f64 { - let (a, b) = (lum(fg), lum(bg)); - let (hi, lo) = if a > b { (a, b) } else { (b, a) }; - (hi + 0.05) / (lo + 0.05) + const AA: f64 = 4.5; + /// WCAG 1.4.11: meaningful non-text marks (status dots, focus rings). + const NON_TEXT: f64 = 3.0; + + #[test] + fn every_text_colour_meets_aa_on_every_surface_it_sits_on() { + let p = PALETTE; + let surfaces = [p.bg, p.surface, p.raised, p.raised_hover, Color32::BLACK]; + for bg in surfaces { + for (name, fg) in [ + ("text", p.text), + ("secondary", p.text_secondary), + ("tertiary", p.text_tertiary), + ("success", p.success), + ("warning", p.warning), + ("danger", p.danger), + ("accent", p.accent), + ] { + let c = contrast(fg, bg); + assert!(c >= AA, "{name} on {bg:?} is {c:.2}:1"); + } + } } #[test] - fn logo_tokens_match_specified_hex() { + fn filled_buttons_keep_their_label_readable_in_every_state() { let p = PALETTE; - assert_eq!(p.bg, Color32::from_rgb(7, 8, 12)); - assert_eq!(p.surface, Color32::from_rgb(16, 18, 25)); - assert_eq!(p.accent, Color32::from_rgb(141, 94, 246)); - assert_eq!(p.accent_fill, Color32::from_rgb(130, 87, 246)); - assert_eq!(p.accent2, Color32::from_rgb(240, 95, 214)); - assert_eq!(p.info, Color32::from_rgb(34, 211, 238)); - assert_eq!(p.on_accent, Color32::WHITE); - assert_ne!(p.well, p.bg); + for fill in [p.primary, p.primary_hover, p.primary_pressed] { + assert!(contrast(p.on_primary, fill) >= AA, "{fill:?}"); + } + assert!(contrast(Color32::WHITE, p.danger_fill) >= AA); } #[test] - fn text_on_surfaces_meets_aa() { + fn focus_and_status_marks_stand_out_from_the_background() { let p = PALETTE; - assert!(contrast(p.text, p.bg) >= 7.0); - assert!(contrast(p.text, p.surface) >= 7.0); - assert!(contrast(p.muted, p.bg) >= 4.5); - assert!(contrast(p.info, p.bg) >= 4.5); - assert!(contrast(p.accent2, p.bg) >= 4.5); - // Foreground accent (links, warnings, pills) must read on both. - assert!(contrast(p.accent, p.bg) >= 4.5); - assert!(contrast(p.accent, p.surface) >= 4.5); - // White button text on the fill must read at 4.5:1. - assert!(contrast(p.on_accent, p.accent_fill) >= 4.5); + for mark in [p.accent, p.success, p.warning, p.danger, p.text_tertiary] { + assert!(contrast(mark, p.bg) >= NON_TEXT, "{mark:?}"); + assert!(contrast(mark, p.surface) >= NON_TEXT, "{mark:?}"); + } + // The focus ring is drawn outside a control, two points clear of + // it, so it is always seen against the background. + } + + #[test] + fn the_scales_step_on_the_grid() { + for s in [ + space::XS, + space::SM, + space::MD, + space::LG, + space::XL, + space::XXL, + space::XXXL, + ] { + assert_eq!(s % 4.0, 0.0, "{s} is off the 4-point grid"); + } + const { assert!(radius::SM < radius::MD && radius::MD < radius::LG) }; + const { assert!(size::HIT_MIN <= size::SWITCH.y + 4.0) }; + const { assert!(size::CONTROL_SM >= size::HIT_MIN) }; } } diff --git a/crates/ui/src/widgets.rs b/crates/ui/src/widgets.rs index ccdfa6e..cf7f37a 100644 --- a/crates/ui/src/widgets.rs +++ b/crates/ui/src/widgets.rs @@ -1,52 +1,158 @@ -//! Composite widgets the host and client screens are assembled from. +//! The components every BroLink screen is assembled from. //! -//! Everything here is drawn from [`PALETTE`](crate::PALETTE) and the text -//! styles registered by [`apply`](crate::apply); none of it carries its own -//! colours, so the two apps stay in step. +//! Everything here takes its colours, sizes and distances from +//! [`theme`](crate::theme); nothing carries its own, so the machine list, +//! the sharing page and the stream overlay stay one product. Custom-painted +//! widgets report themselves to accesskit and draw a focus ring, so the +//! whole window works from the keyboard and with a screen reader. -use crate::theme::{self, stroke, PALETTE as P, RADIUS, RADIUS_LG}; +use crate::theme::{self, radius, size, space, stroke, text, PALETTE as P}; use egui::{ - collapsing_header::CollapsingState, Align, Color32, ColorImage, CornerRadius, Frame, Id, - InnerResponse, Label, Layout, Margin, Rect, Response, RichText, Sense, Stroke, StrokeKind, - TextureHandle, TextureOptions, Ui, UiBuilder, Vec2, WidgetInfo, WidgetType, + collapsing_header::CollapsingState, Align, Align2, Color32, ColorImage, CornerRadius, Frame, + Id, InnerResponse, Label, Layout, Margin, Painter, Pos2, Rect, Response, RichText, Sense, + Shape, Stroke, StrokeKind, TextureHandle, TextureOptions, Ui, UiBuilder, Vec2, WidgetInfo, + WidgetText, WidgetType, }; -/// Semantic colour for pills, notices and status text. +/// What a mark, a notice or a status line means. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Tone { + /// Plain information; no judgement. Neutral, + /// Something happened that is worth a line; drawn like `Neutral`. + Info, + /// In progress: connecting, waking, pairing. Accent, + /// Online, ready, done. Success, + /// Works, but something could be better or needs a decision. + Warning, + /// Failed or unreachable. Danger, - Info, } impl Tone { + /// The colour of this tone's mark and, for text in that tone, its ink. pub fn color(self) -> Color32 { match self { - Tone::Neutral => P.muted, + Tone::Neutral | Tone::Info => P.text_tertiary, Tone::Accent => P.accent, Tone::Success => P.success, + Tone::Warning => P.warning, Tone::Danger => P.danger, - Tone::Info => P.info, + } + } + + /// The outline of a card or notice in this tone. + fn line(self) -> Color32 { + match self { + Tone::Neutral | Tone::Info => P.border, + t => t.color().gamma_multiply(0.5), + } + } + + /// The faint wash behind a notice in this tone. + fn wash(self) -> Color32 { + match self { + Tone::Neutral | Tone::Info => P.surface, + t => t.color().gamma_multiply(0.07), + } + } +} + +fn margin(x: f32, y: f32) -> Margin { + Margin::symmetric(x as i8, y as i8) +} + +// --------------------------------------------------------------------------- +// Icons +// --------------------------------------------------------------------------- + +/// Small line icons, painted rather than taken from a font so they match +/// on every machine and at every scale. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Icon { + ChevronDown, + ChevronRight, + /// Three dots: more actions. + More, + Check, + Close, + /// Opens elsewhere (a browser). + External, +} + +/// Paint `icon` in a 12-point box centred on `c`. +pub fn paint_icon(painter: &Painter, c: Pos2, icon: Icon, color: Color32) { + let s = stroke(1.5, color); + let p = |x: f32, y: f32| c + Vec2::new(x, y); + match icon { + Icon::ChevronDown => { + painter.add(Shape::line( + vec![p(-4.0, -2.0), p(0.0, 2.0), p(4.0, -2.0)], + s, + )); + } + Icon::ChevronRight => { + painter.add(Shape::line( + vec![p(-2.0, -4.0), p(2.0, 0.0), p(-2.0, 4.0)], + s, + )); + } + Icon::More => { + for x in [-5.0, 0.0, 5.0] { + painter.circle_filled(p(x, 0.0), 1.5, color); + } + } + Icon::Check => { + painter.add(Shape::line( + vec![p(-4.0, 0.0), p(-1.5, 3.0), p(4.0, -3.5)], + s, + )); + } + Icon::Close => { + painter.line_segment([p(-4.0, -4.0), p(4.0, 4.0)], s); + painter.line_segment([p(-4.0, 4.0), p(4.0, -4.0)], s); + } + Icon::External => { + painter.line_segment([p(-3.5, 3.5), p(3.5, -3.5)], s); + painter.add(Shape::line( + vec![p(-1.5, -3.5), p(3.5, -3.5), p(3.5, 1.5)], + s, + )); } } } +/// Keep keyboard navigation visible for controls with custom painting: an +/// accent outline two points outside the control, so it shows against the +/// background whatever the control's own fill. +pub fn focus_ring(ui: &Ui, response: &Response, corner: u8) { + if response.has_focus() && ui.is_enabled() { + ui.painter().rect_stroke( + response.rect.expand(2.0), + CornerRadius::same(corner.saturating_add(2)), + stroke(1.5, P.accent), + StrokeKind::Outside, + ); + } +} + // --------------------------------------------------------------------------- // Containers // --------------------------------------------------------------------------- -/// The frame every card is drawn with. +/// The frame of a card: a surface one step up from the window, with a +/// hairline around it. pub fn card_frame() -> Frame { Frame::new() .fill(P.surface) - .stroke(stroke(1.0, P.border)) - .corner_radius(CornerRadius::same(RADIUS_LG)) - .inner_margin(Margin::same(18)) + .stroke(stroke(1.0, P.hairline)) + .corner_radius(CornerRadius::same(radius::MD)) + .inner_margin(Margin::same(space::LG as i8)) } -/// A full-width surface with a hairline border. +/// A full-width card for free-form content. pub fn card(ui: &mut Ui, add: impl FnOnce(&mut Ui) -> R) -> InnerResponse { card_frame().show(ui, |ui| { ui.set_min_width(ui.available_width()); @@ -54,29 +160,32 @@ pub fn card(ui: &mut Ui, add: impl FnOnce(&mut Ui) -> R) -> InnerResponse }) } -/// A card whose border carries a tone, for the one thing on screen that -/// needs attention right now (a pairing PIN, a setup problem). +/// A card whose outline carries a tone, for the one thing on screen that +/// needs attention now (a connection in progress, a failure, setup). pub fn toned_card(ui: &mut Ui, tone: Tone, add: impl FnOnce(&mut Ui) -> R) -> InnerResponse { card_frame() - .stroke(stroke(1.0, tone.color().gamma_multiply(0.7))) + .stroke(stroke(1.0, tone.line())) .show(ui, |ui| { ui.set_min_width(ui.available_width()); add(ui) }) } -/// Card title with an optional one-line explanation underneath. +/// A card's title with an optional one-line explanation underneath. pub fn heading(ui: &mut Ui, title: &str, subtitle: Option<&str>) { - ui.label( - RichText::new(title) - .text_style(theme::title()) - .color(P.text), + ui.add( + Label::new( + RichText::new(title) + .font(theme::semibold(text::TITLE)) + .color(P.text), + ) + .wrap(), ); if let Some(s) = subtitle { - ui.add_space(-2.0); + ui.add_space(-space::XS); caption(ui, s); } - ui.add_space(8.0); + ui.add_space(space::XS); } /// [`card`] with a [`heading`]. @@ -92,13 +201,72 @@ pub fn titled_card( }) } -/// A sunken area for a log or a block of monospace text. +/// A grouped list: rows separated by hairlines on one surface, as in the +/// machine list and every settings section. Rows bring their own padding. +pub fn group(ui: &mut Ui, add: impl FnOnce(&mut Ui) -> R) -> InnerResponse { + Frame::new() + .fill(P.surface) + .stroke(stroke(1.0, P.hairline)) + .corner_radius(CornerRadius::same(radius::MD)) + .inner_margin(margin(space::LG, 0.0)) + .show(ui, |ui| { + ui.set_min_width(ui.available_width()); + ui.spacing_mut().item_spacing.y = 0.0; + add(ui) + }) +} + +/// A mono uppercase label over a section, as on the product site. +pub fn section_label(ui: &mut Ui, label: &str) -> Response { + ui.add( + Label::new( + RichText::new(label.to_uppercase()) + .font(theme::mono_medium(text::LABEL)) + .extra_letter_spacing(text::LABEL_TRACKING) + .color(P.text_tertiary), + ) + .truncate(), + ) +} + +/// A [`section_label`] over a [`group`]. +pub fn section(ui: &mut Ui, label: &str, add: impl FnOnce(&mut Ui) -> R) -> R { + ui.vertical(|ui| { + ui.spacing_mut().item_spacing.y = space::SM; + section_label(ui, label); + group(ui, add).inner + }) + .inner +} + +/// A page's title, an optional line under it, and controls on the right. +pub fn page_header(ui: &mut Ui, title: &str, subtitle: Option<&str>, right: impl FnOnce(&mut Ui)) { + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = space::MD; + ui.add( + Label::new( + RichText::new(title) + .font(theme::semibold(text::PAGE_TITLE)) + .extra_letter_spacing(text::TITLE_TRACKING) + .color(P.text), + ) + .truncate(), + ); + ui.with_layout(Layout::right_to_left(Align::Center), right); + }); + if let Some(s) = subtitle { + ui.add_space(-space::XS); + muted(ui, s); + } +} + +/// A sunken area for a log or a block of code. pub fn well(ui: &mut Ui, add: impl FnOnce(&mut Ui) -> R) -> InnerResponse { Frame::new() .fill(P.well) - .stroke(stroke(1.0, P.border)) - .corner_radius(CornerRadius::same(RADIUS)) - .inner_margin(Margin::symmetric(12, 10)) + .stroke(stroke(1.0, P.hairline)) + .corner_radius(CornerRadius::same(radius::SM)) + .inner_margin(margin(space::MD, space::SM)) .show(ui, |ui| { ui.set_min_width(ui.available_width()); add(ui) @@ -108,7 +276,7 @@ pub fn well(ui: &mut Ui, add: impl FnOnce(&mut Ui) -> R) -> InnerResponse /// A scrolling log in a [`well`], newest line at the bottom. pub fn log_view(ui: &mut Ui, id: impl std::hash::Hash, lines: &[String], max_height: f32) { well(ui, |ui| { - ui.spacing_mut().item_spacing.y = 3.0; + ui.spacing_mut().item_spacing.y = space::XXS; egui::ScrollArea::vertical() .id_salt(id) .max_height(max_height) @@ -116,52 +284,110 @@ pub fn log_view(ui: &mut Ui, id: impl std::hash::Hash, lines: &[String], max_hei .show(ui, |ui| { ui.set_min_width(ui.available_width()); if lines.is_empty() { - ui.label(RichText::new("Nothing yet.").monospace().color(P.faint)); + ui.label( + RichText::new("Nothing logged yet.") + .font(theme::mono(text::MONO)) + .color(P.text_tertiary), + ); } for line in lines { - ui.label(RichText::new(line).monospace().color(P.muted)); + ui.add( + Label::new( + RichText::new(line) + .font(theme::mono(text::MONO)) + .color(P.text_secondary), + ) + .wrap(), + ); } }); }); } -/// A card that opens and closes from its title row. `id` is global to the -/// context, not to the enclosing `Ui`; use distinct ids within an app. -pub fn collapsible( +/// A row that opens and closes the content under it, with a painted +/// chevron. `id` is global to the context; use distinct ids. +pub fn disclosure( ui: &mut Ui, id: impl std::hash::Hash, title: &str, default_open: bool, body: impl FnOnce(&mut Ui) -> R, ) -> Option { - let id = collapsible_id(id); - card(ui, |ui| { - let state = CollapsingState::load_with_default_open(ui.ctx(), id, default_open); - let (_, _, body) = state - .show_header(ui, |ui| { - ui.label( - RichText::new(title) - .text_style(theme::title()) - .color(P.text), - ); - }) - .body_unindented(|ui| { - ui.add_space(8.0); - body(ui) - }); - body.map(|b| b.inner) - }) - .inner + let id = Id::new("brolink.disclosure").with(id); + let mut state = CollapsingState::load_with_default_open(ui.ctx(), id, default_open); + let galley = ui.painter().layout_no_wrap( + title.to_owned(), + theme::medium(text::BODY), + Color32::PLACEHOLDER, + ); + let h = size::CONTROL; + let (rect, response) = ui.allocate_exact_size( + Vec2::new(ui.available_width().max(galley.size().x + 24.0), h), + Sense::click(), + ); + if response.clicked() { + state.toggle(ui); + } + let open = state.is_open(); + response.widget_info(|| { + WidgetInfo::selected(WidgetType::CollapsingHeader, ui.is_enabled(), open, title) + }); + if ui.is_rect_visible(rect) { + let hot = response.hovered() || response.has_focus(); + let color = if hot { P.text } else { P.text_secondary }; + paint_icon( + ui.painter(), + Pos2::new(rect.left() + 6.0, rect.center().y), + if open { + Icon::ChevronDown + } else { + Icon::ChevronRight + }, + color, + ); + ui.painter().galley( + Pos2::new(rect.left() + 20.0, rect.center().y - galley.size().y / 2.0), + galley, + color, + ); + focus_ring(ui, &response, radius::SM); + } + let out = state.show_body_unindented(ui, |ui| { + ui.add_space(space::XS); + body(ui) + }); + out.map(|r| r.inner) } -fn collapsible_id(id: impl std::hash::Hash) -> Id { - Id::new("brolink.collapsible").with(id) +/// A [`card`] whose content folds away under a [`disclosure`] row. +pub fn collapsible( + ui: &mut Ui, + id: impl std::hash::Hash, + title: &str, + default_open: bool, + body: impl FnOnce(&mut Ui) -> R, +) -> Option { + Frame::new() + .fill(P.surface) + .stroke(stroke(1.0, P.hairline)) + .corner_radius(CornerRadius::same(radius::MD)) + .inner_margin(margin(space::MD, space::XS)) + .show(ui, |ui| { + ui.set_min_width(ui.available_width()); + let r = disclosure(ui, id, title, default_open, body); + if r.is_some() { + ui.add_space(space::SM); + } + r + }) + .inner } -/// Centre the content in a column no wider than `max_width`. +/// Centre the content in a column no wider than `max_width`, with at +/// least the page gutter on each side. pub fn content_column(ui: &mut Ui, max_width: f32, add: impl FnOnce(&mut Ui) -> R) -> R { let avail = ui.available_width(); - let w = (avail - 40.0).max(0.0).min(max_width); + let w = (avail - 2.0 * space::XL).max(0.0).min(max_width); let pad = ((avail - w) / 2.0).max(0.0); ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 0.0; @@ -175,102 +401,253 @@ pub fn content_column(ui: &mut Ui, max_width: f32, add: impl FnOnce(&mut Ui) .inner } -/// The bar across the top of a window. +/// The bar across the top of the window: brand, tabs, status. pub fn top_bar(ctx: &egui::Context, id: impl Into, add: impl FnOnce(&mut Ui) -> R) -> R { egui::TopBottomPanel::top(id) - .frame( - Frame::new() - .fill(P.bg) - .inner_margin(Margin::symmetric(20, 12)), - ) + .frame(Frame::new().fill(P.bg).inner_margin(margin(space::LG, 0.0))) + .exact_height(size::TOP_BAR) .show_separator_line(true) - .show(ctx, add) + .show(ctx, |ui| { + ui.horizontal_centered(|ui| { + ui.spacing_mut().item_spacing.x = space::SM; + add(ui) + }) + .inner + }) .inner } -/// The bar across the bottom of a window: version, ports, other small print. +/// The bar across the bottom of the window: version, account, small print. pub fn bottom_bar(ctx: &egui::Context, id: impl Into, add: impl FnOnce(&mut Ui) -> R) -> R { egui::TopBottomPanel::bottom(id) - .frame( - Frame::new() - .fill(P.bg) - .inner_margin(Margin::symmetric(20, 8)), - ) + .frame(Frame::new().fill(P.bg).inner_margin(margin(space::LG, 6.0))) .show_separator_line(true) .show(ctx, |ui| { - ui.style_mut().override_text_style = Some(theme::caption()); - ui.visuals_mut().override_text_color = Some(P.faint); + ui.style_mut().override_font_id = Some(theme::mono(text::LABEL + 0.5)); + ui.visuals_mut().override_text_color = Some(P.text_tertiary); add(ui) }) .inner } -/// The translucent frame the stream overlay sits in. +/// The frame of anything that floats over the stream picture: toasts, +/// questions, the toolbar's menus. pub fn overlay_frame() -> Frame { Frame::new() - .fill(Color32::from_black_alpha(196)) - .stroke(stroke(1.0, Color32::from_white_alpha(26))) - .corner_radius(CornerRadius::same(10)) - .inner_margin(Margin::symmetric(12, 8)) + .fill(P.overlay) + .stroke(stroke(1.0, Color32::from_white_alpha(22))) + .corner_radius(CornerRadius::same(radius::LG)) + .inner_margin(margin(space::MD, space::MD)) + .shadow(theme::elevation::OVERLAY) +} + +/// The frame of a panel over the stream (stream settings, performance). +pub fn panel_frame() -> Frame { + Frame::new() + .fill(P.surface) + .stroke(stroke(1.0, P.border)) + .corner_radius(CornerRadius::same(radius::LG)) + .inner_margin(Margin::same(space::LG as i8)) + .shadow(theme::elevation::OVERLAY) +} + +/// A floating panel's title row with a close button. Returns true when +/// the close button was pressed. +pub fn panel_header(ui: &mut Ui, title: &str) -> bool { + let mut close = false; + ui.horizontal(|ui| { + ui.label( + RichText::new(title) + .font(theme::semibold(text::TITLE)) + .color(P.text), + ); + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + close = icon_button(ui, Icon::Close, &format!("Close {title}")).clicked(); + }); + }); + ui.add_space(space::XS); + close } // --------------------------------------------------------------------------- // Text // --------------------------------------------------------------------------- -/// Secondary text: hints, help, metadata. +/// Secondary text under a label: hints, help, metadata. Wraps. pub fn caption(ui: &mut Ui, text: impl Into) -> Response { - ui.label( - RichText::new(text) - .text_style(theme::caption()) - .color(P.muted), + ui.add( + Label::new( + RichText::new(text) + .text_style(theme::caption()) + .color(P.text_secondary), + ) + .wrap(), ) } -/// Body text in the muted colour. +/// Small print: the quietest text that still reads at AA. +pub fn small_print(ui: &mut Ui, text: impl Into) -> Response { + ui.add( + Label::new( + RichText::new(text) + .text_style(theme::caption()) + .color(P.text_tertiary), + ) + .wrap(), + ) +} + +/// Body text in the secondary colour. Wraps. pub fn muted(ui: &mut Ui, text: impl Into) -> Response { - ui.label(RichText::new(text).color(P.muted)) + ui.add(Label::new(RichText::new(text).color(P.text_secondary)).wrap()) +} + +/// Body text in the primary colour. Wraps. +pub fn body(ui: &mut Ui, text: impl Into) -> Response { + ui.add(Label::new(RichText::new(text).color(P.text)).wrap()) } -/// Slightly heavier body text, for the name in a list row. +/// Medium-weight body text, for a name in a row. pub fn strong(ui: &mut Ui, text: impl Into) -> Response { - ui.label(RichText::new(text).font(theme::medium(14.0)).color(P.text)) + ui.label( + RichText::new(text) + .font(theme::medium(text::BODY)) + .color(P.text), + ) } -/// A large, letter-spaced code such as a pairing PIN. -pub fn display_digits(ui: &mut Ui, digits: &str) -> Response { +/// Mono data: an address, a version, a rate. +pub fn mono(ui: &mut Ui, text: impl Into) -> Response { ui.label( - RichText::new(digits) - .text_style(theme::display()) - .extra_letter_spacing(6.0) - .color(P.accent), + RichText::new(text) + .font(theme::mono(text::MONO)) + .color(P.text_secondary), ) } -/// Vertically centred, muted text for an empty list. Wraps: a line that -/// ran past the card would widen everything laid out after it. +/// A code such as the pairing PIN, one digit to a box. +pub fn display_digits(ui: &mut Ui, digits: &str) -> Response { + let font = theme::mono_medium(text::DISPLAY); + let cell = Vec2::new(44.0, 56.0); + let n = digits.chars().count().max(1) as f32; + let total = Vec2::new(n * cell.x + (n - 1.0) * space::SM, cell.y); + let (rect, response) = ui.allocate_exact_size(total, Sense::hover()); + response.widget_info(|| { + let spoken: Vec = digits.chars().map(String::from).collect(); + WidgetInfo::labeled(WidgetType::Label, true, format!("PIN {}", spoken.join(" "))) + }); + if ui.is_rect_visible(rect) { + for (i, ch) in digits.chars().enumerate() { + let min = rect.min + Vec2::new(i as f32 * (cell.x + space::SM), 0.0); + let r = Rect::from_min_size(min, cell); + ui.painter().rect( + r, + CornerRadius::same(radius::SM), + P.raised, + stroke(1.0, P.border_strong), + StrokeKind::Inside, + ); + ui.painter() + .text(r.center(), Align2::CENTER_CENTER, ch, font.clone(), P.text); + } + } + response +} + +/// Text for a list with nothing in it yet, with a spinner while something +/// is being looked for. Wraps: a line that ran past the card would widen +/// everything laid out after it. pub fn empty_state(ui: &mut Ui, text: &str, busy: bool) { ui.horizontal_wrapped(|ui| { + ui.spacing_mut().item_spacing.x = space::SM; if busy { - ui.add(spinner(14.0, P.faint)); + ui.add(spinner(14.0, P.text_tertiary)); } - ui.label(RichText::new(text).color(P.muted)); + ui.label(RichText::new(text).color(P.text_secondary)); }); } -/// An inline message tinted by tone. Errors, warnings, confirmations. -pub fn notice(ui: &mut Ui, tone: Tone, text: &str) -> Response { - let color = tone.color(); +/// A dot in a fixed gutter and wrapped text beside it: every line of the +/// text starts at the same x, however many there are. +fn dotted(ui: &mut Ui, tone: Tone, add: impl FnOnce(&mut Ui) -> R) -> R { + let gutter = 16.0; + let avail = ui.available_rect_before_wrap(); + let text_rect = Rect::from_min_max( + Pos2::new(avail.left() + gutter, avail.top()), + Pos2::new(avail.right(), avail.bottom()), + ); + let mut child = ui.new_child( + UiBuilder::new() + .max_rect(text_rect) + .layout(Layout::top_down(Align::Min)), + ); + let r = add(&mut child); + let used = child.min_rect(); + // The dot sits on the first line's centre. + let first_line = ui.text_style_height(&egui::TextStyle::Body); + let dot_c = Pos2::new( + avail.left() + size::DOT / 2.0, + avail.top() + first_line / 2.0, + ); + ui.painter() + .circle_filled(dot_c, size::DOT / 2.0 - 0.5, tone.color()); + ui.allocate_rect( + Rect::from_min_max(avail.min, Pos2::new(avail.right(), used.bottom())), + Sense::hover(), + ); + r +} + +fn notice_frame(tone: Tone) -> Frame { Frame::new() - .fill(color.gamma_multiply(0.12)) - .stroke(stroke(1.0, color.gamma_multiply(0.5))) - .corner_radius(CornerRadius::same(RADIUS)) - .inner_margin(Margin::symmetric(12, 9)) + .fill(tone.wash()) + .stroke(stroke(1.0, tone.line())) + .corner_radius(CornerRadius::same(radius::MD)) + .inner_margin(margin(space::MD, space::MD - 2.0)) +} + +/// An inline message: errors, warnings, confirmations. The wash and outline +/// carry the tone; the text stays in the primary colour. +pub fn notice(ui: &mut Ui, tone: Tone, text: &str) -> Response { + notice_frame(tone) + .show(ui, |ui| { + ui.set_min_width(ui.available_width()); + dotted(ui, tone, |ui| { + ui.add(Label::new(RichText::new(text).color(P.text)).wrap()); + }); + }) + .response +} + +/// A [`notice`] with a title, a body and a row of actions under them. +pub fn banner( + ui: &mut Ui, + tone: Tone, + title: &str, + body: Option<&str>, + actions: impl FnOnce(&mut Ui), +) -> Response { + notice_frame(tone) .show(ui, |ui| { ui.set_min_width(ui.available_width()); - ui.horizontal_wrapped(|ui| { - ui.label(RichText::new("●").size(9.0).color(color)); - ui.label(RichText::new(text).color(P.text)); + dotted(ui, tone, |ui| { + ui.spacing_mut().item_spacing.y = space::XS; + ui.add( + Label::new( + RichText::new(title) + .font(theme::medium(text::BODY)) + .color(P.text), + ) + .wrap(), + ); + if let Some(b) = body { + muted(ui, b); + } + ui.add_space(space::XS); + ui.horizontal_wrapped(|ui| { + ui.spacing_mut().item_spacing.x = space::SM; + actions(ui) + }); }); }) .response @@ -280,28 +657,104 @@ pub fn notice(ui: &mut Ui, tone: Tone, text: &str) -> Response { // Key / value // --------------------------------------------------------------------------- +/// One line of a [`kv_grid`]. +pub struct Kv<'a> { + pub key: &'a str, + pub value: String, + /// A status dot before the value. + pub tone: Option, + /// Set the value in mono, for figures that should line up. + pub mono: bool, +} + +impl<'a> Kv<'a> { + pub fn new(key: &'a str, value: impl Into) -> Self { + Self { + key, + value: value.into(), + tone: None, + mono: false, + } + } + + pub fn mono(mut self) -> Self { + self.mono = true; + self + } + + pub fn tone(mut self, tone: Tone) -> Self { + self.tone = Some(tone); + self + } +} + /// Aligned key/value rows. A value wraps inside the card: an `egui::Grid` /// extends instead, and one row wider than the card widens every widget -/// laid out after it (the card, the rows below, the toggles off-screen). -pub fn kv_grid(ui: &mut Ui, rows: &[(&str, String)]) { - let font = egui::TextStyle::Body.resolve(ui.style()); +/// laid out after it. Below 300 points the key goes above its value. +pub fn kv_grid(ui: &mut Ui, rows: &[Kv<'_>]) { + let font = theme::regular(text::CAPTION); let key_w = rows .iter() - .map(|(k, _)| { + .map(|kv| { ui.painter() - .layout_no_wrap((*k).to_owned(), font.clone(), Color32::PLACEHOLDER) + .layout_no_wrap(kv.key.to_owned(), font.clone(), Color32::PLACEHOLDER) .size() .x }) - .fold(0.0_f32, f32::max); + .fold(0.0_f32, f32::max) + .min(ui.available_width() * 0.4); + let stacked = ui.available_width() < 300.0; ui.vertical(|ui| { - ui.spacing_mut().item_spacing = Vec2::new(18.0, 6.0); - for (k, v) in rows { - ui.horizontal_top(|ui| { - let key = ui.label(RichText::new(*k).color(P.muted)); - ui.add_space((key_w - key.rect.width()).max(0.0)); - ui.add(Label::new(RichText::new(v).color(P.text)).wrap()); - }); + ui.spacing_mut().item_spacing = Vec2::new(space::LG, space::SM); + for kv in rows { + let rich = || { + let t = RichText::new(&kv.value).color(P.text); + if kv.mono { + t.font(theme::mono(text::MONO)) + } else { + t + } + }; + let value = |ui: &mut Ui| { + if let Some(t) = kv.tone { + dotted(ui, t, |ui| { + ui.add(Label::new(rich()).wrap()); + }); + } else { + ui.add(Label::new(rich()).wrap()); + } + }; + if stacked { + ui.vertical(|ui| { + ui.spacing_mut().item_spacing.y = space::XXS; + ui.label( + RichText::new(kv.key) + .font(font.clone()) + .color(P.text_tertiary), + ); + value(ui); + }); + } else { + ui.horizontal_top(|ui| { + ui.allocate_ui_with_layout( + Vec2::new(key_w, 18.0), + Layout::top_down(Align::Min), + |ui| { + ui.set_width(key_w); + ui.add_space(1.5); + ui.add( + Label::new( + RichText::new(kv.key) + .font(font.clone()) + .color(P.text_tertiary), + ) + .truncate(), + ); + }, + ); + ui.vertical(value); + }); + } } }); } @@ -310,140 +763,475 @@ pub fn kv_grid(ui: &mut Ui, rows: &[(&str, String)]) { // Buttons // --------------------------------------------------------------------------- -fn text_button( +/// The five kinds of button. One `Primary` per screen at most. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Kind { + /// The screen's main action: white, like the site's primary button. + Primary, + /// A real action that is not the main one: outlined. + Secondary, + /// Everything else: text until hovered. + Quiet, + /// A quiet button that ends or removes something: red text. + Danger, + /// The confirming step of a destructive action: red fill. + Destructive, +} + +struct Look { + fill: Color32, + line: Color32, + ink: Color32, +} + +fn look(kind: Kind, enabled: bool, hovered: bool, pressed: bool) -> Look { + let none = Color32::TRANSPARENT; + if !enabled { + return match kind { + Kind::Primary | Kind::Destructive => Look { + fill: P.raised, + line: P.border, + ink: P.text_disabled, + }, + Kind::Secondary => Look { + fill: none, + line: P.hairline, + ink: P.text_disabled, + }, + Kind::Quiet | Kind::Danger => Look { + fill: none, + line: none, + ink: P.text_disabled, + }, + }; + } + match kind { + Kind::Primary => Look { + fill: if pressed { + P.primary_pressed + } else if hovered { + P.primary_hover + } else { + P.primary + }, + line: none, + ink: P.on_primary, + }, + Kind::Destructive => Look { + fill: if pressed || hovered { + lerp_color(P.danger_fill, Color32::BLACK, 0.12) + } else { + P.danger_fill + }, + line: none, + ink: Color32::WHITE, + }, + Kind::Secondary => Look { + fill: if pressed { + P.pressed + } else if hovered { + P.raised_hover + } else { + P.raised + }, + line: if hovered { P.border_strong } else { P.border }, + ink: P.text, + }, + Kind::Quiet => Look { + fill: if pressed { + P.pressed + } else if hovered { + P.raised_hover + } else { + none + }, + line: none, + ink: if hovered || pressed { + P.text + } else { + P.text_secondary + }, + }, + Kind::Danger => Look { + fill: if pressed || hovered { + P.danger.gamma_multiply(0.14) + } else { + none + }, + line: none, + ink: P.danger, + }, + } +} + +/// A text button of any [`Kind`], at the height of the surrounding +/// `interact_size` (32 points, 28 in the stream toolbar). +pub fn button(ui: &mut Ui, kind: Kind, label: &str) -> Response { + labelled_button(ui, kind, label, None, label) +} + +fn labelled_button( ui: &mut Ui, + kind: Kind, label: &str, - font: egui::FontId, - pad: Vec2, - paint: impl FnOnce(&Ui, &Response, egui::Rect) -> Color32, + trailing: Option, + a11y: &str, ) -> Response { + let font = theme::medium(if ui.spacing().interact_size.y < size::CONTROL { + text::CAPTION + 0.5 + } else { + text::BODY + }); let galley = ui .painter() .layout_no_wrap(label.to_owned(), font, Color32::PLACEHOLDER); - let mut size = galley.size() + 2.0 * pad; - size.y = size.y.max(ui.spacing().interact_size.y); - let (rect, response) = ui.allocate_exact_size(size, Sense::click()); + let h = ui.spacing().interact_size.y; + let pad = space::MD; + let icon_w = if trailing.is_some() { 16.0 } else { 0.0 }; + let w = (galley.size().x + 2.0 * pad + icon_w).max(h); + let (rect, response) = ui.allocate_exact_size(Vec2::new(w, h), Sense::click()); + response.widget_info(|| WidgetInfo::labeled(WidgetType::Button, ui.is_enabled(), a11y)); + if ui.is_rect_visible(rect) { + let l = look( + kind, + ui.is_enabled(), + response.hovered() || response.has_focus(), + response.is_pointer_button_down_on(), + ); + ui.painter().rect( + rect, + CornerRadius::same(radius::SM), + l.fill, + stroke(1.0, l.line), + StrokeKind::Inside, + ); + let text_x = rect.left() + pad + (w - 2.0 * pad - icon_w - galley.size().x) / 2.0; + let pos = Pos2::new(text_x, rect.center().y - galley.size().y / 2.0); + ui.painter().galley(pos, galley.clone(), l.ink); + if let Some(icon) = trailing { + paint_icon( + ui.painter(), + Pos2::new(pos.x + galley.size().x + 10.0, rect.center().y), + icon, + l.ink, + ); + } + focus_ring(ui, &response, radius::SM); + } + response +} + +/// The one white button on a screen. +pub fn primary_button(ui: &mut Ui, label: &str) -> Response { + button(ui, Kind::Primary, label) +} + +/// An outlined button for a real but secondary action. +pub fn secondary_button(ui: &mut Ui, label: &str) -> Response { + button(ui, Kind::Secondary, label) +} + +/// A quiet button: text only until hovered. +pub fn ghost_button(ui: &mut Ui, label: &str) -> Response { + button(ui, Kind::Quiet, label) +} + +/// A quiet button that ends or removes something. +pub fn danger_button(ui: &mut Ui, label: &str) -> Response { + button(ui, Kind::Danger, label) +} + +/// The red, filled confirming step of a destructive action. +pub fn destructive_button(ui: &mut Ui, label: &str) -> Response { + button(ui, Kind::Destructive, label) +} + +/// A square quiet button showing only `icon`; `label` is what a screen +/// reader says and the tooltip shows. +pub fn icon_button(ui: &mut Ui, icon: Icon, label: &str) -> Response { + let s = ui.spacing().interact_size.y; + let (rect, response) = ui.allocate_exact_size(Vec2::splat(s), Sense::click()); response.widget_info(|| WidgetInfo::labeled(WidgetType::Button, ui.is_enabled(), label)); if ui.is_rect_visible(rect) { - let fg = paint(ui, &response, rect); - let pos = rect.center() - galley.size() / 2.0; - ui.painter().galley(pos, galley, fg); - focus_ring(ui, &response, RADIUS); + let l = look( + Kind::Quiet, + ui.is_enabled(), + response.hovered() || response.has_focus(), + response.is_pointer_button_down_on(), + ); + ui.painter().rect( + rect, + CornerRadius::same(radius::SM), + l.fill, + Stroke::NONE, + StrokeKind::Inside, + ); + paint_icon(ui.painter(), rect.center(), icon, l.ink); + focus_ring(ui, &response, radius::SM); + } + response.on_hover_text(label) +} + +/// A link: primary text with a hairline underline that brightens on hover. +pub fn link(ui: &mut Ui, label: &str) -> Response { + let font = theme::regular(text::CAPTION); + let galley = ui + .painter() + .layout_no_wrap(label.to_owned(), font, Color32::PLACEHOLDER); + let size = Vec2::new(galley.size().x, galley.size().y.max(size::HIT_MIN)); + let (rect, response) = ui.allocate_exact_size(size, Sense::click()); + response.widget_info(|| WidgetInfo::labeled(WidgetType::Link, ui.is_enabled(), label)); + if ui.is_rect_visible(rect) { + let hot = response.hovered() || response.has_focus(); + let top = rect.center().y - galley.size().y / 2.0; + let base = top + galley.size().y + 1.0; + let w = galley.size().x; + ui.painter() + .galley(Pos2::new(rect.left(), top), galley, P.text); + ui.painter().hline( + rect.left()..=rect.left() + w, + base, + stroke(1.0, if hot { P.text } else { P.border_strong }), + ); + focus_ring(ui, &response, radius::SM); + } + response +} + +// --------------------------------------------------------------------------- +// Menus +// --------------------------------------------------------------------------- + +/// Open a popup menu under `trigger` while it is toggled on. A real egui +/// popup: `any_popup_open` is true while it shows, Escape and a click +/// outside close it. It hangs from the trigger's nearer edge, so a menu at +/// the right of the window opens leftwards instead of running off it. +fn popup_menu(ui: &mut Ui, trigger: &Response, add: impl FnOnce(&mut Ui) -> R) -> Option { + let id = trigger.id.with("menu"); + if trigger.clicked() { + ui.memory_mut(|m| m.toggle_popup(id)); + } + if !ui.memory(|m| m.is_popup_open(id)) { + return None; + } + let screen = ui.ctx().screen_rect(); + let (pos, pivot) = if trigger.rect.center().x > screen.center().x { + (trigger.rect.right_bottom(), Align2::RIGHT_TOP) + } else { + (trigger.rect.left_bottom(), Align2::LEFT_TOP) + }; + let shown = egui::Area::new(id) + .kind(egui::UiKind::Popup) + .order(egui::Order::Foreground) + .fixed_pos(pos + Vec2::new(0.0, space::XS)) + .pivot(pivot) + .constrain(true) + .show(ui.ctx(), |ui| { + Frame::popup(ui.style()) + .show(ui, |ui| { + ui.set_min_width(200.0); + ui.set_max_width(320.0); + ui.spacing_mut().item_spacing.y = 0.0; + ui.spacing_mut().interact_size.y = size::CONTROL_SM; + ui.with_layout(Layout::top_down_justified(Align::Min), add) + .inner + }) + .inner + }); + let outside = trigger.clicked_elsewhere() && shown.response.clicked_elsewhere(); + if outside || ui.input(|i| i.key_pressed(egui::Key::Escape)) { + ui.memory_mut(|m| m.close_popup()); + } + Some(shown.inner) +} + +/// A quiet button with a chevron that opens a menu. +pub fn menu_button(ui: &mut Ui, label: &str, add: impl FnOnce(&mut Ui) -> R) -> Option { + let trigger = labelled_button(ui, Kind::Quiet, label, Some(Icon::ChevronDown), label); + popup_menu(ui, &trigger, add) +} + +/// An icon button that opens a menu; `label` is its accessible name. +pub fn icon_menu( + ui: &mut Ui, + icon: Icon, + label: &str, + add: impl FnOnce(&mut Ui) -> R, +) -> Option { + let trigger = icon_button(ui, icon, label); + popup_menu(ui, &trigger, add) +} + +fn menu_row(ui: &mut Ui, label: &str, checked: Option, enabled: bool) -> Response { + let font = theme::regular(text::BODY - 0.5); + // The check column is always there, so every item's text lines up. + let gutter = 26.0; + let wrap = ui.available_width() - gutter - space::SM; + let galley = ui + .painter() + .layout(label.to_owned(), font, Color32::PLACEHOLDER, wrap); + let h = (galley.size().y + 10.0).max(size::CONTROL_SM); + let w = ui.available_width(); + let sense = if enabled { + Sense::click() + } else { + Sense::hover() + }; + let (rect, response) = ui.allocate_exact_size(Vec2::new(w, h), sense); + response.widget_info(|| match checked { + Some(on) => WidgetInfo::selected(WidgetType::RadioButton, enabled, on, label), + None => WidgetInfo::labeled(WidgetType::Button, enabled, label), + }); + if ui.is_rect_visible(rect) { + let hot = enabled && (response.hovered() || response.has_focus()); + if hot { + ui.painter() + .rect_filled(rect, CornerRadius::same(radius::SM), P.raised_hover); + } + let ink = if !enabled { + P.text_disabled + } else if hot { + P.text + } else { + P.text_secondary + }; + if checked == Some(true) { + paint_icon( + ui.painter(), + Pos2::new(rect.left() + 12.0, rect.top() + h.min(28.0) / 2.0), + Icon::Check, + P.text, + ); + } + ui.painter().galley( + Pos2::new( + rect.left() + gutter, + rect.center().y - galley.size().y / 2.0, + ), + galley, + ink, + ); + } + if response.clicked() { + ui.memory_mut(|m| m.close_popup()); } response } -/// Keep keyboard navigation visible for controls with custom painting. -fn focus_ring(ui: &Ui, response: &Response, radius: u8) { - if response.has_focus() && ui.is_enabled() { - ui.painter().rect_stroke( - response.rect.shrink(2.0), - CornerRadius::same(radius.saturating_sub(2)), - stroke(2.0, P.text), - StrokeKind::Inside, - ); - } +/// An item in a menu. Closes the menu when clicked. +pub fn menu_item(ui: &mut Ui, label: &str) -> Response { + menu_row(ui, label, None, true) } -/// The one accent-filled button on a screen. -pub fn primary_button(ui: &mut Ui, label: &str) -> Response { - filled_button(ui, label, P.accent_fill, P.on_accent) +/// A menu item that can be greyed out. +pub fn menu_item_enabled(ui: &mut Ui, enabled: bool, label: &str) -> Response { + menu_row(ui, label, None, enabled) } -/// A filled button in a tone other than the accent: red for the confirm -/// step of a destructive action. -pub fn toned_button(ui: &mut Ui, label: &str, tone: Tone) -> Response { - filled_button(ui, label, tone.color(), P.text) +/// A menu item with a check mark when chosen. +pub fn menu_choice(ui: &mut Ui, label: &str, chosen: bool) -> Response { + menu_row(ui, label, Some(chosen), true) } -fn filled_button(ui: &mut Ui, label: &str, color: Color32, fg: Color32) -> Response { - let pad = ui.spacing().button_padding + Vec2::new(4.0, 1.0); - text_button(ui, label, theme::medium(14.0), pad, |ui, r, rect| { - let enabled = ui.is_enabled(); - let (fill, border, text) = if !enabled { - (P.raised, P.border_strong.gamma_multiply(0.6), P.faint) - } else if r.is_pointer_button_down_on() { - ( - lerp_color(color, Color32::BLACK, 0.12), - Color32::TRANSPARENT, - fg, - ) - } else if r.hovered() { - ( - lerp_color(color, Color32::BLACK, 0.10), - Color32::TRANSPARENT, - fg, - ) - } else { - (color, Color32::TRANSPARENT, fg) - }; - ui.painter().rect( - rect, - CornerRadius::same(RADIUS), - fill, - stroke(1.0, border), - StrokeKind::Inside, - ); - text - }) +/// A heading inside a menu. +pub fn menu_label(ui: &mut Ui, label: &str) { + ui.add_space(space::XS + 2.0); + ui.horizontal(|ui| { + ui.add_space(26.0); + section_label(ui, label); + }); + ui.add_space(space::XS); } -/// A menu button with a painted chevron; the bundled fonts carry no -/// triangle glyph. -pub fn menu_button( - ui: &mut Ui, - label: &str, - add_contents: impl FnOnce(&mut Ui) -> R, -) -> Option { - let r = ui.menu_button(format!("{label} "), add_contents); - let rect = r.response.rect; - let c = egui::pos2(rect.right() - 13.0, rect.center().y - 1.0); - let col = ui.style().interact(&r.response).fg_stroke.color; - let s = stroke(1.5, col); - ui.painter().line_segment( - [egui::pos2(c.x - 4.0, c.y - 2.0), egui::pos2(c.x, c.y + 2.0)], - s, - ); - ui.painter().line_segment( - [egui::pos2(c.x, c.y + 2.0), egui::pos2(c.x + 4.0, c.y - 2.0)], - s, - ); - r.inner +/// A line of help at the foot of a menu. +pub fn menu_note(ui: &mut Ui, note: &str) { + ui.add_space(space::XS); + Frame::new() + .inner_margin(Margin { + left: 26, + right: space::SM as i8, + top: space::XS as i8, + bottom: space::XS as i8, + }) + .show(ui, |ui| { + small_print(ui, note); + }); } -/// A quiet button: text only until hovered. -pub fn ghost_button(ui: &mut Ui, label: &str) -> Response { - ghost(ui, label, P.muted, P.text) +/// A hairline between groups of menu items. +pub fn menu_separator(ui: &mut Ui) { + ui.add_space(space::XS); + let w = ui.available_width(); + let (rect, _) = ui.allocate_exact_size(Vec2::new(w, 1.0), Sense::hover()); + ui.painter() + .hline(rect.x_range(), rect.center().y, stroke(1.0, P.hairline)); + ui.add_space(space::XS); } -/// A quiet button for a destructive action; turns red on hover. -pub fn danger_button(ui: &mut Ui, label: &str) -> Response { - ghost(ui, label, P.muted, P.danger) -} +// --------------------------------------------------------------------------- +// Choices +// --------------------------------------------------------------------------- -fn ghost(ui: &mut Ui, label: &str, idle: Color32, hot: Color32) -> Response { - let pad = Vec2::new(10.0, 6.0); - text_button(ui, label, theme::medium(13.5), pad, |ui, r, rect| { - let enabled = ui.is_enabled(); - if enabled && (r.hovered() || r.is_pointer_button_down_on()) { - ui.painter().rect( - rect, - CornerRadius::same(RADIUS), - P.raised_hover, - Stroke::NONE, - StrokeKind::Inside, - ); - hot - } else if enabled { - idle - } else { - P.faint +/// Tabs in the top bar: text, with a rule under the chosen one sitting on +/// the bar's bottom hairline. Each option carries a tooltip (its keyboard +/// shortcut, say); empty for none. Returns true when the choice changed. +pub fn tabs( + ui: &mut Ui, + options: &[(T, &str, String)], + selected: &mut T, +) -> bool { + let mut changed = false; + let h = ui.available_height().max(size::CONTROL); + ui.spacing_mut().item_spacing.x = 0.0; + for (value, label, tip) in options { + let on = *selected == *value; + let galley = ui.painter().layout_no_wrap( + (*label).to_owned(), + theme::medium(text::BODY - 0.5), + Color32::PLACEHOLDER, + ); + let w = galley.size().x + 2.0 * space::MD; + let (rect, mut response) = ui.allocate_exact_size(Vec2::new(w, h), Sense::click()); + response.widget_info(|| { + WidgetInfo::selected(WidgetType::SelectableLabel, ui.is_enabled(), on, *label) + }); + if !tip.is_empty() { + response = response.on_hover_text(tip); } - }) + if response.clicked() && !on { + *selected = *value; + changed = true; + } + if ui.is_rect_visible(rect) { + let hot = response.hovered() || response.has_focus(); + let ink = if on || hot { P.text } else { P.text_secondary }; + ui.painter() + .galley(rect.center() - galley.size() / 2.0, galley, ink); + if on { + let y = rect.bottom() - 1.0; + ui.painter().line_segment( + [ + Pos2::new(rect.left() + space::MD, y), + Pos2::new(rect.right() - space::MD, y), + ], + stroke(2.0, P.text), + ); + } + if response.has_focus() { + ui.painter().rect_stroke( + rect.shrink2(Vec2::new(2.0, 10.0)), + CornerRadius::same(radius::SM), + stroke(1.5, P.accent), + StrokeKind::Outside, + ); + } + } + } + changed } -/// A row of mutually exclusive choices; the selected one is filled with the -/// accent. Returns true when the selection changed. +/// A row of mutually exclusive choices. The chosen one is filled white, +/// like "on" everywhere else. Returns true when the choice changed. /// /// Sized and allocated as one block, so it sits correctly in any parent /// layout (including the right-to-left side of a [`setting_row`]). @@ -452,8 +1240,8 @@ pub fn segmented( options: &[(T, &str)], selected: &mut T, ) -> bool { - let font = theme::medium(13.5); - let pad_x = 14.0; + let font = theme::medium(text::CAPTION + 0.5); + let pad_x = space::MD; let h = ui.spacing().interact_size.y; let galleys: Vec<_> = options .iter() @@ -466,11 +1254,20 @@ pub fn segmented( let total = Vec2::new(widths.iter().sum(), h); let (rect, block) = ui.allocate_exact_size(total, Sense::hover()); let visible = ui.is_rect_visible(rect); + if visible { + ui.painter().rect( + rect, + CornerRadius::same(radius::SM), + P.raised, + stroke(1.0, P.border), + StrokeKind::Inside, + ); + } let n = options.len(); let mut changed = false; let mut x = rect.left(); for (i, ((value, label), galley)) in options.iter().zip(galleys).enumerate() { - let seg = egui::Rect::from_min_size(egui::pos2(x, rect.top()), Vec2::new(widths[i], h)); + let seg = Rect::from_min_size(Pos2::new(x, rect.top()), Vec2::new(widths[i], h)); x += widths[i]; let resp = ui.interact(seg, block.id.with(i), Sense::click()); resp.widget_info(|| { @@ -489,33 +1286,30 @@ pub fn segmented( continue; } let on = *selected == *value; - let r = RADIUS; + let r = radius::SM; let corner = CornerRadius { nw: if i == 0 { r } else { 0 }, sw: if i == 0 { r } else { 0 }, ne: if i + 1 == n { r } else { 0 }, se: if i + 1 == n { r } else { 0 }, }; + let hot = resp.hovered() || resp.has_focus(); let (fill, fg) = if on { - (P.accent_fill, P.on_accent) - } else if resp.hovered() { + (P.primary, P.on_primary) + } else if hot { (P.raised_hover, P.text) } else { - (P.raised, P.muted) + (Color32::TRANSPARENT, P.text_secondary) }; ui.painter() .rect(seg, corner, fill, Stroke::NONE, StrokeKind::Inside); + if i > 0 && !on && *selected != options[i - 1].0 { + ui.painter() + .vline(seg.left(), seg.y_range().shrink(7.0), stroke(1.0, P.border)); + } ui.painter() .galley(seg.center() - galley.size() / 2.0, galley, fg); - focus_ring(ui, &resp, RADIUS); - } - if visible { - ui.painter().rect_stroke( - rect, - CornerRadius::same(RADIUS), - stroke(1.0, P.border_strong.gamma_multiply(0.6)), - StrokeKind::Inside, - ); + focus_ring(ui, &resp, radius::SM); } changed } @@ -526,8 +1320,11 @@ pub fn toggle(ui: &mut Ui, on: &mut bool) -> Response { } fn labelled_toggle(ui: &mut Ui, on: &mut bool, label: &str) -> Response { - let size = Vec2::new(40.0, 22.0); - let (rect, mut response) = ui.allocate_exact_size(size, Sense::click()); + let track = size::SWITCH; + let (rect, mut response) = ui.allocate_exact_size( + Vec2::new(track.x, track.y.max(size::HIT_MIN)), + Sense::click(), + ); if response.clicked() { *on = !*on; response.mark_changed(); @@ -535,36 +1332,172 @@ fn labelled_toggle(ui: &mut Ui, on: &mut bool, label: &str) -> Response { response .widget_info(|| WidgetInfo::selected(WidgetType::Checkbox, ui.is_enabled(), *on, label)); if ui.is_rect_visible(rect) { - let how_on = ui.ctx().animate_bool_responsive(response.id, *on); - let enabled = ui.is_enabled(); - let off = if response.hovered() { - P.raised_hover - } else { - P.raised - }; - let mut track = lerp_color(off, P.accent_fill, how_on); - let mut knob = lerp_color(P.muted, P.on_accent, how_on); - if !enabled { - track = track.gamma_multiply(0.5); + let t = ui.ctx().animate_bool_responsive(response.id, *on); + let track_rect = Rect::from_center_size(rect.center(), track); + let hot = response.hovered() || response.has_focus(); + let off = if hot { P.raised_hover } else { P.raised }; + let mut fill = lerp_color(off, P.primary, t); + let mut knob = lerp_color(P.text_tertiary, P.on_primary, t); + let mut line = lerp_color(P.border_strong, P.primary, t); + if !ui.is_enabled() { + fill = fill.gamma_multiply(0.5); knob = knob.gamma_multiply(0.5); + line = line.gamma_multiply(0.5); } - let radius = rect.height() / 2.0; + let r = track.y / 2.0; ui.painter().rect( - rect, - CornerRadius::same(radius as u8), - track, - stroke(1.0, P.border_strong.gamma_multiply(0.6 * (1.0 - how_on))), + track_rect, + CornerRadius::same(r as u8), + fill, + stroke(1.0, line), StrokeKind::Inside, ); - let x = egui::lerp((rect.left() + radius)..=(rect.right() - radius), how_on); + let x = egui::lerp((track_rect.left() + r)..=(track_rect.right() - r), t); ui.painter() - .circle_filled(egui::pos2(x, rect.center().y), radius - 4.0, knob); - focus_ring(ui, &response, radius as u8); + .circle_filled(Pos2::new(x, track_rect.center().y), r - 4.0, knob); + if response.has_focus() && ui.is_enabled() { + ui.painter().rect_stroke( + track_rect.expand(2.0), + CornerRadius::same(r as u8 + 2), + stroke(1.5, P.accent), + StrokeKind::Outside, + ); + } + } + response +} + +/// A dropdown in the house style, with a painted chevron. `add` fills the +/// list with `ui.selectable_value` rows. +pub fn select( + ui: &mut Ui, + id: impl std::hash::Hash, + selected: impl Into, + width: f32, + add: impl FnOnce(&mut Ui) -> R, +) -> Option { + egui::ComboBox::from_id_salt(id) + .selected_text(selected) + .width(width) + .height(320.0) + .icon(|ui, rect, visuals, _open, _| { + paint_icon( + ui.painter(), + rect.center(), + Icon::ChevronDown, + visuals.fg_stroke.color, + ); + }) + .show_ui(ui, |ui| { + ui.spacing_mut().item_spacing.y = 0.0; + ui.spacing_mut().button_padding = Vec2::new(space::SM, 6.0); + add(ui) + }) + .inner +} + +/// A horizontal slider for a whole number, with its value in mono beside +/// it. Drag or click the track; with keyboard focus the arrow keys step by +/// one and Shift steps by ten. +pub fn slider( + ui: &mut Ui, + value: &mut u32, + range: std::ops::RangeInclusive, + width: f32, + label: &str, +) -> Response { + let (lo, hi) = (*range.start(), *range.end()); + let knob = 7.0; + let (rect, mut response) = ui.allocate_exact_size( + Vec2::new(width, size::HIT_MIN.max(ui.spacing().interact_size.y)), + Sense::click_and_drag(), + ); + let rail = Rect::from_min_max( + Pos2::new(rect.left() + knob, rect.center().y - 2.0), + Pos2::new(rect.right() - knob, rect.center().y + 2.0), + ); + let before = *value; + if let Some(p) = response.interact_pointer_pos() { + if response.dragged() || response.clicked() { + let t = ((p.x - rail.left()) / rail.width()).clamp(0.0, 1.0); + *value = (lo as f32 + t * (hi - lo) as f32).round() as u32; + } + } + if response.has_focus() { + ui.memory_mut(|m| { + m.set_focus_lock_filter( + response.id, + egui::EventFilter { + horizontal_arrows: true, + vertical_arrows: true, + ..Default::default() + }, + ) + }); + let (up, down, big) = ui.input(|i| { + ( + i.key_pressed(egui::Key::ArrowRight) || i.key_pressed(egui::Key::ArrowUp), + i.key_pressed(egui::Key::ArrowLeft) || i.key_pressed(egui::Key::ArrowDown), + i.modifiers.shift, + ) + }); + let step = if big { 10 } else { 1 }; + if up { + *value = value.saturating_add(step); + } + if down { + *value = value.saturating_sub(step); + } + } + *value = (*value).clamp(lo, hi); + if *value != before { + response.mark_changed(); + } + response.widget_info(|| WidgetInfo::slider(ui.is_enabled(), f64::from(*value), label)); + if ui.is_rect_visible(rect) { + let t = (*value - lo) as f32 / (hi - lo).max(1) as f32; + let x = egui::lerp(rail.left()..=rail.right(), t); + let enabled = ui.is_enabled(); + let r = CornerRadius::same(2); + ui.painter().rect_filled(rail, r, P.border_strong); + ui.painter().rect_filled( + Rect::from_min_max(rail.min, Pos2::new(x, rail.max.y)), + r, + if enabled { P.text_secondary } else { P.border }, + ); + let hot = response.hovered() || response.dragged() || response.has_focus(); + let c = Pos2::new(x, rail.center().y); + ui.painter().circle( + c, + if hot { knob + 1.0 } else { knob }, + if enabled { P.primary } else { P.text_disabled }, + stroke(2.0, P.surface), + ); + if response.has_focus() { + ui.painter() + .circle_stroke(c, knob + 4.0, stroke(1.5, P.accent)); + } } response } -fn lerp_color(a: Color32, b: Color32, t: f32) -> Color32 { +/// The value beside a [`slider`], in a fixed width so the row does not +/// shift as it changes. +pub fn slider_value(ui: &mut Ui, text: &str) { + ui.allocate_ui_with_layout( + Vec2::new(64.0, size::HIT_MIN), + Layout::right_to_left(Align::Center), + |ui| { + ui.label( + RichText::new(text) + .font(theme::mono(text::MONO)) + .color(P.text), + ); + }, + ); +} + +pub(crate) fn lerp_color(a: Color32, b: Color32, t: f32) -> Color32 { let l = |x: u8, y: u8| (x as f32 + (y as f32 - x as f32) * t).round() as u8; Color32::from_rgba_unmultiplied( l(a.r(), b.r()), @@ -575,41 +1508,45 @@ fn lerp_color(a: Color32, b: Color32, t: f32) -> Color32 { } // --------------------------------------------------------------------------- -// Settings rows +// Rows // --------------------------------------------------------------------------- /// A row with text on the left and controls on the right. The controls are /// laid out first, so the text gets exactly the width they leave and wraps /// there instead of running underneath them. When they leave too little, /// the text takes the full width below the controls. +/// +/// `min_h` is the row's height with padding; controls centre in it and +/// text starts `text_top` below its top. fn split_row( ui: &mut Ui, - min_height: f32, + min_h: f32, + text_top: f32, right: impl FnOnce(&mut Ui) -> R, left: impl FnOnce(&mut Ui), ) -> R { let avail = ui.available_rect_before_wrap(); - let row = Rect::from_min_size(avail.min, Vec2::new(avail.width(), min_height)); + let row = Rect::from_min_size(avail.min, Vec2::new(avail.width(), min_h)); let mut right_ui = ui.new_child( UiBuilder::new() .max_rect(row) .layout(Layout::right_to_left(Align::Center)), ); - right_ui.spacing_mut().item_spacing.x = 6.0; + right_ui.spacing_mut().item_spacing.x = space::SM; let r = right(&mut right_ui); let used = right_ui.min_rect(); let taken = if used.is_positive() { - row.max.x - used.min.x + 14.0 + row.max.x - used.min.x + space::LG } else { 0.0 }; let beside = row.width() - taken; let text_rect = if beside >= MIN_TEXT_BESIDE_CONTROLS || taken == 0.0 { - Rect::from_min_size(row.min, Vec2::new(beside, min_height)) + Rect::from_min_size(row.min + Vec2::new(0.0, text_top), Vec2::new(beside, min_h)) } else { Rect::from_min_size( - egui::pos2(row.min.x, used.max.y + 6.0), - Vec2::new(row.width(), min_height), + Pos2::new(row.min.x, used.max.y + space::XS), + Vec2::new(row.width(), min_h), ) }; let mut left_ui = ui.new_child( @@ -617,32 +1554,35 @@ fn split_row( .max_rect(text_rect) .layout(Layout::top_down(Align::Min)), ); - left_ui.spacing_mut().item_spacing.y = 1.0; + left_ui.spacing_mut().item_spacing.y = space::XXS; left(&mut left_ui); - let bottom = left_ui - .min_rect() - .max - .y + let bottom = (left_ui.min_rect().max.y + text_top) .max(used.max.y) - .max(row.min.y + min_height); + .max(row.min.y + min_h); ui.allocate_rect( - Rect::from_min_max(row.min, egui::pos2(row.max.x, bottom)), + Rect::from_min_max(row.min, Pos2::new(row.max.x, bottom)), Sense::hover(), ); r } /// Below this, the text goes under the controls instead of beside them. -const MIN_TEXT_BESIDE_CONTROLS: f32 = 160.0; +const MIN_TEXT_BESIDE_CONTROLS: f32 = 180.0; -/// Label (and optional hint) on the left, a control on the right. +/// A settings row: a label (and optional hint) on the left, a control on +/// the right. pub fn setting_row( ui: &mut Ui, label: &str, hint: Option<&str>, control: impl FnOnce(&mut Ui) -> R, ) -> R { - split_row(ui, 30.0, control, |ui| { + let (h, top) = if hint.is_some() { + (56.0, 10.0) + } else { + (size::ROW, 13.0) + }; + split_row(ui, h, top, control, |ui| { ui.label(RichText::new(label).color(P.text)); if let Some(h) = hint { caption(ui, h); @@ -650,46 +1590,29 @@ pub fn setting_row( }) } -/// Third-party notices, as bundled at build time. Both windows show these. -pub const NOTICES: &str = include_str!("../../../NOTICE"); - -/// The Settings "Open source" row: a button that unfolds [`NOTICES`] in a -/// scrolling well beneath the row. -pub fn open_source_row(ui: &mut Ui, open: &mut bool) { - setting_row( - ui, - "Open source", - Some("BroLink is GPL-3.0 and builds on other free software."), - |ui| { - if ghost_button( - ui, - if *open { - "Hide notices" - } else { - "Show notices" - }, - ) - .clicked() - { - *open = !*open; - } - }, - ); - if *open { - well(ui, |ui| { - egui::ScrollArea::vertical() - .id_salt("open_source_notices") - .min_scrolled_height(160.0) - .max_height(240.0) - .show(ui, |ui| { - ui.label( - RichText::new(NOTICES) - .text_style(theme::caption()) - .color(P.muted), - ); - }); - }); +/// A settings row whose control is too wide to sit beside its label: the +/// label and hint, then the control under them. +pub fn setting_block( + ui: &mut Ui, + label: &str, + hint: Option<&str>, + control: impl FnOnce(&mut Ui) -> R, +) -> R { + ui.add_space(space::MD - 2.0); + ui.label(RichText::new(label).color(P.text)); + if let Some(h) = hint { + ui.add_space(space::XXS); + caption(ui, h); } + ui.add_space(space::SM); + let r = ui + .horizontal_wrapped(|ui| { + ui.spacing_mut().item_spacing = Vec2::new(space::SM, space::SM); + control(ui) + }) + .inner; + ui.add_space(space::MD); + r } /// A [`setting_row`] whose control is a [`toggle`]. Returns true on change. @@ -699,87 +1622,226 @@ pub fn toggle_row(ui: &mut Ui, on: &mut bool, label: &str, hint: Option<&str>) - }) } -// --------------------------------------------------------------------------- -// Lists -// --------------------------------------------------------------------------- +/// Third-party notices, as bundled at build time. +pub const NOTICES: &str = include_str!("../../../NOTICE"); -/// A row in a list of PCs: name and detail on the left, actions on the -/// right. Add the primary action first; it lands rightmost. The detail -/// stays on one line and is cut with an ellipsis rather than wrapping. -pub fn list_row(ui: &mut Ui, title: &str, detail: &str, actions: impl FnOnce(&mut Ui)) { - split_row(ui, 40.0, actions, |ui| { - strong(ui, title); - ui.add( - Label::new( - RichText::new(detail) - .text_style(theme::caption()) - .color(P.muted), - ) - .truncate(), - ) - .on_hover_text(detail); +/// A row in a list of machines or devices: an optional status dot, the +/// name and one line of detail on the left, actions on the right. Add the +/// primary action first; it lands rightmost. The detail stays on one line +/// and is cut with an ellipsis rather than wrapping; the full text shows +/// on hover. +pub fn list_row( + ui: &mut Ui, + status: Option, + title: &str, + detail: &str, + actions: impl FnOnce(&mut Ui), +) { + let start = ui.available_rect_before_wrap().min; + let indent = if status.is_some() { 20.0 } else { 0.0 }; + split_row(ui, 60.0, 11.0, actions, |ui| { + ui.horizontal(|ui| { + ui.add_space(indent); + ui.vertical(|ui| { + ui.spacing_mut().item_spacing.y = space::XXS; + ui.add( + Label::new( + RichText::new(title) + .font(theme::medium(text::BODY)) + .color(P.text), + ) + .truncate(), + ); + ui.add( + Label::new( + RichText::new(detail) + .text_style(theme::caption()) + .color(P.text_secondary), + ) + .truncate(), + ) + .on_hover_text(detail); + }); + }); }); + if let Some(t) = status { + let c = Pos2::new(start.x + size::DOT / 2.0 + 1.0, start.y + 11.0 + 9.0); + ui.painter().circle_filled(c, size::DOT / 2.0, t.color()); + } } -/// Hairline between list rows. +/// Hairline between rows of a [`group`] or a card. pub fn row_separator(ui: &mut Ui) { - ui.add_space(4.0); let w = ui.available_width(); let (rect, _) = ui.allocate_exact_size(Vec2::new(w, 1.0), Sense::hover()); ui.painter() - .hline(rect.x_range(), rect.center().y, stroke(1.0, P.border)); - ui.add_space(4.0); + .hline(rect.x_range(), rect.center().y, stroke(1.0, P.hairline)); } // --------------------------------------------------------------------------- // Status // --------------------------------------------------------------------------- -/// A rounded chip with a coloured dot: READY, STREAMING, and so on. -pub fn status_pill(ui: &mut Ui, label: &str, tone: Tone) -> Response { - let color = tone.color(); - let galley = ui - .painter() - .layout_no_wrap(label.to_owned(), theme::medium(12.5), color); - let dot = 7.0; - let gap = 7.0; - let pad = Vec2::new(10.0, 5.0); +/// A status dot on its own. +pub fn status_dot(ui: &mut Ui, tone: Tone) -> Response { + let (rect, response) = ui.allocate_exact_size(Vec2::splat(size::DOT), Sense::hover()); + ui.painter() + .circle_filled(rect.center(), size::DOT / 2.0, tone.color()); + response +} + +/// A dot and a short secondary label: "● Online", "● Tailscale off". +/// Painted as one piece, so the dot leads in any layout direction. +pub fn status_text(ui: &mut Ui, tone: Tone, label: &str) -> Response { + let galley = ui.painter().layout_no_wrap( + label.to_owned(), + theme::regular(text::CAPTION), + Color32::PLACEHOLDER, + ); + let gap = space::SM - 2.0; let size = Vec2::new( - galley.size().x + dot + gap + 2.0 * pad.x, - (galley.size().y + 2.0 * pad.y).max(26.0), + size::DOT + gap + galley.size().x, + galley.size().y.max(size::DOT), ); let (rect, response) = ui.allocate_exact_size(size, Sense::hover()); + response.widget_info(|| WidgetInfo::labeled(WidgetType::Label, true, label)); + if ui.is_rect_visible(rect) { + ui.painter().circle_filled( + Pos2::new(rect.left() + size::DOT / 2.0, rect.center().y), + size::DOT / 2.0, + tone.color(), + ); + ui.painter().galley( + Pos2::new( + rect.left() + size::DOT + gap, + rect.center().y - galley.size().y / 2.0, + ), + galley, + P.text_secondary, + ); + } + response +} + +/// A small outlined mono tag for a fact: "1920 × 1080", "HEVC". With a +/// tone, a status dot leads it. +pub fn tag(ui: &mut Ui, tone: Option, label: &str) -> Response { + let font = theme::mono(text::LABEL + 0.5); + let galley = ui + .painter() + .layout_no_wrap(label.to_owned(), font, Color32::PLACEHOLDER); + let dot = if tone.is_some() { size::DOT + 6.0 } else { 0.0 }; + let pad = space::SM; + let size = Vec2::new(galley.size().x + dot + 2.0 * pad, 22.0); + let (rect, response) = ui.allocate_exact_size(size, Sense::hover()); + response.widget_info(|| WidgetInfo::labeled(WidgetType::Label, true, label)); if ui.is_rect_visible(rect) { ui.painter().rect( rect, - CornerRadius::same(13), - color.gamma_multiply(0.14), - stroke(1.0, color.gamma_multiply(0.45)), + CornerRadius::same(radius::SM), + Color32::TRANSPARENT, + stroke(1.0, P.border), StrokeKind::Inside, ); - ui.painter().circle_filled( - egui::pos2(rect.left() + pad.x + dot / 2.0, rect.center().y), - dot / 2.0, - color, - ); + if let Some(t) = tone { + ui.painter().circle_filled( + Pos2::new(rect.left() + pad + size::DOT / 2.0, rect.center().y), + size::DOT / 2.0 - 0.5, + t.color(), + ); + } ui.painter().galley( - egui::pos2( - rect.left() + pad.x + dot + gap, + Pos2::new( + rect.left() + pad + dot, rect.center().y - galley.size().y / 2.0, ), galley, - color, + P.text_secondary, ); } response } -/// A small coloured dot followed by text, for inline status lines. +/// A small coloured dot followed by wrapped text, for checklists. pub fn dot_label(ui: &mut Ui, tone: Tone, text: &str) { + dotted(ui, tone, |ui| { + ui.add(Label::new(RichText::new(text).color(P.text)).wrap()); + }); +} + +/// A key as printed on the keyboard, in a box. +pub fn keycap(ui: &mut Ui, key: &str) -> Response { + let font = theme::mono_medium(text::LABEL); + let galley = ui + .painter() + .layout_no_wrap(key.to_owned(), font, Color32::PLACEHOLDER); + let size = Vec2::new((galley.size().x + 10.0).max(20.0), 20.0); + let (rect, response) = ui.allocate_exact_size(size, Sense::hover()); + if ui.is_rect_visible(rect) { + ui.painter().rect( + rect, + CornerRadius::same(radius::SM), + P.raised, + stroke(1.0, P.border_strong), + StrokeKind::Inside, + ); + ui.painter() + .galley(rect.center() - galley.size() / 2.0, galley, P.text); + } + response +} + +/// A chord as keycaps, then what it does: `[Ctrl] [Alt] releases the mouse`. +pub fn shortcut(ui: &mut Ui, keys: &[&str], does: &str) -> Response { + let label = format!("{} {does}", keys.join("+")); + let r = ui + .horizontal(|ui| { + ui.spacing_mut().item_spacing.x = space::XS; + for k in keys { + keycap(ui, k); + } + ui.add_space(space::XS); + ui.label( + RichText::new(does) + .text_style(theme::caption()) + .color(P.text_secondary), + ); + }) + .response; + r.widget_info(|| WidgetInfo::labeled(WidgetType::Label, true, &label)); + r +} + +/// Where a multi-step job is: done steps ticked, the current one in the +/// accent, the rest quiet. Mono uppercase, as on the product site. +pub fn steps(ui: &mut Ui, labels: &[&str], current: usize) { ui.horizontal_wrapped(|ui| { - ui.spacing_mut().item_spacing.x = 6.0; - ui.label(RichText::new("●").size(9.0).color(tone.color())); - ui.label(RichText::new(text).color(P.text)); + ui.spacing_mut().item_spacing = Vec2::new(space::SM, space::XS); + for (i, label) in labels.iter().enumerate() { + if i > 0 { + let (r, _) = ui.allocate_exact_size(Vec2::new(16.0, 16.0), Sense::hover()); + ui.painter() + .hline(r.x_range(), r.center().y, stroke(1.0, P.border)); + } + let (ink, mark) = match i.cmp(¤t) { + std::cmp::Ordering::Less => (P.text_secondary, Some(Icon::Check)), + std::cmp::Ordering::Equal => (P.accent, None), + std::cmp::Ordering::Greater => (P.text_tertiary, None), + }; + if let Some(icon) = mark { + let (r, _) = ui.allocate_exact_size(Vec2::splat(12.0), Sense::hover()); + paint_icon(ui.painter(), r.center(), icon, ink); + } else if i == current { + let (r, _) = ui.allocate_exact_size(Vec2::splat(12.0), Sense::hover()); + ui.painter().circle_filled(r.center(), 3.5, ink); + } + ui.label( + RichText::new(label.to_uppercase()) + .font(theme::mono_medium(text::LABEL)) + .extra_letter_spacing(text::LABEL_TRACKING) + .color(ink), + ); + } }); } @@ -787,7 +1849,7 @@ pub fn dot_label(ui: &mut Ui, tone: Tone, text: &str) { // Brand // --------------------------------------------------------------------------- -/// The app icon as a texture, for the window header. +/// The app icon as a texture, for the window's top bar. pub struct Brand { tex: TextureHandle, } @@ -802,24 +1864,27 @@ impl Brand { } } - /// Icon and product name on the left; `right` is laid out from the - /// right edge. - pub fn header(&self, ui: &mut Ui, title: &str, right: impl FnOnce(&mut Ui)) { + /// The mark and the product name, as on the product site. + pub fn lockup(&self, ui: &mut Ui, name: &str) { ui.horizontal(|ui| { - ui.spacing_mut().item_spacing.x = 10.0; - ui.add(egui::Image::new((self.tex.id(), Vec2::splat(28.0)))); + ui.spacing_mut().item_spacing.x = space::SM; + ui.add( + egui::Image::new((self.tex.id(), Vec2::splat(22.0))) + .corner_radius(CornerRadius::same(5)), + ); ui.label( - RichText::new(title) - .font(theme::semibold(17.0)) + RichText::new(name) + .font(theme::semibold(text::TITLE + 1.0)) + .extra_letter_spacing(-0.3) .color(P.text), ); - ui.with_layout(Layout::right_to_left(Align::Center), right); }); } } /// A paced activity indicator: unlike egui's immediate-repaint spinner this -/// stays bounded when the low-latency stream renderer has vsync disabled. +/// asks for at most 30 frames a second, and only while it is on screen, so +/// it stays bounded when the low-latency stream renderer has vsync off. pub fn spinner(size: f32, color: egui::Color32) -> impl egui::Widget { move |ui: &mut egui::Ui| { let (rect, response) = @@ -833,7 +1898,7 @@ pub fn spinner(size: f32, color: egui::Color32) -> impl egui::Widget { }) .collect(); ui.painter() - .add(egui::Shape::line(points, egui::Stroke::new(2.0_f32, color))); + .add(egui::Shape::line(points, egui::Stroke::new(1.5_f32, color))); ui.ctx() .request_repaint_after(std::time::Duration::from_millis(33)); } @@ -844,32 +1909,54 @@ pub fn spinner(size: f32, color: egui::Color32) -> impl egui::Widget { #[cfg(test)] mod tests { use super::*; + use crate::theme::contrast; - fn lin(c: u8) -> f64 { - let s = f64::from(c) / 255.0; - if s <= 0.04045 { - s / 12.92 - } else { - ((s + 0.055) / 1.055).powf(2.4) + #[test] + fn every_button_kind_keeps_its_label_readable() { + let surfaces = [P.bg, P.surface]; + for kind in [ + Kind::Primary, + Kind::Secondary, + Kind::Quiet, + Kind::Danger, + Kind::Destructive, + ] { + for (hovered, pressed) in [(false, false), (true, false), (true, true)] { + let l = look(kind, true, hovered, pressed); + for s in surfaces { + let behind = if l.fill.a() == 255 { + l.fill + } else if l.fill == Color32::TRANSPARENT { + s + } else { + // A translucent wash over the surface. + lerp_color(s, l.fill.to_opaque(), f32::from(l.fill.a()) / 255.0) + }; + let c = contrast(l.ink, behind); + assert!( + c >= 4.5, + "{kind:?} hovered={hovered} pressed={pressed}: {c:.2}:1" + ); + } + } } } - fn contrast(fg: Color32, bg: Color32) -> f64 { - let lum = |c: Color32| 0.2126 * lin(c.r()) + 0.7152 * lin(c.g()) + 0.0722 * lin(c.b()); - let (a, b) = (lum(fg), lum(bg)); - let (hi, lo) = if a > b { (a, b) } else { (b, a) }; - (hi + 0.05) / (lo + 0.05) - } - #[test] - fn accent_button_text_meets_aa_in_all_states() { - let idle = P.accent_fill; - let hovered = lerp_color(idle, Color32::BLACK, 0.10); - let pressed = lerp_color(idle, Color32::BLACK, 0.12); - assert!(contrast(P.on_accent, idle) >= 4.5); - assert!(contrast(P.on_accent, hovered) >= 4.5); - assert!(contrast(P.on_accent, pressed) >= 4.5); - // Hover must darken, never lighten: lightening drops the ratio below AA. - assert!(contrast(P.on_accent, lerp_color(idle, Color32::WHITE, 0.10)) < 4.5); + fn tones_have_distinct_marks_except_the_two_plain_ones() { + assert_eq!(Tone::Neutral.color(), Tone::Info.color()); + let marks = [ + Tone::Neutral, + Tone::Accent, + Tone::Success, + Tone::Warning, + Tone::Danger, + ] + .map(Tone::color); + for (i, a) in marks.iter().enumerate() { + for b in &marks[i + 1..] { + assert_ne!(a, b); + } + } } } From d35496fac64e66ebca37ed258d1e99e4460c4da3 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:33:09 -0700 Subject: [PATCH 26/52] Keep one copy of the tailnet check, the PowerShell probe and window setup - The 100.64.0.0/10 test lived in core's wake.rs and again in the host service, with the IPv6 variant only in the service. Both are now tailscale::is_tailnet and is_tailnet_ip. - wake.rs and virtual_display.rs each carried the same powershell() runner; setup.rs, with the other PowerShell helpers, has the one. - brolink-host and the development viewer each built their own window options, and only the viewer's explained the vsync-off choice they share. brolink_client::native_options() is used by both, and the host no longer depends on egui and egui-wgpu directly. Co-Authored-By: Claude Opus 5.5 (1M context) --- Cargo.lock | 2 -- crates/client/src/lib.rs | 37 +++++++++++++++++++++++++++++ crates/client/src/main.rs | 38 +++--------------------------- crates/core/src/tailscale.rs | 28 +++++++++++++++++++++- crates/core/src/wake.rs | 9 +++---- crates/host/Cargo.toml | 2 -- crates/host/src/main.rs | 26 +------------------- crates/host/src/service.rs | 30 ++--------------------- crates/host/src/setup.rs | 18 ++++++++++++++ crates/host/src/virtual_display.rs | 20 +--------------- crates/host/src/wake.rs | 19 +-------------- 11 files changed, 93 insertions(+), 136 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2ae5ffe..c4f01d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -740,8 +740,6 @@ dependencies = [ "brolink-ui", "clap", "eframe", - "egui", - "egui-wgpu", "egui_kittest", "image", "parking_lot", diff --git a/crates/client/src/lib.rs b/crates/client/src/lib.rs index e3bec19..283fd84 100644 --- a/crates/client/src/lib.rs +++ b/crates/client/src/lib.rs @@ -13,3 +13,40 @@ pub mod share; pub mod stream; pub mod update; pub mod video; + +/// How a BroLink window opens: the brolink-host app and the development +/// viewer alike. +pub fn native_options() -> eframe::NativeOptions { + let icon = if cfg!(target_os = "macos") { + // eframe calls NSApplication.setApplicationIconImage with this + // bitmap, which replaces BroLink.icns in the Dock with an unmasked + // square. An empty icon leaves the bundle icon in place. + egui::IconData::default() + } else { + egui::IconData { + rgba: brolink_core::icon::render(64), + width: 64, + height: 64, + } + }; + eframe::NativeOptions { + viewport: egui::ViewportBuilder::default() + .with_inner_size([1100.0, 720.0]) + .with_min_inner_size([640.0, 420.0]) + .with_title("BroLink") + .with_icon(icon), + renderer: eframe::Renderer::Wgpu, + // A decoded frame goes to the screen as soon as it is drawn rather + // than waiting for the display's next refresh: up to a whole + // refresh interval less between the PC's picture and the eye. The + // picture only changes when a frame arrives, so there is nothing + // to tear. + vsync: false, + wgpu_options: egui_wgpu::WgpuConfiguration { + present_mode: egui_wgpu::wgpu::PresentMode::AutoNoVsync, + desired_maximum_frame_latency: Some(1), + ..Default::default() + }, + ..Default::default() + } +} diff --git a/crates/client/src/main.rs b/crates/client/src/main.rs index a4a1108..ccd9771 100644 --- a/crates/client/src/main.rs +++ b/crates/client/src/main.rs @@ -1,19 +1,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -fn viewport_icon() -> egui::IconData { - if cfg!(target_os = "macos") { - // eframe calls NSApplication.setApplicationIconImage with this - // bitmap, which replaces BroLink.icns in the Dock with an unmasked - // square. An empty icon leaves the bundle icon in place. - egui::IconData::default() - } else { - egui::IconData { - rgba: brolink_core::icon::render(64), - width: 64, - height: 64, - } - } -} +//! The viewer on its own, for development: `cargo run -p brolink-client`. +//! What ships is `brolink-host`, which is this window plus sharing. fn main() -> anyhow::Result<()> { tracing_subscriber::fmt() @@ -23,29 +11,9 @@ fn main() -> anyhow::Result<()> { ) .with_target(false) .init(); - let native = eframe::NativeOptions { - viewport: egui::ViewportBuilder::default() - .with_inner_size([1100.0, 720.0]) - .with_min_inner_size([640.0, 420.0]) - .with_title("BroLink") - .with_icon(viewport_icon()), - renderer: eframe::Renderer::Wgpu, - // A decoded frame goes to the screen as soon as it is drawn rather - // than waiting for the display's next refresh: up to a whole - // refresh interval less between the PC's picture and the eye. The - // picture only changes when a frame arrives, so there is nothing - // to tear. - vsync: false, - wgpu_options: egui_wgpu::WgpuConfiguration { - present_mode: egui_wgpu::wgpu::PresentMode::AutoNoVsync, - desired_maximum_frame_latency: Some(1), - ..Default::default() - }, - ..Default::default() - }; eframe::run_native( "BroLink", - native, + brolink_client::native_options(), Box::new(|cc| Ok(Box::new(brolink_client::app::ClientApp::new(cc)))), ) .map_err(|e| anyhow::anyhow!("{e}")) diff --git a/crates/core/src/tailscale.rs b/crates/core/src/tailscale.rs index 19ff41c..843ec78 100644 --- a/crates/core/src/tailscale.rs +++ b/crates/core/src/tailscale.rs @@ -5,7 +5,7 @@ use anyhow::{bail, Context, Result}; use serde::Deserialize; use std::collections::BTreeMap; use std::io::Read; -use std::net::Ipv4Addr; +use std::net::{IpAddr, Ipv4Addr}; use std::path::PathBuf; use std::process::{Command, Stdio}; use std::sync::OnceLock; @@ -328,6 +328,20 @@ pub fn netcheck() -> Result { parse_netcheck(&out) } +/// An address Tailscale hands out: IPv4 from 100.64.0.0/10. +pub fn is_tailnet(ip: Ipv4Addr) -> bool { + let o = ip.octets(); + o[0] == 100 && (64..128).contains(&o[1]) +} + +/// [`is_tailnet`], or IPv6 unique-local (Tailscale uses fd7a:115c:a1e0::/48). +pub fn is_tailnet_ip(ip: IpAddr) -> bool { + match ip { + IpAddr::V4(v4) => is_tailnet(v4), + IpAddr::V6(v6) => v6.octets()[0] & 0xfe == 0xfc, + } +} + /// The three-letter code of a DERP region, as `tailscale status` prints it. pub fn derp_code(region: i32) -> &'static str { match region { @@ -704,6 +718,18 @@ mod tests { assert_eq!(output.len(), 1048576); } + #[test] + fn tailnet_range_is_recognised() { + assert!(is_tailnet("100.64.0.1".parse().unwrap())); + assert!(is_tailnet("100.127.255.254".parse().unwrap())); + assert!(!is_tailnet("100.128.0.1".parse().unwrap())); + assert!(!is_tailnet("192.168.1.2".parse().unwrap())); + assert!(is_tailnet_ip("100.111.100.57".parse().unwrap())); + assert!(is_tailnet_ip("fd7a:115c:a1e0::9e2a:381c".parse().unwrap())); + assert!(!is_tailnet_ip("8.8.8.8".parse().unwrap())); + assert!(!is_tailnet_ip("2001:4860:4860::8888".parse().unwrap())); + } + #[test] fn the_tailnet_name_is_read_when_present() { let st = parse_status( diff --git a/crates/core/src/wake.rs b/crates/core/src/wake.rs index a51dfbd..1b44dd3 100644 --- a/crates/core/src/wake.rs +++ b/crates/core/src/wake.rs @@ -84,7 +84,9 @@ pub fn plan( .unwrap_or_default() .into_iter() .filter_map(|i| match i.addr { - if_addrs::IfAddr::V4(a) if !a.ip.is_loopback() && !is_tailnet(a.ip) => { + if_addrs::IfAddr::V4(a) + if !a.ip.is_loopback() && !crate::tailscale::is_tailnet(a.ip) => + { Some((a.ip, a.broadcast)) } _ => None, @@ -111,11 +113,6 @@ pub fn plan( out } -fn is_tailnet(ip: Ipv4Addr) -> bool { - let o = ip.octets(); - o[0] == 100 && (64..128).contains(&o[1]) -} - /// Fire the packet everywhere in [`plan`]. Returns how many sends the OS /// accepted; unroutable targets are normal and only logged. pub fn send(mac: MacAddr, lan_ip: Option, public_ip: Option) -> usize { diff --git a/crates/host/Cargo.toml b/crates/host/Cargo.toml index 2af4207..40e3c53 100644 --- a/crates/host/Cargo.toml +++ b/crates/host/Cargo.toml @@ -19,7 +19,6 @@ path = "src/main.rs" brolink-core.workspace = true brolink-client.workspace = true brolink-ui.workspace = true -egui-wgpu.workspace = true anyhow.workspace = true serde = { workspace = true } serde_json.workspace = true @@ -30,7 +29,6 @@ rand.workspace = true clap.workspace = true semver = "1" eframe.workspace = true -egui.workspace = true [target.'cfg(not(windows))'.dependencies] arboard = "3" diff --git a/crates/host/src/main.rs b/crates/host/src/main.rs index 21605fc..f25e85c 100644 --- a/crates/host/src/main.rs +++ b/crates/host/src/main.rs @@ -79,33 +79,9 @@ fn main() -> Result<()> { }); } service::ensure_service_running(); - let icon = if cfg!(target_os = "macos") { - eframe::egui::IconData::default() - } else { - eframe::egui::IconData { - rgba: brolink_core::icon::render(64), - width: 64, - height: 64, - } - }; - let native = eframe::NativeOptions { - viewport: eframe::egui::ViewportBuilder::default() - .with_inner_size([1100.0, 720.0]) - .with_min_inner_size([640.0, 420.0]) - .with_title("BroLink") - .with_icon(icon), - renderer: eframe::Renderer::Wgpu, - vsync: false, - wgpu_options: egui_wgpu::WgpuConfiguration { - present_mode: egui_wgpu::wgpu::PresentMode::AutoNoVsync, - desired_maximum_frame_latency: Some(1), - ..Default::default() - }, - ..Default::default() - }; eframe::run_native( "BroLink", - native, + brolink_client::native_options(), Box::new(|cc| Ok(Box::new(NodeApp::new(cc)))), ) .map_err(|e| anyhow::anyhow!("{e}")) diff --git a/crates/host/src/service.rs b/crates/host/src/service.rs index 4d0d8d3..65c62ff 100644 --- a/crates/host/src/service.rs +++ b/crates/host/src/service.rs @@ -20,7 +20,8 @@ use brolink_core::api::{ CLIPBOARD_PATH, UPDATE_PATH, }; use brolink_core::http::{self, Request, Response}; -use brolink_core::{tailscale, CONTROL_PORT}; +use brolink_core::tailscale::{self, is_tailnet, is_tailnet_ip}; +use brolink_core::CONTROL_PORT; use parking_lot::Mutex; use std::collections::{HashMap, VecDeque}; use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener}; @@ -915,37 +916,10 @@ fn control_host(host: &str) -> bool { }) } -/// Tailscale hands out addresses from 100.64.0.0/10. -fn is_tailnet(ip: Ipv4Addr) -> bool { - let o = ip.octets(); - o[0] == 100 && (64..128).contains(&o[1]) -} - -/// IPv4 CGNAT overlay, or IPv6 unique-local (Tailscale uses fd7a:115c:a1e0::/48). -fn is_tailnet_ip(ip: IpAddr) -> bool { - match ip { - IpAddr::V4(v4) => is_tailnet(v4), - IpAddr::V6(v6) => v6.octets()[0] & 0xfe == 0xfc, - } -} - #[cfg(test)] mod tests { use super::*; - #[test] - fn tailnet_range_is_recognised() { - assert!(is_tailnet("100.64.0.1".parse().unwrap())); - assert!(is_tailnet("100.127.255.254".parse().unwrap())); - assert!(is_tailnet("100.64.0.10".parse().unwrap())); - assert!(!is_tailnet("100.128.0.1".parse().unwrap())); - assert!(!is_tailnet("192.168.1.2".parse().unwrap())); - assert!(is_tailnet_ip("100.111.100.57".parse().unwrap())); - assert!(is_tailnet_ip("fd7a:115c:a1e0::9e2a:381c".parse().unwrap())); - assert!(!is_tailnet_ip("8.8.8.8".parse().unwrap())); - assert!(!is_tailnet_ip("2001:4860:4860::8888".parse().unwrap())); - } - fn whois(id: u64, login: &str) -> tailscale::WhoIs { tailscale::WhoIs { user_profile: tailscale::User { diff --git a/crates/host/src/setup.rs b/crates/host/src/setup.rs index acf5320..ba27ff5 100644 --- a/crates/host/src/setup.rs +++ b/crates/host/src/setup.rs @@ -599,6 +599,24 @@ pub fn run(p: &Plan<'_>) -> Result<()> { } } +/// Run a read-only PowerShell probe and return what it printed. +#[cfg(windows)] +pub(crate) fn powershell(script: &str) -> Result { + use std::os::windows::process::CommandExt; + let out = std::process::Command::new("powershell") + .args(["-NoProfile", "-NonInteractive", "-Command", script]) + .creation_flags(0x0800_0000) + .output() + .context("run powershell")?; + anyhow::ensure!( + out.status.success(), + "powershell exited with {}: {}", + out.status, + String::from_utf8_lossy(&out.stderr).trim() + ); + Ok(String::from_utf8_lossy(&out.stdout).into_owned()) +} + // Only the Windows setup path is compiled in a release build; tests use it everywhere. #[cfg_attr(not(windows), allow(dead_code))] /// A PowerShell single-quoted literal: only `'` needs escaping, nothing is diff --git a/crates/host/src/virtual_display.rs b/crates/host/src/virtual_display.rs index 6fdfb05..cb528b4 100644 --- a/crates/host/src/virtual_display.rs +++ b/crates/host/src/virtual_display.rs @@ -119,7 +119,7 @@ pub fn probe() -> Option { "{}if ($vddDevices.Count -eq 0) {{ 'none' }} elseif ($vddMissing.Count -eq 0) {{ 'ok' }} else {{ 'missing' }}", locate_ps() ); - match powershell(&script) { + match crate::setup::powershell(&script) { Ok(o) => parse_probe(&o), Err(e) => { tracing::debug!("virtual display probe: {e:#}"); @@ -140,24 +140,6 @@ fn parse_probe(out: &str) -> Option { } } -#[cfg(windows)] -fn powershell(script: &str) -> anyhow::Result { - use anyhow::Context; - use std::os::windows::process::CommandExt; - let out = std::process::Command::new("powershell") - .args(["-NoProfile", "-NonInteractive", "-Command", script]) - .creation_flags(0x0800_0000) - .output() - .context("run powershell")?; - anyhow::ensure!( - out.status.success(), - "powershell exited with {}: {}", - out.status, - String::from_utf8_lossy(&out.stderr).trim() - ); - Ok(String::from_utf8_lossy(&out.stdout).into_owned()) -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/host/src/wake.rs b/crates/host/src/wake.rs index 718f660..3115a48 100644 --- a/crates/host/src/wake.rs +++ b/crates/host/src/wake.rs @@ -57,7 +57,7 @@ pub fn probe() -> WakeInfo { $armed = @(powercfg /devicequery wake_armed) -contains $n.InterfaceDescription; \ $fs = (Get-ItemProperty 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Power' -ErrorAction SilentlyContinue).HiberbootEnabled; \ Write-Output \"$($n.MacAddress)|$($n.Name)|$($n.InterfaceDescription)|$ip|$wol|$armed|$fs\""; - match powershell(SCRIPT) { + match crate::setup::powershell(SCRIPT) { Ok(o) => parse_probe(&o), Err(e) => { tracing::debug!("wake probe: {e:#}"); @@ -103,23 +103,6 @@ fn parse_probe(line: &str) -> WakeInfo { } } -#[cfg(windows)] -fn powershell(script: &str) -> Result { - use std::os::windows::process::CommandExt; - let out = std::process::Command::new("powershell") - .args(["-NoProfile", "-NonInteractive", "-Command", script]) - .creation_flags(0x0800_0000) - .output() - .context("run powershell")?; - anyhow::ensure!( - out.status.success(), - "powershell exited with {}: {}", - out.status, - String::from_utf8_lossy(&out.stderr).trim() - ); - Ok(String::from_utf8_lossy(&out.stdout).into_owned()) -} - #[cfg(test)] mod tests { use super::*; From f4fcad74e1a4d4e3ca04bd8c75917281f9fdb73d Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:33:36 -0700 Subject: [PATCH 27/52] Drop the widgets no screen uses body, titled_card, toggle and steps had no callers after the redesign, and card_frame, focus_ring and paint_icon are only used inside the kit, so they are private now. A component that nothing draws is one more thing to keep consistent for no gain. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/ui/src/widgets.rs | 64 +++------------------------------------- 1 file changed, 4 insertions(+), 60 deletions(-) diff --git a/crates/ui/src/widgets.rs b/crates/ui/src/widgets.rs index cf7f37a..c6901ee 100644 --- a/crates/ui/src/widgets.rs +++ b/crates/ui/src/widgets.rs @@ -83,7 +83,7 @@ pub enum Icon { } /// Paint `icon` in a 12-point box centred on `c`. -pub fn paint_icon(painter: &Painter, c: Pos2, icon: Icon, color: Color32) { +fn paint_icon(painter: &Painter, c: Pos2, icon: Icon, color: Color32) { let s = stroke(1.5, color); let p = |x: f32, y: f32| c + Vec2::new(x, y); match icon { @@ -127,7 +127,7 @@ pub fn paint_icon(painter: &Painter, c: Pos2, icon: Icon, color: Color32) { /// Keep keyboard navigation visible for controls with custom painting: an /// accent outline two points outside the control, so it shows against the /// background whatever the control's own fill. -pub fn focus_ring(ui: &Ui, response: &Response, corner: u8) { +fn focus_ring(ui: &Ui, response: &Response, corner: u8) { if response.has_focus() && ui.is_enabled() { ui.painter().rect_stroke( response.rect.expand(2.0), @@ -144,7 +144,7 @@ pub fn focus_ring(ui: &Ui, response: &Response, corner: u8) { /// The frame of a card: a surface one step up from the window, with a /// hairline around it. -pub fn card_frame() -> Frame { +fn card_frame() -> Frame { Frame::new() .fill(P.surface) .stroke(stroke(1.0, P.hairline)) @@ -188,19 +188,6 @@ pub fn heading(ui: &mut Ui, title: &str, subtitle: Option<&str>) { ui.add_space(space::XS); } -/// [`card`] with a [`heading`]. -pub fn titled_card( - ui: &mut Ui, - title: &str, - subtitle: Option<&str>, - add: impl FnOnce(&mut Ui) -> R, -) -> InnerResponse { - card(ui, |ui| { - heading(ui, title, subtitle); - add(ui) - }) -} - /// A grouped list: rows separated by hairlines on one surface, as in the /// machine list and every settings section. Rows bring their own padding. pub fn group(ui: &mut Ui, add: impl FnOnce(&mut Ui) -> R) -> InnerResponse { @@ -502,11 +489,6 @@ pub fn muted(ui: &mut Ui, text: impl Into) -> Response { ui.add(Label::new(RichText::new(text).color(P.text_secondary)).wrap()) } -/// Body text in the primary colour. Wraps. -pub fn body(ui: &mut Ui, text: impl Into) -> Response { - ui.add(Label::new(RichText::new(text).color(P.text)).wrap()) -} - /// Medium-weight body text, for a name in a row. pub fn strong(ui: &mut Ui, text: impl Into) -> Response { ui.label( @@ -1314,11 +1296,6 @@ pub fn segmented( changed } -/// An on/off switch. Returns the response; `changed()` fires on toggle. -pub fn toggle(ui: &mut Ui, on: &mut bool) -> Response { - labelled_toggle(ui, on, "") -} - fn labelled_toggle(ui: &mut Ui, on: &mut bool, label: &str) -> Response { let track = size::SWITCH; let (rect, mut response) = ui.allocate_exact_size( @@ -1615,7 +1592,7 @@ pub fn setting_block( r } -/// A [`setting_row`] whose control is a [`toggle`]. Returns true on change. +/// A [`setting_row`] whose control is an on/off switch. Returns true on change. pub fn toggle_row(ui: &mut Ui, on: &mut bool, label: &str, hint: Option<&str>) -> bool { setting_row(ui, label, hint, |ui| { labelled_toggle(ui, on, label).changed() @@ -1812,39 +1789,6 @@ pub fn shortcut(ui: &mut Ui, keys: &[&str], does: &str) -> Response { r } -/// Where a multi-step job is: done steps ticked, the current one in the -/// accent, the rest quiet. Mono uppercase, as on the product site. -pub fn steps(ui: &mut Ui, labels: &[&str], current: usize) { - ui.horizontal_wrapped(|ui| { - ui.spacing_mut().item_spacing = Vec2::new(space::SM, space::XS); - for (i, label) in labels.iter().enumerate() { - if i > 0 { - let (r, _) = ui.allocate_exact_size(Vec2::new(16.0, 16.0), Sense::hover()); - ui.painter() - .hline(r.x_range(), r.center().y, stroke(1.0, P.border)); - } - let (ink, mark) = match i.cmp(¤t) { - std::cmp::Ordering::Less => (P.text_secondary, Some(Icon::Check)), - std::cmp::Ordering::Equal => (P.accent, None), - std::cmp::Ordering::Greater => (P.text_tertiary, None), - }; - if let Some(icon) = mark { - let (r, _) = ui.allocate_exact_size(Vec2::splat(12.0), Sense::hover()); - paint_icon(ui.painter(), r.center(), icon, ink); - } else if i == current { - let (r, _) = ui.allocate_exact_size(Vec2::splat(12.0), Sense::hover()); - ui.painter().circle_filled(r.center(), 3.5, ink); - } - ui.label( - RichText::new(label.to_uppercase()) - .font(theme::mono_medium(text::LABEL)) - .extra_letter_spacing(text::LABEL_TRACKING) - .color(ink), - ); - } - }); -} - // --------------------------------------------------------------------------- // Brand // --------------------------------------------------------------------------- From f70d2ce915994ea97c93f33ebe80d043b47f17e5 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:33:48 -0700 Subject: [PATCH 28/52] Stop telling Windows and Linux viewers about "this Mac" Every install is a viewer now, but the connection errors and the path explanations still said "this Mac", and told people to "open BroLink Host and run setup", a window and a button that no longer exist. They now say "this machine" and point at the Sharing tab, and each error ends with what to do. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/client/src/app.rs | 2 +- crates/client/src/path.rs | 8 ++++---- crates/client/src/session.rs | 21 ++++++++++++--------- crates/client/src/update.rs | 2 +- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/crates/client/src/app.rs b/crates/client/src/app.rs index 0f416b6..20b97b6 100644 --- a/crates/client/src/app.rs +++ b/crates/client/src/app.rs @@ -2494,7 +2494,7 @@ pub(crate) mod snapshots { ), ( "failed", - failed("Office", "Office did not wake up. A wake packet only reaches it from its own network, or through a router that forwards UDP 9 to it."), + failed("Office", "Office didn't wake up. A wake packet reaches it only from its own network, or through a router that forwards UDP port 9 to it. Its Sharing tab in BroLink shows whether Wake-on-LAN is ready."), ), ( "disconnected", diff --git a/crates/client/src/path.rs b/crates/client/src/path.rs index dae7007..26f943c 100644 --- a/crates/client/src/path.rs +++ b/crates/client/src/path.rs @@ -137,13 +137,13 @@ pub fn explain( } if p.ipv6 && !m.ipv6 { t.push_str(&format!( - " {pc} has IPv6; a network with IPv6 on this Mac's side would also connect directly." + " {pc} has IPv6; a network with IPv6 on this machine's side would also connect directly." )); } t } else if mac_hard { format!( - "{pc}'s network is fine; this Mac's is a hard NAT with no UPnP. From another network, or with UPnP on this router, the connection is direct." + "{pc}'s network is fine; this machine's is a hard NAT with no UPnP. From another network, or with UPnP on this router, the connection is direct." ) } else { "Both networks look easy to traverse, so Tailscale should switch to a direct path within a minute of traffic. If it never does, the routers may be blocking UDP between them.".to_string() @@ -164,7 +164,7 @@ pub fn explain( "{pc}'s router is a hard NAT with no UPnP. Turn UPnP or NAT-PMP on in that router, or forward a UDP port to {pc}; Tailscale then connects directly." ) } else { - format!("{pc}'s network looks easy to traverse; this Mac's side has not been checked yet.") + format!("{pc}'s network looks easy to traverse; this machine's side has not been checked yet.") } } (None, None) => { @@ -398,7 +398,7 @@ mod tests { let pc_easy = nat(true, Some(false), true, false); let t = explain("Gaming-PC", &relayed, Some(&pc_easy), Some(&mac_hard)).unwrap(); - assert!(t.contains("this Mac's is a hard NAT"), "{t}"); + assert!(t.contains("this machine's is a hard NAT"), "{t}"); let t = explain("Gaming-PC", &relayed, Some(&pc_easy), Some(&mac_easy)).unwrap(); assert!(t.contains("within a minute"), "{t}"); diff --git a/crates/client/src/session.rs b/crates/client/src/session.rs index 56a3d0f..2abdd08 100644 --- a/crates/client/src/session.rs +++ b/crates/client/src/session.rs @@ -573,7 +573,7 @@ fn run(c: &Connect, generation: u64) -> Result<()> { // 1. Wake it if nothing answers. if !t.online && !port_open(t.ip, SUNSHINE_PORT, Duration::from_millis(1200)) { let mac = t.mac.ok_or_else(|| { - anyhow!("{} is not answering and this Mac does not know how to wake it yet. Turn the PC on once while BroLink Host is running so it can learn.", t.name) + anyhow!("{} isn't answering, and BroLink doesn't know how to wake it yet. Turn it on once while BroLink runs there, so this machine can learn how.", t.name) })?; let start = Instant::now(); let mut last_wake = Instant::now() - Duration::from_secs(60); @@ -597,7 +597,7 @@ fn run(c: &Connect, generation: u64) -> Result<()> { } if start.elapsed() > Duration::from_secs(120) { bail!( - "{} did not wake up. A wake packet only reaches it from its own network, or through a router that forwards UDP 9 to it. Check Wake-on-LAN in BroLink Host on the PC.", + "{} didn't wake up. A wake packet reaches it only from its own network, or through a router that forwards UDP port 9 to it. Its Sharing tab in BroLink shows whether Wake-on-LAN is ready.", t.name ); } @@ -617,7 +617,7 @@ fn run(c: &Connect, generation: u64) -> Result<()> { ); if start.elapsed() > Duration::from_secs(60) { bail!( - "{} is up but nothing is streaming from it. Open BroLink Host on the PC and run setup.", + "{} is on, but nothing is streaming from it. Open BroLink there and set up sharing on its Sharing tab.", t.name ); } @@ -698,7 +698,7 @@ fn run(c: &Connect, generation: u64) -> Result<()> { } if stopped.elapsed() >= Duration::from_secs(10) { bail!( - "{} did not stop the previous stream. Check BroLink Host there.", + "{} didn't stop its previous stream. Try again; if it keeps happening, restart that machine.", t.name ); } @@ -813,7 +813,7 @@ fn submit_pin(ip: Ipv4Addr, pin: &str, progress: &Mutex) { Err(e) => { tracing::info!("BroLink Host did not take the PIN: {e}"); progress.lock().detail = - "BroLink Host is not answering on the PC. Open BroLink Host there and run setup, then try again.".into(); + "BroLink on that machine isn't answering, so it can't enter the PIN. Open BroLink there and set up sharing, then try again.".into(); } } std::thread::sleep(Duration::from_millis(800)); @@ -852,16 +852,19 @@ pub fn wake_test(pc: &Pc) -> Result { wake_only(pc)?; std::thread::sleep(Duration::from_millis(1500)); let st = host_status(ip, Duration::from_secs(3)) - .ok_or_else(|| anyhow!("BroLink Host on {} is not answering", pc.name))?; + .ok_or_else(|| anyhow!("BroLink on {} isn't answering", pc.name))?; Ok(st.wake_packet_age_secs.is_some_and(|s| s <= 5)) } /// Send the wake packet once, without connecting. pub fn wake_only(pc: &Pc) -> Result { let t = Target::from_pc(pc).ok_or_else(|| anyhow!("no address for {}", pc.name))?; - let mac = t - .mac - .ok_or_else(|| anyhow!("this Mac does not know {}'s MAC address yet", pc.name))?; + let mac = t.mac.ok_or_else(|| { + anyhow!( + "BroLink doesn't know how to wake {} yet; turn it on once while BroLink runs there", + pc.name + ) + })?; Ok(wake::send(mac, t.lan_ip, t.public_ip)) } diff --git a/crates/client/src/update.rs b/crates/client/src/update.rs index 0cbf6cd..02ef47e 100644 --- a/crates/client/src/update.rs +++ b/crates/client/src/update.rs @@ -473,7 +473,7 @@ fn should_push(running: &Version, rel: &Release) -> bool { pub fn old_host_message(name: &str, version: &Version) -> String { format!( - "{name} runs BroLink Host {version}, which cannot take an update over the network. Connect to it and choose PC → Update BroLink Host in the toolbar: this Mac installs the new version through the stream. After that, updates are automatic." + "{name} runs BroLink Host {version}, which cannot take an update over the network. Connect to it and choose PC → Update BroLink Host in the toolbar: this machine installs the new version through the stream. After that, updates are automatic." ) } From 07156791448dc85448ec14c3aefc121154b6e034 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:33:57 -0700 Subject: [PATCH 29/52] Run the release updater only on the Mac Since 4.0 the unified window runs on every OS, and with it update::spawn. The updater assumes a Mac throughout: it swaps an app bundle and pulls brolink-host.exe out of the Windows zip with /usr/bin/unzip. On a Windows PC with another Windows PC in the tailnet it would fetch the release and then fail to push, again every three minutes, racing the Mac that does the same job. Off macOS spawn() now only says where updates come from. Also corrects the module docs that still called the repository private. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 5 +++++ crates/client/src/update.rs | 17 +++++++++++++++-- crates/core/src/update.rs | 8 ++++---- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9446d5c..5b7187a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,11 @@ GitHub release notes, so each version gets a heading of the form the new app in place. It kept running the old version until the next login, so a Mac that shares its desktop served other machines from the previous release. +- A Windows machine no longer runs the Mac's updater. Since 4.0 every + machine runs the same window, so a Windows PC also checked GitHub and + would try to send other Windows PCs their update with a Mac-only unzip, + failing every few minutes. Its settings now say that updates arrive + from the Mac. - Cancel stops a connection attempt straight away while the PC is not answering. The pairing check retried eight times regardless, which could hold the window on "Checking pairing…" for most of a minute after Cancel. diff --git a/crates/client/src/update.rs b/crates/client/src/update.rs index 0cbf6cd..38a4f7d 100644 --- a/crates/client/src/update.rs +++ b/crates/client/src/update.rs @@ -1,7 +1,7 @@ //! Keeping this app and every BroLink Host it can see on the newest release. //! -//! The Mac is the one machine with a GitHub login (the repository is -//! private), so it does the fetching for everyone. Every few hours it asks +//! The Mac does the fetching for everyone (with a GitHub login if the +//! repository is private; see `brolink_core::update`). Every few hours it asks //! GitHub for the latest release. A newer app is downloaded, verified against //! GitHub's digest and its own code signature, and swapped into place once no //! stream is running; the app then relaunches itself. A newer host is @@ -64,6 +64,19 @@ pub fn spawn( progress: Arc>, ctx: egui::Context, ) { + if !cfg!(target_os = "macos") { + // Everything below assumes a Mac: it replaces an app bundle and + // unpacks the Windows zip with /usr/bin/unzip. A Windows PC gets + // its new host pushed from the Mac, and a Linux node is rebuilt, so + // there is nothing here for either to fetch or send. + let _ = (discovery, live, progress, ctx); + state.lock().message = if cfg!(windows) { + "New versions arrive from the Mac on your Tailscale account.".into() + } else { + "BroLink updates itself on a Mac; install new releases here by hand.".into() + }; + return; + } std::thread::spawn(move || { let mut next = Instant::now() + FIRST_CHECK; let mut release: Option = None; diff --git a/crates/core/src/update.rs b/crates/core/src/update.rs index 8899a1b..724cfe9 100644 --- a/crates/core/src/update.rs +++ b/crates/core/src/update.rs @@ -1,9 +1,9 @@ //! Finding and fetching BroLink releases on GitHub, for both apps' updaters. //! -//! The repository is private, so the calls need a token: the caller's -//! setting, `BROLINK_GITHUB_TOKEN`, or on macOS whatever git has stored for -//! github.com, which is the login the install script uses too. Without one -//! the requests still go out, so a public repository works unchanged. +//! A public repository needs no login. For a private one the calls carry a +//! token: the caller's setting, `BROLINK_GITHUB_TOKEN`, or on macOS whatever +//! git has stored for github.com, which is the login the install script +//! uses too. Without one the requests still go out. //! //! Only the Mac talks to GitHub. It replaces its own bundle and sends each //! PC the new `brolink-host.exe` over the control API; see From ca87bcfa7aba5447b72b6ca905e78123ca5c0d4d Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:36:52 -0700 Subject: [PATCH 30/52] Tab through each row's controls in the order they read egui focuses widgets in the order they are made, and a right-to-left layout makes the rightmost first, so Tab went to a machine's menu before its Connect button, and through the stream toolbar from Disconnect backwards. Controls on the right are now laid out left to right and pushed against the edge, measured on the previous pass (a changed width discards the pass, so nothing is ever drawn misplaced). A key that expires within 30 days now shows at the top of the machine list, with the other things that need doing, rather than under it. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/client/src/app.rs | 79 +++++++++++++++++++++++++++++------ crates/client/src/settings.rs | 6 +-- crates/client/src/stream.rs | 28 ++++++------- crates/host/src/app.rs | 6 +-- crates/ui/src/widgets.rs | 69 ++++++++++++++++++++++++++---- 5 files changed, 146 insertions(+), 42 deletions(-) diff --git a/crates/client/src/app.rs b/crates/client/src/app.rs index 20b97b6..1732056 100644 --- a/crates/client/src/app.rs +++ b/crates/client/src/app.rs @@ -759,6 +759,9 @@ impl ClientApp { if let Some((tone, text)) = self.updates.lock().notice.clone() { ui::notice(ui, tone, &text); } + for text in key_expiry_warnings(disc).iter().filter(|t| urgent(t)) { + ui::notice(ui, Tone::Danger, text); + } if prog.active() { self.session_card(ui, prog); } else if let Step::Ended { error } = &prog.step { @@ -857,7 +860,6 @@ impl ClientApp { first = false; let (tone, detail) = describe(pc); ui::list_row(ui, Some(tone), &pc.name, &detail, |ui| { - self.row_menu(ui, pc); if pc.can_stream() && !pc.remembered { let label = if pc.online { "Connect" @@ -875,6 +877,7 @@ impl ClientApp { self.connect(ctx, pc); } } + self.row_menu(ui, pc); }); } }); @@ -1028,23 +1031,19 @@ impl ClientApp { }); } - /// Key expiry that is about to cut a machine off, then everything else - /// worth knowing about the paths, folded away. + /// Everything worth knowing about the paths and keys that is not + /// urgent, folded away. Urgent key expiry is at the top of the page. fn details(&self, ui: &mut egui::Ui, disc: &Discovery) { - let mut later = Vec::new(); - for text in key_expiry_warnings(disc) { - let urgent = text.contains("expired") || text.contains(" days") && days_in(&text) <= 30; - if urgent { - ui::notice(ui, Tone::Danger, &text); - } else { - later.push((Tone::Neutral, text)); - } - } let mut items: Vec<(Tone, String)> = path_warnings(disc) .into_iter() .map(|t| (Tone::Warning, t)) .collect(); - items.extend(later); + items.extend( + key_expiry_warnings(disc) + .into_iter() + .filter(|t| !urgent(t)) + .map(|t| (Tone::Neutral, t)), + ); if items.is_empty() { return; } @@ -1614,6 +1613,11 @@ fn key_expiry_warnings(disc: &Discovery) -> Vec { out } +/// A key-expiry line that needs doing now: expired, or within 30 days. +fn urgent(text: &str) -> bool { + text.contains("expired") || text.contains(" days") && days_in(text) <= 30 +} + /// The day count inside a warning line, for its tone. fn days_in(text: &str) -> i64 { text.split(" in ") @@ -2621,6 +2625,29 @@ pub(crate) mod snapshots { ); } + #[test] + fn tab_reaches_a_rows_controls_in_reading_order() { + use egui_kittest::kittest::By; + let mut h = build(Setup::new(one()), MIN, 1.0, false); + let mut order = Vec::new(); + for _ in 0..6 { + h.press_key(egui::Key::Tab); + h.run_steps(1); + if let Some(n) = h.query_all(By::new().predicate(|n| n.is_focused())).next() { + order.push(n.label().unwrap_or_default()); + } + } + let at = |label: &str| order.iter().position(|l| l == label); + let (Some(connect), Some(more)) = (at("Connect"), at("More for Studio")) else { + panic!("Tab never reached the row: {order:?}"); + }; + assert!(connect < more, "Connect comes before its menu: {order:?}"); + assert!( + at("Machines").is_some_and(|m| m < connect), + "the tabs come first: {order:?}" + ); + } + #[test] fn this_machine_leads_the_list_and_opens_sharing() { let mut h = build( @@ -2932,6 +2959,32 @@ pub(crate) mod snapshots { save(h.render().unwrap(), "client-details-open-1280x800@2x.png"); } + /// Keyboard focus is visible on every kind of control. + #[test] + #[ignore = "renders with a GPU; run on demand to review the UI"] + fn snapshots_keyboard_focus() { + for (name, page, tabs) in [ + ("lobby", Page::Machines, 5), + ("settings", Page::Settings, 5), + ("settings-switch", Page::Settings, 13), + ] { + let mut h = build(Setup::new(pcs()).page(page), TYPICAL, 2.0, true); + for _ in 0..tabs { + h.press_key(egui::Key::Tab); + h.run_steps(1); + } + h.run_steps(2); + assert!( + h.ctx.memory(|m| m.focused()).is_some(), + "Tab reaches a control on {name}" + ); + save( + h.render().unwrap(), + &format!("client-focus-{name}-1280x800@2x.png"), + ); + } + } + /// The stream screen as the window shows it, before any picture has /// arrived: the session points at an address that never answers. #[test] diff --git a/crates/client/src/settings.rs b/crates/client/src/settings.rs index c1cd046..df0f35e 100644 --- a/crates/client/src/settings.rs +++ b/crates/client/src/settings.rs @@ -105,13 +105,11 @@ pub fn stream_controls(ui: &mut egui::Ui, s: &mut StreamSettings, native: (u32, } else { s.bitrate_kbps / 1000 }; - // Right to left: the value first, then the track before it. - ui::slider_value(ui, &format!("{mbps} Mbps")); - let w = (ui.available_width() - space::LG).clamp(96.0, 180.0); - if ui::slider(ui, &mut mbps, 2..=150, w, "Bitrate").changed() { + if ui::slider(ui, &mut mbps, 2..=150, 180.0, "Bitrate").changed() { s.quality = Quality::Custom; s.bitrate_kbps = mbps * 1000; } + ui::slider_value(ui, &format!("{mbps} Mbps")); }, ); ui::row_separator(ui); diff --git a/crates/client/src/stream.rs b/crates/client/src/stream.rs index 6f17128..489e95e 100644 --- a/crates/client/src/stream.rs +++ b/crates/client/src/stream.rs @@ -19,8 +19,8 @@ use crate::video; use brolink_core::api::PowerAction; use brolink_ui::{self as ui, size, space, theme, Kv, Tone, PALETTE as P}; use egui::{ - Align, Align2, Color32, CursorIcon, Event, Frame, Id, Layout, Margin, Pos2, Rect, RichText, - Vec2, ViewportCommand, + Align2, Color32, CursorIcon, Event, Frame, Id, Margin, Pos2, Rect, RichText, Vec2, + ViewportCommand, }; use semver::Version; use std::time::{Duration, Instant}; @@ -528,25 +528,25 @@ impl View { let (keys, _) = host_key(); ui::shortcut(ui, keys, "hides this bar"); } - ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + ui::trailing(ui, |ui| { ui.spacing_mut().item_spacing.x = space::XS; - if ui::danger_button(ui, "Disconnect").clicked() { - actions.push(Action::Disconnect); + if overflow { + self.more_menu(ui, ctx, env, actions); + } else { + self.mouse_menu(ui, ctx, live, cfg, actions); + self.keys_menu(ui, live, cfg, actions); + self.stats_button(ui); + self.fullscreen_button(ui, env, actions); + self.power_menu(ui, env, actions); } - toolbar_rule(ui); if ui::ghost_button(ui, "Stream settings").clicked() { self.held.release_all(&live.input); self.set_captured(ctx, false); self.settings = Some(cfg.stream.clone()); } - if overflow { - self.more_menu(ui, ctx, env, actions); - } else { - self.power_menu(ui, env, actions); - self.fullscreen_button(ui, env, actions); - self.stats_button(ui); - self.keys_menu(ui, live, cfg, actions); - self.mouse_menu(ui, ctx, live, cfg, actions); + toolbar_rule(ui); + if ui::danger_button(ui, "Disconnect").clicked() { + actions.push(Action::Disconnect); } }); }); diff --git a/crates/host/src/app.rs b/crates/host/src/app.rs index 43d0094..467a2fd 100644 --- a/crates/host/src/app.rs +++ b/crates/host/src/app.rs @@ -728,11 +728,11 @@ impl HostApp { let id = format!("ID {}", &uuid[..uuid.len().min(8)]); ui::list_row(ui, None, name, &id, |ui| { if self.confirm_unpair.as_deref() == Some(uuid) { - if ui::destructive_button(ui, "Unpair").clicked() { - self.unpair(ui.ctx(), name, uuid); + if ui::ghost_button(ui, "Keep").clicked() { self.confirm_unpair = None; } - if ui::ghost_button(ui, "Keep").clicked() { + if ui::destructive_button(ui, "Unpair").clicked() { + self.unpair(ui.ctx(), name, uuid); self.confirm_unpair = None; } } else if ui::danger_button(ui, "Unpair…") diff --git a/crates/ui/src/widgets.rs b/crates/ui/src/widgets.rs index c6901ee..29dfc6a 100644 --- a/crates/ui/src/widgets.rs +++ b/crates/ui/src/widgets.rs @@ -1488,13 +1488,54 @@ pub(crate) fn lerp_color(a: Color32, b: Color32, t: f32) -> Color32 { // Rows // --------------------------------------------------------------------------- +/// Lay out `add` left to right, pushed against the right edge of the space +/// left in `ui`. Controls sit where a right-to-left layout would put them, +/// but keyboard focus and screen readers meet them in reading order: egui +/// focuses widgets in the order they are made, and a right-to-left layout +/// makes the rightmost first. The width is measured on the previous pass; +/// when it changes, the pass is discarded and laid out again before +/// anything is shown. +pub fn trailing(ui: &mut Ui, add: impl FnOnce(&mut Ui) -> R) -> InnerResponse { + let avail = ui.available_rect_before_wrap(); + let key = ui.auto_id_with("brolink.trailing"); + let known: f32 = ui.data(|d| d.get_temp(key)).unwrap_or(0.0); + let w = known.min(avail.width()).max(0.0); + let rect = Rect::from_min_max(Pos2::new(avail.max.x - w, avail.min.y), avail.max); + let mut child = ui.new_child( + UiBuilder::new() + .max_rect(rect) + .layout(Layout::left_to_right(Align::Center)), + ); + let inner = add(&mut child); + let used = child.min_rect(); + let got = if used.is_positive() { + used.width() + } else { + 0.0 + }; + if (got - known).abs() > 0.5 { + ui.data_mut(|d| d.insert_temp(key, got)); + ui.ctx().request_discard("trailing controls changed width"); + } + let response = ui.allocate_rect( + if used.is_positive() { + used + } else { + Rect::from_min_size(avail.right_top(), Vec2::ZERO) + }, + Sense::hover(), + ); + InnerResponse::new(inner, response) +} + /// A row with text on the left and controls on the right. The controls are /// laid out first, so the text gets exactly the width they leave and wraps /// there instead of running underneath them. When they leave too little, /// the text takes the full width below the controls. /// /// `min_h` is the row's height with padding; controls centre in it and -/// text starts `text_top` below its top. +/// text starts `text_top` below its top. Controls are added left to right +/// (see [`trailing`]). fn split_row( ui: &mut Ui, min_h: f32, @@ -1507,11 +1548,19 @@ fn split_row( let mut right_ui = ui.new_child( UiBuilder::new() .max_rect(row) - .layout(Layout::right_to_left(Align::Center)), + .layout(Layout::top_down(Align::Min)), ); - right_ui.spacing_mut().item_spacing.x = space::SM; - let r = right(&mut right_ui); - let used = right_ui.min_rect(); + let placed = trailing(&mut right_ui, |ui| { + ui.spacing_mut().item_spacing.x = space::SM; + ui.set_min_height(min_h); + right(ui) + }); + let r = placed.inner; + let used = if placed.response.rect.width() > 0.0 { + placed.response.rect + } else { + Rect::NOTHING + }; let taken = if used.is_positive() { row.max.x - used.min.x + space::LG } else { @@ -1534,7 +1583,11 @@ fn split_row( left_ui.spacing_mut().item_spacing.y = space::XXS; left(&mut left_ui); let bottom = (left_ui.min_rect().max.y + text_top) - .max(used.max.y) + .max(if used.is_positive() { + used.max.y + } else { + row.min.y + }) .max(row.min.y + min_h); ui.allocate_rect( Rect::from_min_max(row.min, Pos2::new(row.max.x, bottom)), @@ -1603,8 +1656,8 @@ pub fn toggle_row(ui: &mut Ui, on: &mut bool, label: &str, hint: Option<&str>) - pub const NOTICES: &str = include_str!("../../../NOTICE"); /// A row in a list of machines or devices: an optional status dot, the -/// name and one line of detail on the left, actions on the right. Add the -/// primary action first; it lands rightmost. The detail stays on one line +/// name and one line of detail on the left, actions on the right, added in +/// reading order (left to right). The detail stays on one line /// and is cut with an ellipsis rather than wrapping; the full text shows /// on hover. pub fn list_row( From 0212d9a7bf08fd6234353da991f6cf6147357f04 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:38:32 -0700 Subject: [PATCH 31/52] Accept a Tailscale IPv6 address in the control API's Host header The rebinding guard allowed loopback and Tailscale IPv4 in the Host header while the peer check itself admits Tailscale IPv6 too, so a client reaching the service over IPv6 was told browser access is disabled. Both now use tailscale::is_tailnet_ip. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/host/src/service.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/host/src/service.rs b/crates/host/src/service.rs index 65c62ff..1276685 100644 --- a/crates/host/src/service.rs +++ b/crates/host/src/service.rs @@ -20,7 +20,7 @@ use brolink_core::api::{ CLIPBOARD_PATH, UPDATE_PATH, }; use brolink_core::http::{self, Request, Response}; -use brolink_core::tailscale::{self, is_tailnet, is_tailnet_ip}; +use brolink_core::tailscale::{self, is_tailnet_ip}; use brolink_core::CONTROL_PORT; use parking_lot::Mutex; use std::collections::{HashMap, VecDeque}; @@ -911,8 +911,7 @@ fn control_host(host: &str) -> bool { return true; } host.parse::().is_ok_and(|addr| { - addr.port() == CONTROL_PORT - && (addr.ip().is_loopback() || matches!(addr.ip(), IpAddr::V4(ip) if is_tailnet(ip))) + addr.port() == CONTROL_PORT && (addr.ip().is_loopback() || is_tailnet_ip(addr.ip())) }) } @@ -1009,9 +1008,13 @@ mod tests { "localhost:47850", "100.64.0.10:47850", "[::1]:47850", + "[fd7a:115c:a1e0::1]:47850", ] { assert!(control_host(host), "{host}"); } + for host in ["8.8.8.8:47850", "[2001:db8::1]:47850", "100.64.0.10:80"] { + assert!(!control_host(host), "{host}"); + } let req = Request { headers: vec![("content-type".into(), "application/json-not-really".into())], ..Default::default() From 4813cc6c0a12b18b0db398baa0f178c2e7a98aaf Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:38:35 -0700 Subject: [PATCH 32/52] Describe the tabs, the Sharing page and the new toolbar in the docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The guides still sent people to "Set up this PC", the status pill, the This PC card and a Settings card the unified window does not have, and described the toolbar's old Mouse, Keys and quick-profile controls. They now describe what is on screen: the Machines, Sharing and Settings tabs, a machine's … menu, the toolbar and its More menu on narrow windows, and the grouped Settings. CONTRIBUTING says what the snapshot run renders and where new visual elements go. Co-Authored-By: Claude Opus 5.5 (1M context) --- CONTRIBUTING.md | 13 ++++- README.md | 23 ++++---- crates/client/src/app.rs | 2 +- crates/host/src/app.rs | 2 +- docs/MACOS.md | 119 ++++++++++++++++++++++----------------- docs/WINDOWS.md | 46 ++++++++------- 6 files changed, 118 insertions(+), 87 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 49970ed..b0c6760 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,7 +45,18 @@ keep their meaning. cargo test -p brolink-client -p brolink-host snapshots -- --ignored ``` -renders every screen to `target/ui-snapshots/*.png`. +renders every screen to `target/ui-snapshots/*.png`: each state of the +machine list, Settings, the Sharing page and the stream overlay at the +minimum window (640×420), a typical one (1280×800) and a large one +(1920×1200), at 1× and 2× (`client-lobby-1280x800@2x.png`); `*-full.png` +show whole scrolling pages. The window is built headless with default +settings, so nothing reads or writes your real configuration. The same +states run unignored, checking that nothing runs past the window and no +two controls overlap, at the minimum and the large size. + +Every colour, size and distance comes from `crates/ui/src/theme.rs`, and +every control from `crates/ui/src/widgets.rs`; a screen that needs +something new adds it there, with accesskit info and a focus ring. ## Tests against a real Sunshine diff --git a/README.md b/README.md index 7e5b597..ddd43c5 100644 --- a/README.md +++ b/README.md @@ -15,16 +15,17 @@ machine answers only peers signed in to the same Tailscale account. ## How a session goes -1. Open BroLink. Every machine on your Tailscale account is listed with - what it can do right now. **Share this machine** sets up the streaming - engine so others can Connect here. +1. Open BroLink. **Machines** lists every machine on your Tailscale + account, with a dot and a line for what it can do right now. The + **Sharing** tab sets up the streaming engine so others can Connect here. 2. Click **Connect**. If a Windows PC is asleep, BroLink wakes it and waits. The first time, it pairs by itself: the PIN goes to BroLink on that machine over Tailscale, which enters it for you. 3. The desktop appears, full screen by default, and nothing else: the mouse is captured the moment you click the picture, as in a game, so - games that read raw mouse movement work. **Ctrl+Alt** is the host key, - as in a hypervisor: it frees the mouse and drops a toolbar over the + games that read raw mouse movement work. **Ctrl+Alt** (Control-Option + on a Mac) is the host key, as in a hypervisor: it frees the mouse and + drops a toolbar over the picture with the stream details, whether the path is direct or relayed, the mouse mode, a **Keys** menu for Ctrl+Alt+Del and friends and what the Command key does, stats, full screen, the PC's power menu, **Stream @@ -46,7 +47,7 @@ machine answers only peers signed in to the same Tailscale account. [latest release](https://github.com/MrBeldum/brolink/releases/latest) and unzip it. Run `brolink-host.exe`, or `install-host.ps1` for shortcuts and start-at-logon. -3. Click **Share this machine**. One administrator prompt installs the bundled +3. On the **Sharing** tab, click **Set up as administrator**. One administrator prompt installs the bundled streaming engine as a Windows service (if none is installed), gives BroLink a login to it, opens the control port to your tailnet only, turns Fast Startup off and arms the network card for Wake-on-LAN. @@ -76,8 +77,8 @@ open BroLink.app ``` The `xattr` step is needed for a browser download because the app is -ad-hoc signed rather than Developer-ID signed. Open BroLink and click -**Share this machine** so a Windows PC (or another Mac) can Connect here; +ad-hoc signed rather than Developer-ID signed. Open BroLink and set up +sharing on the **Sharing** tab so a Windows PC (or another Mac) can Connect here; macOS will ask for Screen Recording the first time. Details, including the toolbar and keyboard behaviour, in [docs/MACOS.md](docs/MACOS.md). @@ -106,7 +107,7 @@ from the Mac to every PC whose host reports an older version, over the same Tailscale-authenticated control API that can put the PC to sleep; the host verifies the digest, replaces its executable and restarts. That path updates only `brolink-host.exe`. The Windows zip on GitHub also contains -the pinned engine archive for first-time **Set up this PC**; a host update +the pinned engine archive for first-time setup; a host update does not install or migrate the engine. A PC that is asleep gets the host update the next time the Mac sees it. Nothing is downloaded on the PC, and no GitHub login is needed there. @@ -145,8 +146,8 @@ and to the PC's public address. | Same network as the PC | works | works if the board's firmware allows wake from power off | | Elsewhere | needs the PC's router to forward UDP 9 to the PC, or a Tailscale subnet router on that network | same, and the firmware condition | -**Test wake** in the PC menu on the Mac settles it for the network you are -on: it sends the packet while the PC is awake and asks BroLink Host whether +**Test waking it from this network** in a machine's **…** menu settles it for the network you are +on: it sends the packet while the PC is awake and asks BroLink there whether it arrived. Setup on the PC takes care of Windows' side (Fast Startup off, the adapter's wake keywords, wake allowed in power management). Wired Ethernet is strongly preferred; most Wi-Fi adapters cannot wake a PC. diff --git a/crates/client/src/app.rs b/crates/client/src/app.rs index 1732056..19495d2 100644 --- a/crates/client/src/app.rs +++ b/crates/client/src/app.rs @@ -2493,7 +2493,7 @@ pub(crate) mod snapshots { Setup::new(pcs()).step( "Gaming-PC", Step::Pairing { pin: "4821".into() }, - "BroLink on Gaming-PC isn't answering. Open BroLink there and choose Share this machine, then try again.", + "BroLink on that machine isn't answering, so it can't enter the PIN. Open BroLink there and set up sharing, then try again.", ), ), ( diff --git a/crates/host/src/app.rs b/crates/host/src/app.rs index 467a2fd..d90c0d4 100644 --- a/crates/host/src/app.rs +++ b/crates/host/src/app.rs @@ -461,7 +461,7 @@ impl HostApp { ), Os::Mac => ( "Share this Mac", - "BroLink installs the streaming engine for you. The first time someone connects, macOS asks once for Screen Recording.", + "BroLink installs the streaming engine for you. macOS then asks once to let it record the screen.", ), Os::Linux => ( "Share this machine", diff --git a/docs/MACOS.md b/docs/MACOS.md index e03a36c..45fb57b 100644 --- a/docs/MACOS.md +++ b/docs/MACOS.md @@ -1,9 +1,8 @@ # macOS client (Apple Silicon) BroLink for the Mac is one native app: every machine on your Tailscale -account, a Connect button each, a power menu, the stream itself, and -**Share this machine** so a Windows PC or another Mac can open this -desktop. BroLink's stream protocol is compiled in, video is decoded by +account, a Connect button each, a power menu, the stream itself, and a +**Sharing** tab so a Windows PC or another Mac can open this desktop. BroLink's stream protocol is compiled in, video is decoded by VideoToolbox (H.264 and HEVC), audio by Opus, and the picture is drawn in BroLink's own window under a toolbar. @@ -12,8 +11,8 @@ BroLink's own window under a toolbar. - macOS 13+ on Apple Silicon - [Tailscale](https://tailscale.com/download/mac), signed in with the same account as your other machines -- Screen Recording permission when you first **Share this machine** (so - others can see this Mac's desktop) +- Screen Recording permission for the streaming engine, which macOS asks + for once after you set up sharing (so others can see this Mac's desktop) ## Install a prebuilt app @@ -67,17 +66,21 @@ xcrun stapler staple dist/BroLink.app ## First run -1. If Tailscale is missing or signed out, the top card says so and **Get - Tailscale** opens the download page. -2. **Your PCs** lists the Windows machines on your Tailscale account. Each - line says whether it is ready, asleep, or missing something on the PC - side. -3. Click **Connect**. The first time with a PC, BroLink pairs with it: it - picks a random PIN, sends it to BroLink Host over Tailscale, and the host - enters it for you. You see the PIN but never need it. If there is no - BroLink Host on the PC, pairing cannot finish: open BroLink Host there - and run setup first. -4. The PC's desktop appears. +1. If Tailscale is missing or signed out, a banner at the top of + **Machines** says so; **Get Tailscale** opens the download page. +2. **Machines** lists every machine on your Tailscale account, this Mac + first. A dot and one line say whether each is ready, asleep, or + missing something on its side. +3. Click **Connect** (**Wake and connect** for a machine that is asleep). + The first time with a machine, BroLink pairs with it: it picks a + random PIN, sends it to BroLink on that machine over Tailscale, and + BroLink there enters it for you. You see the PIN but never need it. If + BroLink is not sharing on that machine, pairing cannot finish: open + BroLink there and set up sharing on its **Sharing** tab first. +4. The desktop appears. + +Command-1 opens **Machines**, Command-2 **Sharing** and Command-comma +**Settings**; Escape goes back to **Machines**. ## While streaming @@ -87,10 +90,13 @@ bar on any side; a window is resized to the stream's proportions when the session starts, for the same reason. A manual 16:9 size on a display of another shape leaves a bar above and below. -**Ctrl+Alt** is the host key, as in VMware or VirtualBox. Press it and the -mouse is freed and the toolbar drops over the top of the picture; press it -again, or click the picture, and the toolbar goes away and the mouse is -captured back. A menu or a question keeps the toolbar there until it is +**Control-Option** (Ctrl+Alt on Windows and Linux) is the host key, as in +VMware or VirtualBox. Press it and the mouse is freed and the toolbar drops +over the top of the picture; press it again, or click the picture, and the +toolbar goes away and the mouse is captured back. The keys are shown when +a stream starts, and on the toolbar itself. Before the first picture +arrives, a **Cancel** button under the progress ends the attempt without +the host key. A menu or a question keeps the toolbar there until it is answered. The status line, with the session clock, the frame rate and bitrate against what was asked for, and the round trip, is drawn over the picture's bottom-left corner while **Stats** is open, or in the bar below @@ -105,18 +111,21 @@ the Mac cursor's position is sent 1:1 instead and nothing is captured. | Toolbar item | What it does | |--------------|--------------| -| **● PC name · 1920×1080 · 60 fps · HEVC** | The stream as negotiated; the dot turns red while the connection is poor | -| **Direct · 38 ms / Relayed via Tokyo · 210 ms / Via your relay · 60 ms** | The path Tailscale found to the PC and its round trip. Red means every packet goes through a Tailscale relay, which adds delay; the lobby's **Connection details** say why and what would give a direct path. What BroLink asks for is the same on every path | -| **Mouse: captured / free** | Captured (the default): a click on the picture hides the cursor and sends raw movement, which games read; **Ctrl+Alt** frees it. Free: the Mac cursor's position is sent 1:1 and nothing is captured. The choice is remembered | -| **Keys** | Ctrl+Alt+Del, Windows key, Alt+Tab, Esc, Print Screen; and the switch for what ⌘ does on the PC | -| **Stats** | A Stream performance window: received against target bitrate and frame rate, round trip, packet loss, host, assembly, queue and decode times, and whether the decoder is hardware. **Copy diagnostics** puts it all on the clipboard | +| **● Machine name · 1920×1080 · 60 fps · HEVC** | The stream as negotiated (the numbers show on wide windows); the dot turns amber while the connection is poor | +| **Direct · 38 ms / Relayed via Tokyo · 210 ms / Via your relay · 60 ms** | The path Tailscale found and its round trip. Amber means every packet goes through one of Tailscale's shared relays, which adds delay; **Connection details** under the machine list say why and what would give a direct path. What BroLink asks for is the same on every path | +| **Mouse** | **Captured, for games** (the default): a click on the picture hides the cursor and sends raw movement, which games read; the host key frees it. **Free, for desktops**: the Mac cursor's position is sent 1:1 and nothing is captured. The choice is remembered, and is also in Settings | +| **Keys** | Ctrl+Alt+Delete, Windows key, Alt+Tab, Escape, Print Screen; and **Command acts as Ctrl** | +| **Stats** | The Stream performance panel: frame rate and bitrate against what was asked for, round trip, packet loss, encode, assembly, queue and decode times, and the decoder. **Copy diagnostics** puts it all on the clipboard | | **Full screen** | Toggle; the setting decides how a session starts | -| **PC** | Sleep, Restart…, Shut down… (the latter two ask twice); **Update BroLink Host…** for a PC whose host is older than 3.1 | -| **Stream settings** | The same panel as in the lobby: quick profiles, picture quality, resolution, frame rate, bitrate target and video format. **Apply and reconnect** restarts the session with them in a few seconds | +| **PC** (or **Mac**, **Machine**) | Sleep, Restart…, Shut down… (the latter two ask first); **Update BroLink Host…** for a PC whose host is older than 3.1 | +| **Stream settings** | The same controls as Settings: quality profile, resolution, frame rate, bitrate and codec. **Apply and reconnect** restarts the session with them in a second or two; Escape or the close button leaves them as they were | | **Disconnect** | End the session | -Everything else on the keyboard goes to the PC as pressed. With **⌘ acts -as Ctrl** on (the default), ⌘C, ⌘V, ⌘Z and the rest do on Windows what +On a narrow window everything but **Stream settings** and **Disconnect** +folds into one **More** menu. + +Everything else on the keyboard goes to the PC as pressed. With **Command +acts as Ctrl** on (the default), ⌘C, ⌘V, ⌘Z and the rest do on Windows what they do on the Mac, and ⌘ can stay held across several of them. ### Clipboard @@ -132,7 +141,7 @@ something crossed. With an older host, ⌘V pastes what the PC last copied. Tailscale connects the Mac and the PC directly when it can punch through both routers. When it cannot, every packet goes through a relay: your own -peer relay if you run one (the Relay card in Settings), otherwise one of +peer relay if you run one (**Relay** in Settings), otherwise one of Tailscale's (DERP). A relay adds a detour, and the round trip it adds is a floor under how quickly the PC answers a click; it changes nothing about what BroLink asks for. Recommended quality is this screen's size at 50 @@ -149,39 +158,43 @@ fix it: UPnP or NAT-PMP turned on in the PC's router, a UDP port forwarded to the PC, or IPv6 on both networks. Both machines report their own side (`tailscale netcheck`); the PC's report needs BroLink Host 3.1. -## The PC menu in the lobby +## A machine's menu -Next to a ready PC, **PC** offers **Sleep**, **Restart…**, **Shut -down…** and **Test wake**. Restart and shut down ask for a second click; -both force-close programs on the PC. The menu only appears if the host -allows remote power actions. +The **…** button at the end of each row offers what that machine allows: +**Wake** (asleep, and BroLink has learned how to wake it; sends the packet +without connecting), **Test waking it from this network** (awake), **Sleep**, +**Restart…** and **Shut down…** (when that machine allows remote power +actions), and **Copy Tailscale address**. Restart and shut down ask first; +both force-close programs there. -**Test wake** sends the wake packet to the awake PC and asks BroLink Host -whether it arrived. A pass means waking the PC from where you are will -work; a failure means the packets are not reaching it from this network -(see below). **Wake** appears next to a PC that is asleep and whose wake -details BroLink has learned; it sends the packet without connecting. +**Test waking it from this network** sends the wake packet to the awake +machine and asks BroLink there whether it arrived. A pass means waking it +from where you are will work; a failure means the packets are not reaching +it from this network (see below). ## Settings | Setting | What it changes | |---------|-----------------| -| Quick profiles | **Smooth** 1080p · 60 fps · 20 Mbps, **Balanced** match screen · 60 · 50, **Sharp** match screen · 60 · 100; **Reset** returns to Recommended | -| Picture quality | **Recommended** (the default) asks for this screen's own size at 50 Mbps, on every path. **Manual** uses the rows below; changing the resolution or the bitrate switches to it | +| Quality | **Recommended** (the default) asks for this screen's own size at 50 Mbps, on every path. **Smooth** is 1080p · 60 fps · 20 Mbps, **Balanced** match screen · 60 · 50, **Sharp** match screen · 60 · 100. **Custom** uses the rows below; changing one of them switches to it | | Resolution | **Match screen** is this display's own pixel size (3024×1964 on a 14" MacBook Pro), so the picture fills it exactly. **1080p**, **1440p** and **4K** are the standard 16:9 sizes, 1920×1080, 2560×1440 and 3840×2160; on a display of another shape they leave a bar above and below. The list shows the pixel size of each. The PC switches its display to the size asked for; a PC with no monitor needs a virtual display that lists that size, which setup on the PC arranges for the Virtual Display Driver | | Frame rate | 30 to 240; the PC's display is switched to match when it can | -| Bitrate target | 2 to 150 Mbps. The encoder holds this rate. Large swings mean the path is dropping packets | -| Video format | Auto (HEVC when the PC can encode it), HEVC, H.264 | -| Full screen | How a session starts | -| App | The app on the PC to launch; "Desktop" is the whole PC. The list fills in after the first connection | -| Command key acts as Ctrl | Off makes ⌘ the Windows key | -| Offer to sleep the PC after each session | Off by default. Asleep, Tailscale is off; this Mac can only wake the PC from that PC's own network | +| Bitrate | 2 to 150 Mbps; arrow keys step by 1, Shift-arrow by 10. The encoder holds this rate. Large swings mean the path is dropping packets | +| Codec | Auto (HEVC when both ends can), HEVC, H.264 | +| Open streams full screen | How a session starts | +| Capture the mouse | The toolbar's Mouse choice, remembered | +| Command acts as Ctrl | Off makes ⌘ the Windows key (Mac only) | +| App to open | The app on the PC to launch; "Desktop" is the whole screen. The list fills in after the first connection | +| Offer to sleep a machine after a session | Off by default. Asleep, Tailscale is off there; this Mac can only wake it from its own network | +| Keep BroLink up to date | Updates this app and the Windows PCs' BroLink Host; **Check now** checks at once | +| Relay | Whether a relay of your own is in use, and the grant it needs | +| About | Version, licence, source, and the open-source notices | The stream rows are the panel that **Stream settings** opens during a -session, and the pill under them says exactly what the next connect asks -for; the lobby's **Next session** card shows the same. A session also -reconnects by itself, at the new size, when this Mac's window moves to a -display of a different size. +session, and **Next stream** under them says exactly what the next connect +asks for; the machine list shows the same. A session also reconnects by +itself, at the new size, when this Mac's window moves to a display of a +different size. Settings are saved in `~/Library/Application Support/dev.brolink.BroLink/client.toml`, along with the MAC, LAN address and pairing certificate of each PC BroLink @@ -190,7 +203,7 @@ has paired with. ## Waking a PC from another network Asleep, the PC's Tailscale is off, so BroLink cannot reach it over the -tailnet. Leave the PC on (BroLink Host keeps it awake while plugged in) +tailnet. Leave the PC on (BroLink keeps it awake while plugged in) if you want **Connect** from anywhere. The wake packet is sent to the LAN broadcast, to the PC's LAN address and diff --git a/docs/WINDOWS.md b/docs/WINDOWS.md index a0c51cb..9215052 100644 --- a/docs/WINDOWS.md +++ b/docs/WINDOWS.md @@ -18,7 +18,8 @@ streaming engine, which ships in the same zip, does the capture. 1. Unzip the release and run `brolink-host.exe`, or run `install-host.ps1` to copy it (and the bundled engine archive) to `%LOCALAPPDATA%\BroLink`, add shortcuts, and register the background service to start at logon. -2. Click **Share this machine**. One UAC prompt runs a script that: +2. Open the **Sharing** tab and click **Set up as administrator**. One UAC + prompt runs a script that: - unpacks the bundled engine archive into `%ProgramFiles%\BroLink\engine` (downloads that pinned release if the archive is not beside the exe), and gives its executables BroLink's @@ -44,18 +45,19 @@ streaming engine, which ships in the same zip, does the capture. device wake the PC (`powercfg /deviceenablewake`). The script's transcript is in `%LOCALAPPDATA%\BroLink\setup.log` and is - shown in the window if something fails. Every step is idempotent; run - setup again after fixing whatever it complained about. -3. The status pill turns green. Leave the window closed; the service keeps - running. + shown on the Sharing tab if something fails. Every step is idempotent; + run setup again after fixing whatever it complained about. +3. The Sharing tab says **Shared** with a green dot. Leave the window + closed; the service keeps running. Advanced engine settings are not exposed anywhere in BroLink: BroLink configures the engine itself. ## Slow streams: the network, or the encoder -The **This PC** card has a **Network** line from `tailscale netcheck`. "Hard -NAT with no UPnP" means a Mac on another network can only reach this PC +The **This PC** section of the Sharing tab has a **Network** line from +`tailscale netcheck`. "Hard NAT without UPnP" means a machine on another +network can only reach this PC through a Tailscale relay, which adds a detour. What the Mac asks for is the same either way, so a relayed stream is laggier, not blurrier; the Mac shows the same thing as **Relayed via …** next to the PC. Turn UPnP (or NAT-PMP) on in the router, or forward a UDP port to @@ -131,8 +133,8 @@ It supplies a display even when the physical monitor is off and works with BroLi bundled engine. Setup lists every screen size a Mac can ask for in the driver's settings file, and the engine switches the virtual display to the Mac's size at each connect, so there is nothing to choose in Windows Display settings. If the -driver was installed after setup, run **Set up this PC** again; the setup card -says when sizes are missing. Installing a display does not require pairing the +driver was installed after setup, choose **Run setup again** on the Sharing +tab; its setup list says when sizes are missing. Installing a display does not require pairing the Mac again. ## Using an engine you installed yourself @@ -148,8 +150,9 @@ own engine; the API and config layout are the same. ## Gamepads The engine no longer installs the virtual controller driver (ViGEmBus) by -itself. The **This PC** card shows whether it is present and offers -**Install controller driver**; a reboot afterwards is recommended. +itself. The **This PC** section of the Sharing tab shows whether it is +present and offers **Install controller driver**; a reboot afterwards is +recommended. ## Troubleshooting @@ -157,22 +160,24 @@ itself. The **This PC** card shows whether it is present and offers switch to the size the Mac asked for. On a PC with the Virtual Display Driver, run setup again so that size is listed; a monitor only offers its own sizes, and the stream is scaled from the nearest. -- **Orange "Needs setup" that will not clear**: read `setup.log`. The +- **Amber "Needs setup" that will not clear**: open **Setup log** on the + Sharing tab, or read `setup.log`. The usual causes are the UAC prompt being dismissed, or a driver that refuses the wake keywords. -- **The Mac lists the PC as online but with no BroLink Host**: the control - service is not running or the firewall rule is missing. Open BroLink Host - (it restarts the service) and run setup again. +- **Other machines list the PC as online, but "BroLink isn't installed"**: + the control service is not running or the firewall rule is missing. Open + BroLink on the PC (it restarts the service) and choose **Run setup again** + on the Sharing tab. - **The Mac says the PC is asleep but it is on**: Tailscale's online flag lags by up to half a minute; Connect probes the PC directly and will work. -- **Test wake on the Mac says the packet did not arrive**: the packet is +- **Test waking it from this network says the packet did not arrive**: the packet is being sent but not delivered. From another network the PC's router has to forward UDP 9 to the PC's LAN address (give the PC a DHCP reservation so the address is stable). On the same network, check that Windows classes it as Private; a Public network drops the broadcast. - **Test wake passes but the PC does not wake**: the network path is fine - and Windows is the problem. Check the This PC card for "off" or "Fast - Startup on" and run setup again; for wake from power off, look for the + and Windows is the problem. Check **Wake-on-LAN** on the Sharing tab for + "Off" or "Fast Startup is on" and run setup again; for wake from power off, look for the firmware setting named above. ## Updates @@ -203,8 +208,9 @@ automatic. The background service starts with Windows by default and sets that again at every start, so a PC nobody can reach in person comes back after a -restart; the toggle in Settings is the only thing that turns it off. -It also holds Windows awake while plugged in (same Settings card), because +restart; **Start BroLink when you log in** on the Sharing tab is the only +thing that turns it off. It also holds Windows awake while plugged in +(**Keep this PC awake while plugged in**, same place), because a sleeping PC's Tailscale is asleep and a Mac on another network cannot wake it. The engine runs as a Windows service and streams the sign-in screen, so a From 181fac39622b297725997e6f244dffa38fdadc06 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:40:01 -0700 Subject: [PATCH 33/52] Make "Turn off HDR" the first thing a black picture offers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a stream's picture is black and the machine says its HDR desktop is the cause, the fix was a quiet text button after "Restart stream", which on its own cannot help, and the headline kept saying "Asking the PC why…" after the PC had answered. The fix is now the primary button, Restart stream is secondary, and the stale half of the headline goes once the answer is in. The snapshot run renders this overlay. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/client/src/stream.rs | 60 +++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/crates/client/src/stream.rs b/crates/client/src/stream.rs index 489e95e..bf45b48 100644 --- a/crates/client/src/stream.rs +++ b/crates/client/src/stream.rs @@ -134,6 +134,10 @@ pub struct View { /// until both keys are up. `release_all` clears `Held.modifiers`, so /// deriving "was down" from that retriggered the toggle every frame. capture_chord_held: bool, + /// A video problem to show in place of the session's, for rendering + /// the recovery overlay without a broken capture. + #[cfg(test)] + problem: Option, } impl Default for View { @@ -156,6 +160,8 @@ impl Default for View { hint_since: None, pastes_seen: 0, capture_chord_held: false, + #[cfg(test)] + problem: None, } } } @@ -278,6 +284,11 @@ impl View { Vec2::new(screen.width(), self.bar_h), )); } + #[cfg(test)] + let stats = brolink_stream::Stats { + video_problem: stats.video_problem.clone().or(self.problem.clone()), + ..stats + }; self.toasts( ctx, screen, @@ -867,39 +878,40 @@ impl View { }); } if let Some(problem) = video_problem { + let help = env.video_help.as_ref(); + // Once the machine has answered, "asking why" is stale. + let headline = match help { + Some(_) => problem.trim_end_matches("Asking the PC why…").trim_end(), + None => problem, + }; ui::overlay_frame().show(ui, |ui| { ui.set_width(w); ui.spacing_mut().item_spacing.y = space::XS; ui.add( egui::Label::new( - RichText::new(problem) + RichText::new(headline) .font(theme::medium(theme::text::BODY)) .color(P.text), ) .wrap(), ); // The machine's own answer, once it has given one. - if let Some(help) = env.video_help.as_ref() { + if let Some(help) = help { ui::muted(ui, &help.message); } ui.add_space(space::XS); ui.horizontal(|ui| { + if let Some(h) = help.filter(|h| h.hdr_is_on) { + let label = format!("Turn off {} there", h.mode); + if h.busy { + ui::empty_state(ui, &format!("Turning {} off…", h.mode), true); + } else if ui::primary_button(ui, &label).clicked() { + actions.push(Action::TurnOffHdr); + } + } if ui::secondary_button(ui, "Restart stream").clicked() { actions.push(Action::RestartStream); } - let Some(help) = env.video_help.as_ref() else { - return; - }; - if !help.hdr_is_on { - return; - } - if help.busy { - ui::empty_state(ui, &format!("Turning {} off…", help.mode), true); - } else if ui::ghost_button(ui, &format!("Turn off {} there", help.mode)) - .clicked() - { - actions.push(Action::TurnOffHdr); - } }); }); } @@ -1761,6 +1773,23 @@ pub(crate) mod tests { windows(), None, ), + ( + "black-picture", + View { + problem: Some("The PC is sending a black picture: the connection and the video are healthy, but every frame is blank. Asking the PC why…".into()), + ..View::default() + }, + Extra { + video_help: Some(crate::display::Help { + message: "The PC's desktop is composed in HDR. The capture converts it to an ordinary picture, and on this PC that conversion comes out black. Turning HDR off fixes it.".into(), + hdr_is_on: true, + mode: "HDR".into(), + busy: false, + }), + ..windows() + }, + None, + ), ( "toasts", { @@ -1806,6 +1835,7 @@ pub(crate) mod tests { .collect(), poor: view.poor, poor_hinted: view.poor_hinted, + problem: view.problem.clone(), ..View::default() }; let mut h = harness_with(size, ppp, true, fixture(view, extra.clone())); From beef87842490a0966c68a89216002d4c83f4afa3 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:40:15 -0700 Subject: [PATCH 34/52] Serve the handover download from a blocking socket The handover server polls a non-blocking listener so it can notice a stop. On macOS a socket accepted from a non-blocking listener inherits O_NONBLOCK, so the timeouts set on it did nothing: a read before the PC's request arrived failed at once, and writing the ~20 MB executable failed with WouldBlock as soon as the send buffer filled. The PC then got a truncated file, the script's SHA-256 check refused it, and the install through the stream could not complete from a Mac. Accepted connections are now switched back to blocking. The new test writes 16 MB through a connection accepted this way; it fails without the change. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 4 +++ crates/client/src/handover.rs | 56 +++++++++++++++++++++++++++++++---- 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b7187a..29c1ddd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,10 @@ GitHub release notes, so each version gets a heading of the form would try to send other Windows PCs their update with a Mac-only unzip, failing every few minutes. Its settings now say that updates arrive from the Mac. +- Installing BroLink Host through the stream (for a PC on 3.0) can finish + on a Mac. The Mac served the file from a socket that macOS left + non-blocking, so sending the executable stopped as soon as the network + buffer filled and the PC rejected the truncated download. - Cancel stops a connection attempt straight away while the PC is not answering. The pairing check retried eight times regardless, which could hold the window on "Checking pairing…" for most of a minute after Cancel. diff --git a/crates/client/src/handover.rs b/crates/client/src/handover.rs index aff28ed..4737612 100644 --- a/crates/client/src/handover.rs +++ b/crates/client/src/handover.rs @@ -19,7 +19,7 @@ use brolink_core::HANDOVER_PORT; use brolink_stream::Input; use parking_lot::Mutex; use semver::Version; -use std::net::{Ipv4Addr, SocketAddr, TcpListener}; +use std::net::{Ipv4Addr, SocketAddr, TcpListener, TcpStream}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::{Duration, Instant}; @@ -254,8 +254,8 @@ fn run( if fetched_at.is_some_and(|t| t.elapsed() > AFTER_FETCH) { return Ok(()); } - match listener.accept() { - Ok((mut stream, peer)) => { + match accept(&listener) { + Ok(Some((mut stream, peer))) => { let _ = stream.set_read_timeout(Some(Duration::from_secs(10))); let _ = stream.set_write_timeout(Some(Duration::from_secs(180))); if peer.ip() != pc_ip { @@ -296,14 +296,28 @@ fn run( } } } - Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { - std::thread::sleep(Duration::from_millis(150)); - } + Ok(None) => std::thread::sleep(Duration::from_millis(150)), Err(e) => return Err(anyhow!("accept: {e}")), } } } +/// The next connection, if one is waiting. The listener is non-blocking so +/// the loop can notice a stop, and on macOS a socket accepted from it +/// inherits that: the first read found nothing yet, and writing the 20 MB +/// executable failed the moment the send buffer filled, so the PC got a +/// truncated file. The connection itself blocks, within its timeouts. +fn accept(listener: &TcpListener) -> std::io::Result> { + match listener.accept() { + Ok((stream, peer)) => { + stream.set_nonblocking(false)?; + Ok(Some((stream, peer))) + } + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(None), + Err(e) => Err(e), + } +} + #[cfg(test)] mod tests { use super::*; @@ -326,6 +340,36 @@ mod tests { assert!(!s.contains("{{"), "no leftover braces: {s}"); } + #[test] + fn a_large_reply_goes_out_whole_from_the_non_blocking_listener() { + use std::io::{Read, Write}; + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + listener.set_nonblocking(true).unwrap(); + assert!(accept(&listener).unwrap().is_none(), "nothing waiting yet"); + let addr = listener.local_addr().unwrap(); + let body = vec![7u8; 16 * 1024 * 1024]; + let reader = std::thread::spawn(move || { + let mut c = TcpStream::connect(addr).unwrap(); + // Let the sender fill the socket buffer before anything is read. + std::thread::sleep(Duration::from_millis(200)); + let mut got = Vec::new(); + c.read_to_end(&mut got).unwrap(); + got.len() + }); + let (mut stream, _) = loop { + if let Some(conn) = accept(&listener).unwrap() { + break conn; + } + std::thread::sleep(Duration::from_millis(10)); + }; + stream + .set_write_timeout(Some(Duration::from_secs(10))) + .unwrap(); + stream.write_all(&body).unwrap(); + drop(stream); + assert_eq!(reader.join().unwrap(), body.len()); + } + #[test] fn only_a_release_with_an_update_route_is_worth_typing() { let rel = |v: &str| Release { From d6433d6824af4cc1608e1d02ca3bbf6bb8c50759 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:41:11 -0700 Subject: [PATCH 35/52] Pass the user's data folder to elevated setup as one argument setup::run starts `brolink-host --setup-elevated --local-app-data

` through `Start-Process -ArgumentList @(...)`. Start-Process joins that array with spaces and quotes nothing (as its documentation says), so for a Windows user whose profile folder contains a space the elevated process got `--local-app-data C:\Users\Ada` plus a stray `Lovelace\...` argument, which clap rejects, and setup failed. The folder now carries its own double quotes, without a trailing backslash that would escape the closing one. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 5 +++++ crates/host/src/setup.rs | 45 ++++++++++++++++++++++++++++++++-------- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29c1ddd..2aa0133 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,11 @@ GitHub release notes, so each version gets a heading of the form on a Mac. The Mac served the file from a socket that macOS left non-blocking, so sending the executable stopped as soon as the network buffer filled and the PC rejected the truncated download. +- **Share this machine** works on a Windows account whose folder has a + space in it ("C:\Users\Ada Lovelace"). The elevated setup was handed + that folder through PowerShell's Start-Process, which splits unquoted + arguments at spaces, so it received half a path and an unexpected + extra argument and failed. - Cancel stops a connection attempt straight away while the PC is not answering. The pairing check retried eight times regardless, which could hold the window on "Checking pairing…" for most of a minute after Cancel. diff --git a/crates/host/src/setup.rs b/crates/host/src/setup.rs index ba27ff5..0eaa73a 100644 --- a/crates/host/src/setup.rs +++ b/crates/host/src/setup.rs @@ -573,15 +573,12 @@ pub fn run(p: &Plan<'_>) -> Result<()> { // UAC may run the helper as another administrator, whose profile // holds no host.toml: pass this user's folder so the engine login // and setup.log stay with the account that shares the machine. - let args = std::env::var_os("LOCALAPPDATA") - .filter(|dir| !dir.is_empty()) - .map(|dir| { - format!( - "'--setup-elevated', '--local-app-data', '{}'", - q(&dir.to_string_lossy()) - ) - }) - .unwrap_or_else(|| "'--setup-elevated'".into()); + let args = elevated_args( + std::env::var_os("LOCALAPPDATA") + .filter(|dir| !dir.is_empty()) + .map(|dir| dir.to_string_lossy().into_owned()) + .as_deref(), + ); let launch = format!( "$p = Start-Process -FilePath '{exe}' -Verb RunAs -Wait -PassThru -WindowStyle Hidden -ArgumentList @({args}); if ($null -eq $p) {{ exit 1 }}; exit $p.ExitCode" ); @@ -617,6 +614,22 @@ pub(crate) fn powershell(script: &str) -> Result { Ok(String::from_utf8_lossy(&out.stdout).into_owned()) } +/// The `-ArgumentList` array for the elevated `--setup-elevated` run. +/// Start-Process joins the elements with spaces and quotes none of them, +/// so a folder with a space in it ("C:\Users\Ada Lovelace\...") has to +/// carry its own double quotes or it arrives as two arguments. A trailing +/// backslash would escape the closing quote, and a folder never needs one. +#[cfg(any(windows, test))] +fn elevated_args(local_app_data: Option<&str>) -> String { + match local_app_data { + Some(dir) => format!( + "'--setup-elevated', '--local-app-data', '\"{}\"'", + q(dir.trim_end_matches('\\')) + ), + None => "'--setup-elevated'".into(), + } +} + // Only the Windows setup path is compiled in a release build; tests use it everywhere. #[cfg_attr(not(windows), allow(dead_code))] /// A PowerShell single-quoted literal: only `'` needs escaping, nothing is @@ -828,6 +841,20 @@ mod tests { .all(|b| b.is_ascii_alphanumeric() || b == b'+' || b == b'/' || b == b'=')); } + #[test] + fn the_elevated_run_gets_the_whole_folder_as_one_argument() { + assert_eq!(elevated_args(None), "'--setup-elevated'"); + assert_eq!( + elevated_args(Some(r"C:\Users\Ada Lovelace\AppData\Local")), + r#"'--setup-elevated', '--local-app-data', '"C:\Users\Ada Lovelace\AppData\Local"'"# + ); + assert_eq!( + elevated_args(Some(r"C:\Users\O'Brien\AppData\Local\")), + r#"'--setup-elevated', '--local-app-data', '"C:\Users\O''Brien\AppData\Local"'"#, + "quote escaped for PowerShell, no trailing backslash before the quote" + ); + } + #[test] fn run_value_quotes_the_path_and_asks_for_background() { let p = PathBuf::from(r"C:\Users\Ada\AppData\Local\BroLink\brolink-host.exe"); From 1a8aa5b385041ad6a93d101612761d68ff83c868 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:41:29 -0700 Subject: [PATCH 36/52] Stop listing phones as machines missing BroLink An iPhone or Android device on the tailnet showed "BroLink isn't installed", which sends someone looking for an app that does not exist. It now says phones and tablets can't share a desktop. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/client/src/app.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/crates/client/src/app.rs b/crates/client/src/app.rs index 19495d2..e36bd04 100644 --- a/crates/client/src/app.rs +++ b/crates/client/src/app.rs @@ -1641,6 +1641,16 @@ fn os_label(os: &str) -> &'static str { fn describe(pc: &Pc) -> (Tone, String) { let os = os_label(&pc.os); let at = pc.ip.map(|ip| format!(" · {ip}")).unwrap_or_default(); + // A phone on the tailnet is listed, but it will never share a desktop: + // "BroLink isn't installed" would send someone looking for an app. + let lower = pc.os.to_ascii_lowercase(); + if lower == "ios" || lower == "android" || lower == "ipados" { + let name = if lower == "android" { "Android" } else { "iOS" }; + return ( + Tone::Neutral, + format!("{name}{at} · Phones and tablets can't share a desktop"), + ); + } if pc.remembered { let seen = pc.known.as_ref().and_then(|k| k.last_seen_unix); return ( @@ -1806,6 +1816,16 @@ mod tests { pc.host.as_mut().unwrap().setup = vec!["The streaming engine is not installed.".into()]; assert_eq!(describe(&pc).0, Tone::Warning, "set up there, not ready"); pc.host.as_mut().unwrap().setup.clear(); + let phone = Pc { + os: "iOS".into(), + online: true, + ..pc.clone() + }; + assert!( + line(&phone).contains("can't share a desktop"), + "{}", + line(&phone) + ); pc.path = crate::path::Path { direct: Some(false), relay: "tok".into(), From 6a7ff9bdcb2b556f810be6af23983a696fb4525b Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:42:11 -0700 Subject: [PATCH 37/52] Log the NAT when it changes, not when the nearest relay does tailscale netcheck's preferred DERP region moves between runs on the same network (this Mac's service.log: Paris, Chicago, Paris within an hour), and every move logged a full network line. The window keeps the last 80 lines, so the flips pushed out what mattered. The line is now logged when UDP, the address families, the NAT's hardness or port mapping change; the report itself still carries the current region. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/host/src/service.rs | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/crates/host/src/service.rs b/crates/host/src/service.rs index 1276685..80dc47a 100644 --- a/crates/host/src/service.rs +++ b/crates/host/src/service.rs @@ -374,7 +374,7 @@ impl Service { Ok(n) => { let report = n.report(); let mut cur = svc.nat.lock(); - if cur.as_ref() != Some(&report) { + if cur.as_ref().is_none_or(|c| nat_changed(c, &report)) { svc.log(describe_nat(&report)); } *cur = Some(report); @@ -846,6 +846,15 @@ impl Owner { } } +/// Whether the NAT itself changed. The nearest relay region moves between +/// runs on its own (Paris, then Chicago, then Paris again on the same +/// network), and logging each move pushed useful lines out of the 80 the +/// window shows. +fn nat_changed(old: &NatReport, new: &NatReport) -> bool { + (old.udp, old.ipv4, old.ipv6, old.hard, old.portmap) + != (new.udp, new.ipv4, new.ipv6, new.hard, new.portmap) +} + /// True when a service answers on loopback. pub fn service_alive() -> bool { http::request( @@ -1184,6 +1193,26 @@ mod tests { assert_eq!(r.status, 200); } + #[test] + fn a_new_nearest_relay_is_not_a_new_nat() { + let paris = NatReport { + udp: true, + hard: Some(true), + derp: "par".into(), + ..Default::default() + }; + let chicago = NatReport { + derp: "ord".into(), + ..paris.clone() + }; + assert!(!nat_changed(&paris, &chicago)); + let mapped = NatReport { + portmap: true, + ..paris.clone() + }; + assert!(nat_changed(&paris, &mapped)); + } + #[test] fn the_nat_line_names_the_problem() { let hard = NatReport { From 4488f53200216800c5bf58765431a7153e74f8fc Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:42:56 -0700 Subject: [PATCH 38/52] Log a peer once, not every time its whois answer is refreshed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The control service caches a whois answer for a minute and logged the peer on every refresh. A machine polling /v1/status every few seconds therefore wrote "hermes (…) asked" once a minute for as long as it was up: 2,707 of 2,810 lines in this Mac's service.log, and the whole of the 80 lines the window shows. Answers are now remembered for an hour, and a peer is logged when it is first seen, when its answer changes, or after an hour without asking. The one-minute trust in an answer is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 6 ++++++ crates/host/src/service.rs | 42 +++++++++++++++++++++----------------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2aa0133..76fa43d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,12 @@ GitHub release notes, so each version gets a heading of the form that folder through PowerShell's Start-Process, which splits unquoted arguments at spaces, so it received half a path and an unexpected extra argument and failed. +- The service log keeps what matters. Every machine that polled the + service was logged again each minute ("hermes (…) asked" was 2,707 of + the 2,810 lines in this Mac's `service.log`), and every change of the + nearest Tailscale relay region logged a network line. Each peer is now + logged when first seen, when its answer changes or after an hour away, + and the network line only when the NAT itself changes. - Cancel stops a connection attempt straight away while the PC is not answering. The pairing check retried eight times regardless, which could hold the window on "Checking pairing…" for most of a minute after Cancel. diff --git a/crates/host/src/service.rs b/crates/host/src/service.rs index 80dc47a..169af01 100644 --- a/crates/host/src/service.rs +++ b/crates/host/src/service.rs @@ -32,6 +32,8 @@ use std::time::{Duration, Instant}; const LOG_LINES: usize = 80; /// How long a `whois` answer is trusted before asking again. const AUTH_TTL: Duration = Duration::from_secs(60); +/// How long an answer is remembered, so the same peer is not logged again. +const AUTH_REMEMBER: Duration = Duration::from_secs(3600); const AUTH_CACHE_LIMIT: usize = 256; /// Ticks (of 5 s) between runs of `tailscale netcheck`: it takes seconds /// and the network does not move often. @@ -492,14 +494,11 @@ impl Service { return false; } let now = Instant::now(); - let cached = self - .auth - .lock() - .get(&ip) - .filter(|(at, _)| now - *at < AUTH_TTL) - .map(|(_, allowed)| *allowed); - if let Some(allowed) = cached { - return allowed; + let previous = self.auth.lock().get(&ip).copied(); + if let Some((at, allowed)) = previous { + if now - at < AUTH_TTL { + return allowed; + } } // Copied out so the lock is not held across the `whois` call. let owner = match &*self.tailscale.lock() { @@ -509,16 +508,21 @@ impl Service { let allowed = match tailscale::whois(ip) { Ok(w) => { let allowed = owner.admits(&w); - self.log(format!( - "{} ({}) asked{}", - w.node.computed_name, - w.user_profile.login_name, - if allowed { - "" - } else { - ": not this account, refused" - } - )); + // Once per peer, and again if the answer changes or it has + // been away an hour: a Mac polling every few seconds used + // to put a line here every minute, 96% of a Mac's log. + if previous.map(|(_, a)| a) != Some(allowed) { + self.log(format!( + "{} ({}) asked{}", + w.node.computed_name, + w.user_profile.login_name, + if allowed { + "" + } else { + ": not this account, refused" + } + )); + } allowed } Err(e) => { @@ -527,7 +531,7 @@ impl Service { } }; let mut cache = self.auth.lock(); - cache.retain(|_, (at, _)| at.elapsed() < AUTH_TTL); + cache.retain(|_, (at, _)| at.elapsed() < AUTH_REMEMBER); if cache.len() >= AUTH_CACHE_LIMIT { if let Some(oldest) = cache .iter() From cb2a2829b88d0e8f54de1439c76ce43cbc9ff208 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:43:03 -0700 Subject: [PATCH 39/52] Write down the UI audit and the design system it led to Every problem found on 4.0.2, with its severity and what happened to it, the tokens and components the screens are now built from, and what is left or could not be verified without running the app. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/design/AUDIT.md | 169 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 docs/design/AUDIT.md diff --git a/docs/design/AUDIT.md b/docs/design/AUDIT.md new file mode 100644 index 0000000..46e616e --- /dev/null +++ b/docs/design/AUDIT.md @@ -0,0 +1,169 @@ +# UI audit and redesign, 4.0.2 → unreleased + +A harsh pass over every screen of the unified window, the Sharing page +and the stream overlay, as rendered by the snapshot harness and as read +in code. Severity is the effect on someone using BroLink, not the effort +to fix. Every item was found on 4.0.2 (`f96e1a3`); the last column is +its state on this branch. + +Renders: `cargo test -p brolink-client -p brolink-host snapshots -- --ignored` +writes `target/ui-snapshots/`. Names below refer to that folder +(`client-lobby-1280x800@2x.png` and so on). + +## Findings + +### Structure and flows + +| # | Severity | Finding | State | +|---|----------|---------|-------| +| A1 | Critical | The unified window never drew the host's panel. `NodeApp` used `HostApp` only for its state; the panel with this machine's Tailscale, engine, network, Wake-on-LAN and controller status, the paired devices and Unpair, the service log, Open log folder, Stop service and Repair was reachable only from snapshot tests. On a real install none of it could be seen. | Fixed: the **Sharing** tab is that page, drawn by the host crate inside the window (`SharePage`). | +| A2 | High | The snapshot harness loaded the real `client.toml` and `host.toml`, so renders showed the developer's own settings (20 Mbps, 1080p in the 4.0.2 set) and a test that clicked a switch would have written them. | Fixed: headless constructors with default settings that never save; host actions are inert when headless. | +| A3 | High | The six relay-state renders were byte-identical: the Relay card sat below the bottom of a 1100-point render, so the harness showed none of the states it named. | Fixed: relay states are asserted by query at the minimum size, and `*-full.png` renders whole pages. | +| A4 | High | `client-stream-toolbar.png` never showed the toolbar (it is hidden until the host key), the tests that did draw it were named so the `snapshots` filter skipped them, and two tests wrote the same file. | Fixed: `stream-*` renders cover the toolbar at every size and each menu, panel and overlay. | +| A5 | Medium | Renders existed at one or two sizes, mostly 2×, with three machines. No many-machine list, long names, empty, looking, offline, Tailscale-stopped, updating, pairing-stuck, confirm or session-ended states. | Fixed: every state at 640×420, 1280×800 and 1920×1200, at 1× and 2×, plus unignored fit tests at the minimum and large sizes. | +| A6 | Medium | Settings was a toggle ("Settings" / "Close settings") that replaced the list; there was no navigation, and no way to reach settings from the keyboard. | Fixed: tabs (Machines, Sharing, Settings) with Command/Ctrl-1, 2, comma and Escape. | +| A7 | Medium | "This machine" in the list offered only Share or Repair; the paired devices, options and diagnostics lived in a window no one saw (A1), and the three sharing switches were in the viewer's Settings, away from the thing they configure. | Fixed: the row summarises and opens Sharing; the switches live there. | + +### Performance and threads + +| # | Severity | Finding | State | +|---|----------|---------|-------| +| P1 | High | The idle window repainted every 400 ms forever (`request_repaint_after(400ms)` on every frame), and the host poller asked for a repaint every second whether anything changed or not. | Fixed: nothing repaints on a timer when idle (tested: `an_idle_window_asks_for_no_repaint`); the poller repaints on change. | +| P2 | High | Every two seconds the UI thread called `tailscale::cli()`, which, when Tailscale is not installed, spawns `tailscale --version` and waits for it. | Fixed: probed on its own thread. | +| P3 | Medium | Unpair, Install controller driver and the setup request (a 600 ms HTTP probe plus a process spawn) ran on the UI thread. | Fixed: worker threads, with the result shown as a notice. | +| P4 | Low | `NodeApp` cloned the host's whole status, service log included, into the viewer every frame, stream frames included. | Fixed: copied only when it changes (`Shared::rev`). | +| P5 | — | Stream view per-frame cost. | Unchanged: the toolbar, menus and panels are laid out only while shown; notices ask for a 500 ms repaint only while up. | + +### Visual design + +| # | Severity | Finding | State | +|---|----------|---------|-------| +| V1 | High | No design system: one palette struct, but sizes chosen inline (13.5, 12.5, 17, 28, 30, 40 pt), screen-local `semibold(28.0)` headlines, an inline `egui::Grid` for stats, inline colours. | Fixed: tokens in `theme.rs` (below); screens use only tokens and components. | +| V2 | High | The app did not look like the product site: violet, pink and cyan chrome in Inter against bardbro.com's black, white, hairlines and Geist. | Fixed: Geist and Geist Mono, neutral surfaces, white primary, colour only for status. | +| V3 | Medium | A marketing headline inside the app ("Your workspace, anywhere.") at 30 pt, pushing the machine list below the fold at 640×420. | Fixed: a plain page title. | +| V4 | Medium | Status pills used as data chips ("1920 × 1080" in cyan, "60 fps" in grey): colour that meant nothing. | Fixed: mono tags; colour only on status. | +| V5 | Medium | Three filled violet Connect buttons in a three-row list: the "primary" colour repeated until it said nothing. | Fixed: outlined Connect; one white primary per screen at most. | +| V6 | Medium | Four control styles side by side: egui's framed menu buttons ("PC ⌄"), frameless ghost buttons, egui's filled-triangle dropdowns, egui's default collapsing triangle, and "●/○" glyphs as radio buttons in menus. | Fixed: one button family, painted chevrons, check marks, popup menus. | +| V7 | Medium | Notices glued the dot to the text ("●Gaming-PC's…") and wrapped the second line under the dot. | Fixed: a fixed gutter; every line starts at the same x. | +| V8 | Medium | Host setup card: the caption shared a wrapping row with the button and wrapped under it; checklist lines 14 pt apart. | Fixed. | +| V9 | Low | The stream toolbar was a rectangle with a white hairline on all four sides. | Fixed: bottom hairline only. | +| V10 | Low | Bitrate slider: large hollow knob, violet trail, a drag box beside it. | Fixed: a slim slider with a mono value, arrow-key steps. | +| V11 | Low | The App field was 20 pt tall beside 30-pt controls. | Fixed. | + +### Contrast (WCAG 2.2 AA, computed) + +| # | Severity | Finding | State | +|---|----------|---------|-------| +| C1 | High | Faint text `#5c6676` (footer, small print, stream status line): 3.45:1 on the window, 3.22:1 on cards, 3.62:1 on black. Fails 4.5:1. | Fixed: tertiary `#8c8c8c`, ≥ 4.5:1 on every surface up to a hovered control. | +| C2 | High | Destructive confirm button: `#e8ecf2` on `#f85149`, 2.83:1. | Fixed: white on `#cd2b31`, 5.27:1. | +| C3 | Medium | Violet pill text on its own tint ("Connecting", "Needs setup"): 3.89:1. | Fixed: status text is secondary grey beside a coloured dot. | +| C4 | Low | Violet accent as text at 4.51:1 on cards: passing by 0.01. | Fixed: the accent is `#a78bfa` (6.9:1) and is never text on a fill. | + +All pairs are asserted in `crates/ui/src/theme.rs` and `widgets.rs` tests. + +### Copy and behaviour + +| # | Severity | Finding | State | +|---|----------|---------|-------| +| U1 | High | "This Mac" everywhere, on Windows and Linux viewers too: errors, key-expiry notices, path explanations, settings hints. | Fixed: "this machine", or the right noun per OS. | +| U2 | High | Errors sent people to a UI that no longer exists: "Open BroLink Host on the PC and run setup", "Check Wake-on-LAN in BroLink Host". | Fixed: they point at the Sharing tab and say what to do. | +| U3 | Medium | "Keep this machine awake while plugged in" on Mac and Linux, where `keep_awake` does nothing. | Fixed: Windows only. | +| U4 | Medium | "Command key acts as Ctrl" on Windows and Linux viewers, which have no Command key. | Fixed: Mac only. | +| U5 | Medium | Connect buttons vanished while any connection ran, reflowing the list. | Fixed: disabled in place, with a tooltip. | +| U6 | Medium | "Wake" beside "Connect", which also wakes. | Fixed: "Wake and connect"; Wake alone is in the row's menu. | +| U7 | Medium | The lobby's power menu was "PC" for every OS and hid Test wake behind power permissions. | Fixed: a "…" menu with what each machine allows, plus Copy Tailscale address. | +| U8 | Medium | While the app updated itself the connection card had an empty title. | Fixed: "Installing a BroLink update". | +| U9 | Medium | Every failure offered "Try again at Smooth (1080p · 20 Mbps)", even a wake failure it cannot help, and a connection that never started was titled "… disconnected". | Fixed: "Couldn't connect to …" or "… disconnected"; the lighter profile only for stream failures. | +| U10 | Medium | "Connecting failed at {stage} (code {code})." | Fixed: says the machine answered and points at the network. | +| U11 | Medium | Pairing called the other side "BroLink Host"; a stuck PIN hand-off showed as a violet notice. | Fixed: PIN in boxes, plain explanation, amber notice. | +| U12 | Medium | "Tailscale: Tailscale is stopped." beside "Sign in to Tailscale to see your machines" over a list of remembered machines. | Fixed: one banner that says what happened and what to do; the list stays. | +| U13 | Medium | Stream quality was two controls for one choice: four unselectable "quick profile" buttons plus Recommended/Manual, and a Reset. | Fixed: one control, Recommended · Smooth · Balanced · Sharp · Custom. | +| U14 | Low | Settings was one long card; the licence only as raw NOTICE text. | Fixed: grouped sections and an About section. | +| U15 | Low | Relay copy: "That is not a denial." | Fixed: "That does not mean access is denied." | +| U16 | Low | A key expiring in 176 days shown in a bright notice on the main page. | Fixed: folded into Connection details; within 30 days it goes to the top in red. | +| U17 | Medium | Open log folder ran `explorer` on every OS. | Fixed: `open` on macOS, `xdg-open` on Linux. | +| U18 | Low | Setup failure showed the script's raw error. | Fixed: what happened and what to do, raw error in small print. | +| U19 | Medium | No way out of "Connecting to …" in the stream view without knowing the host key. | Fixed: a Cancel button. | +| U20 | Medium | The host key was only a five-second text toast, and read "Ctrl+Alt" on a Mac keyboard labelled control and option. | Fixed: keycaps at stream start and on the toolbar, named per platform. | +| U21 | Low | Mouse menu items were whole sentences with "●" prefixes; Keys had "○ ⌘ acts as Ctrl (now the Windows key)". | Fixed. | +| U22 | Low | Stream performance used egui's title bar and a raw grid; its "Auto · Balanced" label did not match Settings' "Recommended". | Fixed. | +| U23 | Low | Stream settings used egui's title bar; Escape did nothing. | Fixed. | +| U24 | Medium | Black picture: the fix (Turn off HDR) was a quiet button after Restart stream, and the headline kept "Asking the PC why…" after the answer. | Fixed. | +| U25 | Low | A "Next session" card of pills plus a "Configure stream" button repeated the settings summary. | Fixed: one mono line with a Change link. | +| U26 | Low | "Paired Macs", "Start the background service with Windows" on a Mac. | Fixed. | +| U27 | Low | The poor-network toast said "Open Stats" without saying how to reach the toolbar. | Fixed. | +| U28 | Low | The Sharing page's network line said "a Mac … reaches this PC" on a Mac. | Fixed in the page; the service log's own line is unchanged. | +| U29 | Low | Phones on the tailnet were listed as "BroLink isn't installed". | Fixed. | +| U30 | Low | A relayed path was red, like a failure. | Fixed: amber, with the explanation in Connection details. | + +### Accessibility and input + +| # | Severity | Finding | State | +|---|----------|---------|-------| +| X1 | High | Tab order ran backwards through right-aligned controls (egui focuses in creation order; right-to-left layouts create the rightmost first): a machine's menu before its Connect, the toolbar from Disconnect leftwards. | Fixed: `trailing` layout; tested. | +| X2 | Medium | Focus rings drawn inside controls in text colour, and egui's own widgets showed focus only as the pressed style. | Fixed: a violet ring outside every custom control and on built-in ones. | +| X3 | Medium | No window shortcuts. | Fixed: Command/Ctrl-1, 2, comma; Escape. | +| X4 | Low | Switch hit target 40×22 (< 24). | Fixed: 36×24. | +| X5 | Low | Pills and other painted text were missing from the accessibility tree. | Fixed: status, tags, keycaps and the PIN report labels. | + +## Design system + +All in `crates/ui/src/theme.rs`; components in `widgets.rs`. + +**Colour** (dark only, by design) + +| Token | Value | Use | +|-------|-------|-----| +| `bg` | `#0a0a0a` | Window, elevation 0 | +| `surface` | `#111111` | Cards, grouped lists, panels, elevation 1 | +| `raised` / `raised_hover` / `pressed` | `#1a1a1a` / `#242424` / `#2e2e2e` | Controls and their states | +| `well` | `#060606` | Logs, code | +| `overlay` | `#080808` at 92% | Panels over the stream | +| `hairline` / `border` / `border_strong` | `#242424` / `#363636` / `#4a4a4a` | Separators, control outlines, hovered outlines | +| `text` / `text_secondary` / `text_tertiary` / `text_disabled` | `#ededed` / `#a1a1a1` / `#8c8c8c` / `#5e5e5e` | 16.9, 7.7, 5.9:1 on `bg` | +| `primary` (+ hover, pressed) / `on_primary` | `#ededed` / `#0a0a0a` | The one main action; a switch that is on; the chosen segment | +| `accent` | `#a78bfa` | Keyboard focus, selection, progress. Never a fill behind text | +| `success` / `warning` / `danger` | `#4cc38a` / `#f0b249` / `#ff6369` | Ready / needs attention / broken | +| `danger_fill` | `#cd2b31` | Behind white text on a confirming destructive button | + +**Type**: Geist and Geist Mono (OFL 1.1). Page title 22 semibold, −0.44 +tracking · card title 15 semibold · body 14 regular, controls 14 medium · +caption 12.5 · data 12.5 mono · section label 11 mono medium, uppercase, ++0.66 tracking (the site's label style) · PIN 32 mono medium. + +**Spacing**: 2 · 4 · 8 · 12 · 16 · 24 · 32 · 48. Page gutter 24; content +column 880 (lists) or 720 (forms). + +**Sizes**: control 32, compact control 28, minimum hit target 24, top bar +48, toolbar 44, list and settings rows 44 to 60, status dot 8, switch 36×20. + +**Radii**: 4 controls and keycaps · 6 cards, lists, notices, menus · 10 +panels over the stream. + +**Elevation**: 0 window · 1 surface with a hairline, no shadow · popup +(menus, dropdowns) shadow 0/8/24 at 55% black · overlay (stream panels) +shadow 0/12/32 at 63% black. + +**Components**: buttons in five kinds (primary, secondary, quiet, danger, +destructive), icon button, link, popup menu with items, choices, labels, +notes and separators, tabs, segmented control, switch, slider, select, +setting row and block, list row, grouped list and section, card and toned +card, notice and banner, key/value grid, disclosure, well and log, +status dot and text, tag, keycap and shortcut, PIN digits, spinner, +brand lockup, top and bottom bars, overlay and panel frames. + +## Not done, and not verified + +- The stream picture itself: every stream render has no video behind the + overlay, because the harness has no engine to connect to. How the + toolbar and notices read over a real, bright desktop is unverified. +- Real windows: font hinting at 1× on Windows, Retina at 2× on a Mac, + VoiceOver and Narrator reading the accesskit tree, IME, and the + Command/Ctrl shortcuts with a real keyboard were not tried; the rules + forbid launching the app here. Windows-only code compiles in CI only. +- The service's own log lines (`service.rs`) still say "the Mac"; they + are logs, and the file belongs to the service. +- `Live::quality_label` in `session.rs` has no caller now; the stream + overlay names profiles as Settings does. +- The product site's screenshots show the 4.0.2 window. +- No light theme. From 4c077903dafca08586ca8c89ab76d9fa00890b9a Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:43:50 -0700 Subject: [PATCH 40/52] Say plainly when setup is done, and keep Mac power hints to what works After a successful setup the Sharing page kept a card titled "Sharing is set up" with another Run setup again button and a note that "the list above clears" when there was no list. It now shows one line that other machines can connect. The power switch no longer mentions waking on a Mac, which BroLink does not do, and Settings' update row is labelled for what it shows. The changelog's size figure is now measured: the macOS release binary went from 12,167,440 to 11,689,120 bytes. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 4 ++-- crates/client/src/app.rs | 2 +- crates/host/src/app.rs | 20 +++++++++++++++++--- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 540aac3..235dca0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,8 +49,8 @@ GitHub release notes, so each version gets a heading of the form every two seconds on the UI thread now runs on its own thread. Unpairing and installing the controller driver no longer block the window. -- The app is about 570 KB smaller: Geist's five cuts replace Inter's - three. +- The app is about 470 KB smaller (11.7 MB for the macOS release + binary): Geist's five cuts replace Inter's three. ## 4.0.2 (2026-09-22) diff --git a/crates/client/src/app.rs b/crates/client/src/app.rs index e36bd04..cbfbf24 100644 --- a/crates/client/src/app.rs +++ b/crates/client/src/app.rs @@ -1372,7 +1372,7 @@ impl ClientApp { } else { format!("{message} · {}", update::ago(checked)) }; - ui::setting_row(ui, "Status", Some(&hint), |ui| { + ui::setting_row(ui, "Last check", Some(&hint), |ui| { let clicked = ui .add_enabled_ui(self.cfg.auto_update, |ui| { ui::secondary_button(ui, "Check now") diff --git a/crates/host/src/app.rs b/crates/host/src/app.rs index d90c0d4..5fa59ff 100644 --- a/crates/host/src/app.rs +++ b/crates/host/src/app.rs @@ -439,7 +439,17 @@ impl HostApp { .collect(); let tailscale_item = s.setup.iter().find(|l| l.starts_with("Tailscale")); let done = admin_items.is_empty() && tailscale_item.is_none(); - if done && result.is_none() && !running { + if done && !running && !matches!(result, Some(Err(_))) { + if result.is_some() { + ui::notice( + ui, + Tone::Success, + &format!( + "Setup finished. Other machines can connect to this {} now.", + self.os.noun() + ), + ); + } return; } let migrate = crate::migrate::uses_old_engine(&s.streamer.kind); @@ -544,7 +554,7 @@ impl HostApp { ui::notice( ui, Tone::Success, - "Setup finished. The list above clears as the service re-checks, within a few seconds.", + "Setup finished. The service is re-checking; the items above clear within a few seconds.", ); } Some(Err(e)) => { @@ -776,7 +786,11 @@ impl HostApp { ui, &mut self.cfg.power_allowed, &format!("Let other machines sleep, restart or shut down this {noun}"), - Some(&format!("Any machine your Tailscale access rules allow can ask. Waking this {noun} works only from its own network.")), + Some(&if self.os == Os::Windows { + format!("Any machine your Tailscale access rules allow can ask. Waking this {noun} works only from its own network.") + } else { + "Any machine your Tailscale access rules allow can ask.".to_string() + }), ) { self.dirty = true; } From 53daa018225044b241de163aab177ce2e0529f0c Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:43:51 -0700 Subject: [PATCH 41/52] Name the output device sound actually moved to MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the Mac's default output changes during a stream, cpal 0.18's CoreAudio backend reroutes the default-device stream itself and reports ErrorKind::DeviceChanged through the error callback. BroLink logged that as a warning (28 of them in this Mac's panel.log) and kept the old device's name in Output::Playing, so the stats said "audio → WH-1000XM4" while the sound played on AirPods. The callback now takes the new default's name for a DeviceChanged and still warns about anything else. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 3 +++ crates/stream/src/audio.rs | 35 +++++++++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76fa43d..9ffbd10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,9 @@ GitHub release notes, so each version gets a heading of the form nearest Tailscale relay region logged a network line. Each peer is now logged when first seen, when its answer changes or after an hour away, and the network line only when the NAT itself changes. +- When the Mac's sound output changes mid-stream (AirPods connect, say), + the stream's stats name the device the sound moved to. macOS already + moved it; BroLink kept naming the old one and logged a warning. - Cancel stops a connection attempt straight away while the PC is not answering. The pairing check retried eight times regardless, which could hold the window on "Checking pairing…" for most of a minute after Cancel. diff --git a/crates/stream/src/audio.rs b/crates/stream/src/audio.rs index 12e68fd..bcad24b 100644 --- a/crates/stream/src/audio.rs +++ b/crates/stream/src/audio.rs @@ -283,14 +283,37 @@ fn output_thread( source_rate as usize * source_channels as usize * 40 / 1000, ) }; - let err_fn = |e| tracing::warn!("audio output: {e}"); + // CoreAudio moves a default-device stream to the new default by + // itself and reports it as an error; only the name shown needs + // to follow. It used to be logged as a warning and the stats kept + // naming the device sound had left. + let err_fn = { + let output = output.clone(); + move |e: cpal::Error| { + if e.kind() != cpal::ErrorKind::DeviceChanged { + tracing::warn!("audio output: {e}"); + return; + } + let Some(now) = cpal::default_host() + .default_output_device() + .map(|d| d.to_string()) + .filter(|n| !n.is_empty()) + else { + return; + }; + tracing::info!("audio: now playing on {now}"); + if let Output::Playing { device, .. } = &mut *output.lock() { + *device = now; + } + } + }; let stream = match supported.sample_format() { cpal::SampleFormat::F32 => { let mut m = mixer(); device.build_output_stream( config, move |out: &mut [f32], _| m.fill(out), - err_fn, + err_fn.clone(), None, ) } @@ -299,7 +322,7 @@ fn output_thread( device.build_output_stream( config, move |out: &mut [i16], _| m.fill_i16(out), - err_fn, + err_fn.clone(), None, ) } @@ -308,7 +331,7 @@ fn output_thread( device.build_output_stream( config, move |out: &mut [i32], _| m.fill_i32(out), - err_fn, + err_fn.clone(), None, ) } @@ -318,7 +341,7 @@ fn output_thread( match device.build_output_stream( config, move |out: &mut [f32], _| m.fill(out), - err_fn, + err_fn.clone(), None, ) { Ok(s) => Ok(s), @@ -328,7 +351,7 @@ fn output_thread( device.build_output_stream( config, move |out: &mut [i16], _| m.fill_i16(out), - err_fn, + err_fn.clone(), None, ) } From a84dcec51626a64c3a6347dc8edc6728270d8efe Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:44:40 -0700 Subject: [PATCH 42/52] Count a pushed host that is already on the release as updated This Mac's panel.log has "host update attempt 1/3: connection closed" followed by "host update for Hermes: this PC already runs BroLink Host 4.0.2": the first POST landed, Hermes answered and handed over to the new executable, the reply was lost with the old process, and the retry got 409 NotNewer, which the lobby showed as "Could not update Hermes". After a dropped connection or on a retry, push_host now asks the host's /v1/status (for up to 15 s, while it restarts) and treats a version at or past the release as success. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 5 +++ crates/client/src/update.rs | 75 +++++++++++++++++++++++++++++++++++-- 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ffbd10..9ab64d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,11 @@ GitHub release notes, so each version gets a heading of the form - When the Mac's sound output changes mid-stream (AirPods connect, say), the stream's stats name the device the sound moved to. macOS already moved it; BroLink kept naming the old one and logged a warning. +- A host update that lands is no longer reported as a failure. A PC can + take the new executable and restart before its reply reaches the Mac; + the Mac then retried, heard "this PC already runs BroLink Host 4.0.2", + and showed "Could not update" in red. It now asks the PC which version + it runs before calling a push failed. - Cancel stops a connection attempt straight away while the PC is not answering. The pairing check retried eight times regardless, which could hold the window on "Checking pairing…" for most of a minute after Cancel. diff --git a/crates/client/src/update.rs b/crates/client/src/update.rs index 38a4f7d..bbdd9b2 100644 --- a/crates/client/src/update.rs +++ b/crates/client/src/update.rs @@ -16,14 +16,14 @@ use crate::config::ClientConfig; use crate::session::{Discovery, Live, Progress}; use anyhow::{anyhow, bail, Context, Result}; -use brolink_core::api::{Ack, UPDATE_PATH, UPDATE_SHA256_HEADER, UPDATE_VERSION_HEADER}; +use brolink_core::api::{Ack, Status, UPDATE_PATH, UPDATE_SHA256_HEADER, UPDATE_VERSION_HEADER}; use brolink_core::update::{self, Release, HOST_EXE, MAC_ASSET, WINDOWS_ASSET}; use brolink_core::{http, CONTROL_PORT}; use brolink_ui::Tone; use parking_lot::Mutex; use semver::Version; use std::collections::{BTreeMap, BTreeSet}; -use std::net::Ipv4Addr; +use std::net::{Ipv4Addr, SocketAddr}; use std::path::PathBuf; use std::sync::Arc; use std::time::{Duration, Instant}; @@ -506,21 +506,51 @@ fn push_host(rel: &Release, token: Option<&str>, ip: Ipv4Addr) -> Result<()> { let exe = host_exe(rel, token)?; let sha = update::sha256_hex(&exe); let version = rel.version.to_string(); + let host = SocketAddr::from((ip, CONTROL_PORT)); let mut last = None; for attempt in 1..=3 { match send_host(ip, &version, &sha, &exe) { Ok(()) => return Ok(()), - Err(e) if attempt < 3 && is_transient(&e) => { + Err(e) => { + // A host can take the file and restart before its reply + // gets out, so the Mac sees a dropped connection and the + // retry hears "already runs". Ask the host what it runs + // before calling either a failure. + if (attempt > 1 || is_transient(&e)) + && runs_at_least(host, &rel.version, Duration::from_secs(15)) + { + return Ok(()); + } + if attempt == 3 || !is_transient(&e) { + return Err(e); + } tracing::warn!("host update attempt {attempt}/3: {e:#}"); std::thread::sleep(Duration::from_secs(2 * attempt as u64)); last = Some(e); } - Err(e) => return Err(e), } } Err(last.unwrap_or_else(|| anyhow!("update failed"))) } +/// Whether the host at `addr` reports `want` or newer within `within`: it +/// may still be handing over to the new executable when first asked. +fn runs_at_least(addr: SocketAddr, want: &Version, within: Duration) -> bool { + let deadline = Instant::now() + within; + loop { + let running = http::get_json::(addr, "/v1/status", Duration::from_secs(2)) + .ok() + .and_then(|s| Version::parse(&s.version).ok()); + if running.is_some_and(|v| v >= *want) { + return true; + } + if Instant::now() >= deadline { + return false; + } + std::thread::sleep(Duration::from_secs(1)); + } +} + fn send_host(ip: Ipv4Addr, version: &str, sha: &str, exe: &[u8]) -> Result<()> { let r = http::request_with( (ip, CONTROL_PORT), @@ -663,6 +693,43 @@ mod tests { assert!(!msg.contains("os error"), "{msg}"); } + /// A one-reply HTTP server saying the host runs `version`. + fn status_server(version: &'static str) -> SocketAddr { + use std::io::{Read, Write}; + let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); + let addr = listener.local_addr().unwrap(); + std::thread::spawn(move || { + for mut s in listener.incoming().flatten() { + let mut buf = [0u8; 1024]; + let _ = s.read(&mut buf); + let body = format!(r#"{{"app":"brolink","version":"{version}"}}"#); + let _ = write!( + s, + "HTTP/1.1 200 OK\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", + body.len() + ); + } + }); + addr + } + + #[test] + fn a_host_already_on_the_release_counts_as_updated() { + let want = Version::new(4, 0, 3); + assert!(runs_at_least(status_server("4.0.3"), &want, Duration::ZERO)); + assert!(runs_at_least(status_server("4.1.0"), &want, Duration::ZERO)); + assert!(!runs_at_least( + status_server("4.0.2"), + &want, + Duration::ZERO + )); + let closed = std::net::TcpListener::bind("127.0.0.1:0") + .unwrap() + .local_addr() + .unwrap(); + assert!(!runs_at_least(closed, &want, Duration::ZERO)); + } + #[test] fn pipe_and_eagain_are_retried() { assert!(is_transient(&anyhow!("connection closed"))); From ed251f65e130a9710d11434a9e310eef0d057ba6 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 02:00:22 -0700 Subject: [PATCH 43/52] Log a failing whois once, not at every request A failed whois is not cached, so a peer polling every three seconds logged the same failure twenty times a minute and emptied the window's 80-line log. The same failure is now logged once until another one takes its place. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/host/src/service.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/host/src/service.rs b/crates/host/src/service.rs index 169af01..9bbcdf8 100644 --- a/crates/host/src/service.rs +++ b/crates/host/src/service.rs @@ -56,6 +56,8 @@ pub struct Service { log: Mutex>, /// `whois` answers for recent peers: when, and whether they may ask. auth: Mutex>, + /// The last `whois` failure logged. + whois_failed: Mutex>, /// This PC's side of the NAT story, from `tailscale netcheck`. nat: Mutex>, nat_running: AtomicBool, @@ -75,6 +77,7 @@ impl Service { streamer: Mutex::new(Streamer::default()), log: Mutex::new(VecDeque::new()), auth: Mutex::new(HashMap::new()), + whois_failed: Mutex::new(None), nat: Mutex::new(None), nat_running: AtomicBool::new(false), update_running: Arc::new(AtomicBool::new(false)), @@ -526,7 +529,14 @@ impl Service { allowed } Err(e) => { - self.log(format!("whois {ip}: {e}")); + // Not remembered, so asked again on the next request; say + // so once rather than at every poll. + let line = format!("whois {ip}: {e}"); + let mut last = self.whois_failed.lock(); + if last.as_deref() != Some(line.as_str()) { + self.log(line.clone()); + *last = Some(line); + } return false; } }; From b68c802235d9cdadb22fad8cbda79985c50e9c22 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 02:00:36 -0700 Subject: [PATCH 44/52] Give the PC's display report a minute to arrive The black-picture report runs PowerShell with WMI queries and a screen sample on the PC, then asks the engine's web API for its config and log (curl, up to 8 s each). The Mac gave the whole thing 25 s and this Mac's panel.log has three "display report: timed out", after which the notice never says why the picture is black. The report now gets 60 s; it is asked for once per stream, off the UI thread. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/client/src/display.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/client/src/display.rs b/crates/client/src/display.rs index 7355814..1b6aaaf 100644 --- a/crates/client/src/display.rs +++ b/crates/client/src/display.rs @@ -12,7 +12,11 @@ use serde_json::Value; use std::net::Ipv4Addr; use std::time::Duration; +/// Turning HDR off answers within seconds. const TIMEOUT: Duration = Duration::from_secs(25); +/// The report is slower: PowerShell with WMI and a screen sample, then two +/// calls to the engine's web API of up to 8 s each. +const REPORT_TIMEOUT: Duration = Duration::from_secs(60); /// What the PC said, in a form the stream's notice can show. #[derive(Debug, Clone, Default, PartialEq, Eq)] @@ -30,7 +34,7 @@ pub struct Help { } pub fn ask(ip: Ipv4Addr) -> Result { - http::get_json::((ip, CONTROL_PORT), "/v1/display", TIMEOUT) + http::get_json::((ip, CONTROL_PORT), "/v1/display", REPORT_TIMEOUT) } /// Turn the PC's HDR desktop off (or back on) and return its new state. From 5191f9294b418a444616b7fac2503e9daa2f8c80 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 02:01:41 -0700 Subject: [PATCH 45/52] Leave the PC as found after the live stream test, and note the speed-ups stream_real now ends the app it launched (not one it resumed) before it asserts, so a live run does not leave a Desktop session open on the PC. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 9 +++++++++ crates/stream/src/session.rs | 13 +++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ab64d1..b037b03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,6 +72,15 @@ GitHub release notes, so each version gets a heading of the form - A Windows or Linux viewer no longer calls its stream HEVC: those decode H.264 only, and the toolbar named the codec the PC could offer instead of the one in use. +- The check that notices a black picture costs the decoder 0.14 ms a frame + instead of 2.5 ms at 3024×1964 when the picture really is black (15% of + a core at 60 fps, on the path every frame takes to the screen). +- An idle window redraws about once a second instead of two or three + times, about a third less CPU while the lobby sits open. +- A Mac or Linux machine that shares its desktop no longer runs a program + (`scutil` or `hostname`) for every status request it answers. +- When the picture is black, the PC's explanation gets a minute to arrive + instead of 25 seconds, which its slower checks sometimes needed. ## 4.0.2 (2026-09-22) diff --git a/crates/stream/src/session.rs b/crates/stream/src/session.rs index 349248c..ee1a796 100644 --- a/crates/stream/src/session.rs +++ b/crates/stream/src/session.rs @@ -1056,15 +1056,20 @@ mod real { std::thread::sleep(Duration::from_millis(5)); } let stats = session.stats(); - eprintln!("stats: {stats:?}, frames published: {}", frames.seq()); - assert!(connected, "never connected"); - assert!(frames.seq() > 60, "too few frames: {}", frames.seq()); + let published = frames.seq(); + eprintln!("stats: {stats:?}, frames published: {published}"); session.stop(); let t = Instant::now(); while !session.finished() && t.elapsed() < Duration::from_secs(10) { std::thread::sleep(Duration::from_millis(20)); } - assert!(session.finished(), "session did not finish"); eprintln!("stopped in {:?}", t.elapsed()); + // Leave the PC as it was found: end the app this test started. + if !resume { + let _ = client.quit(); + } + assert!(connected, "never connected"); + assert!(published > 60, "too few frames: {published}"); + assert!(session.finished(), "session did not finish"); } } From 30f2e4af283b5084715006ddd7f3308f0e408496 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 02:02:57 -0700 Subject: [PATCH 46/52] Word the service's log for any viewer, and name who asked Since 4.0 a Windows or Linux machine is as likely as a Mac to be the one asking, but the service still logged "the Mac sent the new BroLink Host", "sleep requested from the Mac", "the Mac turned the HDR desktop off" and "a Mac can only reach this PC through a relay". Those lines are now OS-neutral, and a power, update or display change names the tailnet address that asked for it. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/host/src/service.rs | 42 ++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/crates/host/src/service.rs b/crates/host/src/service.rs index 9bbcdf8..de3eddf 100644 --- a/crates/host/src/service.rs +++ b/crates/host/src/service.rs @@ -1,4 +1,4 @@ -//! The background control service: what a Mac on the tailnet talks to. +//! The background control service: what the other machines on the tailnet talk to. //! //! One TCP listener on every interface, one rule for who gets an answer: //! loopback (the host's own control panel), or a tailnet machine that @@ -48,7 +48,7 @@ pub struct Service { wake: Mutex, /// When a magic packet for this PC last arrived. wake_seen: Mutex>, - /// Whether the virtual display lists every size a Mac can ask for; + /// Whether the virtual display lists every screen size a viewer can ask for; /// `None` on a PC without one. See `virtual_display`. virtual_display: Mutex>, install: Mutex>, @@ -348,7 +348,7 @@ impl Service { (Some(mac), Some(true)) => self.log(format!("Wake-on-LAN ready on {} ({mac})", w.adapter)), (Some(_), Some(false)) => self.log(format!("Wake-on-LAN is off on {}; run setup", w.adapter)), (Some(_), None) => self.log(format!("Wake-on-LAN state on {} is unknown", w.adapter)), - (None, _) => self.log("no wired adapter with a MAC found; the Mac will not be able to wake this PC"), + (None, _) => self.log("no wired adapter with a MAC found; other machines will not be able to wake this one"), } *cur = w; } @@ -357,10 +357,10 @@ impl Service { if *cur != v { match v { Some(true) => { - self.log("the virtual display lists every size a Mac can ask for") + self.log("the virtual display lists every screen size a viewer can ask for") } Some(false) => self - .log("the virtual display is missing sizes a Mac can ask for; run setup"), + .log("the virtual display is missing screen sizes a viewer can ask for; run setup"), None => {} } *cur = v; @@ -435,7 +435,7 @@ impl Service { } if *self.virtual_display.lock() == Some(false) { setup.push( - "The virtual display does not list every screen size a Mac can ask for.".into(), + "The virtual display does not list every screen size a viewer can ask for.".into(), ); } Status { @@ -590,10 +590,10 @@ impl Service { match (req.method.as_str(), req.path.as_str()) { ("GET", "/v1/status") => Response::json(200, &self.status(local)), ("GET", "/v1/display") => Response::json(200, &self.display()), - ("POST", "/v1/display") => self.set_display(req), + ("POST", "/v1/display") => self.set_display(peer.ip(), req), ("POST", "/v1/pin") => self.pin(req), - ("POST", "/v1/power") => self.power(req), - ("POST", p) if p == UPDATE_PATH => self.update(req), + ("POST", "/v1/power") => self.power(peer.ip(), req), + ("POST", p) if p == UPDATE_PATH => self.update(peer.ip(), req), ("GET", p) if p == CLIPBOARD_PATH => match clipboard::read() { Ok(c) => Response::json(200, &c), Err(e) => Response::json(500, &Ack::err(format!("clipboard: {e}"))), @@ -619,7 +619,7 @@ impl Service { /// A newer `brolink-host.exe` from the Mac: stage it, answer, then swap /// it in and hand over. See [`crate::update`]. - fn update(&self, req: &Request) -> Response { + fn update(&self, from: IpAddr, req: &Request) -> Response { if !cfg!(windows) { // A Mac replaces its own app from GitHub and a container is // rebuilt; swapping a Windows executable in here would only @@ -651,7 +651,7 @@ impl Service { match update::stage(req, &exe) { Ok(version) => { self.log(format!( - "updating to {version}: the Mac sent the new BroLink Host" + "updating to {version}: {from} sent the new BroLink Host" )); let running = self.update_running.clone(); std::thread::spawn(move || { @@ -732,14 +732,14 @@ impl Service { /// setting the Mac can change: an HDR desktop on a PC with no monitor /// captures as black, and nobody can reach the PC's settings to fix it /// when the picture is the thing that is broken. - fn set_display(&self, req: &Request) -> Response { + fn set_display(&self, from: IpAddr, req: &Request) -> Response { let Ok(want) = req.json::() else { return Response::json(400, &Ack::err("expected {\"advanced_color\": true|false}")); }; match crate::display::set_advanced_color(want.advanced_color) { Ok(state) => { self.log(format!( - "the Mac turned the HDR desktop {}", + "{from} turned the HDR desktop {}", if want.advanced_color { "on" } else { "off" } )); Response::json(200, &state) @@ -748,7 +748,7 @@ impl Service { } } - fn power(&self, req: &Request) -> Response { + fn power(&self, from: IpAddr, req: &Request) -> Response { let Ok(p) = req.json::() else { return Response::json( 400, @@ -770,7 +770,7 @@ impl Service { .close_app(); } self.log(format!( - "{} requested from the Mac", + "{} requested by {from}", p.action.label().to_lowercase() )); // Reply first: sleep can suspend the machine before the bytes leave. @@ -795,11 +795,11 @@ impl Default for Service { pub fn describe_nat(n: &NatReport) -> String { let city = tailscale::derp_city(&n.derp); if !n.udp { - "network: UDP is blocked here, so a Mac can only reach this PC through a Tailscale relay" + "network: UDP is blocked here, so other machines reach this one only through a Tailscale relay" .into() } else if n.hard == Some(true) && !n.portmap { format!( - "network: hard NAT with no UPnP; a Mac on another network reaches this PC through the {city} relay unless the router gets UPnP or a forwarded UDP port" + "network: hard NAT with no UPnP; a machine on another network reaches this one through the {city} relay unless the router gets UPnP or a forwarded UDP port" ) } else if n.hard == Some(true) { "network: hard NAT, but the router maps ports; direct connections should work".into() @@ -1051,15 +1051,15 @@ mod tests { let req = Request::default(); svc.update_running.store(true, Ordering::Release); if cfg!(windows) { - assert_eq!(svc.update(&req).status, 409); + assert_eq!(svc.update(LOOPBACK, &req).status, 409); } else { // Not a Windows PC: refused before the slot is even looked at. - let r = svc.update(&req); + let r = svc.update(LOOPBACK, &req); assert_eq!(r.status, 400); assert!(r.body.contains("Windows PC"), "{}", r.body); } svc.update_running.store(false, Ordering::Release); - assert_eq!(svc.update(&req).status, 400); + assert_eq!(svc.update(LOOPBACK, &req).status, 400); assert!(!svc.update_running.load(Ordering::Acquire)); } @@ -1147,6 +1147,8 @@ mod tests { ); } + const LOOPBACK: IpAddr = IpAddr::V4(Ipv4Addr::LOCALHOST); + fn json_header() -> Vec<(String, String)> { vec![("content-type".into(), "application/json".into())] } From 92eaaa6a9a69cac9c9f7e8de691996ab837c6376 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 02:03:30 -0700 Subject: [PATCH 47/52] Redraw the window from discovery only when the listing changes The discovery thread asked for a frame after every three-second scan, and the netcheck and peer-relay probes asked for one after each of their runs, whether or not anything had changed. The probes now only fill their slots; the scan that picks their results up redraws the window when the machines, their status, the relays or this machine's network differ from the last scan. The time of the scan alone does not count. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/client/src/session.rs | 73 +++++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 17 deletions(-) diff --git a/crates/client/src/session.rs b/crates/client/src/session.rs index 1bf8b18..1547e4f 100644 --- a/crates/client/src/session.rs +++ b/crates/client/src/session.rs @@ -30,7 +30,7 @@ const NETCHECK_EVERY: Duration = Duration::from_secs(15 * 60); const PEER_RELAY_EVERY: Duration = Duration::from_secs(30); /// A machine on the tailnet BroLink can open, as far as this node can tell. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, PartialEq)] pub struct Pc { pub node_id: String, pub name: String, @@ -66,7 +66,7 @@ impl Pc { } /// A tailnet node tagged `tag:relay`: a candidate peer relay, never a PC. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, PartialEq)] pub struct Relay { pub name: String, pub ip: Option, @@ -86,7 +86,7 @@ pub enum PeerRelayServers { Known(Vec), } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, PartialEq)] pub struct Discovery { /// Why nothing can be probed, when Tailscale is down. The list then /// holds what was remembered. @@ -110,9 +110,9 @@ pub struct Discovery { /// be woken. pub fn spawn_discovery(shared: Arc>, ctx: egui::Context) { let nat: Arc>> = Arc::default(); - spawn_netcheck(nat.clone(), ctx.clone()); + spawn_netcheck(nat.clone()); let servers: Arc> = Arc::default(); - spawn_peer_relay_probe(servers.clone(), ctx.clone()); + spawn_peer_relay_probe(servers.clone()); std::thread::spawn(move || { // Round trips per PC, newest last; the smallest of the last few is // the path's real round trip (a first connect also pays for the @@ -134,21 +134,33 @@ pub fn spawn_discovery(shared: Arc>, ctx: egui::Context) { scan.self_nat = nat.lock().clone(); scan.peer_relay_servers = servers.lock().clone(); learn(&scan); + let changed = listing_changed(&shared.lock(), &scan); *shared.lock() = scan; - ctx.request_repaint(); + if changed { + ctx.request_repaint(); + } std::thread::sleep(Duration::from_secs(3)); } }); } -/// `tailscale netcheck` now and then, for [`Discovery::self_nat`]. -fn spawn_netcheck(slot: Arc>>, ctx: egui::Context) { +/// Whether a new scan shows the window anything new. Every scan has a new +/// time; that alone is no reason to draw the window again. +fn listing_changed(old: &Discovery, new: &Discovery) -> bool { + old.refreshed.is_none() != new.refreshed.is_none() + || *old + != (Discovery { + refreshed: old.refreshed, + ..new.clone() + }) +} + +/// `tailscale netcheck` now and then, for [`Discovery::self_nat`]. The next +/// scan carries it to the window. +fn spawn_netcheck(slot: Arc>>) { std::thread::spawn(move || loop { match tailscale::netcheck() { - Ok(n) => { - *slot.lock() = Some(n.report()); - ctx.request_repaint(); - } + Ok(n) => *slot.lock() = Some(n.report()), Err(e) => tracing::info!("netcheck: {e}"), } std::thread::sleep(NETCHECK_EVERY); @@ -158,15 +170,14 @@ fn spawn_netcheck(slot: Arc>>, ctx: egui::Context) { /// `tailscale debug peer-relay-servers`, at most once every 30 s, on its own /// thread so the scan loop and UI never wait on it. The command itself is /// bounded (see `tailscale::PEER_RELAY_SERVERS_TIMEOUT`). Any failure leaves -/// the result `Unknown`, never a denial. -fn spawn_peer_relay_probe(slot: Arc>, ctx: egui::Context) { +/// the result `Unknown`, never a denial. The next scan carries it to the +/// window. +fn spawn_peer_relay_probe(slot: Arc>) { std::thread::spawn(move || loop { - let result = match tailscale::peer_relay_servers() { + *slot.lock() = match tailscale::peer_relay_servers() { Ok(servers) => PeerRelayServers::Known(servers), Err(_) => PeerRelayServers::Unknown, }; - *slot.lock() = result; - ctx.request_repaint(); std::thread::sleep(PEER_RELAY_EVERY); }); } @@ -1039,6 +1050,34 @@ mod tests { assert_eq!(r.unwrap(), 2); } + #[test] + fn only_a_scan_that_changes_something_redraws() { + let first = Discovery { + refreshed: Some(Instant::now()), + pcs: vec![Pc { + name: "Gaming-PC".into(), + online: true, + ..Default::default() + }], + ..Default::default() + }; + assert!( + listing_changed(&Discovery::default(), &first), + "the first scan" + ); + let again = Discovery { + refreshed: Some(Instant::now() + Duration::from_secs(3)), + ..first.clone() + }; + assert!(!listing_changed(&first, &again), "only the time moved"); + let mut asleep = again.clone(); + asleep.pcs[0].online = false; + assert!(listing_changed(&first, &asleep)); + let mut relay = again; + relay.peer_relay_servers = PeerRelayServers::Known(vec!["100.64.0.40".into()]); + assert!(listing_changed(&first, &relay)); + } + #[test] fn progress_tracks_step_changes() { let mut p = Progress::default(); From 2fa888f1be4340a7b9d1d479b9071c419fff0d79 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 07:34:47 -0700 Subject: [PATCH 48/52] Stop re-sending a host update the PC rolled back, and say so A pushed brolink-host.exe that dies on start is put back by the PC, but only tracing heard about it, and the Mac, having had a 200, sent the same 20 MB again after every three-minute grace, forever. The PC now writes the failure to the service log the window shows. The Mac remembers which PCs took a release; one still on the old version after the grace has rolled back, gets a warning that stays while it is true, and is not sent that release again until BroLink restarts. The updater also asks for a frame when it sets "updating" before installing, and when a host notice fades, so neither waits for the mouse. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 2 ++ crates/client/src/update.rs | 63 +++++++++++++++++++++++++++++++------ crates/host/src/service.rs | 24 ++++++++------ 3 files changed, 69 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45c09d1..3dd901c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,8 @@ GitHub release notes, so each version gets a heading of the form launched, so a replacement that died straight away left the PC with no BroLink service until someone logged in again. It now waits two seconds, and puts the previous executable back if the new one has already exited. + The PC's log says so, and the Mac warns once instead of sending the same + release again every three minutes. - Update downloads try each of a server's addresses in turn. Only the first one the name resolved to was ever tried, so one unreachable address (an IPv6 one on a network without IPv6, say) failed the whole update check. diff --git a/crates/client/src/update.rs b/crates/client/src/update.rs index f237646..fe34d7b 100644 --- a/crates/client/src/update.rs +++ b/crates/client/src/update.rs @@ -53,6 +53,12 @@ pub struct State { pub notice: Option<(Tone, String)>, /// Hosts sent an update recently: node id to (version, when). pub pushed: BTreeMap, + /// Hosts that took the file for this version: node id to version. + pub delivered: BTreeMap, + /// Hosts that took a version and came back on the old one, which means + /// the new executable died on start and the host put the old one back. + /// Sending the same file again would only repeat that. + pub rolled_back: BTreeMap, /// Hosts too old to receive `/v1/update`; told the user once. pub told_old: BTreeSet, } @@ -87,7 +93,9 @@ pub fn spawn( if !cfg.auto_update { let mut st = state.lock(); st.message = "Off. This app and the PCs stay on their current versions.".into(); - st.notice = None; + if st.notice.take().is_some() { + ctx.request_repaint(); + } continue; } let due = state.lock().check_now || Instant::now() >= next; @@ -162,6 +170,7 @@ pub fn spawn( let mut p = progress.lock(); if live.is_none() && !p.active() { p.updating = true; + ctx.request_repaint(); true } else { false @@ -207,16 +216,43 @@ pub fn spawn( } continue; } + if !rel.is_newer_than(&v) { + // Updated after all (through the stream, say): the + // rollback warning is no longer true. + let mut st = state.lock(); + if st.rolled_back.remove(&pc.node_id).is_some() + && matches!(st.notice, Some((Tone::Warning, _))) + { + st.notice = None; + ctx.request_repaint(); + } + } if !should_push(&v, rel) || streaming_to == Some(ip) { continue; } - let recently = state - .lock() - .pushed - .get(&pc.node_id) - .is_some_and(|(pv, at)| *pv == rel.version && at.elapsed() < PUSH_GRACE); - if recently { - continue; + { + let mut st = state.lock(); + let recently = st + .pushed + .get(&pc.node_id) + .is_some_and(|(pv, at)| *pv == rel.version && at.elapsed() < PUSH_GRACE); + if recently || st.rolled_back.get(&pc.node_id) == Some(&rel.version) { + continue; + } + if st.delivered.get(&pc.node_id) == Some(&rel.version) { + // It took the file, the grace is over, and it still + // runs the old version. + st.rolled_back + .insert(pc.node_id.clone(), rel.version.clone()); + st.message = format!( + "BroLink Host {} did not start on {}, so it kept {v}. It is not sent again until BroLink restarts; its log says why.", + rel.version, pc.name + ); + st.notice = Some((Tone::Warning, st.message.clone())); + tracing::warn!("{} rolled back host {}", pc.name, rel.version); + ctx.request_repaint(); + continue; + } } let outcome = push_host(rel, token.as_deref(), ip); let mut st = state.lock(); @@ -224,6 +260,7 @@ pub fn spawn( .insert(pc.node_id.clone(), (rel.version.clone(), Instant::now())); match outcome { Ok(()) => { + st.delivered.insert(pc.node_id.clone(), rel.version.clone()); st.message = format!( "Sent BroLink Host {} to {}; it restarts by itself.", rel.version, pc.name @@ -239,10 +276,16 @@ pub fn spawn( } ctx.request_repaint(); } - // Notices about hosts fade once the grace period has passed. + // Notices about hosts fade once the grace period has passed, + // except a rollback, which stays until someone reads it. let mut st = state.lock(); - if st.ready.is_none() && st.pushed.values().all(|(_, at)| at.elapsed() > PUSH_GRACE) { + let fades = !matches!(st.notice, None | Some((Tone::Warning, _))); + if fades + && st.ready.is_none() + && st.pushed.values().all(|(_, at)| at.elapsed() > PUSH_GRACE) + { st.notice = None; + ctx.request_repaint(); } } }); diff --git a/crates/host/src/service.rs b/crates/host/src/service.rs index de3eddf..e8926e1 100644 --- a/crates/host/src/service.rs +++ b/crates/host/src/service.rs @@ -53,7 +53,7 @@ pub struct Service { virtual_display: Mutex>, install: Mutex>, streamer: Mutex, - log: Mutex>, + log: Arc>>, /// `whois` answers for recent peers: when, and whether they may ask. auth: Mutex>, /// The last `whois` failure logged. @@ -75,7 +75,7 @@ impl Service { virtual_display: Mutex::new(None), install: Mutex::new(None), streamer: Mutex::new(Streamer::default()), - log: Mutex::new(VecDeque::new()), + log: Arc::new(Mutex::new(VecDeque::new())), auth: Mutex::new(HashMap::new()), whois_failed: Mutex::new(None), nat: Mutex::new(None), @@ -85,13 +85,7 @@ impl Service { } pub fn log(&self, msg: impl Into) { - let msg = msg.into(); - tracing::info!("{msg}"); - let mut log = self.log.lock(); - if log.len() >= LOG_LINES { - log.pop_front(); - } - log.push_back(msg); + push_log(&self.log, msg.into()); } fn session_active(&self) -> bool { @@ -654,6 +648,7 @@ impl Service { "updating to {version}: {from} sent the new BroLink Host" )); let running = self.update_running.clone(); + let log = self.log.clone(); std::thread::spawn(move || { std::thread::sleep(Duration::from_millis(500)); match update::apply(&exe) { @@ -662,7 +657,7 @@ impl Service { std::process::exit(0); } Err(e) => { - tracing::error!("update to {version} failed: {e:#}"); + push_log(&log, format!("update to {version} failed: {e:#}")); running.store(false, Ordering::Release); } } @@ -938,6 +933,15 @@ fn control_host(host: &str) -> bool { }) } +/// One line in the service log the window shows (and in the log file). +fn push_log(log: &Mutex>, msg: String) { + tracing::info!("{msg}"); + let mut log = log.lock(); + if log.len() >= LOG_LINES { + log.pop_front(); + } + log.push_back(msg); +} #[cfg(test)] mod tests { use super::*; From d69d3e0ff7227f75c9eba7689cc490a9a95c9fc5 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 07:34:47 -0700 Subject: [PATCH 49/52] Redraw what the merged branches left stale, and tidy three edges MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The redesign stopped redrawing on a timer and the engineering pass made discovery redraw only on change, so anything that changed without asking for a frame stayed on screen until the mouse moved: - pairing's "BroLink on that machine isn't answering" (no spinner there); - the Machines row after setup ends, which has no clock of its own. Also: - The host poller no longer redraws every second forever once a wake packet has arrived: its age counting up is not news. The Sharing page keeps that clock current itself, while it is shown, in s, min or h. - Windows and Linux show where their updates come from instead of an updater toggle and a Check now button that did nothing there. - A stopped service reads "Not shared" on the Machines row, not "Starting BroLink's background service…". - Key-expiry urgency comes from the day count, not from reading the sentence back, so a machine named "Mac in office" is judged right. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/client/src/app.rs | 89 ++++++++++++++++++++---------------- crates/client/src/session.rs | 18 ++++++-- crates/client/src/share.rs | 2 + crates/host/src/app.rs | 41 +++++++++++++++-- crates/host/src/product.rs | 1 + 5 files changed, 101 insertions(+), 50 deletions(-) diff --git a/crates/client/src/app.rs b/crates/client/src/app.rs index cc101f7..9bfbded 100644 --- a/crates/client/src/app.rs +++ b/crates/client/src/app.rs @@ -759,7 +759,10 @@ impl ClientApp { if let Some((tone, text)) = self.updates.lock().notice.clone() { ui::notice(ui, tone, &text); } - for text in key_expiry_warnings(disc).iter().filter(|t| urgent(t)) { + for (_, text) in key_expiry_warnings(disc) + .iter() + .filter(|(urgent, _)| *urgent) + { ui::notice(ui, Tone::Danger, text); } if prog.active() { @@ -907,6 +910,7 @@ impl ClientApp { fn this_machine_row(&mut self, ui: &mut egui::Ui, slot: &share::Slot) { let g = slot.lock(); let running = g.setup_running; + let stopped = g.service_stopped; let status = g.status.clone(); drop(g); let noun = this_noun(status.as_ref().map(|s| s.os.as_str()).unwrap_or_default()); @@ -919,6 +923,10 @@ impl ClientApp { .as_ref() .is_some_and(|s| s.streamer.running && s.streamer.api_ok); let (tone, detail) = match &status { + None if stopped => ( + Tone::Neutral, + "Not shared · the background service is stopped".to_string(), + ), None => ( Tone::Neutral, "Starting BroLink's background service…".to_string(), @@ -1041,8 +1049,8 @@ impl ClientApp { items.extend( key_expiry_warnings(disc) .into_iter() - .filter(|t| !urgent(t)) - .map(|t| (Tone::Neutral, t)), + .filter(|(urgent, _)| !urgent) + .map(|(_, t)| (Tone::Neutral, t)), ); if items.is_empty() { return; @@ -1352,6 +1360,13 @@ impl ClientApp { fn updates_section(&mut self, ui: &mut egui::Ui) { ui::section(ui, "Updates", |ui| { + if !cfg!(target_os = "macos") { + // Only a Mac runs the updater (see update::spawn); here it + // has left a line saying where new versions come from. + let message = self.updates.lock().message.clone(); + ui::setting_row(ui, "New versions", Some(&message), |_| {}); + return; + } if ui::toggle_row( ui, &mut self.cfg.auto_update, @@ -1584,49 +1599,44 @@ fn relay_state(disc: &Discovery) -> RelayState { } /// One line per machine whose Tailscale key expires, this one included. -fn key_expiry_warnings(disc: &Discovery) -> Vec { +/// A line for each Tailscale key that expires, and whether it needs doing +/// now: expired, or within 30 days. +fn key_expiry_warnings(disc: &Discovery) -> Vec<(bool, String)> { + let urgent = |d: i64| d <= 30; let mut out = Vec::new(); for pc in &disc.pcs { if let Some(d) = pc.key_expiry_days { - out.push(if d <= 0 { - format!( - "{}'s Tailscale key has expired. It is off the tailnet until someone signs in to Tailscale on it.", - pc.name - ) - } else { - format!( - "{}'s Tailscale key expires in {d} days. Turn off key expiry for it in the Tailscale admin console (login.tailscale.com/admin/machines), or it will need a sign-in on that machine.", - pc.name - ) - }); + out.push(( + urgent(d), + if d <= 0 { + format!( + "{}'s Tailscale key has expired. It is off the tailnet until someone signs in to Tailscale on it.", + pc.name + ) + } else { + format!( + "{}'s Tailscale key expires in {d} days. Turn off key expiry for it in the Tailscale admin console (login.tailscale.com/admin/machines), or it will need a sign-in on that machine.", + pc.name + ) + }, + )); } } if let Some(d) = disc.self_key_days { - out.push(if d <= 0 { - "This machine's Tailscale key has expired. Sign in to Tailscale again.".to_string() - } else { - format!( - "This machine's Tailscale key expires in {d} days. Turn off key expiry for it in the admin console too." - ) - }); + out.push(( + urgent(d), + if d <= 0 { + "This machine's Tailscale key has expired. Sign in to Tailscale again.".to_string() + } else { + format!( + "This machine's Tailscale key expires in {d} days. Turn off key expiry for it in the admin console too." + ) + }, + )); } out } -/// A key-expiry line that needs doing now: expired, or within 30 days. -fn urgent(text: &str) -> bool { - text.contains("expired") || text.contains(" days") && days_in(text) <= 30 -} - -/// The day count inside a warning line, for its tone. -fn days_in(text: &str) -> i64 { - text.split(" in ") - .nth(1) - .and_then(|rest| rest.split_whitespace().next()) - .and_then(|n| n.parse().ok()) - .unwrap_or(i64::MAX) -} - /// "Windows", "macOS", "Linux". fn os_label(os: &str) -> &'static str { let n = tailscale::Node { @@ -1780,10 +1790,9 @@ mod tests { }; let w = key_expiry_warnings(&disc); assert_eq!(w.len(), 3, "{w:?}"); - assert!(w[0].contains("Gaming-PC") && w[0].contains("176 days")); - assert_eq!(days_in(&w[0]), 176); - assert!(w[1].contains("Office") && w[1].contains("expired")); - assert!(w[2].starts_with("This machine") && days_in(&w[2]) == 12); + assert!(!w[0].0 && w[0].1.contains("Gaming-PC") && w[0].1.contains("176 days")); + assert!(w[1].0 && w[1].1.contains("Office") && w[1].1.contains("expired")); + assert!(w[2].0 && w[2].1.starts_with("This machine") && w[2].1.contains("12 days")); assert!(key_expiry_warnings(&Discovery::default()).is_empty()); } diff --git a/crates/client/src/session.rs b/crates/client/src/session.rs index 00fdd69..fb09f01 100644 --- a/crates/client/src/session.rs +++ b/crates/client/src/session.rs @@ -646,7 +646,7 @@ fn run(c: &Connect, generation: u64) -> Result<()> { let pin = format!("{:04}", rand::random::() % 10_000); report(Step::Pairing { pin: pin.clone() }, String::new()); let der = client.pair_cancellable(&pin, &brolink_core::config::machine_name(), || { - submit_pin(t.ip, &pin, &c.progress); + submit_pin(t.ip, &pin, &c.progress, &c.ctx); stale(&c.progress, generation) })?; remember_cert(&t.node_id, &t.name, &der); @@ -809,11 +809,20 @@ fn retry( /// Hand the PIN to BroLink Host on the PC, which types it into Sunshine. If /// there is no BroLink Host, the PIN stays on screen for someone at the PC. -fn submit_pin(ip: Ipv4Addr, pin: &str, progress: &Mutex) { +fn submit_pin(ip: Ipv4Addr, pin: &str, progress: &Mutex, ctx: &egui::Context) { let req = PinRequest { pin: pin.into(), name: brolink_core::config::machine_name(), }; + // Pairing draws no spinner, so a new reason has to ask for its frame. + let show = |detail: String| { + let mut p = progress.lock(); + if p.detail != detail { + p.detail = detail; + drop(p); + ctx.request_repaint(); + } + }; let start = Instant::now(); while start.elapsed() < Duration::from_secs(75) { if progress.lock().cancel { @@ -825,12 +834,11 @@ fn submit_pin(ip: Ipv4Addr, pin: &str, progress: &Mutex) { Ok(a) => { let reason = a.error.unwrap_or_else(|| "PIN not accepted yet".into()); tracing::info!("PIN not accepted yet: {reason}"); - progress.lock().detail = reason; + show(reason); } Err(e) => { tracing::info!("BroLink Host did not take the PIN: {e}"); - progress.lock().detail = - "BroLink on that machine isn't answering, so it can't enter the PIN. Open BroLink there and set up sharing, then try again.".into(); + show("BroLink on that machine isn't answering, so it can't enter the PIN. Open BroLink there and set up sharing, then try again.".into()); } } std::thread::sleep(Duration::from_millis(800)); diff --git a/crates/client/src/share.rs b/crates/client/src/share.rs index 0bb4a45..6a41048 100644 --- a/crates/client/src/share.rs +++ b/crates/client/src/share.rs @@ -12,6 +12,8 @@ pub struct LocalShare { pub status: Option, pub setup_running: bool, pub setup_result: Option>, + /// Someone stopped the background service from the Sharing page. + pub service_stopped: bool, } pub type Slot = Arc>; diff --git a/crates/host/src/app.rs b/crates/host/src/app.rs index 5fa59ff..508089b 100644 --- a/crates/host/src/app.rs +++ b/crates/host/src/app.rs @@ -191,7 +191,7 @@ impl HostApp { /// Generate a Sunshine login if there is none, save it, and run the /// setup on a thread. - fn start_setup(&mut self, status: &Status) { + fn start_setup(&mut self, ctx: &egui::Context, status: &Status) { let migrate = crate::migrate::uses_old_engine(&status.streamer.kind); let shared = self.shared.clone(); { @@ -212,6 +212,7 @@ impl HostApp { } } let cfg = self.cfg.clone(); + let ctx = ctx.clone(); let install_engine = !status.streamer.installed || migrate || !status.streamer.running; let adapter = status.wake_adapter.clone(); let desc = status.wake_adapter_description.clone(); @@ -240,6 +241,9 @@ impl HostApp { s.setup_log = read_setup_log(); s.setup_result = Some(result); s.touch(); + drop(s); + // The Machines page shows setup too, and it has no timer. + ctx.request_repaint(); }); } @@ -323,6 +327,13 @@ impl HostApp { /// What the service error says after the user stopped it from here. const STOPPED: &str = "Stopped from this window."; +impl Shared { + /// The service is down because someone pressed Stop, not by accident. + pub(crate) fn stopped_by_user(&self) -> bool { + self.service_error.as_deref() == Some(STOPPED) + } +} + impl HostApp { /// Draw the page into `ui`, a column the window has already laid out. pub fn page(&mut self, ui: &mut egui::Ui) { @@ -532,7 +543,7 @@ impl HostApp { ui::secondary_button(ui, label).clicked() }; if clicked { - self.start_setup(s); + self.start_setup(ui.ctx(), s); } let note = if migrate { Some("Keeps this PC's pairings and web login.") @@ -668,7 +679,15 @@ impl HostApp { wake.push_str(" · Fast Startup is on"); } if let Some(age) = s.wake_packet_age_secs { - wake.push_str(&format!(" · Last wake packet {age} s ago")); + // The poller ignores this clock (only_aged), so it is kept + // current here, and only while it is on screen. + let (text, next) = match age { + 0..60 => (format!("{age} s"), 1), + 60..3600 => (format!("{} min", age / 60), 60 - age % 60), + _ => (format!("{} h", age / 3600), 3600 - age % 3600), + }; + wake.push_str(&format!(" · Last wake packet {text} ago")); + ui.ctx().request_repaint_after(Duration::from_secs(next)); } rows.push(Kv::new("Wake-on-LAN", wake).tone(tone)); } @@ -843,7 +862,7 @@ impl HostApp { .inner .clicked(); if clicked { - self.start_setup(s); + self.start_setup(ui.ctx(), s); } }, ); @@ -995,6 +1014,18 @@ fn relaunch_this_exe() -> bool { /// Poll the service every second; refresh Sunshine's client list now and /// then; restart the service after an update. The window repaints only /// when an answer differs from the last. +/// Whether `new` is `old` a little later: the wake packet's age only counts +/// up, and the Sharing page redraws that clock itself while it is shown. +fn only_aged(old: &Status, new: &Status) -> bool { + let aged = match (old.wake_packet_age_secs, new.wake_packet_age_secs) { + (Some(a), Some(b)) => b >= a, + (a, b) => a == b, + }; + let mut old = old.clone(); + old.wake_packet_age_secs = new.wake_packet_age_secs; + aged && old == *new +} + fn spawn_poller(shared: Arc>, ctx: egui::Context) { std::thread::spawn(move || { let mut failures = 0u32; @@ -1048,7 +1079,7 @@ fn spawn_poller(shared: Arc>, ctx: egui::Context) { s.gamepad_driver = gamepad; } let mut s = shared.lock(); - changed |= s.status.as_ref() != Some(&st); + changed |= !s.status.as_ref().is_some_and(|old| only_aged(old, &st)); s.status = Some(st); if s.service_error.as_deref().is_some_and(|e| e != STOPPED) { s.service_error = None; diff --git a/crates/host/src/product.rs b/crates/host/src/product.rs index 41d4219..f1e6163 100644 --- a/crates/host/src/product.rs +++ b/crates/host/src/product.rs @@ -85,6 +85,7 @@ impl NodeApp { g.status = shared.status.clone(); g.setup_running = shared.setup_running; g.setup_result = shared.setup_result.clone(); + g.service_stopped = shared.stopped_by_user(); } } From 3d021b615334c59738c212d9b92044b3a070159c Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 07:35:41 -0700 Subject: [PATCH 50/52] Let a tagged machine answer people only, not other tagged machines A tagged host admitted any peer with its own user ID. Every tagged machine is Tailscale's "tagged-devices" user, and nothing guarantees that ID differs between tailnets, so a tagged machine shared in from another tailnet could have matched. No BroLink flow has one tagged machine call another, so the rule now fails closed: a tagged host answers only a person's login from its own tailnet. Checked against this tailnet: CurrentTailnet.Name is the Mac's login, so the relay VPS still answers the Mac. Co-Authored-By: Claude Opus 5.5 (1M context) --- README.md | 3 ++- crates/host/src/service.rs | 16 ++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4a02a03..707e74f 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,8 @@ Ethernet is strongly preferred; most Wi-Fi adapters cannot wake a PC. - The host's control service listens on TCP 47850 and answers only loopback and Tailscale addresses that `tailscale whois` attributes to the account the PC is signed in as. A tagged server (the relay VPS) belongs - to no account and answers the members of its own tailnet. Everyone else, + to no account and answers the people of its own tailnet, not other + tagged machines. Everyone else, including machines shared in from other tailnets, gets a 403. The firewall rule setup adds is scoped to `100.64.0.0/10`. - The stream is the GameStream protocol (moonlight-common-c on the viewer, diff --git a/crates/host/src/service.rs b/crates/host/src/service.rs index e8926e1..00e5f08 100644 --- a/crates/host/src/service.rs +++ b/crates/host/src/service.rs @@ -841,10 +841,13 @@ impl Owner { if user == 0 { return false; } - if user == self.user { - return true; + if !self.tagged { + return user == self.user; } - if !self.tagged || self.tailnet.is_empty() { + // Every tagged machine is the "tagged-devices" user, and nothing + // says that user's ID differs between tailnets, so a matching ID + // proves nothing here: only a person's login does. + if self.tailnet.is_empty() { return false; } let login = peer.user_profile.login_name.as_str(); @@ -992,8 +995,8 @@ mod tests { assert!(vps.tagged); assert!(vps.admits(&mac), "a tagged VPS serves its tailnet's owner"); assert!( - vps.admits(&relay), - "and machines tagged in the same tailnet" + !vps.admits(&relay), + "a tagged ID is no account: it may be a tagged machine anywhere" ); assert!(!vps.admits(&friend)); @@ -1004,12 +1007,13 @@ mod tests { }; assert!(org.admits(&whois(5, "ada@EXAMPLE.com"))); assert!(!org.admits(&whois(6, "eve@example.com.evil"))); - // Without a tailnet name a tagged machine takes nobody but its kind. + // Without a tailnet name a tagged machine takes nobody. let unnamed = Owner { tailnet: String::new(), ..vps }; assert!(!unnamed.admits(&mac)); + assert!(!unnamed.admits(&relay)); } #[test] From 9f1434853a3de7ca7b5d7af9f0a35636af350f67 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 07:41:51 -0700 Subject: [PATCH 51/52] Keep every menu inside the window at the smallest size At 640x420, the smallest window, the stream toolbar's More menu ran past the bottom edge: "Command acts as Ctrl" and the PC's power items were cut off, and Tab could move focus onto them without showing them. Stream performance ran off the bottom as well, and both stream panels sat partly under the toolbar. Menus now choose where to open in crates/ui: below their control when they fit, above it when there is more room there (a machine's menu near the foot of the list), and capped at that side's room, scrolling inside with the scroll bar showing when neither side has room for all of it. Dropdown lists, Stream settings, Stream performance and the restart and install questions are capped the same way, below the toolbar. A control scrolls into view when Tab reaches it, and focus rings are no longer cut off at a window's edge. Tests open the More, Keys, Mouse, PC and machine menus, both panels, the questions and a dropdown at 640x420 (the separate toolbar menus from the narrowest width that shows them), Tab to the last item and check by rect that it and the whole menu or panel are on screen. The snapshot run renders each of those states at 1x and 2x. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 7 + crates/client/src/app.rs | 67 ++++- crates/client/src/settings.rs | 4 +- crates/client/src/stream.rs | 457 ++++++++++++++++++++++++++++------ crates/ui/src/theme.rs | 25 +- crates/ui/src/widgets.rs | 252 +++++++++++++++++-- docs/design/AUDIT.md | 1 + 7 files changed, 710 insertions(+), 103 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 235dca0..6738ba9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,13 @@ GitHub release notes, so each version gets a heading of the form connecting screen has a **Cancel** button, so leaving no longer needs the host key. Stream settings and Stream performance are proper panels with a close button; Escape closes Stream settings. +- Every menu, dropdown and panel fits the window down to its smallest + size (640×420). A menu opens upwards when there is more room above, + and scrolls, with its scroll bar showing, when neither side has room + for all of it; at the smallest size the stream toolbar's More menu cut + off "Command acts as Ctrl" and the PC's power items. Stream settings + and Stream performance stay below the toolbar, and Tab scrolls the + control it reaches into view. - A relayed path is amber, not red: it works, and Connection details says how to get a direct one. - The idle window no longer repaints on a timer (it redrew 2.5 times a diff --git a/crates/client/src/app.rs b/crates/client/src/app.rs index cbfbf24..df4555b 100644 --- a/crates/client/src/app.rs +++ b/crates/client/src/app.rs @@ -1322,7 +1322,7 @@ impl ClientApp { } else { ui::select(ui, "app_pick", app.clone(), 160.0, |ui| { for a in &prog.apps { - if ui.selectable_value(&mut app, a.clone(), a).clicked() { + if ui::select_option(ui, &mut app, a.clone(), a).clicked() { changed = true; } } @@ -2645,6 +2645,37 @@ pub(crate) mod snapshots { ); } + #[test] + fn row_menus_stay_inside_the_smallest_window() { + // Gaming-PC's menu is the longest; Den's row is the lowest, so its + // menu has to open upwards. + for pc in ["Gaming-PC", "Office", "Den"] { + let mut h = build(Setup::new(pcs()), MIN, 1.0, false); + crate::stream::tests::assert_menu_fits( + &mut h, + MIN, + &format!("More for {pc}"), + "Copy Tailscale address", + ); + } + // A row near the bottom of the window, as the page first shows it: + // its menu has no room below, so it opens upwards. + let mut h = build(Setup::new(many()), MIN, 1.0, false); + h.get_by_label("More for vps-sanjose").simulate_click(); + h.run_steps(3); + let menu = ui::open_menu_rect(&h.ctx).expect("the menu is open"); + let trigger = h.get_by_label("More for vps-sanjose").raw_bounds().unwrap(); + assert!( + f64::from(menu.bottom()) <= trigger.y0, + "a low row's menu opens upwards: {menu:?}" + ); + crate::stream::tests::assert_inside( + "vps-sanjose's menu", + menu, + egui::Rect::from_min_size(egui::Pos2::ZERO, MIN), + ); + } + #[test] fn tab_reaches_a_rows_controls_in_reading_order() { use egui_kittest::kittest::By; @@ -2973,6 +3004,40 @@ pub(crate) mod snapshots { h.get_by_label("More for Gaming-PC").click(); h.run_steps(3); save(h.render().unwrap(), "client-row-menu-1280x800@2x.png"); + // At the smallest size: Tab reaches the longest menu's last item, + // and a low row's menu, opened where the page first shows it, + // opens upwards. + for ppp in [1.0, 2.0] { + let mut h = build(Setup::new(pcs()), MIN, ppp, true); + crate::stream::tests::assert_menu_fits( + &mut h, + MIN, + "More for Gaming-PC", + "Copy Tailscale address", + ); + save( + h.render().unwrap(), + &file_name("client", "row-menu", MIN, ppp), + ); + let mut h = build(Setup::new(many()), MIN, ppp, true); + h.get_by_label("More for vps-sanjose").simulate_click(); + h.run_steps(3); + let menu = ui::open_menu_rect(&h.ctx).expect("the menu is open"); + let trigger = h.get_by_label("More for vps-sanjose").raw_bounds().unwrap(); + assert!( + f64::from(menu.bottom()) <= trigger.y0, + "a low row's menu opens upwards: {menu:?}" + ); + crate::stream::tests::assert_inside( + "vps-sanjose's menu", + menu, + egui::Rect::from_min_size(egui::Pos2::ZERO, MIN), + ); + save( + h.render().unwrap(), + &file_name("client", "row-menu-low-row", MIN, ppp), + ); + } let mut h = build(Setup::new(pcs()), TYPICAL, 2.0, true); h.get_by_label_contains("Connection details").click(); h.run_steps(3); diff --git a/crates/client/src/settings.rs b/crates/client/src/settings.rs index df0f35e..6376c4c 100644 --- a/crates/client/src/settings.rs +++ b/crates/client/src/settings.rs @@ -72,7 +72,7 @@ pub fn stream_controls(ui: &mut egui::Ui, s: &mut StreamSettings, native: (u32, Resolution::P1440, Resolution::P2160, ] { - ui.selectable_value(&mut resolution, r, r.describe(native)); + ui::select_option(ui, &mut resolution, r, r.describe(native)); } }); if resolution != s.resolution { @@ -89,7 +89,7 @@ pub fn stream_controls(ui: &mut egui::Ui, s: &mut StreamSettings, native: (u32, |ui| { ui::select(ui, "stream-fps", format!("{} fps", s.fps), 120.0, |ui| { for fps in [30, 60, 90, 120, 144, 165, 240] { - ui.selectable_value(&mut s.fps, fps, format!("{fps} fps")); + ui::select_option(ui, &mut s.fps, fps, format!("{fps} fps")); } }); }, diff --git a/crates/client/src/stream.rs b/crates/client/src/stream.rs index bf45b48..f853be4 100644 --- a/crates/client/src/stream.rs +++ b/crates/client/src/stream.rs @@ -298,9 +298,13 @@ impl View { &mut actions, ); - self.settings_panel(ctx, env, &mut actions); + // Panels keep clear of the toolbar while it shows. + let below_bar = bar_rect + .map(|b| Rect::from_min_max(Pos2::new(screen.left(), b.bottom()), screen.max)) + .unwrap_or(screen); + self.settings_panel(ctx, below_bar, &mut actions); if self.stats { - self.diagnostics(ctx, env, &stats); + self.diagnostics(ctx, env, below_bar, &stats); } self.input(ctx, live, env.cfg, video, bar_rect); actions @@ -349,16 +353,15 @@ impl View { }); } - fn settings_panel(&mut self, ctx: &egui::Context, env: &Env<'_>, actions: &mut Vec) { - let _ = env; + fn settings_panel(&mut self, ctx: &egui::Context, region: Rect, actions: &mut Vec) { let Some(settings) = self.settings.as_mut() else { return; }; let mut apply = false; let mut close = false; - let screen = ctx.screen_rect(); - let w = overlay_width(520.0, screen.width()) - 2.0 * space::LG; + let w = overlay_width(520.0, region.width()) - 2.0 * space::LG; egui::Window::new("Stream settings") + .constrain_to(region) .title_bar(false) .frame(ui::panel_frame()) .collapsible(false) @@ -369,16 +372,19 @@ impl View { .show(ctx, |ui| { ui.set_width(w); close = ui::panel_header(ui, "Stream settings"); - egui::ScrollArea::vertical() - .max_height((screen.height() - 190.0).max(120.0)) - .show(ui, |ui| { + ui::clipped_scroll( + ui, + "stream-settings", + (region.height() - 190.0).max(120.0), + |ui| { ui.spacing_mut().item_spacing.y = 0.0; crate::settings::stream_controls( ui, settings, crate::app::ClientApp::native_pixels(ctx), ); - }); + }, + ); ui.add_space(space::MD); ui.horizontal_wrapped(|ui| { ui.spacing_mut().item_spacing.x = space::SM; @@ -403,10 +409,17 @@ impl View { } } - fn diagnostics(&mut self, ctx: &egui::Context, env: &Env<'_>, stats: &brolink_stream::Stats) { + fn diagnostics( + &mut self, + ctx: &egui::Context, + env: &Env<'_>, + region: Rect, + stats: &brolink_stream::Stats, + ) { let live = env.live; let mut close = false; egui::Window::new("Stream performance") + .constrain_to(region) .title_bar(false) .frame(ui::panel_frame()) .resizable(false) @@ -417,50 +430,55 @@ impl View { .show(ctx, |ui| { ui.set_width(340.0); close = ui::panel_header(ui, "Stream performance"); - let path = env.path.as_ref().unwrap_or(&live.path); - ui.horizontal_wrapped(|ui| { - ui::tag(ui, Some(path.tone()), &path.label()); - ui::tag(ui, None, &profile_label(&live.settings)); - }); - ui.add_space(space::SM); - let rows = [ - Kv::new("Resolution", format!("{} × {}", stats.width, stats.height)), - Kv::new( - "Frame rate", - format!("{:.1} of {} fps", stats.fps, live.requested.2), - ), - Kv::new( - "Bitrate", - format!( - "{:.1} of {} Mbps", - stats.mbps, - live.settings.bitrate_kbps / 1000 + // Everything under the title scrolls when the window is too + // short for it, so the panel always ends inside the window. + let room = ui::room_below(ui, space::LG + 1.0); + ui::clipped_scroll(ui, "stream-performance", room, |ui| { + let path = env.path.as_ref().unwrap_or(&live.path); + ui.horizontal_wrapped(|ui| { + ui::tag(ui, Some(path.tone()), &path.label()); + ui::tag(ui, None, &profile_label(&live.settings)); + }); + ui.add_space(space::SM); + let rows = [ + Kv::new("Resolution", format!("{} × {}", stats.width, stats.height)), + Kv::new( + "Frame rate", + format!("{:.1} of {} fps", stats.fps, live.requested.2), ), - ), - Kv::new( - "Round trip", - format!("{} ± {} ms", stats.rtt_ms, stats.rtt_var_ms), - ), - Kv::new("Packet loss", format!("{:.2}%", stats.loss_pct)), - Kv::new("Encode on host", format!("{:.2} ms", stats.host_ms)), - Kv::new("Frame assembly", format!("{:.2} ms", stats.assembly_ms)), - Kv::new("Decoder queue", format!("{:.2} ms", stats.queue_ms)), - Kv::new("Decode", format!("{:.2} ms", stats.decode_ms)), - Kv::new("Decoder", stats.decoder.to_string()), - ] - .map(Kv::mono); - ui::kv_grid(ui, &rows); - ui.add_space(space::SM); - ui::small_print(ui, "The encoder aims at the bitrate while the picture changes; a still screen needs less. Received bitrate falling well short during motion, or swinging, means packets are being lost on the way."); - if !stats.audio.is_empty() { - ui::small_print(ui, &stats.audio); - } - ui.add_space(space::XS); - if ui::secondary_button(ui, "Copy diagnostics").clicked() { - ctx.copy_text(format!("BroLink {}\n{}\nRequested: {} × {}, {} fps, {} Mbps\n{stats:#?}", - env!("CARGO_PKG_VERSION"), path.label(), live.requested.0, live.requested.1, - live.requested.2, live.settings.bitrate_kbps / 1000)); - } + Kv::new( + "Bitrate", + format!( + "{:.1} of {} Mbps", + stats.mbps, + live.settings.bitrate_kbps / 1000 + ), + ), + Kv::new( + "Round trip", + format!("{} ± {} ms", stats.rtt_ms, stats.rtt_var_ms), + ), + Kv::new("Packet loss", format!("{:.2}%", stats.loss_pct)), + Kv::new("Encode on host", format!("{:.2} ms", stats.host_ms)), + Kv::new("Frame assembly", format!("{:.2} ms", stats.assembly_ms)), + Kv::new("Decoder queue", format!("{:.2} ms", stats.queue_ms)), + Kv::new("Decode", format!("{:.2} ms", stats.decode_ms)), + Kv::new("Decoder", stats.decoder.to_string()), + ] + .map(Kv::mono); + ui::kv_grid(ui, &rows); + ui.add_space(space::SM); + ui::small_print(ui, "The encoder aims at the bitrate while the picture changes; a still screen needs less. Received bitrate falling well short during motion, or swinging, means packets are being lost on the way."); + if !stats.audio.is_empty() { + ui::small_print(ui, &stats.audio); + } + ui.add_space(space::XS); + if ui::secondary_button(ui, "Copy diagnostics").clicked() { + ctx.copy_text(format!("BroLink {}\n{}\nRequested: {} × {}, {} fps, {} Mbps\n{stats:#?}", + env!("CARGO_PKG_VERSION"), path.label(), live.requested.0, live.requested.1, + live.requested.2, live.settings.bitrate_kbps / 1000)); + } + }); }); if close { self.stats = false; @@ -645,25 +663,20 @@ impl View { ) { let live = env.live; let cfg = env.cfg; - let max_h = (ctx.screen_rect().height() - self.bar_h - space::LG).max(120.0); ui::menu_button(ui, "More", |ui| { - egui::ScrollArea::vertical() - .max_height(max_h) - .show(ui, |ui| { - ui::menu_label(ui, "View"); - if ui::menu_choice(ui, "Full screen", env.fullscreen).clicked() { - actions.push(Action::Fullscreen(!env.fullscreen)); - } - if ui::menu_choice(ui, "Stream performance", self.stats).clicked() { - self.stats = !self.stats; - } - ui::menu_label(ui, "Mouse"); - self.mouse_items(ui, ctx, live, cfg, actions); - ui::menu_label(ui, "Send keys"); - self.key_items(ui, live, cfg, actions); - ui::menu_label(ui, machine_noun(&env.os)); - self.power_items(ui, env, actions); - }); + ui::menu_label(ui, "View"); + if ui::menu_choice(ui, "Full screen", env.fullscreen).clicked() { + actions.push(Action::Fullscreen(!env.fullscreen)); + } + if ui::menu_choice(ui, "Stream performance", self.stats).clicked() { + self.stats = !self.stats; + } + ui::menu_label(ui, "Mouse"); + self.mouse_items(ui, ctx, live, cfg, actions); + ui::menu_label(ui, "Send keys"); + self.key_items(ui, live, cfg, actions); + ui::menu_label(ui, machine_noun(&env.os)); + self.power_items(ui, env, actions); }); } @@ -1300,8 +1313,14 @@ fn question(ctx: &egui::Context, id: &str, bar: Rect, w: f32, add: impl FnOnce(& .show(ctx, |ui| { ui::overlay_frame().show(ui, |ui| { ui.set_width(w); - ui.spacing_mut().item_spacing.y = space::SM; - add(ui) + // A long question on a short window scrolls inside its + // frame instead of running off the bottom. + let room = ui::room_below(ui, OVERLAY_PAD + 1.0); + ui::clipped_scroll(ui, id, room, |ui| { + ui.set_width(w); + ui.spacing_mut().item_spacing.y = space::SM; + add(ui) + }); }); }); } @@ -1690,12 +1709,300 @@ pub(crate) mod tests { fixture(view, Extra::default()), ); assert!(h.query_by_label("Apply and reconnect").is_some()); - crate::app::snapshots::assert_fits(&h, 640.0); + // The screen waiting for the first picture lies under the panel, on + // a lower layer, with a Cancel of its own; `assert_fits` sees every + // layer at once, so the panel is checked on its own. + let screen = Rect::from_min_size(Pos2::ZERO, Vec2::new(640.0, 420.0)); + let panel = h + .ctx + .memory(|m| m.area_rect(Id::new("Stream settings"))) + .expect("the panel is showing"); + assert_inside("Stream settings", panel, below_bar(&h, screen)); + for control in ["Apply and reconnect", "Close Stream settings"] { + assert_inside(control, node_rect(&h.get_by_label(control)), panel); + } + assert_cancel_inside(&h, panel); h.get_by_label("Close Stream settings").click(); h.run_steps(2); assert!(h.state().view.settings.is_none()); } + fn node_rect(node: &egui_kittest::kittest::Node<'_>) -> Rect { + let b = node.raw_bounds().expect("node has bounds"); + Rect::from_min_max( + Pos2::new(b.x0 as f32, b.y0 as f32), + Pos2::new(b.x1 as f32, b.y1 as f32), + ) + } + + /// Press Tab until the control called `label` has keyboard focus. + pub(crate) fn tab_to(h: &mut egui_kittest::Harness<'_, S>, label: &str) { + for _ in 0..60 { + if h.query_by_label(label).is_some_and(|n| n.is_focused()) { + h.run_steps(2); + return; + } + h.press_key(egui::Key::Tab); + h.run_steps(1); + } + panic!("Tab never reached {label:?}"); + } + + /// `inner` lies inside `outer`, give or take half a point of rounding. + pub(crate) fn assert_inside(what: &str, inner: Rect, outer: Rect) { + assert!( + outer.expand(0.5).contains_rect(inner), + "{what} {inner:?} runs outside {outer:?}" + ); + } + + /// Open the menu behind `trigger` and check the whole menu is on + /// screen and that Tab brings its last item, `last`, into view. + pub(crate) fn assert_menu_fits( + h: &mut egui_kittest::Harness<'_, S>, + size: Vec2, + trigger: &str, + last: &str, + ) { + let screen = Rect::from_min_size(Pos2::ZERO, size); + h.get_by_label(trigger).simulate_click(); + h.run_steps(3); + let menu = brolink_ui::open_menu_rect(&h.ctx).expect("the menu is open"); + assert_inside(&format!("{trigger} menu at {size:?}"), menu, screen); + tab_to(h, last); + let menu = brolink_ui::open_menu_rect(&h.ctx).expect("Tab keeps the menu open"); + assert_inside(&format!("{trigger} menu after Tab"), menu, screen); + let item = node_rect(&h.get_by_label(last)); + assert_inside(&format!("{last:?} in the {trigger} menu"), item, menu); + } + + fn windows_old_host() -> Extra { + Extra { + os: "windows".into(), + old_host: Some((Version::new(3, 0, 0), Some(Version::new(4, 0, 2)))), + ..Default::default() + } + } + + /// The last item each toolbar menu can show: the power items end the + /// More menu, and with an old host the update item ends them. + const LAST_POWER_ITEM: &str = "Update BroLink Host… (runs 3.0.0)"; + + #[test] + fn every_toolbar_menu_fits_the_smallest_window_and_scrolls_to_its_end() { + let min = Vec2::new(640.0, 420.0); + let mut h = harness_with( + min, + 1.0, + false, + fixture(view_with_bar(), windows_old_host()), + ); + assert_menu_fits(&mut h, min, "More", LAST_POWER_ITEM); + // The separate menus exist from this width up; at the minimum + // height they must fit too. + let short = Vec2::new(OVERFLOW_BELOW, 420.0); + let last_key = if cfg!(target_os = "macos") { + "Command acts as Ctrl" + } else { + "Print Screen" + }; + for (trigger, last) in [ + ("Keys", last_key), + ("Mouse", "Free, for desktops (follows this cursor)"), + ("PC", LAST_POWER_ITEM), + ] { + let mut h = harness_with( + short, + 1.0, + false, + fixture(view_with_bar(), windows_old_host()), + ); + assert_menu_fits(&mut h, short, trigger, last); + } + } + + #[test] + fn the_stream_panels_and_questions_fit_the_smallest_window() { + let min = Vec2::new(640.0, 420.0); + let screen = Rect::from_min_size(Pos2::ZERO, min); + let area = |h: &egui_kittest::Harness<'_, Fixture>, id: &str| { + h.ctx + .memory(|m| m.area_rect(Id::new(id))) + .unwrap_or_else(|| panic!("{id} is showing")) + }; + + let mut view = view_with_bar(); + view.stats = true; + let mut h = harness_with(min, 1.0, false, fixture(view, windows_old_host())); + tab_to(&mut h, "Copy diagnostics"); + let below = below_bar(&h, screen); + assert_inside("Stream performance", area(&h, "Stream performance"), below); + let copy = node_rect(&h.get_by_label("Copy diagnostics")); + assert_inside("Copy diagnostics", copy, area(&h, "Stream performance")); + + let mut view = view_with_bar(); + view.settings = Some(ClientConfig::default().stream); + let mut h = harness_with(min, 1.0, false, fixture(view, windows_old_host())); + tab_to(&mut h, "H.264"); + let panel = area(&h, "Stream settings"); + assert_inside("Stream settings", panel, below_bar(&h, screen)); + for control in ["H.264", "Apply and reconnect"] { + assert_inside(control, node_rect(&h.get_by_label(control)), panel); + } + assert_cancel_inside(&h, panel); + + for (id, view, action) in [ + ( + "stream-confirm", + View { + confirm: Some(PowerAction::Shutdown), + ..view_with_bar() + }, + PowerAction::Shutdown.label(), + ), + ( + "stream-install", + View { + confirm_install: true, + ..view_with_bar() + }, + "Install through the stream", + ), + ] { + let mut h = harness_with(min, 1.0, false, fixture(view, windows_old_host())); + tab_to(&mut h, action); + assert_inside(id, area(&h, id), below_bar(&h, screen)); + assert_inside(action, node_rect(&h.get_by_label(action)), area(&h, id)); + assert_cancel_inside(&h, area(&h, id)); + } + } + + /// The window below the toolbar: where panels and questions go. + fn below_bar(h: &egui_kittest::Harness<'_, Fixture>, screen: Rect) -> Rect { + Rect::from_min_max(Pos2::new(0.0, h.state().view.bar_h), screen.max) + } + + /// The Cancel that ends a panel or question lies inside it. (The + /// screen waiting for the first picture has a Cancel of its own.) + fn assert_cancel_inside(h: &egui_kittest::Harness<'_, Fixture>, area: Rect) { + assert!( + h.query_all_by_label("Cancel") + .any(|n| area.expand(0.5).contains_rect(node_rect(&n))), + "no Cancel inside {area:?}" + ); + } + + #[test] + #[ignore = "renders with a GPU; run on demand to review the UI"] + fn snapshots_stream_menus_at_the_smallest_size() { + use crate::app::snapshots::{file_name, save}; + let min = Vec2::new(640.0, 420.0); + let short = Vec2::new(OVERFLOW_BELOW, 420.0); + let last_key = if cfg!(target_os = "macos") { + "Command acts as Ctrl" + } else { + "Print Screen" + }; + for ppp in [1.0, 2.0] { + for (name, size, trigger, last) in [ + ("more-end", min, "More", LAST_POWER_ITEM), + ("keys-end", short, "Keys", last_key), + ( + "mouse-end", + short, + "Mouse", + "Free, for desktops (follows this cursor)", + ), + ("power-end", short, "PC", LAST_POWER_ITEM), + ] { + let mut h = harness_with( + size, + ppp, + true, + fixture(view_with_bar(), windows_old_host()), + ); + assert_menu_fits(&mut h, size, trigger, last); + save(h.render().unwrap(), &file_name("stream", name, size, ppp)); + } + for (name, view, last) in [ + ( + "stats-end", + View { + stats: true, + ..view_with_bar() + }, + "Copy diagnostics", + ), + ( + "settings-end", + View { + settings: Some(ClientConfig::default().stream), + ..view_with_bar() + }, + "H.264", + ), + ( + "confirm-shutdown", + View { + confirm: Some(PowerAction::Shutdown), + ..view_with_bar() + }, + PowerAction::Shutdown.label(), + ), + ( + "install-host", + View { + confirm_install: true, + ..view_with_bar() + }, + "Install through the stream", + ), + ] { + let mut h = harness_with(min, ppp, true, fixture(view, windows_old_host())); + tab_to(&mut h, last); + save(h.render().unwrap(), &file_name("stream", name, min, ppp)); + } + // A dropdown in the settings panel opens where it has room. + let view = View { + settings: Some(ClientConfig::default().stream), + ..view_with_bar() + }; + let mut h = harness_with(min, ppp, true, fixture(view, windows_old_host())); + // The frame rate select comes just before the bitrate slider. + use egui::accesskit::Role; + use egui_kittest::kittest::By; + let focused = |h: &egui_kittest::Harness<'_, Fixture>, role: Role| { + h.query_all(By::new().predicate(|n| n.is_focused())) + .any(|n| n.role() == role) + }; + for _ in 0..30 { + if focused(&h, Role::Slider) { + break; + } + h.press_key(egui::Key::Tab); + h.run_steps(1); + } + h.press_key_modifiers(egui::Modifiers::SHIFT, egui::Key::Tab); + h.run_steps(2); + assert!(focused(&h, Role::ComboBox), "Shift-Tab reaches the select"); + h.query_all(By::new().predicate(|n| n.is_focused())) + .next() + .unwrap() + .simulate_click(); + h.run_steps(3); + let screen = Rect::from_min_size(Pos2::ZERO, min); + let list = brolink_ui::open_menu_rect(&h.ctx).expect("the list is open"); + assert_inside("the frame rate list", list, screen); + tab_to(&mut h, "240 fps"); + let list = brolink_ui::open_menu_rect(&h.ctx).expect("Tab keeps the list open"); + assert_inside("240 fps", node_rect(&h.get_by_label("240 fps")), list); + save( + h.render().unwrap(), + &file_name("stream", "fps-open", min, ppp), + ); + } + } + #[test] #[ignore = "renders with a GPU; run on demand to review the UI"] fn snapshots_stream_overlay() { diff --git a/crates/ui/src/theme.rs b/crates/ui/src/theme.rs index 49e45ad..6dcba7b 100644 --- a/crates/ui/src/theme.rs +++ b/crates/ui/src/theme.rs @@ -245,6 +245,27 @@ pub fn stroke(width: f32, color: Color32) -> Stroke { Stroke::new(width, color) } +/// The scroll bar of a menu or panel whose content is cut off by the +/// window: the window's usual floating bar, but with its handle showing at +/// rest, so a clipped list says there is more below without a hover. +pub fn clipped_scroll() -> egui::style::ScrollStyle { + egui::style::ScrollStyle { + floating: true, + bar_width: 6.0, + floating_width: 3.0, + floating_allocated_width: 0.0, + bar_inner_margin: space::XXS, + foreground_color: true, + dormant_background_opacity: 0.0, + dormant_handle_opacity: 0.45, + active_background_opacity: 0.0, + active_handle_opacity: 0.6, + interact_background_opacity: 0.2, + interact_handle_opacity: 0.8, + ..egui::style::ScrollStyle::floating() + } +} + /// Install the fonts and style on a context. Call once at startup. pub fn apply(ctx: &egui::Context) { ctx.set_fonts(fonts()); @@ -385,7 +406,9 @@ fn visuals() -> Visuals { v.selection.stroke = stroke(1.0, p.accent); v.text_cursor.stroke = stroke(2.0, p.accent); v.interact_cursor = Some(egui::CursorIcon::PointingHand); - v.clip_rect_margin = 0.0; + // A focus ring is drawn two points outside its control; a window or a + // scroll area clipped tight to its content would cut it off at the edge. + v.clip_rect_margin = 4.0; let w = &mut v.widgets; let r = CornerRadius::same(radius::SM); diff --git a/crates/ui/src/widgets.rs b/crates/ui/src/widgets.rs index 29dfc6a..12924eb 100644 --- a/crates/ui/src/widgets.rs +++ b/crates/ui/src/widgets.rs @@ -312,6 +312,7 @@ pub fn disclosure( Vec2::new(ui.available_width().max(galley.size().x + 24.0), h), Sense::click(), ); + keep_in_view(&response); if response.clicked() { state.toggle(ui); } @@ -872,6 +873,7 @@ fn labelled_button( let icon_w = if trailing.is_some() { 16.0 } else { 0.0 }; let w = (galley.size().x + 2.0 * pad + icon_w).max(h); let (rect, response) = ui.allocate_exact_size(Vec2::new(w, h), Sense::click()); + keep_in_view(&response); response.widget_info(|| WidgetInfo::labeled(WidgetType::Button, ui.is_enabled(), a11y)); if ui.is_rect_visible(rect) { let l = look( @@ -933,6 +935,7 @@ pub fn destructive_button(ui: &mut Ui, label: &str) -> Response { pub fn icon_button(ui: &mut Ui, icon: Icon, label: &str) -> Response { let s = ui.spacing().interact_size.y; let (rect, response) = ui.allocate_exact_size(Vec2::splat(s), Sense::click()); + keep_in_view(&response); response.widget_info(|| WidgetInfo::labeled(WidgetType::Button, ui.is_enabled(), label)); if ui.is_rect_visible(rect) { let l = look( @@ -962,6 +965,7 @@ pub fn link(ui: &mut Ui, label: &str) -> Response { .layout_no_wrap(label.to_owned(), font, Color32::PLACEHOLDER); let size = Vec2::new(galley.size().x, galley.size().y.max(size::HIT_MIN)); let (rect, response) = ui.allocate_exact_size(size, Sense::click()); + keep_in_view(&response); response.widget_info(|| WidgetInfo::labeled(WidgetType::Link, ui.is_enabled(), label)); if ui.is_rect_visible(rect) { let hot = response.hovered() || response.has_focus(); @@ -984,10 +988,121 @@ pub fn link(ui: &mut Ui, label: &str) -> Response { // Menus // --------------------------------------------------------------------------- +/// Where a popup hung from a control goes, and how tall it may be. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Drop { + /// The corner of the popup that touches the control's edge. + pub pos: Pos2, + pub pivot: Align2, + /// The popup's outer height may not exceed this; anything longer + /// scrolls inside it. + pub max_height: f32, + /// True when it opens upwards, over the control. + pub above: bool, +} + +/// Place a popup of outer height `wanted` against `anchor` inside +/// `screen`. It opens below when it fits there, and otherwise on whichever +/// side has more room (a machine's menu near the bottom of a short window +/// opens upwards). Its height is capped at that side's room, less a margin +/// from the window's edge, so no part of it is ever off screen: what does +/// not fit scrolls. `right` hangs it from the control's right edge, for +/// controls on the right of the window. +pub fn place_popup(screen: Rect, anchor: Rect, wanted: f32, right: bool) -> Drop { + let gap = space::XS; + let edge = space::SM; + let below = (screen.bottom() - edge - (anchor.bottom() + gap)).max(0.0); + let above = (anchor.top() - gap - (screen.top() + edge)).max(0.0); + let x = if right { anchor.right() } else { anchor.left() }; + if wanted <= below || below >= above { + Drop { + pos: Pos2::new(x, anchor.bottom() + gap), + pivot: if right { + Align2::RIGHT_TOP + } else { + Align2::LEFT_TOP + }, + max_height: below, + above: false, + } + } else { + Drop { + pos: Pos2::new(x, anchor.top() - gap), + pivot: if right { + Align2::RIGHT_BOTTOM + } else { + Align2::LEFT_BOTTOM + }, + max_height: above, + above: true, + } + } +} + +/// The height a scrolling region inside a panel may take so the panel +/// ends a margin above the window's bottom edge: from the cursor down, +/// less `below` for what the panel still has to lay out under it (its +/// footer and bottom margin). Never less than one control, so a tiny +/// window still shows something to scroll. +pub fn room_below(ui: &Ui, below: f32) -> f32 { + let bottom = ui.ctx().screen_rect().bottom() - space::SM; + (bottom - ui.cursor().top() - below).max(size::CONTROL) +} + +/// A vertical scroll area for content that may be cut off by the window: +/// its scroll bar shows at rest, so a clipped list reads as one. Keyboard +/// focus moving to a control inside scrolls it into view (see +/// [`keep_in_view`]). +pub fn clipped_scroll( + ui: &mut Ui, + id: impl std::hash::Hash, + max_height: f32, + add: impl FnOnce(&mut Ui) -> R, +) -> egui::scroll_area::ScrollAreaOutput { + let saved = ui.spacing().scroll; + ui.spacing_mut().scroll = theme::clipped_scroll(); + let out = egui::ScrollArea::vertical() + .id_salt(id) + .max_height(max_height) + // A popup or window lays out in the size it had last frame; without + // this the list could never grow past its first, smaller, height. + .min_scrolled_height(max_height) + .show(ui, |ui| { + ui.spacing_mut().scroll = saved; + add(ui) + }); + ui.spacing_mut().scroll = saved; + out +} + +/// When keyboard focus arrives at a control, scroll it into view: egui +/// moves focus to a control clipped away in a scroll area without showing +/// it, and a menu or panel cut short by a small window would otherwise take +/// focus somewhere nobody can see. +pub fn keep_in_view(response: &Response) { + if response.gained_focus() { + response.scroll_to_me_animation(None, egui::style::ScrollAnimation::none()); + } +} + +/// Where the menu open now sits on screen, or the visible part of an open +/// [`select`]'s list, for layout checks. +pub fn open_menu_rect(ctx: &egui::Context) -> Option { + let (id, rect): (Id, Rect) = ctx.data(|d| d.get_temp(open_menu_key()))?; + ctx.memory(|m| m.is_popup_open(id)).then_some(rect) +} + +fn open_menu_key() -> Id { + Id::new("brolink.open_menu") +} + /// Open a popup menu under `trigger` while it is toggled on. A real egui /// popup: `any_popup_open` is true while it shows, Escape and a click /// outside close it. It hangs from the trigger's nearer edge, so a menu at -/// the right of the window opens leftwards instead of running off it. +/// the right of the window opens leftwards instead of running off it, and +/// it never runs past the window's top or bottom: it opens upwards when +/// there is more room above, and scrolls when neither side has room for +/// all of it (see [`place_popup`]). fn popup_menu(ui: &mut Ui, trigger: &Response, add: impl FnOnce(&mut Ui) -> R) -> Option { let id = trigger.id.with("menu"); if trigger.clicked() { @@ -996,35 +1111,49 @@ fn popup_menu(ui: &mut Ui, trigger: &Response, add: impl FnOnce(&mut Ui) -> R if !ui.memory(|m| m.is_popup_open(id)) { return None; } - let screen = ui.ctx().screen_rect(); - let (pos, pivot) = if trigger.rect.center().x > screen.center().x { - (trigger.rect.right_bottom(), Align2::RIGHT_TOP) - } else { - (trigger.rect.left_bottom(), Align2::LEFT_TOP) - }; + let ctx = ui.ctx().clone(); + let screen = ctx.screen_rect(); + let right = trigger.rect.center().x > screen.center().x; + // The menu's full height, measured when it was last laid out. + let key = id.with("height"); + let known: Option = ctx.data(|d| d.get_temp(key)); + let drop = place_popup(screen, trigger.rect, known.unwrap_or(0.0), right); + let style = ui.style().clone(); + let chrome = style.spacing.menu_margin.sum().y + 2.0 * style.visuals.window_stroke.width; let shown = egui::Area::new(id) .kind(egui::UiKind::Popup) .order(egui::Order::Foreground) - .fixed_pos(pos + Vec2::new(0.0, space::XS)) - .pivot(pivot) + .fixed_pos(drop.pos) + .pivot(drop.pivot) .constrain(true) - .show(ui.ctx(), |ui| { - Frame::popup(ui.style()) + .show(&ctx, |ui| { + Frame::popup(&style) .show(ui, |ui| { ui.set_min_width(200.0); ui.set_max_width(320.0); - ui.spacing_mut().item_spacing.y = 0.0; - ui.spacing_mut().interact_size.y = size::CONTROL_SM; - ui.with_layout(Layout::top_down_justified(Align::Min), add) - .inner + let max = (drop.max_height - chrome).max(size::CONTROL_SM); + clipped_scroll(ui, "brolink.menu", max, |ui| { + ui.spacing_mut().item_spacing.y = 0.0; + ui.spacing_mut().interact_size.y = size::CONTROL_SM; + ui.with_layout(Layout::top_down_justified(Align::Min), add) + .inner + }) }) .inner }); + let full = shown.inner.content_size.y + chrome; + if known.is_none_or(|k| (k - full).abs() > 0.5) { + ctx.data_mut(|d| d.insert_temp(key, full)); + if place_popup(screen, trigger.rect, full, right) != drop { + ctx.request_discard("menu moved to fit the window"); + } + } + ctx.data_mut(|d| d.insert_temp(open_menu_key(), (id, shown.response.rect))); let outside = trigger.clicked_elsewhere() && shown.response.clicked_elsewhere(); if outside || ui.input(|i| i.key_pressed(egui::Key::Escape)) { ui.memory_mut(|m| m.close_popup()); } - Some(shown.inner) + Some(shown.inner.inner) } /// A quiet button with a chevron that opens a menu. @@ -1060,6 +1189,7 @@ fn menu_row(ui: &mut Ui, label: &str, checked: Option, enabled: bool) -> R Sense::hover() }; let (rect, response) = ui.allocate_exact_size(Vec2::new(w, h), sense); + keep_in_view(&response); response.widget_info(|| match checked { Some(on) => WidgetInfo::selected(WidgetType::RadioButton, enabled, on, label), None => WidgetInfo::labeled(WidgetType::Button, enabled, label), @@ -1252,6 +1382,7 @@ pub fn segmented( let seg = Rect::from_min_size(Pos2::new(x, rect.top()), Vec2::new(widths[i], h)); x += widths[i]; let resp = ui.interact(seg, block.id.with(i), Sense::click()); + keep_in_view(&resp); resp.widget_info(|| { WidgetInfo::selected( WidgetType::RadioButton, @@ -1302,6 +1433,7 @@ fn labelled_toggle(ui: &mut Ui, on: &mut bool, label: &str) -> Response { Vec2::new(track.x, track.y.max(size::HIT_MIN)), Sense::click(), ); + keep_in_view(&response); if response.clicked() { *on = !*on; response.mark_changed(); @@ -1345,7 +1477,9 @@ fn labelled_toggle(ui: &mut Ui, on: &mut bool, label: &str) -> Response { } /// A dropdown in the house style, with a painted chevron. `add` fills the -/// list with `ui.selectable_value` rows. +/// list with [`select_option`] rows. The list is no taller than the room on +/// the side of the control it opens towards, so it stays inside the window +/// and scrolls, with its scroll bar showing, when it has to. pub fn select( ui: &mut Ui, id: impl std::hash::Hash, @@ -1353,10 +1487,22 @@ pub fn select( width: f32, add: impl FnOnce(&mut Ui) -> R, ) -> Option { - egui::ComboBox::from_id_salt(id) + // egui opens the list below the control when it fits there, and above + // it otherwise; cap it at the larger of the two rooms so either way it + // ends a margin inside the window. + let screen = ui.ctx().screen_rect(); + let y = ui.next_widget_position().y; + let h = ui.spacing().interact_size.y; + let below = screen.bottom() - space::SM - (y + h); + let above = y - h / 2.0 - space::XS - (screen.top() + space::SM); + let chrome = ui.spacing().menu_margin.sum().y + 2.0 * ui.visuals().window_stroke.width; + let height = (below.max(above) - chrome).clamp(size::CONTROL, 320.0); + let mut list = None; + let out = egui::ComboBox::from_id_salt(id) .selected_text(selected) .width(width) - .height(320.0) + // egui's own scroll area never clips; the one inside does. + .height(f32::INFINITY) .icon(|ui, rect, visuals, _open, _| { paint_icon( ui.painter(), @@ -1366,11 +1512,35 @@ pub fn select( ); }) .show_ui(ui, |ui| { - ui.spacing_mut().item_spacing.y = 0.0; - ui.spacing_mut().button_padding = Vec2::new(space::SM, 6.0); - add(ui) - }) - .inner + let out = clipped_scroll(ui, "brolink.select", height, |ui| { + ui.spacing_mut().item_spacing.y = 0.0; + ui.spacing_mut().button_padding = Vec2::new(space::SM, 6.0); + add(ui) + }); + list = Some(out.inner_rect); + out.inner + }); + keep_in_view(&out.response); + if let Some(rect) = list { + // How egui's ComboBox names its popup. + let popup = out.response.id.with("popup"); + ui.ctx() + .data_mut(|d| d.insert_temp(open_menu_key(), (popup, rect))); + } + out.inner +} + +/// A row in a [`select`]'s list: chosen when clicked, marked while chosen, +/// and scrolled into view when Tab reaches it. +pub fn select_option( + ui: &mut Ui, + current: &mut T, + value: T, + label: impl Into, +) -> Response { + let response = ui.selectable_value(current, value, label); + keep_in_view(&response); + response } /// A horizontal slider for a whole number, with its value in mono beside @@ -1389,6 +1559,7 @@ pub fn slider( Vec2::new(width, size::HIT_MIN.max(ui.spacing().interact_size.y)), Sense::click_and_drag(), ); + keep_in_view(&response); let rail = Rect::from_min_max( Pos2::new(rect.left() + knob, rect.center().y - 2.0), Pos2::new(rect.right() - knob, rect.center().y + 2.0), @@ -1939,6 +2110,39 @@ mod tests { } } + #[test] + fn a_popup_never_leaves_the_window() { + let screen = Rect::from_min_size(Pos2::ZERO, Vec2::new(640.0, 420.0)); + for y in [8.0, 40.0, 150.0, 260.0, 380.0] { + for wanted in [60.0, 170.0, 400.0, 900.0] { + let anchor = Rect::from_min_size(Pos2::new(560.0, y), Vec2::splat(32.0)); + let d = place_popup(screen, anchor, wanted, true); + let (top, bottom) = if d.above { + (d.pos.y - d.max_height, d.pos.y) + } else { + (d.pos.y, d.pos.y + d.max_height) + }; + assert!(top >= screen.top() + space::SM, "{y} {wanted}: {d:?}"); + assert!(bottom <= screen.bottom() - space::SM, "{y} {wanted}: {d:?}"); + assert!(d.max_height > 0.0); + } + } + } + + #[test] + fn a_popup_opens_upwards_only_when_it_needs_to_and_there_is_more_room() { + let screen = Rect::from_min_size(Pos2::ZERO, Vec2::new(640.0, 420.0)); + let low = Rect::from_min_size(Pos2::new(560.0, 380.0), Vec2::splat(32.0)); + assert!(place_popup(screen, low, 170.0, true).above); + let middle = Rect::from_min_size(Pos2::new(560.0, 200.0), Vec2::splat(32.0)); + assert!( + !place_popup(screen, middle, 150.0, true).above, + "it fits below" + ); + let high = Rect::from_min_size(Pos2::new(8.0, 8.0), Vec2::splat(28.0)); + assert!(!place_popup(screen, high, 900.0, false).above); + } + #[test] fn tones_have_distinct_marks_except_the_two_plain_ones() { assert_eq!(Tone::Neutral.color(), Tone::Info.color()); diff --git a/docs/design/AUDIT.md b/docs/design/AUDIT.md index 46e616e..5fcba19 100644 --- a/docs/design/AUDIT.md +++ b/docs/design/AUDIT.md @@ -105,6 +105,7 @@ All pairs are asserted in `crates/ui/src/theme.rs` and `widgets.rs` tests. | X3 | Medium | No window shortcuts. | Fixed: Command/Ctrl-1, 2, comma; Escape. | | X4 | Low | Switch hit target 40×22 (< 24). | Fixed: 36×24. | | X5 | Low | Pills and other painted text were missing from the accessibility tree. | Fixed: status, tags, keycaps and the PIN report labels. | +| X6 | High | At 640×420 the stream toolbar's More menu ran past the bottom of the window: "Command acts as Ctrl" and the PC items could not be reached, by pointer or keyboard. Stream performance ran off the bottom, and panels sat under the toolbar. Tab could move focus to a control scrolled out of sight. | Fixed: menus open where they fit (upwards when there is more room above) and scroll inside when neither side has room; dropdown lists, panels and questions do the same, below the toolbar; Tab scrolls the focused control into view; focus rings are no longer cut at a window's edge. Tested at 640×420 by rect, with snapshots. | ## Design system From 61de1b9734905b2e2da3328060b318781193f5f5 Mon Sep 17 00:00:00 2001 From: MrBeldum <157205701+MrBeldum@users.noreply.github.com> Date: Wed, 30 Sep 2026 07:46:16 -0700 Subject: [PATCH 52/52] Line up a notice's actions with its text The failed-connection card's "Dismiss" sat 12 points right of the text above it. A quiet button's label is inset by the button's padding, and with no fill or outline to show the box, the label is what the eye lines up. Actions under a notice now go in an action row that pulls a quiet button leading a line back by that padding; filled and outlined buttons still line up by their edge. Checking the other notices and cards turned up two more: a notice with no actions (Tailscale isn't connected, Installing a BroLink update) kept an empty row's height at its foot, and the setup card's "Get Tailscale" link started at the card's edge instead of under the checklist item it belongs to. The empty row now takes no room, and the link sits in the item's text column. A test checks the label of a quiet action and the edge of a filled one against the notice's title. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 2 ++ crates/client/src/app.rs | 25 ++++++++++++++++ crates/host/src/app.rs | 27 ++++++++++-------- crates/ui/src/widgets.rs | 61 ++++++++++++++++++++++++++++++++++++---- docs/design/AUDIT.md | 1 + 5 files changed, 99 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6738ba9..7bab87a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,8 @@ GitHub release notes, so each version gets a heading of the form off "Command acts as Ctrl" and the PC's power items. Stream settings and Stream performance stay below the toolbar, and Tab scrolls the control it reaches into view. +- A notice's actions line up with its text: "Dismiss" sat 12 points to + the right of the words above it. - A relayed path is amber, not red: it works, and Connection details says how to get a direct one. - The idle window no longer repaints on a timer (it redrew 2.5 times a diff --git a/crates/client/src/app.rs b/crates/client/src/app.rs index df4555b..d3009df 100644 --- a/crates/client/src/app.rs +++ b/crates/client/src/app.rs @@ -2645,6 +2645,31 @@ pub(crate) mod snapshots { ); } + #[test] + fn a_notices_quiet_action_lines_up_with_its_text() { + for state in ["failed", "confirm-restart"] { + let setup = states().into_iter().find(|(n, _)| *n == state).unwrap().1; + let h = build(setup, TYPICAL, 1.0, false); + let (title, action) = match state { + "failed" => ("Couldn't connect to Office", "Dismiss"), + _ => ("Restart Gaming-PC?", "Restart"), + }; + let text = h.get_by_label(title).raw_bounds().unwrap().x0; + let button = h.get_by_label(action).raw_bounds().unwrap().x0; + // A quiet button's label starts its padding in from its box; + // a filled one's edge is what lines up. + let label = if action == "Dismiss" { + button + f64::from(space::MD) + } else { + button + }; + assert!( + (label - text).abs() <= 0.5, + "{state}: {action} starts at {label}, the text at {text}" + ); + } + } + #[test] fn row_menus_stay_inside_the_smallest_window() { // Gaming-PC's menu is the longest; Den's row is the lowest, so its diff --git a/crates/host/src/app.rs b/crates/host/src/app.rs index 5fa59ff..4c5366a 100644 --- a/crates/host/src/app.rs +++ b/crates/host/src/app.rs @@ -489,18 +489,21 @@ impl HostApp { .trim_end_matches("Install it and sign in.") .trim() .trim_end_matches('.'); - ui::dot_label( - ui, - Tone::Danger, - &format!( - "{}. Install Tailscale and sign in with the account your other machines use.", - sentence_case(why) - ), - ); - if ui::link(ui, "Get Tailscale").clicked() { - ui.ctx() - .open_url(egui::OpenUrl::new_tab(self.os.tailscale_url())); - } + // The link belongs to the item: it starts where its text does. + ui::dot_item(ui, Tone::Danger, |ui| { + ui.spacing_mut().item_spacing.y = space::XXS; + ui.add( + egui::Label::new(egui::RichText::new(format!( + "{}. Install Tailscale and sign in with the account your other machines use.", + sentence_case(why) + )).color(ui::PALETTE.text)) + .wrap(), + ); + if ui::link(ui, "Get Tailscale").clicked() { + ui.ctx() + .open_url(egui::OpenUrl::new_tab(self.os.tailscale_url())); + } + }); } for item in &admin_items { ui::dot_label(ui, Tone::Warning, item); diff --git a/crates/ui/src/widgets.rs b/crates/ui/src/widgets.rs index 12924eb..33abd74 100644 --- a/crates/ui/src/widgets.rs +++ b/crates/ui/src/widgets.rs @@ -626,16 +626,55 @@ pub fn banner( if let Some(b) = body { muted(ui, b); } - ui.add_space(space::XS); - ui.horizontal_wrapped(|ui| { - ui.spacing_mut().item_spacing.x = space::SM; - actions(ui) - }); + action_row(ui, space::XS, actions); }); }) .response } +/// A row of actions under a block of text, lined up with the text: a +/// filled or outlined button's edge starts the line, and a quiet button +/// (text until hovered) is pulled back by its padding so its label does. +/// Takes no room at all when `add` adds nothing; otherwise `gap` goes +/// above it. +pub fn action_row(ui: &mut Ui, gap: f32, add: impl FnOnce(&mut Ui)) { + let top = ui.available_rect_before_wrap(); + // One control high, as `horizontal_wrapped` starts: its lines centre + // in that height and further lines go below. + let line = Rect::from_min_size( + top.min + Vec2::new(0.0, gap), + Vec2::new(top.width(), ui.spacing().interact_size.y), + ); + let mut child = ui.new_child( + UiBuilder::new() + .max_rect(line) + .layout(Layout::left_to_right(Align::Center).with_main_wrap(true)), + ); + child.spacing_mut().item_spacing.x = space::SM; + let key = flush_key(child.id()); + child.data_mut(|d| d.insert_temp(key, true)); + add(&mut child); + child.data_mut(|d| d.remove::(key)); + let used = child.min_rect(); + if used.width() > 0.0 && used.height() > 0.0 { + ui.allocate_rect( + Rect::from_min_max(top.min, Pos2::new(top.right(), used.bottom())), + Sense::hover(), + ); + } +} + +fn flush_key(row: Id) -> Id { + Id::new("brolink.action_row").with(row) +} + +/// Whether the next widget starts a line of an [`action_row`]. +fn starts_action_line(ui: &Ui) -> bool { + ui.data(|d| d.get_temp::(flush_key(ui.id()))) + .unwrap_or(false) + && ui.cursor().min.x <= ui.max_rect().min.x + 0.5 +} + // --------------------------------------------------------------------------- // Key / value // --------------------------------------------------------------------------- @@ -872,6 +911,11 @@ fn labelled_button( let pad = space::MD; let icon_w = if trailing.is_some() { 16.0 } else { 0.0 }; let w = (galley.size().x + 2.0 * pad + icon_w).max(h); + if matches!(kind, Kind::Quiet | Kind::Danger) && starts_action_line(ui) { + // No fill or outline marks a quiet button's box, so its label is + // what the eye lines up: pull it back by the padding. + ui.add_space(-pad); + } let (rect, response) = ui.allocate_exact_size(Vec2::new(w, h), Sense::click()); keep_in_view(&response); response.widget_info(|| WidgetInfo::labeled(WidgetType::Button, ui.is_enabled(), a11y)); @@ -1963,6 +2007,13 @@ pub fn tag(ui: &mut Ui, tone: Option, label: &str) -> Response { response } +/// A status dot in the gutter and any content beside it, every line of it +/// starting at the text column: a checklist item with a link under its +/// text, say. +pub fn dot_item(ui: &mut Ui, tone: Tone, add: impl FnOnce(&mut Ui) -> R) -> R { + dotted(ui, tone, add) +} + /// A small coloured dot followed by wrapped text, for checklists. pub fn dot_label(ui: &mut Ui, tone: Tone, text: &str) { dotted(ui, tone, |ui| { diff --git a/docs/design/AUDIT.md b/docs/design/AUDIT.md index 5fcba19..e2970c4 100644 --- a/docs/design/AUDIT.md +++ b/docs/design/AUDIT.md @@ -49,6 +49,7 @@ writes `target/ui-snapshots/`. Names below refer to that folder | V9 | Low | The stream toolbar was a rectangle with a white hairline on all four sides. | Fixed: bottom hairline only. | | V10 | Low | Bitrate slider: large hollow knob, violet trail, a drag box beside it. | Fixed: a slim slider with a mono value, arrow-key steps. | | V11 | Low | The App field was 20 pt tall beside 30-pt controls. | Fixed. | +| V12 | Low | A notice's quiet action ("Dismiss", "Cancel") sat 12 points right of the text above it: its label is inset by the button's padding, and no fill or outline shows the box. A notice with no actions kept an empty row's height; the setup card's "Get Tailscale" link started left of the item it belongs to. | Fixed: `action_row` pulls a leading quiet button back by its padding; an empty row takes no room; `dot_item` puts the link in the item's text column. | ### Contrast (WCAG 2.2 AA, computed)