feat: web automation wrapper (#1349), DATALINK table on web, System theme; honest decode accounting

Ported from 2c38de53 (#1717). Main already carries the hatch PDF draw
order fix (#1345), the numeric set_properties compare (#1348) and the
web open from bytes, so those parts stay out; what remains:

#1349 — window.ocsControl.ready/request promise wrapper with an
ocs-ready event (web/ocs-control.js, loaded by the web page), and the
startup donation prompt is suppressed once a control request is seen
(opt-in pre-boot via localStorage["opencadstudio.automation"] = "1").
docs/automation/web.md documents both. The control protocol stays at 1.

DATALINK CSV-to-table works on web (picker + bytes through the session
registry — was std::fs, silently dead); COLORSCHEME accepts System
(prefers-color-scheme on web, AppsUseLightTheme on Windows).

Decode accounting, made honest per the fix-of-fix review: into_rgba8
allocates the RGBA destination while the native buffer is alive for
every non-Rgba8 layout (L8 5, La8 6, Rgb8 7, L16 6, La16 8 B/px
peaks — the formula now says so), GIF bills the screen+frame
coexistence at 8 B/px, and TIFF drops the 3x multipliers both ways —
tiff 0.11 splits the given budget between frame and scratch (a total
ceiling), so the old double-billing refused honest 16-bit GeoTIFFs
~2.25x early while admitting hostile chunk transients at 3x.

(cherry picked from commit 2c38de53)
This commit is contained in:
Afonsop authored and Hakan Seven committed 2026-10-12 12:02:05 +03:00
1 parent f015c3b5fa
commit db8db14006
37 files changed
+436 -52

No files matched your search

+84
View File
@@ -109,12 +109,77 @@ impl UiThemeConfig {
pub fn to_iced(&self) -> iced::Theme {
if self.name == "Custom" {
iced::Theme::custom("Custom", self.palette.to_iced())
} else if self.name.eq_ignore_ascii_case("System") {
resolve_system_theme()
} else {
builtin_theme(&self.name).unwrap_or(iced::Theme::Oxocarbon)
}
}
}
/// Resolve the `System` color scheme (COLORSCHEME SYSTEM) to a concrete
/// theme: the browser `prefers-color-scheme` media query on the web, the
/// Windows `AppsUseLightTheme` personalization value natively, and the dark
/// default on the remaining native platforms (no cheap OS query there).
/// Read at boot (`apply_config`) and re-read whenever COLORSCHEME SYSTEM
/// runs again — there is no live preference listener by design.
pub fn resolve_system_theme() -> iced::Theme {
#[cfg(target_arch = "wasm32")]
{
let light = web_sys::window().is_some_and(|window| {
window
.match_media("(prefers-color-scheme: light)")
.ok()
.flatten()
.is_some_and(|query| query.matches())
});
if light {
iced::Theme::Light
} else {
iced::Theme::Dark
}
}
#[cfg(all(not(target_arch = "wasm32"), target_os = "windows"))]
{
use windows_sys::Win32::System::Registry::{
RegGetValueW, HKEY_CURRENT_USER, RRF_RT_REG_DWORD,
};
let sub_key: Vec<u16> = "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"
.encode_utf16()
.chain(std::iter::once(0))
.collect();
let value: Vec<u16> = "AppsUseLightTheme"
.encode_utf16()
.chain(std::iter::once(0))
.collect();
let mut data: u32 = 0;
let mut size = std::mem::size_of::<u32>() as u32;
// ERROR_SUCCESS (0) with data 1 means the apps theme is Light; a
// missing value (older Windows, wiped personalization) is Dark.
let light = unsafe {
RegGetValueW(
HKEY_CURRENT_USER,
sub_key.as_ptr(),
value.as_ptr(),
RRF_RT_REG_DWORD,
std::ptr::null_mut(),
&mut data as *mut u32 as *mut core::ffi::c_void,
&mut size,
) == 0
&& data == 1
};
if light {
iced::Theme::Light
} else {
iced::Theme::Dark
}
}
#[cfg(not(any(target_arch = "wasm32", target_os = "windows")))]
{
iced::Theme::Dark
}
}
#[derive(Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct UiThemePalette {
@@ -538,6 +603,25 @@ fn config_path() -> Option<std::path::PathBuf> {
mod tests {
use super::*;
#[test]
fn system_scheme_resolves_to_a_concrete_theme_and_keeps_its_name() {
// Fresh configs keep the existing default; SYSTEM is opt-in.
assert_ne!(UiThemeConfig::default().name, "System");
let mut cfg = UiThemeConfig::default();
cfg.name = "System".to_string();
let resolved = cfg.to_iced();
// Case-insensitive like the other stored names.
cfg.name = "system".to_string();
assert_eq!(cfg.to_iced(), resolved);
assert!(
matches!(resolved, iced::Theme::Light | iced::Theme::Dark),
"SYSTEM must resolve through the platform preference"
);
// The stored name survives resolution; re-runs re-resolve.
assert_eq!(cfg.name, "system");
assert_eq!(resolve_system_theme(), resolved);
}
#[test]
fn test_model_space_defaults() {
let cfg = ModelSpaceThemeConfig::default();