119 lines
4.2 KiB
Rust
119 lines
4.2 KiB
Rust
use itertools::Itertools as _;
|
|
use windows_registry::CURRENT_USER;
|
|
use windows_result::Error as WindowsError;
|
|
|
|
use crate::send_telemetry_from_ctx;
|
|
use crate::server::telemetry::TelemetryEvent;
|
|
|
|
const DOCKER_DESKTOP_WSL_DISTRO_PREFIX: &str = "docker-desktop";
|
|
const RANCHER_DESKTOP_WSL_DISTRO_PREFIX: &str = "rancher-desktop";
|
|
|
|
/// Contains information about WSL distributions available on the user's Windows machine.
|
|
pub(crate) struct WslInfo {
|
|
distributions: Vec<Distribution>,
|
|
}
|
|
|
|
impl Entity for WslInfo {
|
|
type Event = ();
|
|
}
|
|
|
|
impl SingletonEntity for WslInfo {}
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub(crate) struct Distribution {
|
|
uuid: String,
|
|
pub name: String,
|
|
pub is_default: bool,
|
|
}
|
|
|
|
impl WslInfo {
|
|
pub(crate) fn new(ctx: &mut ModelContext<Self>) -> Self {
|
|
let distributions = Self::find_available_distributions()
|
|
.inspect_err(|err| match err {
|
|
// This error merely occurs when user doesn't have WSL installed/enabled.
|
|
Error::MainKey(err) => {
|
|
log::info!("{err:#}");
|
|
send_telemetry_from_ctx!(TelemetryEvent::WSLRegistryError, ctx);
|
|
}
|
|
_ => {
|
|
log::error!("{err:#}");
|
|
}
|
|
})
|
|
.unwrap_or_default();
|
|
Self { distributions }
|
|
}
|
|
|
|
pub(crate) fn distributions(&self) -> impl Iterator<Item = &Distribution> {
|
|
self.distributions.iter()
|
|
}
|
|
|
|
/// Finds available WSL distributions available on the local machine.
|
|
fn find_available_distributions() -> Result<Vec<Distribution>, Error> {
|
|
// The storage format is the following:
|
|
// Lxss/
|
|
// DefaultDistribution: 1787c461-d291-401d-b579-1ceff55f97e3
|
|
// {1787c461-d291-401d-b579-1ceff55f97e3}
|
|
// DistributionName: Ubuntu
|
|
// {63375eca-1f0a-4d5d-9682-16b0d42fded8}
|
|
// DistributionName: Ubuntu-18.04
|
|
let key = CURRENT_USER
|
|
.open("Software\\Microsoft\\Windows\\CurrentVersion\\Lxss")
|
|
.map_err(Error::MainKey)?;
|
|
|
|
let default_distribution_uuid = key
|
|
.get_string("DefaultDistribution")
|
|
.inspect_err(|err| log::warn!("Could not obtain the default distribution: {err:#}"));
|
|
let distribution_keys = key.keys().map_err(Error::DistributionIterator)?;
|
|
let distributions = distribution_keys
|
|
.into_iter()
|
|
.flat_map(|uuid| {
|
|
let distribution_key = key
|
|
.open(&uuid)
|
|
.inspect_err(|err| {
|
|
log::error!("Could not open distribution registry key: {err:#}")
|
|
})
|
|
.ok()?;
|
|
let name = distribution_key
|
|
.get_string("DistributionName")
|
|
.inspect_err(|err| {
|
|
// Some entries don't have names, that's not an error state we need to monitor.
|
|
if err.code() != KEY_NOT_FOUND_ERR {
|
|
log::error!("Unable to read distribution name: {err:#}");
|
|
}
|
|
})
|
|
.ok()?;
|
|
|
|
// Docker uses WSL on windows but we don't expect users to start a new session for
|
|
// that WSL instance. Same with Rancher.
|
|
if name.starts_with(DOCKER_DESKTOP_WSL_DISTRO_PREFIX)
|
|
|| name.starts_with(RANCHER_DESKTOP_WSL_DISTRO_PREFIX)
|
|
{
|
|
return None;
|
|
}
|
|
|
|
Some(Distribution {
|
|
name,
|
|
is_default: default_distribution_uuid
|
|
.as_ref()
|
|
.is_ok_and(|default_distribution_uuid| *default_distribution_uuid == uuid),
|
|
uuid,
|
|
})
|
|
})
|
|
.collect_vec();
|
|
|
|
if distributions.iter().all(|distro| !distro.is_default) {
|
|
log::warn!("No distribution matched the default guid");
|
|
}
|
|
|
|
Ok(distributions)
|
|
}
|
|
}
|
|
|
|
#[derive(thiserror::Error, Debug)]
|
|
enum Error {
|
|
#[error("Error opening the main key: {0:#}")]
|
|
MainKey(#[source] WindowsError),
|
|
#[error("Could not iterate through distributions: {0:#}")]
|
|
DistributionIterator(#[source] WindowsError),
|
|
}
|