Files
driftwood/src/ui/fuse_wizard.rs

195 lines
6.5 KiB
Rust

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("Additional setup needed"))
.xalign(0.0)
.build();
title.add_css_class("title-3");
content.append(&title);
let explanation = gtk::Label::builder()
.label(&i18n(
"Most apps need a small system component called FUSE to start quickly. \
Without it, apps will still work but will take longer to start each time. \
You can install it now (requires your password) or skip and use the slower method.",
))
.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 skip_btn = gtk::Button::builder()
.label(&i18n("Skip and use slower method"))
.tooltip_text(&i18n("Apps will still work, but they will take longer to start because they unpack themselves each time"))
.build();
skip_btn.add_css_class("flat");
skip_btn.add_css_class("pill");
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(&skip_btn);
button_box.append(&install_btn);
content.append(&button_box);
toolbar.set_content(Some(&content));
dialog.set_child(Some(&toolbar));
// Skip button just closes the dialog
let dialog_weak = dialog.downgrade();
skip_btn.connect_clicked(move |_| {
if let Some(dlg) = dialog_weak.upgrade() {
dlg.close();
}
});
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));
}