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            output: flags_map(&self.output),
23            transport: transport_map_from_profile(&self.links),
24            privacy: self.policy.clone(),
25            rate: self.rate.to_expr(),
26            streams: flags_map(&self.streams),
27            codecs: vec![
28                Symbol::qualified("surface", "lisp"),
29                Symbol::qualified("surface", "json"),
30            ],
31        }
32    }
33}
34
35fn display_map_from_profile(symbols: &[Symbol]) -> Expr {
36    let mut entries = vec![(
37        Expr::Symbol(build::keyword("media")),
38        build::list(Vec::new()),
39    )];
40    if let Some(density) = first_named(symbols, &["glance", "compact", "regular", "dense"]) {
41        entries.push((
42            Expr::Symbol(build::keyword("density")),
43            Expr::Symbol(density),
44        ));
45    }
46    if let Some(shape) = first_named(symbols, &["round", "flat"]) {
47        entries.push((Expr::Symbol(build::keyword("shape")), Expr::Symbol(shape)));
48    }
49    if has_symbol(symbols, "stereo") {
50        entries.push((Expr::Symbol(build::keyword("stereo")), Expr::Bool(true)));
51    }
52    if has_symbol(symbols, "hud") {
53        entries.push((Expr::Symbol(build::keyword("lines")), build::uint(2)));
54    }
55    Expr::Map(entries)
56}
57
58fn flags_map(symbols: &[Symbol]) -> Expr {
59    Expr::Map(
60        symbols
61            .iter()
62            .map(|symbol| {
63                (
64                    Expr::Symbol(Symbol::new(symbol.name.clone())),
65                    Expr::Bool(true),
66                )
67            })
68            .collect(),
69    )
70}
71
72fn transport_map_from_profile(links: &[Symbol]) -> Expr {
73    let kind = links
74        .first()
75        .cloned()
76        .unwrap_or_else(|| Symbol::new("local"));
77    build::map(vec![
78        ("kind", Expr::Symbol(kind)),
79        (
80            "offline-queue",
81            Expr::Bool(has_symbol(links, "relay") || has_symbol(links, "phone-relay")),
82        ),
83        ("ordered", Expr::Bool(true)),
84        ("round-trip-ms", build::uint(1)),
85    ])
86}
87
88fn first_named(symbols: &[Symbol], names: &[&str]) -> Option<Symbol> {
89    names
90        .iter()
91        .find_map(|name| symbols.iter().find(|symbol| symbol.name.as_ref() == *name))
92        .cloned()
93}