mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 00:35:38 +08:00
refactor: Linux environment detection logic (#5108)
* fix: wayland framebuffer * refactor(utils): move linux env heuristics into platform helper * refactor(linux): let DMABUF override helper use resolved decision * fix: clippy * fix: clippy * feat: NVIDIA detection * fix: clippy
This commit is contained in:
@@ -59,6 +59,7 @@
|
|||||||
- 修复删除订阅时未能实际删除相关文件
|
- 修复删除订阅时未能实际删除相关文件
|
||||||
- 修复 macOS 连接界面显示异常
|
- 修复 macOS 连接界面显示异常
|
||||||
- 修复规则配置项在不同配置文件间全局共享导致切换被重置的问题
|
- 修复规则配置项在不同配置文件间全局共享导致切换被重置的问题
|
||||||
|
- 修复 Linux Wayland 下部分 GPU 可能出现的 UI 渲染问题
|
||||||
|
|
||||||
## v2.4.2
|
## v2.4.2
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ mod feat;
|
|||||||
mod module;
|
mod module;
|
||||||
mod process;
|
mod process;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use crate::utils::linux;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use crate::utils::window_manager::WindowManager;
|
use crate::utils::window_manager::WindowManager;
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -234,89 +236,7 @@ pub fn run() {
|
|||||||
|
|
||||||
// Set Linux environment variable
|
// Set Linux environment variable
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
linux::configure_environment();
|
||||||
let desktop_env = std::env::var("XDG_CURRENT_DESKTOP")
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_uppercase();
|
|
||||||
let session_desktop = std::env::var("XDG_SESSION_DESKTOP")
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_uppercase();
|
|
||||||
let desktop_session = std::env::var("DESKTOP_SESSION")
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_uppercase();
|
|
||||||
let is_kde_desktop = desktop_env.contains("KDE");
|
|
||||||
let is_plasma_desktop = desktop_env.contains("PLASMA");
|
|
||||||
let is_hyprland_desktop = desktop_env.contains("HYPR")
|
|
||||||
|| session_desktop.contains("HYPR")
|
|
||||||
|| desktop_session.contains("HYPR");
|
|
||||||
|
|
||||||
let is_wayland_session = std::env::var("XDG_SESSION_TYPE")
|
|
||||||
.map(|value| value.eq_ignore_ascii_case("wayland"))
|
|
||||||
.unwrap_or(false)
|
|
||||||
|| std::env::var("WAYLAND_DISPLAY").is_ok();
|
|
||||||
let prefer_native_wayland =
|
|
||||||
is_wayland_session && (is_kde_desktop || is_plasma_desktop || is_hyprland_desktop);
|
|
||||||
let dmabuf_override = std::env::var("WEBKIT_DISABLE_DMABUF_RENDERER");
|
|
||||||
|
|
||||||
if prefer_native_wayland {
|
|
||||||
let compositor_label = if is_hyprland_desktop {
|
|
||||||
"Hyprland"
|
|
||||||
} else if is_plasma_desktop {
|
|
||||||
"KDE Plasma"
|
|
||||||
} else {
|
|
||||||
"KDE"
|
|
||||||
};
|
|
||||||
|
|
||||||
if matches!(dmabuf_override.as_deref(), Ok("1")) {
|
|
||||||
unsafe {
|
|
||||||
std::env::remove_var("WEBKIT_DISABLE_DMABUF_RENDERER");
|
|
||||||
}
|
|
||||||
logging!(
|
|
||||||
info,
|
|
||||||
Type::Setup,
|
|
||||||
"Wayland + {} detected: Re-enabled WebKit DMABUF renderer to avoid Cairo surface failures.",
|
|
||||||
compositor_label
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
logging!(
|
|
||||||
info,
|
|
||||||
Type::Setup,
|
|
||||||
"Wayland + {} detected: Using native Wayland backend for reliable rendering.",
|
|
||||||
compositor_label
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if dmabuf_override.is_err() {
|
|
||||||
unsafe {
|
|
||||||
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force X11 backend for tray icon compatibility on Wayland
|
|
||||||
if is_wayland_session {
|
|
||||||
unsafe {
|
|
||||||
std::env::set_var("GDK_BACKEND", "x11");
|
|
||||||
std::env::remove_var("WAYLAND_DISPLAY");
|
|
||||||
}
|
|
||||||
logging!(
|
|
||||||
info,
|
|
||||||
Type::Setup,
|
|
||||||
"Wayland detected: Forcing X11 backend for tray icon compatibility"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_kde_desktop || is_plasma_desktop {
|
|
||||||
unsafe {
|
|
||||||
std::env::set_var("GTK_CSD", "0");
|
|
||||||
}
|
|
||||||
logging!(
|
|
||||||
info,
|
|
||||||
Type::Setup,
|
|
||||||
"KDE detected: Disabled GTK CSD for better titlebar stability."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create and configure the Tauri builder
|
// Create and configure the Tauri builder
|
||||||
let builder = app_init::setup_plugins(tauri::Builder::default())
|
let builder = app_init::setup_plugins(tauri::Builder::default())
|
||||||
|
|||||||
522
src-tauri/src/utils/linux.rs
Normal file
522
src-tauri/src/utils/linux.rs
Normal file
@@ -0,0 +1,522 @@
|
|||||||
|
use crate::logging;
|
||||||
|
use crate::utils::logging::Type;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
const DRM_PATH: &str = "/sys/class/drm";
|
||||||
|
const INTEL_VENDOR_ID: &str = "0x8086";
|
||||||
|
const NVIDIA_VENDOR_ID: &str = "0x10de";
|
||||||
|
const NVIDIA_VERSION_PATH: &str = "/proc/driver/nvidia/version";
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
|
struct IntelGpuDetection {
|
||||||
|
has_intel: bool,
|
||||||
|
intel_is_primary: bool,
|
||||||
|
inconclusive: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntelGpuDetection {
|
||||||
|
fn should_disable_dmabuf(&self) -> bool {
|
||||||
|
self.intel_is_primary || self.inconclusive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
struct NvidiaGpuDetection {
|
||||||
|
has_nvidia: bool,
|
||||||
|
nvidia_is_primary: bool,
|
||||||
|
missing_boot_vga: bool,
|
||||||
|
open_kernel_module: bool,
|
||||||
|
driver_summary: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum NvidiaDmabufDisableReason {
|
||||||
|
PrimaryOpenKernelModule,
|
||||||
|
MissingBootVga,
|
||||||
|
PreferNativeWayland,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NvidiaGpuDetection {
|
||||||
|
fn disable_reason(&self, session: &SessionEnv) -> Option<NvidiaDmabufDisableReason> {
|
||||||
|
if !session.is_wayland {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.has_nvidia {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.open_kernel_module {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.nvidia_is_primary {
|
||||||
|
return Some(NvidiaDmabufDisableReason::PrimaryOpenKernelModule);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.missing_boot_vga {
|
||||||
|
return Some(NvidiaDmabufDisableReason::MissingBootVga);
|
||||||
|
}
|
||||||
|
|
||||||
|
if session.prefer_native_wayland {
|
||||||
|
return Some(NvidiaDmabufDisableReason::PreferNativeWayland);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct SessionEnv {
|
||||||
|
is_kde_plasma: bool,
|
||||||
|
is_wayland: bool,
|
||||||
|
prefer_native_wayland: bool,
|
||||||
|
compositor_label: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SessionEnv {
|
||||||
|
fn gather() -> Self {
|
||||||
|
let desktop_env = env::var("XDG_CURRENT_DESKTOP")
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_uppercase();
|
||||||
|
let session_desktop = env::var("XDG_SESSION_DESKTOP")
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_uppercase();
|
||||||
|
let desktop_session = env::var("DESKTOP_SESSION")
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_uppercase();
|
||||||
|
|
||||||
|
let is_kde_plasma = desktop_env.contains("KDE")
|
||||||
|
|| session_desktop.contains("KDE")
|
||||||
|
|| desktop_session.contains("KDE")
|
||||||
|
|| desktop_env.contains("PLASMA")
|
||||||
|
|| session_desktop.contains("PLASMA")
|
||||||
|
|| desktop_session.contains("PLASMA");
|
||||||
|
let is_hyprland = desktop_env.contains("HYPR")
|
||||||
|
|| session_desktop.contains("HYPR")
|
||||||
|
|| desktop_session.contains("HYPR");
|
||||||
|
let is_wayland = env::var("XDG_SESSION_TYPE")
|
||||||
|
.map(|value| value.eq_ignore_ascii_case("wayland"))
|
||||||
|
.unwrap_or(false)
|
||||||
|
|| env::var("WAYLAND_DISPLAY").is_ok();
|
||||||
|
let prefer_native_wayland = is_wayland && (is_kde_plasma || is_hyprland);
|
||||||
|
let compositor_label = if is_hyprland {
|
||||||
|
String::from("Hyprland")
|
||||||
|
} else if is_kde_plasma {
|
||||||
|
String::from("KDE Plasma")
|
||||||
|
} else {
|
||||||
|
String::from("Wayland compositor")
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
is_kde_plasma,
|
||||||
|
is_wayland,
|
||||||
|
prefer_native_wayland,
|
||||||
|
compositor_label,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct DmabufOverrides {
|
||||||
|
user_preference: Option<bool>,
|
||||||
|
dmabuf_override: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DmabufOverrides {
|
||||||
|
fn gather() -> Self {
|
||||||
|
let user_preference = env::var("CLASH_VERGE_DMABUF").ok().and_then(|value| {
|
||||||
|
match value.trim().to_ascii_lowercase().as_str() {
|
||||||
|
"1" | "true" | "enable" | "on" => Some(true),
|
||||||
|
"0" | "false" | "disable" | "off" => Some(false),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let dmabuf_override = env::var("WEBKIT_DISABLE_DMABUF_RENDERER").ok();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
user_preference,
|
||||||
|
dmabuf_override,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_env_override(&self) -> bool {
|
||||||
|
self.dmabuf_override.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_override_env(&self, decision: &DmabufDecision) -> bool {
|
||||||
|
if self.user_preference.is_some() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if decision.enable_dmabuf {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
!self.has_env_override()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct DmabufDecision {
|
||||||
|
enable_dmabuf: bool,
|
||||||
|
force_x11_backend: bool,
|
||||||
|
warn: bool,
|
||||||
|
message: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DmabufDecision {
|
||||||
|
fn resolve(
|
||||||
|
session: &SessionEnv,
|
||||||
|
overrides: &DmabufOverrides,
|
||||||
|
intel_gpu: IntelGpuDetection,
|
||||||
|
nvidia_gpu: &NvidiaGpuDetection,
|
||||||
|
) -> Self {
|
||||||
|
let mut decision = Self {
|
||||||
|
enable_dmabuf: true,
|
||||||
|
force_x11_backend: false,
|
||||||
|
warn: false,
|
||||||
|
message: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
match overrides.user_preference {
|
||||||
|
Some(true) => {
|
||||||
|
decision.enable_dmabuf = true;
|
||||||
|
decision.message =
|
||||||
|
Some("CLASH_VERGE_DMABUF=1: 强制启用 WebKit DMABUF 渲染。".into());
|
||||||
|
}
|
||||||
|
Some(false) => {
|
||||||
|
decision.enable_dmabuf = false;
|
||||||
|
decision.message =
|
||||||
|
Some("CLASH_VERGE_DMABUF=0: 强制禁用 WebKit DMABUF 渲染。".into());
|
||||||
|
if session.is_wayland && !session.prefer_native_wayland {
|
||||||
|
decision.force_x11_backend = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
if overrides.has_env_override() {
|
||||||
|
if overrides.dmabuf_override.as_deref() == Some("1") {
|
||||||
|
decision.enable_dmabuf = false;
|
||||||
|
decision.message = Some(
|
||||||
|
"检测到 WEBKIT_DISABLE_DMABUF_RENDERER=1,沿用用户的软件渲染配置。"
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
if session.is_wayland && !session.prefer_native_wayland {
|
||||||
|
decision.force_x11_backend = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
decision.enable_dmabuf = true;
|
||||||
|
let value = overrides.dmabuf_override.clone().unwrap_or_default();
|
||||||
|
decision.message = Some(format!(
|
||||||
|
"检测到 WEBKIT_DISABLE_DMABUF_RENDERER={},沿用用户配置。",
|
||||||
|
value
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else if let Some(reason) = nvidia_gpu.disable_reason(session) {
|
||||||
|
decision.enable_dmabuf = false;
|
||||||
|
decision.warn = true;
|
||||||
|
if session.is_wayland && !session.prefer_native_wayland {
|
||||||
|
decision.force_x11_backend = true;
|
||||||
|
}
|
||||||
|
let summary = nvidia_gpu
|
||||||
|
.driver_summary
|
||||||
|
.as_deref()
|
||||||
|
.and_then(|line| {
|
||||||
|
extract_nvidia_driver_version(line)
|
||||||
|
.map(|version| format!("NVIDIA Open Kernel Module {}", version))
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| String::from("NVIDIA Open Kernel Module"));
|
||||||
|
let message = match reason {
|
||||||
|
NvidiaDmabufDisableReason::PrimaryOpenKernelModule => format!(
|
||||||
|
"Wayland 会话检测到 {}:禁用 WebKit DMABUF 渲染以规避协议错误。",
|
||||||
|
summary
|
||||||
|
),
|
||||||
|
NvidiaDmabufDisableReason::MissingBootVga => format!(
|
||||||
|
"Wayland 会话检测到 {},但缺少 boot_vga 信息:预防性禁用 WebKit DMABUF。",
|
||||||
|
summary
|
||||||
|
),
|
||||||
|
NvidiaDmabufDisableReason::PreferNativeWayland => format!(
|
||||||
|
"Wayland ({}) + {}:检测到 NVIDIA Open Kernel Module 在辅 GPU 上运行,预防性禁用 WebKit DMABUF。",
|
||||||
|
session.compositor_label, summary
|
||||||
|
),
|
||||||
|
};
|
||||||
|
decision.message = Some(message);
|
||||||
|
} else if session.prefer_native_wayland && !intel_gpu.should_disable_dmabuf() {
|
||||||
|
decision.enable_dmabuf = true;
|
||||||
|
decision.message = Some(format!(
|
||||||
|
"Wayland + {} detected: 使用原生 DMABUF 渲染。",
|
||||||
|
session.compositor_label
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
decision.enable_dmabuf = false;
|
||||||
|
if session.is_wayland && !session.prefer_native_wayland {
|
||||||
|
decision.force_x11_backend = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if intel_gpu.should_disable_dmabuf() && session.is_wayland {
|
||||||
|
decision.warn = true;
|
||||||
|
if intel_gpu.inconclusive {
|
||||||
|
decision.message = Some("Wayland 上检测到 Intel GPU,但缺少 boot_vga 信息:预防性禁用 WebKit DMABUF,若确认非主 GPU 可通过 CLASH_VERGE_DMABUF=1 覆盖。".into());
|
||||||
|
} else {
|
||||||
|
decision.message = Some("Wayland 上检测到 Intel 主 GPU (0x8086):禁用 WebKit DMABUF 以避免帧缓冲失败。".into());
|
||||||
|
}
|
||||||
|
} else if session.is_wayland {
|
||||||
|
decision.message = Some(
|
||||||
|
"Wayland 会话未匹配受支持的合成器:禁用 WebKit DMABUF 渲染。".into(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
decision.message =
|
||||||
|
Some("禁用 WebKit DMABUF 渲染以获得更稳定的输出。".into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
decision
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detect_intel_gpu() -> IntelGpuDetection {
|
||||||
|
let Ok(entries) = fs::read_dir(DRM_PATH) else {
|
||||||
|
return IntelGpuDetection::default();
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut detection = IntelGpuDetection::default();
|
||||||
|
let mut seen_devices: HashSet<PathBuf> = HashSet::new();
|
||||||
|
let mut missing_boot_vga = false;
|
||||||
|
|
||||||
|
for entry in entries.flatten() {
|
||||||
|
let name = entry.file_name();
|
||||||
|
let name = name.to_string_lossy();
|
||||||
|
|
||||||
|
if !(name.starts_with("renderD") || name.starts_with("card")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let device_path = entry.path().join("device");
|
||||||
|
let device_key = fs::canonicalize(&device_path).unwrap_or(device_path);
|
||||||
|
|
||||||
|
if !seen_devices.insert(device_key.clone()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let vendor_path = device_key.join("vendor");
|
||||||
|
let Ok(vendor) = fs::read_to_string(&vendor_path) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !vendor.trim().eq_ignore_ascii_case(INTEL_VENDOR_ID) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
detection.has_intel = true;
|
||||||
|
|
||||||
|
let boot_vga_path = device_key.join("boot_vga");
|
||||||
|
match fs::read_to_string(&boot_vga_path) {
|
||||||
|
Ok(flag) => {
|
||||||
|
if flag.trim() == "1" {
|
||||||
|
detection.intel_is_primary = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
missing_boot_vga = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if detection.has_intel && !detection.intel_is_primary && missing_boot_vga {
|
||||||
|
detection.inconclusive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
detection
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detect_nvidia_gpu() -> NvidiaGpuDetection {
|
||||||
|
let mut detection = NvidiaGpuDetection::default();
|
||||||
|
let entries = match fs::read_dir(DRM_PATH) {
|
||||||
|
Ok(entries) => entries,
|
||||||
|
Err(err) => {
|
||||||
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Setup,
|
||||||
|
"无法读取 DRM 设备目录 {}({}),尝试通过 NVIDIA 驱动摘要进行降级检测。",
|
||||||
|
DRM_PATH,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
detection.driver_summary = read_nvidia_driver_summary();
|
||||||
|
if let Some(summary) = detection.driver_summary.as_ref() {
|
||||||
|
detection.open_kernel_module = summary_indicates_open_kernel_module(summary);
|
||||||
|
detection.has_nvidia = true;
|
||||||
|
detection.missing_boot_vga = true;
|
||||||
|
} else {
|
||||||
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Setup,
|
||||||
|
"降级检测失败:未能读取 NVIDIA 驱动摘要,保留 WebKit DMABUF。"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return detection;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut seen_devices: HashSet<PathBuf> = HashSet::new();
|
||||||
|
|
||||||
|
for entry in entries.flatten() {
|
||||||
|
let name = entry.file_name();
|
||||||
|
let name = name.to_string_lossy();
|
||||||
|
|
||||||
|
if !(name.starts_with("renderD") || name.starts_with("card")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let device_path = entry.path().join("device");
|
||||||
|
let device_key = fs::canonicalize(&device_path).unwrap_or(device_path);
|
||||||
|
|
||||||
|
if !seen_devices.insert(device_key.clone()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let vendor_path = device_key.join("vendor");
|
||||||
|
let Ok(vendor) = fs::read_to_string(&vendor_path) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !vendor.trim().eq_ignore_ascii_case(NVIDIA_VENDOR_ID) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
detection.has_nvidia = true;
|
||||||
|
|
||||||
|
let boot_vga_path = device_key.join("boot_vga");
|
||||||
|
match fs::read_to_string(&boot_vga_path) {
|
||||||
|
Ok(flag) => {
|
||||||
|
if flag.trim() == "1" {
|
||||||
|
detection.nvidia_is_primary = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
detection.missing_boot_vga = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if detection.has_nvidia {
|
||||||
|
detection.driver_summary = read_nvidia_driver_summary();
|
||||||
|
match detection.driver_summary.as_ref() {
|
||||||
|
Some(summary) => {
|
||||||
|
detection.open_kernel_module = summary_indicates_open_kernel_module(summary);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Setup,
|
||||||
|
"检测到 NVIDIA 设备,但无法读取 {},默认视为未启用开源内核模块。",
|
||||||
|
NVIDIA_VERSION_PATH
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
detection
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_nvidia_driver_summary() -> Option<String> {
|
||||||
|
match fs::read_to_string(NVIDIA_VERSION_PATH) {
|
||||||
|
Ok(content) => content
|
||||||
|
.lines()
|
||||||
|
.next()
|
||||||
|
.map(|line| line.trim().to_string())
|
||||||
|
.filter(|line| !line.is_empty()),
|
||||||
|
Err(err) => {
|
||||||
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Setup,
|
||||||
|
"读取 {} 失败:{}",
|
||||||
|
NVIDIA_VERSION_PATH,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn summary_indicates_open_kernel_module(summary: &str) -> bool {
|
||||||
|
let normalized = summary.to_ascii_lowercase();
|
||||||
|
const PATTERNS: [&str; 4] = [
|
||||||
|
"open kernel module",
|
||||||
|
"open kernel modules",
|
||||||
|
"open gpu kernel module",
|
||||||
|
"open gpu kernel modules",
|
||||||
|
];
|
||||||
|
|
||||||
|
let is_open = PATTERNS.iter().any(|pattern| normalized.contains(pattern));
|
||||||
|
|
||||||
|
if !is_open && normalized.contains("open") {
|
||||||
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Setup,
|
||||||
|
"检测到 NVIDIA 驱动摘要包含 open 关键字但未匹配已知开源模块格式:{}",
|
||||||
|
summary
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
is_open
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_nvidia_driver_version(summary: &str) -> Option<&str> {
|
||||||
|
summary
|
||||||
|
.split_whitespace()
|
||||||
|
.find(|token| token.chars().all(|c| c.is_ascii_digit() || c == '.'))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn configure_environment() {
|
||||||
|
let session = SessionEnv::gather();
|
||||||
|
let overrides = DmabufOverrides::gather();
|
||||||
|
let intel_gpu = detect_intel_gpu();
|
||||||
|
let nvidia_gpu = detect_nvidia_gpu();
|
||||||
|
let decision = DmabufDecision::resolve(&session, &overrides, intel_gpu, &nvidia_gpu);
|
||||||
|
|
||||||
|
if overrides.should_override_env(&decision) {
|
||||||
|
unsafe {
|
||||||
|
if decision.enable_dmabuf {
|
||||||
|
env::remove_var("WEBKIT_DISABLE_DMABUF_RENDERER");
|
||||||
|
} else {
|
||||||
|
env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(message) = decision.message {
|
||||||
|
if decision.warn {
|
||||||
|
logging!(warn, Type::Setup, "{}", message);
|
||||||
|
} else {
|
||||||
|
logging!(info, Type::Setup, "{}", message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if decision.force_x11_backend {
|
||||||
|
unsafe {
|
||||||
|
env::set_var("GDK_BACKEND", "x11");
|
||||||
|
env::remove_var("WAYLAND_DISPLAY");
|
||||||
|
}
|
||||||
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Setup,
|
||||||
|
"Wayland detected: Forcing X11 backend for WebKit stability."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if session.is_kde_plasma {
|
||||||
|
unsafe {
|
||||||
|
env::set_var("GTK_CSD", "0");
|
||||||
|
}
|
||||||
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Setup,
|
||||||
|
"KDE/Plasma detected: Disabled GTK CSD for better titlebar stability."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ pub mod format;
|
|||||||
pub mod help;
|
pub mod help;
|
||||||
pub mod i18n;
|
pub mod i18n;
|
||||||
pub mod init;
|
pub mod init;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub mod linux;
|
||||||
pub mod logging;
|
pub mod logging;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
pub mod notification;
|
pub mod notification;
|
||||||
|
|||||||
Reference in New Issue
Block a user