Skip to content

Commit 2bee22b

Browse files
committed
fix(linux): 增加白屏启动诊断
Refs #83
1 parent 6941f7d commit 2bee22b

4 files changed

Lines changed: 194 additions & 15 deletions

File tree

src-tauri/src/diagnostics.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::sync::{Arc, Mutex};
77
use tracing_subscriber::fmt::MakeWriter;
88

99
pub const LOG_FILE_ENV: &str = "AQBOT_LOG_FILE";
10+
const LINUX_AUTO_WINDOW_ENV: &str = "AQBOT_LINUX_AUTO_WINDOW";
1011

1112
#[derive(Clone)]
1213
struct SharedLogFile {
@@ -104,10 +105,50 @@ pub fn log_process_startup() {
104105
wayland_display = %env_value("WAYLAND_DISPLAY"),
105106
display = %env_value("DISPLAY"),
106107
gdk_backend = %env_value("GDK_BACKEND"),
108+
aqbot_linux_auto_window = %env_value(LINUX_AUTO_WINDOW_ENV),
107109
"AQBot process startup diagnostics"
108110
);
109111
}
110112

113+
pub fn install_panic_hook() {
114+
std::panic::set_hook(Box::new(|panic_info| {
115+
let location = panic_info
116+
.location()
117+
.map(|location| {
118+
format!(
119+
"{}:{}:{}",
120+
location.file(),
121+
location.line(),
122+
location.column()
123+
)
124+
})
125+
.unwrap_or_else(|| "<unknown>".to_string());
126+
let payload = panic_payload(panic_info.payload());
127+
128+
tracing::error!(
129+
location = %location,
130+
payload = %payload,
131+
"AQBot process panicked"
132+
);
133+
eprintln!("AQBot process panicked at {location}: {payload}");
134+
}));
135+
}
136+
137+
#[cfg(target_os = "linux")]
138+
pub fn show_linux_startup_error_dialog(message: &str) {
139+
if spawn_linux_dialog(
140+
"zenity",
141+
&["--error", "--title", "AQBot", "--text", message],
142+
) {
143+
return;
144+
}
145+
if spawn_linux_dialog("kdialog", &["--title", "AQBot", "--error", message]) {
146+
return;
147+
}
148+
149+
tracing::warn!("No Linux native dialog command available for startup error");
150+
}
151+
111152
fn init_stderr_tracing() {
112153
if let Err(err) = tracing_subscriber::fmt()
113154
.with_env_filter(env_filter())
@@ -126,6 +167,24 @@ fn env_value(key: &str) -> String {
126167
env::var(key).unwrap_or_else(|_| "<unset>".to_string())
127168
}
128169

170+
fn panic_payload(payload: &(dyn std::any::Any + Send)) -> String {
171+
if let Some(message) = payload.downcast_ref::<&str>() {
172+
(*message).to_string()
173+
} else if let Some(message) = payload.downcast_ref::<String>() {
174+
message.clone()
175+
} else {
176+
"<non-string panic payload>".to_string()
177+
}
178+
}
179+
180+
#[cfg(target_os = "linux")]
181+
fn spawn_linux_dialog(command: &str, args: &[&str]) -> bool {
182+
std::process::Command::new(command)
183+
.args(args)
184+
.spawn()
185+
.is_ok()
186+
}
187+
129188
fn log_file_path_from_value(value: Option<OsString>) -> Option<PathBuf> {
130189
let value = value?;
131190
if value.to_string_lossy().trim().is_empty() {

src-tauri/src/lib.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ mod windows_utils;
5757
pub fn run() {
5858
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
5959
diagnostics::init_tracing();
60+
diagnostics::install_panic_hook();
6061
diagnostics::log_process_startup();
6162

6263
#[cfg(target_os = "linux")]
@@ -85,6 +86,16 @@ pub fn run() {
8586
builder = builder.plugin(tauri_plugin_mcp_bridge::init());
8687
}
8788

89+
#[cfg(target_os = "linux")]
90+
let context = {
91+
let mut context = tauri::generate_context!();
92+
linux_webkit::configure_startup_window_creation(&mut context);
93+
context
94+
};
95+
#[cfg(not(target_os = "linux"))]
96+
let context = tauri::generate_context!();
97+
98+
tracing::info!("Building Tauri application");
8899
let build_result = builder
89100
.invoke_handler(tauri::generate_handler![
90101
// providers
@@ -332,6 +343,8 @@ pub fn run() {
332343
commands::skills::check_skill_updates,
333344
])
334345
.setup(|app| {
346+
tracing::info!("AQBot setup closure entered");
347+
335348
// Force overlay (auto-hide) scrollbar style on macOS.
336349
// Apps linked against older SDKs (e.g. macOS 15 CI builds) may
337350
// fall back to classic native scrollbars, ignoring CSS
@@ -517,12 +530,17 @@ pub fn run() {
517530
let _ = rt.block_on(aqbot_core::repo::agent_session::reset_running_sessions(&sea_db));
518531
}
519532

520-
if let Some(main_window) = app.get_webview_window("main") {
521-
tracing::info!("AQBot main window found during setup");
522-
window_lifecycle::configure_main_window(app.handle(), &main_window);
523-
tracing::info!("AQBot main window configured");
524-
} else {
525-
tracing::warn!("AQBot main window was not found during setup");
533+
if let Err(err) = window_lifecycle::ensure_main_window_for_setup(app.handle()) {
534+
tracing::error!(
535+
error = %err,
536+
"Failed to ensure AQBot main window during setup"
537+
);
538+
#[cfg(target_os = "linux")]
539+
diagnostics::show_linux_startup_error_dialog(&format!(
540+
"AQBot 主窗口创建失败:{}",
541+
err
542+
));
543+
return Err(std::io::Error::new(std::io::ErrorKind::Other, err).into());
526544
}
527545

528546
// Initialize auto-backup scheduler if enabled
@@ -728,10 +746,13 @@ pub fn run() {
728746
}
729747
}
730748
})
731-
.build(tauri::generate_context!());
749+
.build(context);
732750

733751
let app = match build_result {
734-
Ok(app) => app,
752+
Ok(app) => {
753+
tracing::info!("Tauri application build returned successfully");
754+
app
755+
}
735756
Err(e) => {
736757
let error_msg = e.to_string();
737758
tracing::error!("Failed to build Tauri application: {}", error_msg);
@@ -763,6 +784,7 @@ pub fn run() {
763784
}
764785
};
765786

787+
tracing::info!("Starting Tauri application event loop");
766788
app.run(|app, event| {
767789
if let tauri::RunEvent::ExitRequested { api, .. } = &event {
768790
let state = app.state::<AppState>();

src-tauri/src/linux_webkit.rs

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ const DMABUF_ENV: &str = "WEBKIT_DISABLE_DMABUF_RENDERER";
99
const COMPOSITING_ENV: &str = "WEBKIT_DISABLE_COMPOSITING_MODE";
1010
#[cfg(target_os = "linux")]
1111
const XDG_SESSION_TYPE_ENV: &str = "XDG_SESSION_TYPE";
12+
#[cfg(target_os = "linux")]
13+
const AUTO_WINDOW_ENV: &str = "AQBOT_LINUX_AUTO_WINDOW";
14+
#[cfg(target_os = "linux")]
15+
const MAIN_WINDOW_LABEL: &str = "main";
1216

1317
#[derive(Debug, PartialEq, Eq)]
1418
enum WorkaroundDecision {
@@ -60,6 +64,41 @@ pub fn apply_startup_workarounds() {
6064
}
6165
}
6266

67+
#[cfg(target_os = "linux")]
68+
pub fn configure_startup_window_creation<R: tauri::Runtime>(context: &mut tauri::Context<R>) {
69+
let auto_window = should_use_tauri_auto_window_from_env();
70+
let window_count = context.config().app.windows.len();
71+
72+
if auto_window {
73+
tracing::info!(
74+
auto_window_env = %env_value(AUTO_WINDOW_ENV),
75+
window_count,
76+
"Using Tauri automatic window creation for Linux diagnostics"
77+
);
78+
return;
79+
}
80+
81+
let mut disabled_labels = Vec::new();
82+
for window in &mut context.config_mut().app.windows {
83+
if window.label == MAIN_WINDOW_LABEL {
84+
window.create = false;
85+
disabled_labels.push(window.label.clone());
86+
}
87+
}
88+
89+
tracing::info!(
90+
auto_window_env = %env_value(AUTO_WINDOW_ENV),
91+
window_count,
92+
disabled_labels = ?disabled_labels,
93+
"Disabled Tauri automatic main window creation for Linux diagnostics"
94+
);
95+
}
96+
97+
#[cfg(target_os = "linux")]
98+
pub fn should_create_main_window_in_setup() -> bool {
99+
!should_use_tauri_auto_window_from_env()
100+
}
101+
63102
fn decide_workaround(
64103
opt_out: Option<&str>,
65104
user_configured_dmabuf: bool,
@@ -95,9 +134,23 @@ fn is_truthy(value: &str) -> bool {
95134
)
96135
}
97136

137+
fn should_use_tauri_auto_window(value: Option<&str>) -> bool {
138+
value.map(is_truthy).unwrap_or(false)
139+
}
140+
141+
#[cfg(target_os = "linux")]
142+
fn should_use_tauri_auto_window_from_env() -> bool {
143+
should_use_tauri_auto_window(env::var(AUTO_WINDOW_ENV).ok().as_deref())
144+
}
145+
146+
#[cfg(target_os = "linux")]
147+
fn env_value(key: &str) -> String {
148+
env::var(key).unwrap_or_else(|_| "<unset>".to_string())
149+
}
150+
98151
#[cfg(test)]
99152
mod tests {
100-
use super::{decide_workaround, SkipReason, WorkaroundDecision};
153+
use super::{decide_workaround, should_use_tauri_auto_window, SkipReason, WorkaroundDecision};
101154

102155
#[test]
103156
fn enables_dmabuf_workaround_for_wayland_by_default() {
@@ -142,4 +195,15 @@ mod tests {
142195
WorkaroundDecision::Skip(SkipReason::NotWayland)
143196
);
144197
}
198+
199+
#[test]
200+
fn tauri_auto_window_is_opt_in() {
201+
assert!(!should_use_tauri_auto_window(None));
202+
assert!(!should_use_tauri_auto_window(Some("")));
203+
assert!(!should_use_tauri_auto_window(Some("0")));
204+
assert!(should_use_tauri_auto_window(Some("1")));
205+
assert!(should_use_tauri_auto_window(Some("true")));
206+
assert!(should_use_tauri_auto_window(Some("yes")));
207+
assert!(should_use_tauri_auto_window(Some("on")));
208+
}
145209
}

src-tauri/src/window_lifecycle.rs

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,28 @@ pub fn configure_main_window(app: &tauri::AppHandle, main_window: &WebviewWindow
5050
}
5151
}
5252

53+
pub fn ensure_main_window_for_setup(app: &tauri::AppHandle) -> Result<(), String> {
54+
if let Some(main_window) = app.get_webview_window(MAIN_WINDOW_LABEL) {
55+
tracing::info!("AQBot main window found during setup");
56+
configure_main_window(app, &main_window);
57+
tracing::info!("AQBot main window configured");
58+
return Ok(());
59+
}
60+
61+
tracing::warn!("AQBot main window was not found during setup");
62+
63+
#[cfg(target_os = "linux")]
64+
if crate::linux_webkit::should_create_main_window_in_setup() {
65+
tracing::info!("Creating AQBot main window manually during Linux setup");
66+
let main_window = create_main_window_from_config(app)?;
67+
tracing::info!("AQBot main window manually created during Linux setup");
68+
configure_main_window(app, &main_window);
69+
tracing::info!("AQBot main window configured");
70+
}
71+
72+
Ok(())
73+
}
74+
5375
pub fn release_main_window_to_tray(window: &tauri::Window) -> Result<(), String> {
5476
let app = window.app_handle();
5577
if should_release_webview(&app) {
@@ -119,6 +141,12 @@ pub fn restore_main_window(app: &tauri::AppHandle) {
119141
}
120142

121143
fn restore_main_window_inner(app: &tauri::AppHandle) -> Result<(), String> {
144+
let window = create_main_window_from_config(app)?;
145+
configure_main_window(app, &window);
146+
Ok(())
147+
}
148+
149+
fn create_main_window_from_config(app: &tauri::AppHandle) -> Result<WebviewWindow, String> {
122150
let config = app
123151
.config()
124152
.app
@@ -127,12 +155,18 @@ fn restore_main_window_inner(app: &tauri::AppHandle) -> Result<(), String> {
127155
.find(|config| config.label == MAIN_WINDOW_LABEL)
128156
.ok_or_else(|| "main window config not found".to_string())?;
129157

130-
let window = tauri::WebviewWindowBuilder::from_config(app, config)
131-
.map_err(|err| err.to_string())?
132-
.build()
133-
.map_err(|err| err.to_string())?;
134-
configure_main_window(app, &window);
135-
Ok(())
158+
tracing::info!(
159+
label = %config.label,
160+
create = config.create,
161+
visible = config.visible,
162+
"Preparing AQBot main window builder from config"
163+
);
164+
let builder =
165+
tauri::WebviewWindowBuilder::from_config(app, config).map_err(|err| err.to_string())?;
166+
tracing::info!("AQBot main window builder created from config");
167+
let window = builder.build().map_err(|err| err.to_string())?;
168+
tracing::info!("AQBot main window WebView build completed");
169+
Ok(window)
136170
}
137171

138172
fn should_release_webview(app: &tauri::AppHandle) -> bool {

0 commit comments

Comments
 (0)