use std::path::PathBuf;
use std::sync::Arc;
use eframe::egui;
use crate::branding;
const WINDOW_TITLE: &str = "Copernicus Viewer — EOPF Zarr";
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum GpuProfile {
Software,
WslGpu,
Native,
}
impl GpuProfile {
fn detect() -> Self {
match std::env::var("COPERNICUS_VIEWER_GL").as_deref() {
Ok("software") => {
if is_wsl() && use_wayland() {
Self::WslGpu
} else {
Self::Software
}
}
Ok("hardware") => {
if is_wsl() {
Self::WslGpu
} else {
Self::Native
}
}
Ok("native") | Ok("wgpu") => Self::Native,
_ => {
if is_wsl() {
if use_wayland() {
Self::WslGpu
} else {
Self::Software
}
} else {
Self::Native
}
}
}
}
fn renderer(self) -> eframe::Renderer {
match self {
Self::Native => eframe::Renderer::Wgpu,
Self::Software | Self::WslGpu => eframe::Renderer::Glow,
}
}
fn hardware_acceleration(self) -> eframe::HardwareAcceleration {
match self {
Self::Software => eframe::HardwareAcceleration::Off,
Self::Native | Self::WslGpu => eframe::HardwareAcceleration::Preferred,
}
}
fn vsync(self) -> bool {
!matches!(self, Self::Software if is_wsl() && !use_wayland())
}
fn needs_mesa_software_env(self) -> bool {
matches!(self, Self::Software) && !use_wayland()
}
}
pub fn init() {
check_linux_windowing_deps();
branding::ensure_linux_desktop_integration();
let profile = GpuProfile::detect();
if std::env::var("COPERNICUS_VIEWER_GL").as_deref() == Ok("software")
&& is_wsl()
&& use_wayland()
{
log::warn!(
"COPERNICUS_VIEWER_GL=software is not supported on WSLg/Wayland; using GPU OpenGL (Glow)"
);
}
if profile.needs_mesa_software_env() {
force_env("LIBGL_ALWAYS_SOFTWARE", "1");
force_env("GALLIUM_DRIVER", "llvmpipe");
force_env("MESA_LOADER_DRIVER_OVERRIDE", "llvmpipe");
}
}
pub fn log_startup() {
let profile = GpuProfile::detect();
match profile {
GpuProfile::Software if is_wsl() => {
log::info!("WSL X11 — software OpenGL (Glow / Mesa llvmpipe)");
}
GpuProfile::Software => {
log::info!("Software OpenGL rendering (Glow / Mesa llvmpipe)");
}
GpuProfile::WslGpu => {
log::info!("WSLg — GPU OpenGL via Glow");
}
GpuProfile::Native => {
log::info!("GPU rendering (wgpu)");
}
}
}
pub fn native_options() -> eframe::NativeOptions {
let profile = GpuProfile::detect();
eframe::NativeOptions {
renderer: profile.renderer(),
hardware_acceleration: profile.hardware_acceleration(),
vsync: profile.vsync(),
centered: true,
viewport: egui::ViewportBuilder::default()
.with_app_id(branding::APP_ID)
.with_title(WINDOW_TITLE)
.with_inner_size([1280.0, 800.0])
.with_min_inner_size([640.0, 480.0])
.with_icon(Arc::new(branding::window_icon())),
..Default::default()
}
}
pub fn configure_egui(cc: &eframe::CreationContext<'_>) {
let ctx = &cc.egui_ctx;
ctx.options_mut(|options| {
options.theme_preference = egui::ThemePreference::System;
options.tessellation_options.feathering = true;
options.tessellation_options.feathering_size_in_pixels = 1.0;
options.tessellation_options.round_text_to_pixels = true;
options.tessellation_options.round_line_segments_to_pixels = true;
options.tessellation_options.round_rects_to_pixels = true;
});
}
pub fn is_wsl() -> bool {
std::fs::read_to_string("/proc/version")
.map(|v| v.to_lowercase().contains("microsoft"))
.unwrap_or(false)
}
fn use_wayland() -> bool {
env_nonempty("WAYLAND_DISPLAY")
.or_else(|| env_nonempty("WAYLAND_SOCKET"))
.is_some()
}
#[cfg(target_os = "linux")]
fn check_linux_windowing_deps() {
if use_wayland() || env_nonempty("DISPLAY").is_none() {
return;
}
if find_library("libxkbcommon-x11.so.0").is_some() {
return;
}
eprintln!(
"\
Copernicus Viewer could not find libxkbcommon-x11 (required for X11 windowing).
On Ubuntu / Debian / WSL, install it with:
sudo apt install libxkbcommon-x11-0
On WSLg, prefer the native Wayland session (WAYLAND_DISPLAY is usually set).
If you use an external X server, the package above is required.
"
);
std::process::exit(1);
}
#[cfg(not(target_os = "linux"))]
fn check_linux_windowing_deps() {}
fn env_nonempty(key: &str) -> Option<String> {
std::env::var(key).ok().filter(|v| !v.is_empty())
}
fn find_library(name: &str) -> Option<PathBuf> {
let dirs = [
"/lib/x86_64-linux-gnu",
"/usr/lib/x86_64-linux-gnu",
"/lib/aarch64-linux-gnu",
"/usr/lib/aarch64-linux-gnu",
];
for dir in dirs {
let path = PathBuf::from(dir).join(name);
if path.exists() {
return Some(path);
}
}
None
}
fn force_env(key: &str, value: &str) {
unsafe {
std::env::set_var(key, value);
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ZarrNativePick {
Directory,
ZipArchive,
}
pub fn zarr_native_pick_for_hint(path_hint: &str) -> ZarrNativePick {
let trimmed = path_hint.trim();
if trimmed.ends_with(".zip") {
ZarrNativePick::ZipArchive
} else {
ZarrNativePick::Directory
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn pick_zarr_product(frame: &eframe::Frame, kind: ZarrNativePick) -> Option<PathBuf> {
match kind {
ZarrNativePick::Directory => rfd::FileDialog::new()
.set_title("Select EOPF Zarr folder")
.set_parent(frame)
.pick_folder(),
ZarrNativePick::ZipArchive => rfd::FileDialog::new()
.set_title("Select EOPF Zarr zip archive")
.set_parent(frame)
.add_filter("Zarr zip archive", &["zip"])
.pick_file(),
}
}
#[cfg(target_arch = "wasm32")]
pub fn pick_zarr_product(_frame: &eframe::Frame, _kind: ZarrNativePick) -> Option<PathBuf> {
None
}
#[cfg(not(target_arch = "wasm32"))]
pub fn pick_download_folder(frame: &eframe::Frame) -> Option<PathBuf> {
rfd::FileDialog::new()
.set_title("Choose download folder")
.set_parent(frame)
.pick_folder()
}
#[cfg(target_arch = "wasm32")]
pub fn pick_download_folder(_frame: &eframe::Frame) -> Option<PathBuf> {
None
}