fix(gui): stop quitting when the last window closes; add device and verify
Accepting the settings dialog quit the app: with a tray-resident program "last window closed" must not mean "quit", but QApplication's default made it so, and on first run the dialog is the only window. Disable quitOnLastWindowClosed. Also fix listOutputDevices hanging forever (its callbacks never signalled the PulseAudio main loop), enlarge the caption window's close button, and add capture-device selection (enumerated sinks, settings combo, --list-devices/--device) plus model integrity verification (size + sha256 in the registry, --verify-model and a "校验完整性" button). 点「确定」后程序自己退出:本程序常驻托盘,"最后一个窗口关闭"不该等于退出, 但 QApplication 默认如此,而首次启动时设置对话框正是唯一窗口。已关闭该默认 行为。同时修复 listOutputDevices 永久卡死(回调没有 signal 主循环)、放大 字幕窗关闭按钮,并新增采集设备选择与模型完整性校验(注册表含 size/sha256, 提供 --verify-model 与设置里的「校验完整性」按钮)。
This commit is contained in:
1 parent
a7a6f3e6b6
commit
0fa0bcb44a
19 files changed
+460
-55
No files matched your search
+51
-7
@@ -12,6 +12,7 @@
|
||||
#include <QMessageBox>
|
||||
#include <QPainter>
|
||||
#include <QPixmap>
|
||||
#include <QPushButton>
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QTimer>
|
||||
|
||||
@@ -72,6 +73,10 @@ int main(int argc, char ** argv)
|
||||
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(
|
||||
Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
|
||||
QApplication app(argc, argv);
|
||||
// 关键:本程序常驻托盘,"最后一个窗口关闭"不等于退出。
|
||||
// 否则首次启动时设置对话框一关(那时它是唯一窗口),Qt 就会把主事件循环也结束掉,
|
||||
// 表现为"点了确定程序就自己退出"。
|
||||
QApplication::setQuitOnLastWindowClosed(false);
|
||||
QCoreApplication::setApplicationName(QStringLiteral("live-caption"));
|
||||
QCoreApplication::setOrganizationName(QStringLiteral("live-caption"));
|
||||
|
||||
@@ -90,6 +95,8 @@ int main(int argc, char ** argv)
|
||||
parser.addOption(QCommandLineOption("screenshot-delay", "截图前等待秒数", "sec", "6"));
|
||||
parser.addOption(QCommandLineOption("no-tray", "不创建托盘图标(测试用)"));
|
||||
parser.addOption(QCommandLineOption("screenshot-settings", "打开设置对话框截图后退出", "path"));
|
||||
parser.addOption(QCommandLineOption("settings-autoclose", "复现用:自动关闭设置对话框(reject|close|accept|ok)", "how"));
|
||||
parser.addOption(QCommandLineOption("quit-after", "运行若干秒后优雅退出(测试退出路径用)", "sec"));
|
||||
parser.process(app);
|
||||
|
||||
if (parser.isSet("asr-model")) cfg.modelId = parser.value("asr-model");
|
||||
@@ -100,19 +107,37 @@ int main(int argc, char ** argv)
|
||||
// 仅用于验证渲染:打开设置对话框截图后退出
|
||||
if (parser.isSet("screenshot-settings")) {
|
||||
const QString path = parser.value("screenshot-settings");
|
||||
auto * dlg = new lc::SettingsDialog(!hasUsableModel());
|
||||
dlg->show();
|
||||
QTimer::singleShot(1200, dlg, [dlg, path] {
|
||||
dlg->grab().save(path);
|
||||
lc::SettingsDialog dlg(!hasUsableModel());
|
||||
dlg.show();
|
||||
QTimer::singleShot(1200, &dlg, [&dlg, path] {
|
||||
dlg.grab().save(path);
|
||||
std::fprintf(stderr, "设置对话框截图: %s\n", qUtf8Printable(path));
|
||||
QCoreApplication::quit();
|
||||
dlg.accept();
|
||||
});
|
||||
return QCoreApplication::exec();
|
||||
dlg.exec();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 复现用:把"点 ✕ / 点确定"自动化,用来定位崩溃
|
||||
const QString autoclose = parser.value("settings-autoclose");
|
||||
const auto scheduleAutoclose = [&autoclose](QDialog & dlg) {
|
||||
if (autoclose.isEmpty()) return;
|
||||
QTimer::singleShot(700, &dlg, [&dlg, &autoclose] {
|
||||
if (autoclose == QLatin1String("reject")) dlg.reject();
|
||||
else if (autoclose == QLatin1String("close")) dlg.close(); // 标题栏 ✕
|
||||
else if (autoclose == QLatin1String("accept")) dlg.accept();
|
||||
else if (autoclose == QLatin1String("ok")) { // 真的点「确定」
|
||||
for (auto * b : dlg.findChildren<QPushButton *>()) {
|
||||
if (b->text() == QObject::tr("确定")) { b->click(); break; }
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// ---- 启动:没有可用模型就先开设置 ----
|
||||
if (!hasUsableModel()) {
|
||||
lc::SettingsDialog dlg(true);
|
||||
scheduleAutoclose(dlg);
|
||||
if (dlg.exec() != QDialog::Accepted || !hasUsableModel()) return 0;
|
||||
}
|
||||
|
||||
@@ -130,6 +155,7 @@ int main(int argc, char ** argv)
|
||||
const auto startTranscriber = [&]() -> bool {
|
||||
live.stop();
|
||||
asr.reset();
|
||||
live.setCaptureDevice(cfg.captureDevice);
|
||||
|
||||
const lc::models::ModelInfo * m = lc::models::ModelStore::find(cfg.modelId);
|
||||
if (!m) {
|
||||
@@ -189,7 +215,7 @@ int main(int argc, char ** argv)
|
||||
if (!parser.isSet("no-tray")) {
|
||||
tray = new QSystemTrayIcon(makeTrayIcon(), &app);
|
||||
tray->setToolTip(QObject::tr("实时字幕"));
|
||||
trayMenu = new QMenu;
|
||||
trayMenu = new QMenu(&window); // 必须有归属,否则退出时是悬空顶层窗口
|
||||
auto * showAct = trayMenu->addAction(QObject::tr("显示/隐藏字幕"));
|
||||
auto * setAct = trayMenu->addAction(QObject::tr("设置…"));
|
||||
trayMenu->addSeparator();
|
||||
@@ -239,6 +265,19 @@ int main(int argc, char ** argv)
|
||||
|
||||
window.show();
|
||||
|
||||
// 复现用:程序起来后再开一次设置并自动关闭(模拟从语言标签/托盘进设置)
|
||||
if (!autoclose.isEmpty() && hasUsableModel()) {
|
||||
QTimer::singleShot(2500, &window, [&] {
|
||||
lc::SettingsDialog dlg(false);
|
||||
scheduleAutoclose(dlg);
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
applyAppearance(window, cfg);
|
||||
if (!startTranscriber()) qWarning("重启转写失败");
|
||||
}
|
||||
QTimer::singleShot(500, qApp, &QCoreApplication::quit);
|
||||
});
|
||||
}
|
||||
|
||||
// 自截图(验证渲染用)
|
||||
if (parser.isSet("screenshot")) {
|
||||
const QString path = parser.value("screenshot");
|
||||
@@ -255,6 +294,11 @@ int main(int argc, char ** argv)
|
||||
});
|
||||
}
|
||||
|
||||
if (parser.isSet("quit-after")) {
|
||||
QTimer::singleShot(parser.value("quit-after").toInt() * 1000, qApp,
|
||||
&QCoreApplication::quit);
|
||||
}
|
||||
|
||||
const int rc = QCoreApplication::exec();
|
||||
// 显式收尾,确保工作线程停下来之后再释放后端
|
||||
live.stop();
|
||||
|
||||
Reference in new issue
Block a user