Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@

## [Unreleased]

### 变更

- 启动门禁只读注册表 `HKLM\...\Lxss\Msi\Version`(与 WSL 自身一致),不再调用 `wsl --version`。缺键即视为未安装。SDK 加载后的版本仍用 `wslc::Service::version()`。
- First-launch WSL check reads only the MSI registry version; no CLI. Live SDK version still comes from `Service::version()`.
- 拆分 SDK 安装逻辑:`wsl_probe`(版本探测)、`sdk_cache`(本地 stamp/DLL)、`sdk_fetch`(NuGet 下载解压),主路径只保留「缓存命中 / 列包 / 下载」。
- Split SDK install into probe / cache / fetch modules so the download path stays short.
- 删除面向 `wsl --version` 的 UTF-16 / 多行文本解析;`sdk_version` 只保留 SemVer 与 NuGet 匹配。
- Drop CLI text/UTF-16 parsing; `sdk_version` keeps SemVer + NuGet matching only.
- 移除未使用的 `session_stats` / `SessionStats` / `Cmd::Stats` 路径。
- Remove unused session-stats plumbing.

## [0.1.2] - 2026-10-11

### 修复
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ ureq = { version = "2.12", default-features = true, features = ["json"] }
zip = { version = "2", default-features = false, features = ["deflate"] }
windows-sys = { version = "0.61", features = [
"Win32_Foundation",
"Win32_System_Registry",
"Win32_System_Threading",
"Win32_UI_WindowsAndMessaging",
] }
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
|------|----------|----------|
| ![总览](docs/screenshots/overview.png) | ![容器日志](docs/screenshots/containers-logs.png) | ![拉取镜像](docs/screenshots/images-pull.png) |

