Wire Updates NVIDIA card to bos-nvidia-setup
Some checks are pending
dev release / build (push) Waiting to run
Some checks are pending
dev release / build (push) Waiting to run
The Install driver button runs the real setup instead of only pacman_install. Prefers pkexec bos-nvidia-setup; falls back to the allowlisted packages plus the same hyprland.lua drop-in. Tells the user to reboot. Still click-only — first boot does not install.
This commit is contained in:
parent
3aad09c9ef
commit
73589ae36d
3 changed files with 167 additions and 9 deletions
|
|
@ -26,6 +26,7 @@
|
||||||
gpu: string;
|
gpu: string;
|
||||||
reason: string;
|
reason: string;
|
||||||
packages: string[];
|
packages: string[];
|
||||||
|
installed: boolean;
|
||||||
}
|
}
|
||||||
interface UpdatesStatus {
|
interface UpdatesStatus {
|
||||||
pacman: PendingUpdate[];
|
pacman: PendingUpdate[];
|
||||||
|
|
@ -67,16 +68,18 @@
|
||||||
<div class="offer-text">
|
<div class="offer-text">
|
||||||
<strong>{status.nvidia.gpu}</strong>
|
<strong>{status.nvidia.gpu}</strong>
|
||||||
<p>{status.nvidia.reason}</p>
|
<p>{status.nvidia.reason}</p>
|
||||||
<Hint
|
{#if status.nvidia.installed}
|
||||||
text="Installs nvidia + nvidia-utils only. Afterward, Hyprland usually needs env = LIBVA_DRIVER_NAME,nvidia / __GLX_VENDOR_LIBRARY_NAME,nvidia / NVD_BACKEND,direct — not written for you. Reboot after install."
|
<Hint text="Driver and Hyprland env drop-in are in place. Reboot to start a working session." />
|
||||||
/>
|
{:else}
|
||||||
|
<Hint text="Installs nvidia + nvidia-utils (not cuda) and writes ~/.config/hypr/nvidia.lua. hyprland.lua loads that file only if it exists. Reboot after." />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
class="primary"
|
class="primary"
|
||||||
disabled={busy}
|
disabled={busy}
|
||||||
onclick={() => run("pacman_install", { packages: status?.nvidia?.packages ?? ["nvidia", "nvidia-utils"] })}
|
onclick={() => run("nvidia_setup")}
|
||||||
>
|
>
|
||||||
Install driver
|
{status.nvidia.installed ? "Re-run setup" : "Install driver"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
|
||||||
|
|
@ -3,23 +3,64 @@
|
||||||
//! not install anything until the user clicks.
|
//! not install anything until the user clicks.
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use tauri::AppHandle;
|
||||||
|
|
||||||
|
use super::streaming;
|
||||||
|
use super::util::{self, command_exists, pacman_installed};
|
||||||
|
|
||||||
|
/// Same drop-in bos-nvidia-setup writes. hyprland.lua dofiles it only
|
||||||
|
/// when the file exists (Mesa machines have no file).
|
||||||
|
/// Hyprland 0.56 (Aquamarine): wiki requires LIBVA + GLX vendor.
|
||||||
|
/// NVD_BACKEND is the current VA-API hint. No WLR_* / GBM_BACKEND.
|
||||||
|
const NVIDIA_LUA: &str = "\
|
||||||
|
-- Written by bos-nvidia-setup. hyprland.lua dofiles this only when it exists.
|
||||||
|
-- Hyprland 0.56 (Aquamarine) — no WLR_* variables.
|
||||||
|
-- https://wiki.hypr.land/Nvidia/
|
||||||
|
hl.env(\"LIBVA_DRIVER_NAME\", \"nvidia\")
|
||||||
|
hl.env(\"__GLX_VENDOR_LIBRARY_NAME\", \"nvidia\")
|
||||||
|
hl.env(\"NVD_BACKEND\", \"direct\")
|
||||||
|
";
|
||||||
|
|
||||||
|
const HYPR_INCLUDE: &str = "\
|
||||||
|
-- bos-nvidia-setup: optional proprietary env; no-op when the file is absent
|
||||||
|
do
|
||||||
|
local nvidia = (os.getenv(\"HOME\") or \"\") .. \"/.config/hypr/nvidia.lua\"
|
||||||
|
local f = io.open(nvidia, \"r\")
|
||||||
|
if f then
|
||||||
|
f:close()
|
||||||
|
pcall(dofile, nvidia)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
";
|
||||||
|
|
||||||
|
const NVIDIA_PACKAGES: &[&str] = &["nvidia", "nvidia-utils"];
|
||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
#[derive(Serialize, Clone)]
|
||||||
pub struct NvidiaOffer {
|
pub struct NvidiaOffer {
|
||||||
gpu: String,
|
gpu: String,
|
||||||
reason: String,
|
reason: String,
|
||||||
packages: Vec<String>,
|
packages: Vec<String>,
|
||||||
|
installed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn offer_paths() -> Vec<std::path::PathBuf> {
|
fn offer_paths() -> Vec<PathBuf> {
|
||||||
let home = std::env::var("HOME").unwrap_or_else(|_| "/root".into());
|
let home = std::env::var("HOME").unwrap_or_else(|_| "/root".into());
|
||||||
let state = std::path::Path::new(&home).join(".local/state/bos");
|
let state = Path::new(&home).join(".local/state/bos");
|
||||||
vec![
|
vec![
|
||||||
state.join("nvidia-offer.json"),
|
state.join("nvidia-offer.json"),
|
||||||
state.join("nvidia-probe.json"),
|
state.join("nvidia-probe.json"),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn nvidia_dropin_path() -> PathBuf {
|
||||||
|
util::hypr_dir().join("nvidia.lua")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nvidia_ready() -> bool {
|
||||||
|
nvidia_dropin_path().is_file() && NVIDIA_PACKAGES.iter().all(|p| pacman_installed(p))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read_nvidia_offer() -> Option<NvidiaOffer> {
|
pub fn read_nvidia_offer() -> Option<NvidiaOffer> {
|
||||||
for path in offer_paths() {
|
for path in offer_paths() {
|
||||||
if !path.is_file() {
|
if !path.is_file() {
|
||||||
|
|
@ -56,11 +97,12 @@ pub fn read_nvidia_offer() -> Option<NvidiaOffer> {
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
})
|
})
|
||||||
.filter(|p| !p.is_empty())
|
.filter(|p| !p.is_empty())
|
||||||
.unwrap_or_else(|| vec!["nvidia".into(), "nvidia-utils".into()]);
|
.unwrap_or_else(|| default_packages());
|
||||||
return Some(NvidiaOffer {
|
return Some(NvidiaOffer {
|
||||||
gpu,
|
gpu,
|
||||||
reason,
|
reason,
|
||||||
packages,
|
packages,
|
||||||
|
installed: nvidia_ready(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return Some(generic_offer());
|
return Some(generic_offer());
|
||||||
|
|
@ -68,11 +110,16 @@ pub fn read_nvidia_offer() -> Option<NvidiaOffer> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_packages() -> Vec<String> {
|
||||||
|
NVIDIA_PACKAGES.iter().map(|s| (*s).to_string()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
fn generic_offer() -> NvidiaOffer {
|
fn generic_offer() -> NvidiaOffer {
|
||||||
NvidiaOffer {
|
NvidiaOffer {
|
||||||
gpu: "NVIDIA GPU".into(),
|
gpu: "NVIDIA GPU".into(),
|
||||||
reason: "BOS found an NVIDIA device. Install the proprietary driver only if you want it — nouveau stays otherwise.".into(),
|
reason: "BOS found an NVIDIA device. Install the proprietary driver only if you want it — nouveau stays otherwise.".into(),
|
||||||
packages: vec!["nvidia".into(), "nvidia-utils".into()],
|
packages: default_packages(),
|
||||||
|
installed: nvidia_ready(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,8 +128,87 @@ pub fn get_nvidia_offer() -> Option<NvidiaOffer> {
|
||||||
read_nvidia_offer()
|
read_nvidia_offer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Install nvidia + nvidia-utils and write the Hyprland env drop-in.
|
||||||
|
/// Prefers `/usr/local/bin/bos-nvidia-setup` (ISO script) so package
|
||||||
|
/// install + env stay one path. Falls back to allowlisted pacman plus
|
||||||
|
/// the same drop-in when the script is not on this install yet.
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn nvidia_setup(app: AppHandle, session_id: String) -> bool {
|
||||||
|
let home = std::env::var("HOME").unwrap_or_default();
|
||||||
|
if command_exists("bos-nvidia-setup") {
|
||||||
|
let ok = streaming::run_hardcoded(
|
||||||
|
app.clone(),
|
||||||
|
session_id.clone(),
|
||||||
|
"pkexec",
|
||||||
|
&["bos-nvidia-setup", "--home", &home],
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
if ok {
|
||||||
|
streaming::emit_line(&app, &session_id, "reboot required");
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
streaming::emit_line(
|
||||||
|
&app,
|
||||||
|
&session_id,
|
||||||
|
"bos-nvidia-setup not on PATH; installing via pacman and writing the env drop-in",
|
||||||
|
);
|
||||||
|
let packages = default_packages();
|
||||||
|
if !streaming::pacman_install(app.clone(), session_id.clone(), packages).await {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
match write_nvidia_dropin() {
|
||||||
|
Ok(()) => {
|
||||||
|
streaming::emit_line(&app, &session_id, "wrote ~/.config/hypr/nvidia.lua");
|
||||||
|
streaming::emit_line(&app, &session_id, "reboot required");
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
streaming::emit_line(&app, &session_id, &format!("Error: {e}"));
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_nvidia_dropin() -> Result<(), String> {
|
||||||
|
let dir = util::hypr_dir();
|
||||||
|
std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?;
|
||||||
|
let path = dir.join("nvidia.lua");
|
||||||
|
super::config::atomic_write(&path, NVIDIA_LUA).map_err(|e| e.to_string())?;
|
||||||
|
ensure_hyprland_include()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn include_already_present(text: &str) -> bool {
|
||||||
|
text.contains("nvidia.lua")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_hyprland_include() -> Result<(), String> {
|
||||||
|
let path = util::hypr_dir().join("hyprland.lua");
|
||||||
|
let existing = std::fs::read_to_string(&path).unwrap_or_default();
|
||||||
|
if include_already_present(&existing) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if existing.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let mut text = existing;
|
||||||
|
if !text.ends_with('\n') {
|
||||||
|
text.push('\n');
|
||||||
|
}
|
||||||
|
text.push('\n');
|
||||||
|
text.push_str(HYPR_INCLUDE);
|
||||||
|
if !text.ends_with('\n') {
|
||||||
|
text.push('\n');
|
||||||
|
}
|
||||||
|
super::config::atomic_write(&path, &text).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_file_is_none() {
|
fn missing_file_is_none() {
|
||||||
// This machine's real probe path is not something the unit test
|
// This machine's real probe path is not something the unit test
|
||||||
|
|
@ -103,4 +229,32 @@ mod tests {
|
||||||
assert!(hide(r#"{"dismissed":true}"#));
|
assert!(hide(r#"{"dismissed":true}"#));
|
||||||
assert!(!hide(r#"{"gpu":"RTX 4060"}"#));
|
assert!(!hide(r#"{"gpu":"RTX 4060"}"#));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dropin_has_current_wiki_env_and_no_obsolete_vars() {
|
||||||
|
assert!(NVIDIA_LUA.contains("LIBVA_DRIVER_NAME"));
|
||||||
|
assert!(NVIDIA_LUA.contains("__GLX_VENDOR_LIBRARY_NAME"));
|
||||||
|
assert!(NVIDIA_LUA.contains("NVD_BACKEND"));
|
||||||
|
assert!(!NVIDIA_LUA.contains("hl.env(\"WLR_"));
|
||||||
|
assert!(!NVIDIA_LUA.contains("hl.env(\"GBM_BACKEND"));
|
||||||
|
assert!(!NVIDIA_LUA.contains("cuda"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn include_snippet_is_conditional() {
|
||||||
|
assert!(HYPR_INCLUDE.contains("nvidia.lua"));
|
||||||
|
assert!(HYPR_INCLUDE.contains("io.open"));
|
||||||
|
assert!(HYPR_INCLUDE.contains("pcall(dofile"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn include_detects_existing_snippet() {
|
||||||
|
assert!(include_already_present("pcall(dofile, nvidia.lua)"));
|
||||||
|
assert!(!include_already_present("hl.env(\"XCURSOR_SIZE\", \"24\")"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn packages_are_nvidia_and_utils_only() {
|
||||||
|
assert_eq!(NVIDIA_PACKAGES, &["nvidia", "nvidia-utils"]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,7 @@ pub fn run() {
|
||||||
commands::breadhelp::open_breadhelp,
|
commands::breadhelp::open_breadhelp,
|
||||||
commands::updates::get_updates_status,
|
commands::updates::get_updates_status,
|
||||||
commands::nvidia::get_nvidia_offer,
|
commands::nvidia::get_nvidia_offer,
|
||||||
|
commands::nvidia::nvidia_setup,
|
||||||
commands::printing::get_printers,
|
commands::printing::get_printers,
|
||||||
commands::printing::set_default_printer,
|
commands::printing::set_default_printer,
|
||||||
commands::printing::add_ipp_printer,
|
commands::printing::add_ipp_printer,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue