Add first-run permission summary dialog before launch
Shows what access the AppImage will have on first launch. Lists file, network, and display server access. Mentions firejail if available. Tracks prompted status in DB so the dialog only appears once per app.
This commit is contained in:
@@ -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: >k::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");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user