Add first-run permission summary dialog before launch

This commit is contained in:
2026-02-28 00:07:49 +02:00
parent a515ee6b4f
commit 45e1c57842
4 changed files with 203 additions and 70 deletions

View File

@@ -101,79 +101,41 @@ pub fn build_detail_page(record: &AppImageRecord, db: &Rc<Database>) -> adw::Nav
let launch_args_raw = record.launch_args.clone();
let db_launch = db.clone();
let toast_launch = toast_overlay.clone();
let first_run_prompted = record.first_run_prompted;
let db_perm = db.clone();
launch_button.connect_clicked(move |btn| {
btn.set_sensitive(false);
let btn_ref = btn.clone();
let path = path.clone();
let app_name = app_name_launch.clone();
let db_launch = db_launch.clone();
let toast_ref = toast_launch.clone();
// Check if first-run permission dialog should be shown
if !first_run_prompted {
let btn_ref = btn.clone();
let path = path.clone();
let app_name = app_name_launch.clone();
let app_name_dialog = app_name.clone();
let db_launch = db_launch.clone();
let toast_ref = toast_launch.clone();
let launch_args_raw = launch_args_raw.clone();
let db_perm = db_perm.clone();
crate::ui::permission_dialog::show_permission_dialog(
btn,
&app_name_dialog,
record_id,
&db_perm,
move || {
do_launch(
&btn_ref, record_id, &path, &app_name,
launcher::parse_launch_args(launch_args_raw.as_deref()),
&db_launch, &toast_ref,
);
},
);
return;
}
let launch_args = launcher::parse_launch_args(launch_args_raw.as_deref());
glib::spawn_future_local(async move {
let path_bg = path.clone();
let result = gio::spawn_blocking(move || {
let appimage_path = std::path::Path::new(&path_bg);
launcher::launch_appimage(
&Database::open().expect("DB open"),
record_id,
appimage_path,
"gui_detail",
&launch_args,
&[],
)
}).await;
btn_ref.set_sensitive(true);
match result {
Ok(launcher::LaunchResult::Started { pid, method }) => {
log::info!("Launched: {} (PID: {}, method: {})", path, pid, method.as_str());
// App survived startup - do Wayland analysis after a delay
let db_wayland = db_launch.clone();
let path_clone = path.clone();
glib::spawn_future_local(async move {
glib::timeout_future(std::time::Duration::from_secs(3)).await;
let analysis_result = gio::spawn_blocking(move || {
wayland::analyze_running_process(pid)
}).await;
if let Ok(Ok(analysis)) = analysis_result {
let status_str = analysis.as_status_str();
log::info!(
"Runtime Wayland: {} -> {} (pid={}, env: {:?})",
path_clone, analysis.status_label(), analysis.pid, analysis.env_vars,
);
db_wayland.update_runtime_wayland_status(record_id, status_str).ok();
}
});
}
Ok(launcher::LaunchResult::Crashed { exit_code, stderr, method }) => {
log::error!("App crashed on launch (exit {}, method: {}): {}", exit_code.unwrap_or(-1), method.as_str(), stderr);
widgets::show_crash_dialog(&btn_ref, &app_name, exit_code, &stderr);
if let Some(app) = gtk::gio::Application::default() {
notification::send_system_notification(
&app,
&format!("crash-{}", record_id),
&format!("{} crashed", app_name),
&stderr.chars().take(200).collect::<String>(),
gtk::gio::NotificationPriority::Urgent,
);
}
}
Ok(launcher::LaunchResult::Failed(msg)) => {
log::error!("Failed to launch: {}", msg);
let toast = adw::Toast::builder()
.title(&format!("Could not launch: {}", msg))
.timeout(5)
.build();
toast_ref.add_toast(toast);
}
Err(_) => {
log::error!("Launch task panicked");
}
}
});
do_launch(
btn, record_id, &path, &app_name_launch,
launch_args, &db_launch, &toast_launch,
);
});
header.pack_end(&launch_button);
// Check for Update button
let update_button = gtk::Button::builder()
@@ -2553,3 +2515,82 @@ fn show_uninstall_dialog(
dialog.present(Some(toast_overlay));
}
fn do_launch(
btn: &gtk::Button,
record_id: i64,
path: &str,
app_name: &str,
launch_args: Vec<String>,
db: &Rc<Database>,
toast_overlay: &adw::ToastOverlay,
) {
btn.set_sensitive(false);
let btn_ref = btn.clone();
let path = path.to_string();
let app_name = app_name.to_string();
let db_launch = db.clone();
let toast_ref = toast_overlay.clone();
glib::spawn_future_local(async move {
let path_bg = path.clone();
let result = gio::spawn_blocking(move || {
let appimage_path = std::path::Path::new(&path_bg);
launcher::launch_appimage(
&Database::open().expect("DB open"),
record_id,
appimage_path,
"gui_detail",
&launch_args,
&[],
)
}).await;
btn_ref.set_sensitive(true);
match result {
Ok(launcher::LaunchResult::Started { pid, method }) => {
log::info!("Launched: {} (PID: {}, method: {})", path, pid, method.as_str());
let db_wayland = db_launch.clone();
let path_clone = path.clone();
glib::spawn_future_local(async move {
glib::timeout_future(std::time::Duration::from_secs(3)).await;
let analysis_result = gio::spawn_blocking(move || {
wayland::analyze_running_process(pid)
}).await;
if let Ok(Ok(analysis)) = analysis_result {
let status_str = analysis.as_status_str();
log::info!(
"Runtime Wayland: {} -> {} (pid={}, env: {:?})",
path_clone, analysis.status_label(), analysis.pid, analysis.env_vars,
);
db_wayland.update_runtime_wayland_status(record_id, status_str).ok();
}
});
}
Ok(launcher::LaunchResult::Crashed { exit_code, stderr, method }) => {
log::error!("App crashed on launch (exit {}, method: {}): {}", exit_code.unwrap_or(-1), method.as_str(), stderr);
widgets::show_crash_dialog(&btn_ref, &app_name, exit_code, &stderr);
if let Some(app) = gtk::gio::Application::default() {
notification::send_system_notification(
&app,
&format!("crash-{}", record_id),
&format!("{} crashed", app_name),
&stderr.chars().take(200).collect::<String>(),
gtk::gio::NotificationPriority::Urgent,
);
}
}
Ok(launcher::LaunchResult::Failed(msg)) => {
log::error!("Failed to launch: {}", msg);
let toast = adw::Toast::builder()
.title(&format!("Could not launch: {}", msg))
.timeout(5)
.build();
toast_ref.add_toast(toast);
}
Err(_) => {
log::error!("Launch task panicked");
}
}
});
}