use sim_kernel::Symbol;
use sim_lib_view::SurfaceCaps;
use crate::{
Degradation, DegradationResolver, DeviceProfile, DeviceProfileParts, DeviceTier, ObservedRoute,
derive_tier,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GlassesRoute {
DirectLinux,
AndroidUsb,
NeckbandLocal,
NeckbandRelay,
MobileDockDisplay,
HaloBleDirect,
HaloWebBluetooth,
HaloPhoneRelay,
ControllerHid,
}
impl GlassesRoute {
pub const fn token(self) -> &'static str {
match self {
Self::DirectLinux => "direct-linux",
Self::AndroidUsb => "android-usb",
Self::NeckbandLocal => "neckband-local",
Self::NeckbandRelay => "neckband-relay",
Self::MobileDockDisplay => "mobile-dock-display",
Self::HaloBleDirect => "ble-direct",
Self::HaloWebBluetooth => "web-bluetooth",
Self::HaloPhoneRelay => "phone-relay",
Self::ControllerHid => "controller-hid",
}
}
pub fn symbol(self) -> Symbol {
Symbol::qualified("device/route", self.token())
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ObservedGlassesDevices {
pub luma_ultra: bool,
pub sensor_usb: bool,
pub neckband: bool,
pub mobile_dock: bool,
pub halo: bool,
pub halo_camera: bool,
pub controller_hid: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub struct ResolvedGlassesProfile {
pub route: GlassesRoute,
pub profile: DeviceProfile,
pub degradation: Degradation,
}
impl ResolvedGlassesProfile {
pub fn tier(&self) -> DeviceTier {
derive_tier(&self.profile)
}
}
pub fn resolve_glasses_route(
route: GlassesRoute,
seen: &ObservedGlassesDevices,
) -> ResolvedGlassesProfile {
match route {
GlassesRoute::DirectLinux | GlassesRoute::AndroidUsb => resolve_viture_usb(route, seen),
GlassesRoute::NeckbandLocal | GlassesRoute::NeckbandRelay => {
resolve_viture_neckband(route, seen)
}
GlassesRoute::MobileDockDisplay => resolve_mobile_dock(route, seen),
GlassesRoute::HaloBleDirect
| GlassesRoute::HaloWebBluetooth
| GlassesRoute::HaloPhoneRelay => resolve_halo(route, seen),
GlassesRoute::ControllerHid => resolve_controller(route, seen),
}
}
fn resolve_viture_usb(
route: GlassesRoute,
seen: &ObservedGlassesDevices,
) -> ResolvedGlassesProfile {
let requested = route_profile("glasses-luma-ultra", route);
if seen.luma_ultra && seen.sensor_usb {
return resolved(route, requested.clone(), requested, None);
}
let profile = route_profile(
if seen.luma_ultra {
"glasses-stereo"
} else {
"glasses"
},
route,
);
let reason = if seen.luma_ultra {
"sensor USB not visible; route is display-only"
} else {
"Luma Ultra not observed; route uses a generic display profile"
};
resolved(route, requested, profile, Some(reason))
}
fn resolve_viture_neckband(
route: GlassesRoute,
seen: &ObservedGlassesDevices,
) -> ResolvedGlassesProfile {
let requested = route_profile("glasses-luma-ultra", route);
if seen.luma_ultra && seen.neckband {
return resolved(route, requested.clone(), requested, None);
}
let profile = route_profile(
if seen.luma_ultra {
"glasses-stereo"
} else {
"glasses"
},
route,
);
resolved(
route,
requested,
profile,
Some("neckband sensor path not observed; route is display-only"),
)
}
fn resolve_mobile_dock(
route: GlassesRoute,
seen: &ObservedGlassesDevices,
) -> ResolvedGlassesProfile {
let requested = route_profile("glasses-luma-ultra", route);
if seen.luma_ultra && seen.mobile_dock && seen.sensor_usb {
return resolved(route, requested.clone(), requested, None);
}
let profile = route_profile(
if seen.luma_ultra && seen.mobile_dock {
"glasses-stereo"
} else {
"glasses"
},
route,
);
let reason = if seen.mobile_dock && !seen.sensor_usb {
"sensor USB not visible; Mobile Dock is display-only"
} else {
"Mobile Dock display path not observed; route uses a generic display profile"
};
resolved(route, requested, profile, Some(reason))
}
fn resolve_halo(route: GlassesRoute, seen: &ObservedGlassesDevices) -> ResolvedGlassesProfile {
let requested = route_profile("glasses-hud-camera", route);
if !seen.halo {
return resolved(
route,
requested,
route_profile("glasses", route),
Some("Halo not observed; route uses a generic display profile"),
);
}
let profile = route_profile(
if seen.halo_camera {
"glasses-hud-camera"
} else {
"glasses-hud"
},
route,
);
let reason = (!seen.halo_camera).then_some("Halo camera lane not observed");
resolved(route, requested, profile, reason)
}
fn resolve_controller(
route: GlassesRoute,
seen: &ObservedGlassesDevices,
) -> ResolvedGlassesProfile {
let requested = controller_profile(route, true);
let profile = controller_profile(route, seen.controller_hid);
let reason = if seen.controller_hid {
Some("controller remains ordinary HID/Intent and does not raise the glasses tier")
} else {
Some("controller HID not observed")
};
resolved(route, requested, profile, reason)
}
fn resolved(
route: GlassesRoute,
requested: DeviceProfile,
profile: DeviceProfile,
reason: Option<&str>,
) -> ResolvedGlassesProfile {
let mut degradation =
DegradationResolver::resolve(&requested, &ObservedRoute::from_profile(&profile));
if let Some(reason) = reason {
degradation.reasons.push(reason.to_owned());
}
degradation.tier = derive_tier(&profile);
ResolvedGlassesProfile {
route,
profile,
degradation,
}
}
fn route_profile(preset: &str, route: GlassesRoute) -> DeviceProfile {
let caps =
SurfaceCaps::from_preset(preset, route.token()).expect("built-in glasses profile preset");
let profile = DeviceProfile::from_surface_caps(&caps);
with_route_fields(profile, route, Vec::new())
}
fn controller_profile(route: GlassesRoute, controller_hid: bool) -> DeviceProfile {
let caps = SurfaceCaps::from_preset("glasses", route.token())
.expect("built-in glasses profile preset");
let profile = DeviceProfile::from_surface_caps(&caps);
let input = controller_hid
.then(|| Symbol::new("controller"))
.into_iter()
.collect();
with_route_fields(profile, route, input)
}
fn with_route_fields(
profile: DeviceProfile,
route: GlassesRoute,
mut extra_input: Vec<Symbol>,
) -> DeviceProfile {
extra_input.extend(profile.input);
DeviceProfile::new(DeviceProfileParts {
kind: profile.kind,
display: profile.display,
input: extra_input,
output: profile.output,
links: vec![Symbol::new(route.token())],
streams: profile.streams,
rate: profile.rate,
policy: profile.policy,
})
}