用独立会话 `wslc-desktop`(`%LOCALAPPDATA%\WSLcDesktop\`)管理容器、镜像与 VHD 卷,与默认 `wslc` CLI 会话隔离。Rust 绑定使用自维护 fork [`timeroute/wslc-rs`](https://github.com/timeroute/wslc-rs)(OpenContainer / Logs / named volumes)。MSI **不捆绑** `wslcsdk.dll`:首次启动以阻塞弹窗安装/更新 SDK,之后复用 `%LOCALAPPDATA%\WSLcDesktop\sdk\` 缓存(仅在版本不兼容或手动重试时再下载)。同一用户会话只允许一个实例。
用独立会话 `wslc-desktop`(`%LOCALAPPDATA%\WSLcDesktop\`)管理容器、镜像与 VHD 卷,与默认 `wslc` CLI 会话隔离。Rust 绑定使用自维护 fork [`timeroute/wslc-rs`](https://github.com/timeroute/wslc-rs)(OpenContainer / Logs / named volumes)。MSI **不捆绑** `wslcsdk.dll`:首次启动以阻塞弹窗安装/更新 SDK,之后复用 `%LOCALAPPDATA%\WSLcDesktop\sdk\` 缓存(仅在版本不兼容或手动重试时再下载)。启动门禁只读注册表 `Lxss\Msi\Version`(与 WSL 一致),SDK 连上后版本来自 `Service::version()`。同一用户会话只允许一个实例。

## 安装(推荐)

Expand Down
4 changes: 2 additions & 2 deletions src/i18n.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ pub static EN: Strings = Strings {
sdk_setup_step_checking: "Checking",
sdk_setup_step_passed: "Passed",
sdk_setup_step_failed: "Failed",
sdk_wsl_missing: "WSL is missing, or its version could not be read. Install or update WSL with `wsl --update`, then retry.",
sdk_wsl_missing: "WSL is missing, or its package version was not found in the registry. Install or update WSL with `wsl --update`, then retry.",
sdk_wsl_too_old: "WSL {found} is older than {required}. Run `wsl --update`, then retry.",
sdk_wsl_ok: "WSL {version} (requires {required}+)",
sdk_install_ok: "SDK {version}",
Expand Down Expand Up @@ -568,7 +568,7 @@ pub static ZH: Strings = Strings {
sdk_setup_step_checking: "检查中",
sdk_setup_step_passed: "通过",
sdk_setup_step_failed: "失败",
sdk_wsl_missing: "未检测到 WSL,或无法读取版本。请运行 `wsl --update` 安装或更新后再重试。",
sdk_wsl_missing: "未检测到 WSL,或注册表中没有包版本。请运行 `wsl --update` 安装或更新后再重试。",
sdk_wsl_too_old: "WSL {found} 低于要求的 {required}。请运行 `wsl --update` 后再重试。",
sdk_wsl_ok: "WSL {version}(要求 {required}+)",
sdk_install_ok: "SDK {version}",
Expand Down
28 changes: 1 addition & 27 deletions src/wslc/live.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use super::catalog::{Catalog, PersistedContainer};
use super::prefs::SessionPrefs;
use super::{
split_ref, BackendKind, ContainerInfo, CreateContainerRequest, EngineStatus, ImageInfo,
PortMapping, PullEvent, SessionStats, VolumeInfo, VolumeMapping,
PortMapping, PullEvent, VolumeInfo, VolumeMapping,
};

const SESSION_NAME: &str = "wslc-desktop";
Expand All @@ -21,7 +21,6 @@ const STOP_TIMEOUT: Duration = Duration::from_secs(10);

pub enum Cmd {
Status(Sender<EngineStatus>),
Stats(Sender<SessionStats>),
ListContainers {
all: bool,
reply: Sender<Result<Vec<ContainerInfo>>>,
Expand Down Expand Up @@ -409,28 +408,6 @@ impl LiveState {
}
}

fn stats(&self) -> SessionStats {
let running = self
.containers
.iter()
.filter(|c| {
matches!(
c.handle.state(),
Ok(wslc::ContainerState::Running)
)
})
.count();
let images = self.session.list_images().map(|v| v.len()).unwrap_or(0);
SessionStats {
cpu_count: self.prefs.cpu_count,
memory_mb: self.prefs.memory_mb,
containers: self.containers.len(),
running,
images,
volumes: self.volumes.len(),
}
}

fn find_container_mut(&mut self, key: &str) -> Result<&mut TrackedContainer> {
self.containers
.iter_mut()
Expand Down Expand Up @@ -923,9 +900,6 @@ fn live_worker(mut state: LiveState, rx: mpsc::Receiver<Cmd>) {
Cmd::Status(reply) => {
let _ = reply.send(state.status());
}
Cmd::Stats(reply) => {
let _ = reply.send(state.stats());
}
Cmd::ListContainers { all, reply } => {
let _ = reply.send(state.list_containers(all));
}
Expand Down
44 changes: 14 additions & 30 deletions src/wslc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@ mod live;
mod paths;
mod prefs;
#[cfg(windows)]
mod sdk_cache;
#[cfg(windows)]
mod sdk_fetch;
mod sdk_version;
pub mod setup_log;
#[cfg(windows)]
mod syspath;
pub mod templates;
mod types;
#[cfg(windows)]
mod wsl_probe;

pub use prefs::SessionPrefs;
pub use templates::{ContainerTemplate, TemplateEditError, TemplateStore, UpsertKind};
Expand All @@ -32,6 +36,7 @@ pub fn min_wsl_version_text() -> String {
sdk_version::MIN_WSL_VERSION.to_string()
}

#[cfg_attr(not(windows), allow(dead_code))] // production use is in `live` (Windows)
pub(super) fn split_ref(reference: &str) -> (String, String) {
if let Some((repo, tag)) = reference.rsplit_once(':') {
if !tag.contains('/') {
Expand Down Expand Up @@ -223,21 +228,19 @@ impl WslcClient {
"Checking WSL version (require {required} or newer)…"
)));

let output = sdk_fetch::probe_wsl_version_output();
if let Some(out) = &output {
for line in out.lines() {
let line = line.trim();
if !line.is_empty() {
let _ = tx.send(SdkSetupEvent::Log(format!("wsl: {line}")));
}
}
// Registry only (same key WSL writes). No CLI — missing key means not installed.
let found = wsl_probe::package_version();
if let Some(version) = &found {
let _ = tx.send(SdkSetupEvent::Log(format!(
"WSL package version from registry (Lxss\\Msi): {version}"
)));
} else {
let _ = tx.send(SdkSetupEvent::Log(
"wsl --version produced no output (is WSL installed and on PATH?)".into(),
"Registry Lxss\\Msi\\Version missing (WSL MSI not installed?)".into(),
));
}

match sdk_version::judge_wsl_version(output.as_deref()) {
match sdk_version::judge_package_version(found.as_deref()) {
sdk_version::WslVersionVerdict::Ok(version) => {
let _ = tx.send(SdkSetupEvent::Log(format!(
"WSL {version} meets the {required}+ requirement"
Expand All @@ -255,7 +258,7 @@ impl WslcClient {
state: SetupStepState::Failed(SetupFault::WslMissing),
});
let _ = tx.send(SdkSetupEvent::Finished(Err(
"WSL is missing or its version could not be read".into(),
"WSL is missing or its version could not be read from the registry".into(),
)));
false
}
Expand Down Expand Up @@ -426,25 +429,6 @@ impl WslcClient {
}
}

#[cfg_attr(not(windows), allow(dead_code))]
#[allow(dead_code)] // reserved for richer Overview refresh
pub fn session_stats(&self) -> SessionStats {
match &self.inner.backend {
#[cfg(windows)]
Backend::Live { tx, .. } => {
let (reply_tx, reply_rx) = std::sync::mpsc::channel();
if tx.send(live::Cmd::Stats(reply_tx)).is_ok() {
if let Ok(stats) = reply_rx.recv() {
return stats;
}
}
SessionStats::default()
}
#[cfg(not(windows))]
Backend::Unsupported => SessionStats::default(),
}
}

pub fn list_volumes(&self) -> Result<Vec<VolumeInfo>> {
match &self.inner.backend {
#[cfg(windows)]
Expand Down
79 changes: 79 additions & 0 deletions src/wslc/sdk_cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//! On-disk cache for extracted `wslcsdk.dll` under `%LOCALAPPDATA%\WSLcDesktop\sdk`.

#![cfg(windows)]

use std::fs;
use std::path::{Path, PathBuf};

use anyhow::Result;
use serde::{Deserialize, Serialize};

use super::paths;
use super::sdk_version::cache_compatible;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActiveStamp {
pub host_version: String,
pub nuget_version: String,
pub arch: String,
}

pub fn root() -> PathBuf {
paths::app_data_dir().join("sdk")
}

pub fn native_arch() -> &'static str {
match std::env::consts::ARCH {
"x86_64" => "win-x64",
"aarch64" => "win-arm64",
_ => "win-x64",
}
}

pub fn stamp_path() -> PathBuf {
root().join("active.json")
}

pub fn package_dir(nuget: &str, arch: &str) -> PathBuf {
root().join(format!("nuget-{nuget}")).join(arch)
}

pub fn dll_ready(dir: &Path) -> bool {
dir.join("wslcsdk.dll").is_file()
}

/// Look up the active stamp's DLL when present and (optionally) host-compatible.
pub fn lookup(host: Option<&str>, arch: &str) -> Option<(PathBuf, ActiveStamp)> {
let raw = fs::read_to_string(stamp_path()).ok()?;
let stamp: ActiveStamp = serde_json::from_str(&raw).ok()?;
if stamp.arch != arch {
return None;
}
if let Some(host) = host
&& !cache_compatible(host, &stamp.nuget_version)
{
return None;
}
let dir = package_dir(&stamp.nuget_version, arch);
dll_ready(&dir).then_some((dir, stamp))
}

pub fn write_stamp(host: &str, nuget: &str, arch: &str) -> Result<()> {
let path = stamp_path();
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let stamp = ActiveStamp {
host_version: host.to_string(),
nuget_version: nuget.to_string(),
arch: arch.to_string(),
};
fs::write(path, serde_json::to_string_pretty(&stamp)?)?;
Ok(())
}

pub fn clear_package(nuget: &str, arch: &str) {
let dll = package_dir(nuget, arch).join("wslcsdk.dll");
let _ = fs::remove_file(dll);
let _ = fs::remove_file(stamp_path());
}
Loading
Loading