Part of #260: a desktop start that fails says why, in a message box naming the log

When eframe could not open the window (no usable graphics device, a
broken driver), main returned the error to standard error, which a
Windows release build (no console) or an app started from Finder never
shows: the app just vanished. The error now goes to the log (the file
#286 added) and into a native message box, in the user's language, that
names the log file. The saved UI language is applied before the window
starts so the message uses it. Scripts and tests (LIGHTCRAFT_NO_PREFS)
get no dialog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01912WaK6Rmew3wxZuPyX7E8
This commit is contained in:
Claude 2026-10-08 19:30:26 +00:00
parent bf224c2a8d
commit 7fb24029f2
No known key found for this signature in database
15 changed files with 72 additions and 3 deletions

View File

@ -523,11 +523,15 @@ fn main() -> eframe::Result {
}
// The log file: opened after the arguments, so `--version`, `--help` and a usage error leave
// no file behind. Records logged until now are written to it first.
let mut log_file = None;
if let Some(logger) = logger {
// a --memory session writes nothing (issue #164), a log file included
match log_dir().filter(|_| !in_memory) {
Some(dir) => match logger.attach_dir(&dir) {
Ok(path) => log::info!("LightCraft {}, log file {}", env!("CARGO_PKG_VERSION"), path.display()),
Ok(path) => {
log::info!("LightCraft {}, log file {}", env!("CARGO_PKG_VERSION"), path.display());
log_file = Some(path);
}
// The file sink has given up by now, so this goes to standard error only.
Err(e) => log::warn!("no log file: {e}"),
},
@ -535,6 +539,10 @@ fn main() -> eframe::Result {
}
}
let (prefs, prefs_warning, keep_prefs_file) = load_prefs(in_memory);
// the saved language from the start, so a failed start is reported in it too
if let Some(ui) = &prefs {
lightcraft_ui_egui::i18n::set_language(ui.language);
}
// --library, else the library last opened from Settings, else the default location
let library_dir = library_dir.or_else(|| {
prefs
@ -564,7 +572,7 @@ fn main() -> eframe::Result {
wgpu_options: window_wgpu_options(),
..Default::default()
};
eframe::run_native(
let started = eframe::run_native(
"LightCraft",
options,
Box::new(move |cc| {
@ -618,7 +626,28 @@ fn main() -> eframe::Result {
menu,
)))
}),
)
);
if let Err(e) = &started {
startup_failed(&e.to_string(), log_file.as_deref());
}
started
}
/// The window could not start (e.g. no usable graphics device): say so in the log and in a native
/// message box naming the log file, instead of exiting without a trace — the release builds have
/// no console on Windows, and an app started from Finder shows no standard error (issue #260).
fn startup_failed(error: &str, log_file: Option<&std::path::Path>) {
log::error!("LightCraft could not open its window: {error}");
if std::env::var_os("LIGHTCRAFT_NO_PREFS").is_some() {
return; // tests and scripts: no dialog to click away
}
let (title, text) = lightcraft_ui_egui::i18n::startup_failed_message(error, log_file.map(|p| p.display().to_string()).as_deref());
let _ = rfd::MessageDialog::new()
.set_level(rfd::MessageLevel::Error)
.set_title(title)
.set_description(text)
.set_buttons(rfd::MessageButtons::Ok)
.show();
}
#[cfg(test)]

View File

@ -67,6 +67,8 @@
"Imported {} photo{}": "{} Foto{} importiert",
"Insert at the cursor (also written {})": "An Cursorposition einfügen (auch als {} geschrieben)",
"Label Set: {name}": "Farbmarkierungssatz: {name}",
"LightCraft could not open its window: {e}": "LightCraft konnte sein Fenster nicht öffnen: {e}",
"LightCraft could not open its window: {e}\n\nThe log file has the details: {path}": "LightCraft konnte sein Fenster nicht öffnen: {e}\n\nEinzelheiten stehen in der Protokolldatei: {path}",
"MIT OR Apache-2.0. Fonts: {} (OFL). Icons: original.": "MIT ODER Apache-2.0. Schriften: {} (OFL). Symbole: eigene Gestaltung.",
"Matches: {}": "Treffer: {}",
"Merging… {stage} {:.0}%": "Wird zusammengefügt… {stage} {:.0}%",

View File

@ -753,6 +753,7 @@
"LightCraft Preset": "LightCraft-Vorgabe",
"LightCraft Presets": "LightCraft-Vorgaben",
"LightCraft Website": "LightCraft-Website",
"LightCraft could not start": "LightCraft konnte nicht starten",
"LightCraft couldn't find where to keep your library.": "LightCraft konnte keinen Speicherort für deine Bibliothek finden.",
"LightCraft couldn't open the library at": "LightCraft konnte die Bibliothek nicht öffnen unter",
"LightCraft no longer crashes at launch on Windows PCs whose Vulkan driver is broken (issue #136, e.g. some Intel UHD": "LightCraft stürzt beim Start auf Windows-PCs mit defektem Vulkan-Treiber nicht mehr ab (Issue #136, etwa bestimmte Intel-UHD-630-Treiber).",

View File

@ -35,6 +35,8 @@
"Imported {n} preset{}": "{n}件のプリセットを読み込みました{:.0}",
"Imported {n} preset{}, {f} file{} not readable": "{n}件のプリセットを読み込みました{:.0}。{f}件は読み込めません{:.0}",
"Imported {} photo{}": "{}枚を読み込みました{:.0}",
"LightCraft could not open its window: {e}": "LightCraftのウィンドウを開けませんでした: {e}",
"LightCraft could not open its window: {e}\n\nThe log file has the details: {path}": "LightCraftのウィンドウを開けませんでした: {e}\n\n詳しくはログファイルをご覧ください: {path}",
"Matches: {}": "一致条件:{}",
"Merging… {stage} {:.0}%": "合成中… {stage} {:.0}%",
"Move {what} to Recently Deleted?": "{what}を「最近削除した写真」に移動しますか?",

View File

@ -531,6 +531,7 @@
"Library State": "ライブラリの状態",
"Library saved": "ライブラリを保存しました",
"Licence:": "ライセンス:",
"LightCraft could not start": "LightCraftを起動できませんでした",
"Line delta": "行数の増減",
"Linear": "リニア",
"Lines +/−": "行数 +/−",

View File

@ -46,6 +46,8 @@
"Imported {n} preset{}, {f} file{} not readable": "Importadas {n} predefinição{:.0}, {f} arquivo{:.0} não legível",
"Imported {} photo{}": "Importadas {} foto{:.0}",
"Label Set: {name}": "Conjunto de rótulos: {name}",
"LightCraft could not open its window: {e}": "O LightCraft não conseguiu abrir sua janela: {e}",
"LightCraft could not open its window: {e}\n\nThe log file has the details: {path}": "O LightCraft não conseguiu abrir sua janela: {e}\n\nO arquivo de log tem os detalhes: {path}",
"Matches: {}": "Correspondências: {}",
"Merging… {stage} {:.0}%": "Mesclando… {stage} {:.0}%",
"Move {what} to Recently Deleted?": "Mover {what} para Excluídos recentemente?",

View File

@ -721,6 +721,7 @@
"LightCraft Preset": "Predefinição do LightCraft",
"LightCraft Presets": "Predefinições do LightCraft",
"LightCraft Website": "Site do LightCraft",
"LightCraft could not start": "Não foi possível iniciar o LightCraft",
"LightCraft couldn't find where to keep your library.": "O LightCraft não conseguiu encontrar onde guardar sua biblioteca.",
"LightCraft couldn't open the library at": "O LightCraft não conseguiu abrir a biblioteca em",
"LightCraft on GitHub": "LightCraft no GitHub",

View File

@ -71,6 +71,8 @@
"Insert at the cursor (also written {})": "Вставить в позицию курсора (также пишется как {})",
"Label Set: {name}": "Набор меток: {name}",
"LightCraft can't decode this raw variant yet ({}). You're editing the camera's embedded JPEG preview, which already includes the camera's picture style (e.g. Monochrome) and white balance.": "LightCraft пока не умеет декодировать этот вариант RAW ({}). Вы редактируете встроенный в файл JPEG-просмотр камеры, в котором уже применены стиль изображения камеры (например, «Монохром») и баланс белого.",
"LightCraft could not open its window: {e}": "LightCraft не удалось открыть окно: {e}",
"LightCraft could not open its window: {e}\n\nThe log file has the details: {path}": "LightCraft не удалось открыть окно: {e}\n\nПодробности в файле журнала: {path}",
"MIT OR Apache-2.0. Fonts: {} (OFL). Icons: original.": "MIT OR Apache-2.0. Шрифты: {} (OFL). Значки: оригинальные.",
"Matches: {}": "Совпадения: {}",
"Merging… {stage} {:.0}%": "Слияние… {stage} {:.0}%",

View File

@ -590,6 +590,7 @@
"LightCraft Preset": "Пресет LightCraft",
"LightCraft Presets": "Пресеты LightCraft",
"LightCraft Website": "Сайт LightCraft",
"LightCraft could not start": "Не удалось запустить LightCraft",
"LightCraft couldn't find where to keep your library.": "LightCraft не удалось найти место для хранения вашей библиотеки.",
"LightCraft couldn't open the library at": "LightCraft не удалось открыть библиотеку в",
"LightCraft on GitHub": "LightCraft на GitHub",

View File

@ -35,6 +35,8 @@
"Imported {n} preset{}": "已导入 {n} 个预设{0:.0}",
"Imported {n} preset{}, {f} file{} not readable": "已导入 {n} 个预设,{f} 个文件无法读取{0:.0}{1:.0}",
"Imported {} photo{}": "已导入 {0} 张照片{1:.0}",
"LightCraft could not open its window: {e}": "LightCraft 无法打开窗口:{e}",
"LightCraft could not open its window: {e}\n\nThe log file has the details: {path}": "LightCraft 无法打开窗口:{e}\n\n详细信息见日志文件:{path}",
"Matches: {}": "匹配项:{0}",
"Merging… {stage} {:.0}%": "合成中… {stage} {0:.0}%",
"Move {what} to Recently Deleted?": "将 {what} 移到最近删除?",

View File

@ -523,6 +523,7 @@
"Lens Blur": "镜头模糊",
"Library saved": "图库已保存",
"Licence:": "许可证:",
"LightCraft could not start": "LightCraft 无法启动",
"Line delta": "行数差",
"Linear": "线性",
"Lines +/−": "行数 +/−",

View File

@ -43,6 +43,8 @@
"Imported {n} preset{}, {f} file{} not readable": "已匯入 {n} 個預設集{:.0},{f} 個檔案無法讀取{:.0}",
"Imported {} photo{}": "已匯入 {} 張照片{:.0}",
"Label Set: {name}": "標籤集:{name}",
"LightCraft could not open its window: {e}": "LightCraft 無法開啟視窗:{e}",
"LightCraft could not open its window: {e}\n\nThe log file has the details: {path}": "LightCraft 無法開啟視窗:{e}\n\n詳細資訊請見記錄檔:{path}",
"Matches: {}": "符合條件:{}",
"Merging… {stage} {:.0}%": "正在合併… {stage} {:.0}%",
"Move {what} to Recently Deleted?": "要將{what}移至「最近刪除」嗎?",

View File

@ -523,6 +523,7 @@
"Lens Blur": "鏡頭模糊",
"Library saved": "照片庫已儲存",
"Licence:": "授權:",
"LightCraft could not start": "LightCraft 無法啟動",
"Line delta": "行數差",
"Linear": "線性",
"Lines +/−": "行數 +/−",

View File

@ -189,6 +189,16 @@ include!(concat!(env!("OUT_DIR"), "/tr-formats.rs"));
/// A stock preset's or profile's name in the UI language; an imported or user-edited name stays
/// exactly as its owner wrote it.
/// The title and text of the message box shown when the desktop app's window can't start
/// (issue #260), in the UI language: the error and, when there is one, the log file to look in.
pub fn startup_failed_message(error: &str, log_file: Option<&str>) -> (String, String) {
let text = match log_file {
Some(path) => tr_format!("LightCraft could not open its window: {e}\n\nThe log file has the details: {path}", e = error, path = path),
None => tr_format!("LightCraft could not open its window: {e}", e = error),
};
(tr("LightCraft could not start").to_string(), text)
}
pub fn builtin_label(source: &str, builtin: bool) -> &str {
if builtin { tr(source) } else { source }
}
@ -404,6 +414,16 @@ mod tests {
}
/// Locale tags are matched leniently, so a system locale reaches a shipped language.
/// The message box for a desktop start that failed (issue #260) names the error and the log
/// file, in the UI language.
#[test]
fn startup_failure_message_names_the_error_and_the_log() {
let (title, text) = startup_failed_message("no adapter", Some("/home/a/.config/lightcraft/logs/lightcraft.log"));
assert_eq!(title, "LightCraft could not start");
assert!(text.contains("no adapter") && text.ends_with("lightcraft.log"), "{text}");
assert!(!startup_failed_message("no adapter", None).1.contains("log file"));
}
#[test]
fn locale_tags_normalize() {
assert_eq!(Locale::parse_tag("en"), Some(Locale::En));

View File

@ -63,6 +63,8 @@
promise, instead of filtering whatever album or folder was open, which often showed nothing (issue #341).
### Reliability
- If the desktop app can't open its window (for example when no graphics device can be used), it now says so in a
message box that names the log file, instead of quitting without a trace (issue #260).
- On macOS, single-key shortcuts that appear in the menu bar now work: E, C, H, M, ⇧P, I, K, D, ratings 0–5,
labels 6–9, P / U and the rest did nothing, because macOS only passes ⌘ / ⌃ combinations and function keys
to the menu bar and the app ignored those keys, assuming the menu bar would handle them. Keys outside the menus