Add FUSE fix wizard with pkexec installation
This commit is contained in:
@@ -307,6 +307,53 @@ fn extract_os_field(content: &str, key: &str) -> Option<String> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the package install command suitable for running via pkexec (no sudo prefix).
|
||||||
|
pub fn get_fuse_install_command() -> Option<String> {
|
||||||
|
let content = std::fs::read_to_string("/etc/os-release").ok()?;
|
||||||
|
let id = extract_os_field(&content, "ID");
|
||||||
|
let version_id = extract_os_field(&content, "VERSION_ID");
|
||||||
|
let id_like = extract_os_field(&content, "ID_LIKE");
|
||||||
|
|
||||||
|
match id.as_deref() {
|
||||||
|
Some("ubuntu") => {
|
||||||
|
let ver: f64 = version_id
|
||||||
|
.as_deref()
|
||||||
|
.and_then(|v| v.parse().ok())
|
||||||
|
.unwrap_or(0.0);
|
||||||
|
if ver >= 24.04 {
|
||||||
|
Some("apt install -y libfuse2t64".to_string())
|
||||||
|
} else {
|
||||||
|
Some("apt install -y libfuse2".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("debian") => Some("apt install -y libfuse2".to_string()),
|
||||||
|
Some("fedora") => Some("dnf install -y fuse-libs".to_string()),
|
||||||
|
Some("arch") | Some("manjaro") | Some("endeavouros") => {
|
||||||
|
Some("pacman -S --noconfirm fuse2".to_string())
|
||||||
|
}
|
||||||
|
Some("opensuse-tumbleweed") | Some("opensuse-leap") => {
|
||||||
|
Some("zypper install -y libfuse2".to_string())
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if let Some(like) = id_like.as_deref() {
|
||||||
|
if like.contains("ubuntu") || like.contains("debian") {
|
||||||
|
return Some("apt install -y libfuse2".to_string());
|
||||||
|
}
|
||||||
|
if like.contains("fedora") {
|
||||||
|
return Some("dnf install -y fuse-libs".to_string());
|
||||||
|
}
|
||||||
|
if like.contains("arch") {
|
||||||
|
return Some("pacman -S --noconfirm fuse2".to_string());
|
||||||
|
}
|
||||||
|
if like.contains("suse") {
|
||||||
|
return Some("zypper install -y libfuse2".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if AppImageLauncher is installed (known conflicts with new runtime).
|
/// Check if AppImageLauncher is installed (known conflicts with new runtime).
|
||||||
pub fn detect_appimagelauncher() -> Option<String> {
|
pub fn detect_appimagelauncher() -> Option<String> {
|
||||||
let output = Command::new("dpkg")
|
let output = Command::new("dpkg")
|
||||||
|
|||||||
@@ -26,6 +26,18 @@ pub fn build_dashboard_page(db: &Rc<Database>) -> adw::NavigationPage {
|
|||||||
.margin_end(18)
|
.margin_end(18)
|
||||||
.build();
|
.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
|
// Section 1: System Status
|
||||||
content.append(&build_system_status_group());
|
content.append(&build_system_status_group());
|
||||||
|
|
||||||
|
|||||||
177
src/ui/fuse_wizard.rs
Normal file
177
src/ui/fuse_wizard.rs
Normal 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));
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ pub mod dashboard;
|
|||||||
pub mod detail_view;
|
pub mod detail_view;
|
||||||
pub mod drop_dialog;
|
pub mod drop_dialog;
|
||||||
pub mod duplicate_dialog;
|
pub mod duplicate_dialog;
|
||||||
|
pub mod fuse_wizard;
|
||||||
pub mod integration_dialog;
|
pub mod integration_dialog;
|
||||||
pub mod library_view;
|
pub mod library_view;
|
||||||
pub mod preferences;
|
pub mod preferences;
|
||||||
|
|||||||
@@ -677,6 +677,16 @@ impl DriftwoodWindow {
|
|||||||
}
|
}
|
||||||
self.add_action(&update_all_action);
|
self.add_action(&update_all_action);
|
||||||
|
|
||||||
|
let fix_fuse_action = gio::SimpleAction::new("fix-fuse", None);
|
||||||
|
{
|
||||||
|
let window_weak = self.downgrade();
|
||||||
|
fix_fuse_action.connect_activate(move |_, _| {
|
||||||
|
let Some(window) = window_weak.upgrade() else { return };
|
||||||
|
crate::ui::fuse_wizard::show_fuse_wizard(&window);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
self.add_action(&fix_fuse_action);
|
||||||
|
|
||||||
// --- Context menu actions (parameterized with record ID) ---
|
// --- Context menu actions (parameterized with record ID) ---
|
||||||
let param_type = Some(glib::VariantTy::INT64);
|
let param_type = Some(glib::VariantTy::INT64);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user