Skip to main content

sim_lib_view_device/
profile_surface.rs

1//! Surface capability conversion for device profiles.
2
3use sim_kernel::{Expr, Symbol};
4use sim_lib_view::SurfaceCaps;
5use sim_value::build;
6
7use crate::profile::{DeviceProfile, has_symbol};
8
9impl DeviceProfile {
10    /// Converts this profile back into open surface capabilities for `client_id`.
11    ///
12    /// This is the bridge used by device peers: the device keeps the typed
13    /// profile for routing and tier decisions, while the synchronized surface
14    /// hub receives ordinary [`SurfaceCaps`] and continues to project scenes by
15    /// open capability metadata.
16    pub fn to_surface_caps(&self, client_id: impl Into<String>) -> SurfaceCaps {
17        SurfaceCaps {
18            client_id: client_id.into(),
19            preset: Symbol::qualified("surface", self.kind.name.clone()),
20            display: display_map_from_profile(&self.display),
21            input: flags_map(&self.input),
22            transport: transport_map_from_profile(&self.links),
23            privacy: self.policy.clone(),
24            rate: self.rate.to_expr(),
25            codecs: vec![
26                Symbol::qualified("surface", "lisp"),
27                Symbol::qualified("surface", "json"),
28            ],
29        }
30    }
31}
32
33fn display_map_from_profile(symbols: &[Symbol]) -> Expr {
34    let mut entries = vec![(
35        Expr::Symbol(build::keyword("media")),
36        build::list(Vec::new()),
37    )];
38    if let Some(density) = first_named(symbols, &["glance", "compact", "regular", "dense"]) {
39        entries.push((
40            Expr::Symbol(build::keyword("density")),
41            Expr::Symbol(density),
42        ));
43    }
44    if let Some(shape) = first_named(symbols, &["round", "flat"]) {
45        entries.push((Expr::Symbol(build::keyword("shape")), Expr::Symbol(shape)));
46    }
47    if has_symbol(symbols, "stereo") {
48        entries.push((Expr::Symbol(build::keyword("stereo")), Expr::Bool(true)));
49    }
50    if has_symbol(symbols, "hud") {
51        entries.push((Expr::Symbol(build::keyword("lines")), build::uint(2)));
52    }
53    Expr::Map(entries)
54}
55
56fn flags_map(symbols: &[Symbol]) -> Expr {
57    Expr::Map(
58        symbols
59            .iter()
60            .map(|symbol| {
61                (
62                    Expr::Symbol(Symbol::new(symbol.name.clone())),
63                    Expr::Bool(true),
64                )
65            })
66            .collect(),
67    )
68}
69
70fn transport_map_from_profile(links: &[Symbol]) -> Expr {
71    let kind = links
72        .first()
73        .cloned()
74        .unwrap_or_else(|| Symbol::new("local"));
75    build::map(vec![
76        ("kind", Expr::Symbol(kind)),
77        (
78            "offline-queue",
79            Expr::Bool(has_symbol(links, "relay") || has_symbol(links, "phone-relay")),
80        ),
81        ("ordered", Expr::Bool(true)),
82        ("round-trip-ms", build::uint(1)),
83    ])
84}
85
86fn first_named(symbols: &[Symbol], names: &[&str]) -> Option<Symbol> {
87    names
88        .iter()
89        .find_map(|name| symbols.iter().find(|symbol| symbol.name.as_ref() == *name))
90        .cloned()
91}