fix: 彻底修复 Windows 托盘图标不显示 + 添加版本编译时间日志

Windows 托盘修复:
- 设置 setQuitOnLastWindowClosed(false),关闭主窗口时不退出应用
- 关闭主窗口改为隐藏 (event->ignore()),托盘继续运行
- 托盘菜单"退出"调用 qApp->quit() 真正退出
- 图标简化为 16x16 QImage ARGB32 + 纯色圆,确保 Windows 兼容
- 先设置图标再 trayIcon_->show()
- 添加托盘气泡通知

日志增强:
- 启动日志显示版本号 (v0.1.0)
- 打印编译时间 (__DATE__ __TIME__),方便确认二进制是否更新

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
impressionyang 2026-06-11 15:39:00 +08:00
parent dc2f8e5c4f
commit 6bf22041f8
3 changed files with 40 additions and 55 deletions

View File

@ -11,7 +11,8 @@ namespace impress {
Application::Application(int& argc, char** argv) Application::Application(int& argc, char** argv)
: QApplication(argc, argv) : QApplication(argc, argv)
{ {
LOG_INFO(kTag, "Impress Voice Input 启动"); LOG_INFO(kTag, QString("Impress Voice Input v%1 启动").arg(applicationVersion()));
LOG_INFO(kTag, QString("编译时间: %1 %2").arg(__DATE__).arg(__TIME__));
configManager_ = std::make_unique<ConfigManager>(this); configManager_ = std::make_unique<ConfigManager>(this);
configManager_->loadDefaults(); configManager_->loadDefaults();

View File

@ -18,6 +18,8 @@
#include <QSystemTrayIcon> #include <QSystemTrayIcon>
#include <QPainter> #include <QPainter>
#include <QIcon> #include <QIcon>
#include <QApplication>
#include <QCloseEvent>
static const char* const kTag = "MainWindow"; static const char* const kTag = "MainWindow";
@ -109,6 +111,9 @@ void MainWindow::setupTrayIcon() {
return; return;
} }
// 防止关闭主窗口时应用退出,保持托盘图标运行
qApp->setQuitOnLastWindowClosed(false);
trayMenu_ = new QMenu(this); trayMenu_ = new QMenu(this);
auto* showAction = trayMenu_->addAction("显示主窗口"); auto* showAction = trayMenu_->addAction("显示主窗口");
connect(showAction, &QAction::triggered, this, [this]() { connect(showAction, &QAction::triggered, this, [this]() {
@ -118,13 +123,19 @@ void MainWindow::setupTrayIcon() {
}); });
trayMenu_->addSeparator(); trayMenu_->addSeparator();
auto* exitAction = trayMenu_->addAction("退出"); auto* exitAction = trayMenu_->addAction("退出");
connect(exitAction, &QAction::triggered, this, &MainWindow::close); connect(exitAction, &QAction::triggered, this, [this]() {
if (voiceInputService_) {
voiceInputService_->stop();
}
qApp->quit();
});
trayIcon_ = new QSystemTrayIcon(this); trayIcon_ = new QSystemTrayIcon(this);
trayIcon_->setContextMenu(trayMenu_); trayIcon_->setContextMenu(trayMenu_);
// 初始状态 // 先设置图标,再显示托盘
updateTrayIcon("语音输入就绪"); updateTrayIcon("语音输入就绪");
trayIcon_->show();
// 双击托盘显示窗口 // 双击托盘显示窗口
connect(trayIcon_, &QSystemTrayIcon::activated, this, [this](QSystemTrayIcon::ActivationReason reason) { connect(trayIcon_, &QSystemTrayIcon::activated, this, [this](QSystemTrayIcon::ActivationReason reason) {
@ -135,8 +146,6 @@ void MainWindow::setupTrayIcon() {
} }
}); });
trayIcon_->show();
// 发送通知气泡,让用户注意到托盘图标 // 发送通知气泡,让用户注意到托盘图标
trayIcon_->showMessage( trayIcon_->showMessage(
"Impress Voice Input", "Impress Voice Input",
@ -145,37 +154,31 @@ void MainWindow::setupTrayIcon() {
3000 3000
); );
LOG_INFO(kTag, QString("系统托盘图标已创建 (可用: %1)").arg(QSystemTrayIcon::isSystemTrayAvailable())); LOG_INFO(kTag, QString("系统托盘图标已创建 (可用: %1, 已设置 quitOnLastWindowClosed=false)")
.arg(QSystemTrayIcon::isSystemTrayAvailable()));
} }
void MainWindow::updateTrayIcon(const QString& status) { void MainWindow::updateTrayIcon(const QString& status) {
if (!trayIcon_) return; if (!trayIcon_) return;
QColor color; QColor color;
QString symbol;
// 根据状态文字匹配图标 // 根据状态文字匹配图标颜色
if (status.contains("正在录音")) { if (status.contains("正在录音")) {
color = QColor("#e74c3c"); // 红色 - 录音中 color = QColor("#e74c3c"); // 红色 - 录音中
symbol = "";
} else if (status.contains("正在识别")) { } else if (status.contains("正在识别")) {
color = QColor("#f39c12"); // 橙色 - 识别中 color = QColor("#f39c12"); // 橙色 - 识别中
symbol = "";
} else if (status.contains("等待长按") || status.contains("PreRecording")) { } else if (status.contains("等待长按") || status.contains("PreRecording")) {
color = QColor("#f1c40f"); // 黄色 - 预录音 color = QColor("#f1c40f"); // 黄色 - 预录音
symbol = "";
} else if (status.contains("已启动") || status.contains("就绪")) { } else if (status.contains("已启动") || status.contains("就绪")) {
color = QColor("#27ae60"); // 绿色 - 就绪 color = QColor("#27ae60"); // 绿色 - 就绪
symbol = "";
} else if (status.contains("已关闭") || status.contains("停止")) { } else if (status.contains("已关闭") || status.contains("停止")) {
color = QColor("#95a5a6"); // 灰色 - 停止 color = QColor("#95a5a6"); // 灰色 - 停止
symbol = "";
} else { } else {
color = QColor("#3498db"); // 蓝色 - 其他 color = QColor("#3498db"); // 蓝色 - 其他
symbol = "";
} }
QPixmap pm = createTrayIcon(color, symbol); QPixmap pm = createTrayIcon(color);
QIcon icon(pm); QIcon icon(pm);
if (icon.isNull()) { if (icon.isNull()) {
LOG_ERROR(kTag, "托盘图标创建失败"); LOG_ERROR(kTag, "托盘图标创建失败");
@ -184,51 +187,24 @@ void MainWindow::updateTrayIcon(const QString& status) {
trayIcon_->setIcon(icon); trayIcon_->setIcon(icon);
trayIcon_->setToolTip(QString("Impress Voice Input - %1").arg(status)); trayIcon_->setToolTip(QString("Impress Voice Input - %1").arg(status));
// 确保托盘图标可见
if (!trayIcon_->isVisible()) {
trayIcon_->show();
}
} }
QPixmap MainWindow::createTrayIcon(const QColor& color, const QString& symbol) { QPixmap MainWindow::createTrayIcon(const QColor& color) {
const int size = 32; // 使用 16x16 标准托盘图标尺寸
QPixmap pixmap(size, size); const int size = 16;
// 使用不透明背景Windows 托盘对透明图标支持有限 QImage image(size, size, QImage::Format_ARGB32);
pixmap.fill(QColor(40, 40, 45)); image.fill(Qt::transparent);
QPainter painter(&pixmap); QPainter painter(&image);
painter.setRenderHint(QPainter::Antialiasing); painter.setRenderHint(QPainter::Antialiasing);
// // 绘制实心彩色
painter.setBrush(color); painter.setBrush(color);
painter.setPen(Qt::NoPen); painter.setPen(Qt::NoPen);
int margin = 4; int margin = 1;
painter.drawEllipse(margin, margin, size - 2 * margin, size - 2 * margin); painter.drawEllipse(margin, margin, size - 2 * margin, size - 2 * margin);
// 内部符号 return QPixmap::fromImage(image);
if (symbol == "") {
// 录音中:内部白色实心圆
painter.setBrush(QColor("#ffffff"));
int innerSize = 8;
int offset = (size - innerSize) / 2;
painter.drawEllipse(offset, offset, innerSize, innerSize);
} else if (symbol == "") {
// 识别中:白色环形
painter.setPen(QPen(QColor("#ffffff"), 3));
painter.setBrush(Qt::NoBrush);
int innerSize = 12;
int offset = (size - innerSize) / 2;
painter.drawEllipse(offset, offset, innerSize, innerSize);
} else {
// 空心:小白点
painter.setBrush(QColor("#ffffff"));
int dotSize = 6;
int offset = (size - dotSize) / 2;
painter.drawEllipse(offset, offset, dotSize, dotSize);
}
return pixmap;
} }
void MainWindow::setupMenuBar() { void MainWindow::setupMenuBar() {
@ -242,7 +218,15 @@ void MainWindow::setupMenuBar() {
auto* exitAction = fileMenu->addAction("退出"); auto* exitAction = fileMenu->addAction("退出");
exitAction->setShortcut(QKeySequence("Ctrl+Q")); exitAction->setShortcut(QKeySequence("Ctrl+Q"));
connect(exitAction, &QAction::triggered, this, &MainWindow::close); connect(exitAction, &QAction::triggered, this, [this]() {
if (trayIcon_) {
trayIcon_->hide();
}
if (voiceInputService_) {
voiceInputService_->stop();
}
qApp->quit();
});
// 帮助菜单 // 帮助菜单
auto* helpMenu = menuBar()->addMenu("帮助"); auto* helpMenu = menuBar()->addMenu("帮助");
@ -271,8 +255,8 @@ void MainWindow::closeEvent(QCloseEvent* event) {
if (voiceInputService_) { if (voiceInputService_) {
voiceInputService_->stop(); voiceInputService_->stop();
} }
LOG_INFO(kTag, "主窗口关闭"); LOG_INFO(kTag, "主窗口关闭 (应用保持运行,托盘图标可见)");
QMainWindow::closeEvent(event); event->ignore(); // 不关闭应用,只隐藏窗口
} }
void MainWindow::updateModelStatus() { void MainWindow::updateModelStatus() {

View File

@ -40,7 +40,7 @@ private:
void setupStatusBar(SenseVoiceEngine* sttEngine); void setupStatusBar(SenseVoiceEngine* sttEngine);
void setupTrayIcon(); void setupTrayIcon();
void updateTrayIcon(const QString& status); void updateTrayIcon(const QString& status);
QPixmap createTrayIcon(const QColor& color, const QString& symbol); QPixmap createTrayIcon(const QColor& color);
void loadStyleSheet(); void loadStyleSheet();
void onVoiceInputConfigChanged(); void onVoiceInputConfigChanged();
void updateModelStatus(); void updateModelStatus();