Add FUSE fix wizard with pkexec installation

Dashboard shows a banner when FUSE is not functional. The wizard
detects the distro, shows the install command, and runs it via pkexec.
Verifies FUSE status after installation completes.
This commit is contained in:
lashman
2026-02-27 23:59:26 +02:00
parent 00a1ed3599
commit 45b45c0724
5 changed files with 247 additions and 0 deletions

View File

@@ -26,6 +26,18 @@ pub fn build_dashboard_page(db: &Rc<Database>) -> adw::NavigationPage {
.margin_end(18)
.build();
// FUSE warning banner if not functional
let fuse_info = fuse::detect_system_fuse();
if !fuse_info.status.is_functional() {
let banner = adw::Banner::builder()
.title("FUSE is not working - some AppImages may not launch")
.button_label("Fix Now")
.revealed(true)
.build();
banner.set_action_name(Some("win.fix-fuse"));
content.append(&banner);
}
// Section 1: System Status
content.append(&build_system_status_group());

177
src/ui/fuse_wizard.rs Normal file
View File

@@ -0,0 +1,177 @@
use adw::prelude::*;
use gtk::gio;
use crate::core::fuse;
use crate::i18n::i18n;
/// Show a FUSE installation wizard dialog.
pub fn show_fuse_wizard(parent: &impl IsA<gtk::Widget>) {
let system_info = fuse::detect_system_fuse();
if system_info.status.is_functional() {
let dialog = adw::AlertDialog::builder()
.heading(&i18n("FUSE is working"))
.body(&i18n("libfuse2 is installed and functional. No action needed."))
.build();
dialog.add_response("ok", &i18n("OK"));
dialog.present(Some(parent));
return;
}
let install_cmd = match fuse::get_fuse_install_command() {
Some(cmd) => cmd,
None => {
let dialog = adw::AlertDialog::builder()
.heading(&i18n("Unknown distribution"))
.body(&i18n(
"Could not detect your Linux distribution. Please install libfuse2 manually using your package manager.",
))
.build();
dialog.add_response("ok", &i18n("OK"));
dialog.present(Some(parent));
return;
}
};
let dialog = adw::Dialog::builder()
.title(&i18n("Install FUSE"))
.content_width(480)
.content_height(350)
.build();
let toolbar = adw::ToolbarView::new();
let header = adw::HeaderBar::new();
toolbar.add_top_bar(&header);
let content = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.spacing(18)
.margin_start(24)
.margin_end(24)
.margin_top(18)
.margin_bottom(18)
.build();
let title = gtk::Label::builder()
.label(&i18n("FUSE is required"))
.xalign(0.0)
.build();
title.add_css_class("title-3");
content.append(&title);
let explanation = gtk::Label::builder()
.label(&i18n(
"Most AppImages require libfuse2 to mount and run. \
Without it, apps will use a slower extract-and-run fallback or may not launch at all.",
))
.wrap(true)
.xalign(0.0)
.build();
content.append(&explanation);
// Show the detected issue
let issue_label = gtk::Label::builder()
.label(&format!("Status: {}", system_info.status.label()))
.xalign(0.0)
.build();
issue_label.add_css_class("heading");
content.append(&issue_label);
// Show the command
let cmd_label = gtk::Label::builder()
.label(&format!("Command: {}", install_cmd))
.xalign(0.0)
.selectable(true)
.build();
cmd_label.add_css_class("monospace");
content.append(&cmd_label);
// Status label for result
let status_label = gtk::Label::builder()
.xalign(0.0)
.wrap(true)
.visible(false)
.build();
content.append(&status_label);
// Install button
let button_box = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal)
.spacing(12)
.halign(gtk::Align::End)
.margin_top(12)
.build();
let install_btn = gtk::Button::builder()
.label(&i18n("Install via pkexec"))
.build();
install_btn.add_css_class("suggested-action");
install_btn.add_css_class("pill");
button_box.append(&install_btn);
content.append(&button_box);
toolbar.set_content(Some(&content));
dialog.set_child(Some(&toolbar));
let cmd = install_cmd.clone();
let status_ref = status_label.clone();
let btn_ref = install_btn.clone();
install_btn.connect_clicked(move |_| {
btn_ref.set_sensitive(false);
status_ref.set_visible(true);
status_ref.set_label(&i18n("Running installation..."));
let cmd = cmd.clone();
let status = status_ref.clone();
let btn = btn_ref.clone();
glib::spawn_future_local(async move {
let result = gio::spawn_blocking(move || {
// Split command into program and args
let parts: Vec<&str> = cmd.split_whitespace().collect();
if parts.is_empty() {
return Err("Empty command".to_string());
}
let program = parts[0];
let args = &parts[1..];
std::process::Command::new("pkexec")
.arg(program)
.args(args)
.status()
.map_err(|e| format!("Failed to run pkexec: {}", e))
})
.await;
match result {
Ok(Ok(exit)) if exit.success() => {
// Verify FUSE is now working
let fuse_info = fuse::detect_system_fuse();
if fuse_info.status.is_functional() {
status.set_label(&i18n("FUSE installed successfully! AppImages should now mount natively."));
status.add_css_class("success");
} else {
status.set_label(&i18n("Installation completed but FUSE still not detected. A reboot may be required."));
status.add_css_class("warning");
}
}
Ok(Ok(_)) => {
status.set_label(&i18n("Installation was cancelled or failed."));
status.add_css_class("error");
btn.set_sensitive(true);
}
Ok(Err(e)) => {
status.set_label(&format!("Error: {}", e));
status.add_css_class("error");
btn.set_sensitive(true);
}
Err(_) => {
status.set_label(&i18n("Task failed unexpectedly."));
status.add_css_class("error");
btn.set_sensitive(true);
}
}
});
});
dialog.present(Some(parent));
}

View File

@@ -5,6 +5,7 @@ pub mod dashboard;
pub mod detail_view;
pub mod drop_dialog;
pub mod duplicate_dialog;
pub mod fuse_wizard;
pub mod integration_dialog;
pub mod library_view;
pub mod preferences;