通过 EnumWindows 枚举并隐藏 Qt 在 Windows 上创建的 WS_EX_TOOLWINDOW 工具窗口(无标题栏、无边框),解决启动时出现的空白方块问题。 同时包含: - Windows 使用原生 windows 风格替代 Fusion,避免渲染问题 - 托盘图标改为圆形轮廓,AudioWaveform 背景主题适配 - QSS 完善(MenuBar、录音按钮动态属性、StackedWidget 背景) - 内联样式表改为 objectName/dynamic property,QSS 统一管理 - 日志记录版本信息、编译时间、Qt 版本、平台信息 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
158 lines
5.1 KiB
C++
158 lines
5.1 KiB
C++
#include "app/application.h"
|
|
#include "ui/main_window.h"
|
|
#include "app/config_manager.h"
|
|
#include "utils/logger.h"
|
|
|
|
#include <QFile>
|
|
#include <QDir>
|
|
#include <QStandardPaths>
|
|
#include <QCommandLineParser>
|
|
#include <QApplication>
|
|
#include <QTimer>
|
|
|
|
#ifdef Q_OS_WIN
|
|
#include <windows.h>
|
|
#include <string>
|
|
|
|
static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) {
|
|
DWORD pid;
|
|
GetWindowThreadProcessId(hwnd, &pid);
|
|
if (pid != GetCurrentProcessId()) return TRUE;
|
|
|
|
wchar_t title[256];
|
|
GetWindowTextW(hwnd, title, 256);
|
|
|
|
RECT rect;
|
|
GetWindowRect(hwnd, &rect);
|
|
|
|
LONG style = GetWindowLong(hwnd, GWL_STYLE);
|
|
LONG exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
|
|
|
|
bool hasTitleBar = (style & WS_CAPTION) != 0;
|
|
bool hasBorder = (style & WS_BORDER) != 0;
|
|
bool isChild = (style & WS_CHILD) != 0;
|
|
bool isVisible = IsWindowVisible(hwnd);
|
|
bool isTool = (exStyle & WS_EX_TOOLWINDOW) != 0;
|
|
|
|
std::wstring wTitle(title);
|
|
std::string utf8Title(wTitle.begin(), wTitle.end());
|
|
|
|
LOG_INFO("WindowEnum", QString("HWND=%1 标题=\"%2\" 位置=[%3,%4,%5,%6] 大小=%7x%8 "
|
|
"可见=%9 标题栏=%10 边框=%11 子窗口=%12 工具窗=%13")
|
|
.arg((qulonglong)hwnd)
|
|
.arg(QString::fromStdWString(wTitle))
|
|
.arg(rect.left).arg(rect.top).arg(rect.right).arg(rect.bottom)
|
|
.arg(rect.right - rect.left).arg(rect.bottom - rect.top)
|
|
.arg(isVisible ? "Y" : "N")
|
|
.arg(hasTitleBar ? "Y" : "N")
|
|
.arg(hasBorder ? "Y" : "N")
|
|
.arg(isChild ? "Y" : "N")
|
|
.arg(isTool ? "Y" : "N"));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void dumpAllWindows() {
|
|
LOG_INFO("WindowEnum", "===== 枚举进程所有窗口 =====");
|
|
EnumWindows(EnumWindowsProc, 0);
|
|
LOG_INFO("WindowEnum", "===== 窗口枚举结束 =====");
|
|
}
|
|
#endif
|
|
|
|
int main(int argc, char* argv[])
|
|
{
|
|
impress::Application app(argc, argv);
|
|
app.setApplicationName("Impress Voice Input");
|
|
app.setApplicationVersion("0.1.0");
|
|
app.setOrganizationName("Impress");
|
|
|
|
// 默认日志目录(配置加载后可能被覆盖)
|
|
QString defaultLogDir = QStandardPaths::writableLocation(
|
|
QStandardPaths::AppDataLocation);
|
|
QDir().mkpath(defaultLogDir);
|
|
|
|
// 命令行参数
|
|
QCommandLineParser parser;
|
|
parser.setApplicationDescription("基于 ONNX 的实时语音转文本输入法");
|
|
parser.addHelpOption();
|
|
parser.addVersionOption();
|
|
parser.addOptions({
|
|
{{"c", "config"}, "指定配置文件路径", "path"},
|
|
{{"m", "model"}, "指定模型路径", "path"},
|
|
{{"l", "log-dir"}, "指定日志文件目录", "path"},
|
|
});
|
|
parser.process(app);
|
|
|
|
// 加载用户配置
|
|
auto* configManager = app.configManager();
|
|
QString configPath = parser.value("config");
|
|
if (configPath.isEmpty()) {
|
|
configPath = defaultLogDir + "/config.json";
|
|
}
|
|
|
|
if (QFile::exists(configPath)) {
|
|
configManager->load(configPath);
|
|
LOG_INFO("Main", QString("已加载配置: %1").arg(configPath));
|
|
} else {
|
|
LOG_INFO("Main", "使用默认配置");
|
|
}
|
|
|
|
// 确定日志目录:命令行 > 配置 > 默认 AppDataLocation
|
|
QString effectiveLogDir = parser.value("log-dir");
|
|
if (effectiveLogDir.isEmpty()) {
|
|
effectiveLogDir = configManager->get("app.log_dir").toString();
|
|
}
|
|
if (effectiveLogDir.isEmpty()) {
|
|
effectiveLogDir = defaultLogDir;
|
|
}
|
|
QDir().mkpath(effectiveLogDir);
|
|
QString logFilePath = effectiveLogDir + "/app.log";
|
|
impress::Logger::init(logFilePath);
|
|
|
|
LOG_INFO("Main", QString("=== Impress Voice Input v%1 ===").arg(app.applicationVersion()));
|
|
LOG_INFO("Main", QString("编译时间: %1 %2").arg(__DATE__).arg(__TIME__));
|
|
LOG_INFO("Main", QString("Qt 版本: %1").arg(qVersion()));
|
|
#ifdef Q_OS_WIN
|
|
LOG_INFO("Main", "平台: Windows");
|
|
#elif defined(Q_OS_LINUX)
|
|
LOG_INFO("Main", "平台: Linux");
|
|
#endif
|
|
|
|
LOG_INFO("Main", QString("日志目录: %1").arg(effectiveLogDir));
|
|
|
|
// 命令行覆盖模型路径
|
|
QString modelPath = parser.value("model");
|
|
if (!modelPath.isEmpty()) {
|
|
configManager->set("stt.model_path", modelPath);
|
|
}
|
|
|
|
// 应用主题和字体
|
|
QString theme = configManager->get("ui.theme").toString();
|
|
int fontSize = configManager->get("ui.font_size").toInt();
|
|
impress::Application::applyTheme(theme);
|
|
if (fontSize > 0) impress::Application::applyFontSize(fontSize);
|
|
LOG_INFO("Main", QString("主题: %1, 字体: %2").arg(theme).arg(fontSize));
|
|
|
|
// 配置加载完成后,启动全局模型加载
|
|
app.loadGlobalModel();
|
|
|
|
// 创建并显示主窗口(传入全局引擎)
|
|
impress::MainWindow mainWindow(configManager, app.sttEngine());
|
|
mainWindow.show();
|
|
|
|
#ifdef Q_OS_WIN
|
|
// 延迟 1 秒后枚举所有窗口,确保所有 Qt 内部窗口都已创建
|
|
QTimer::singleShot(1000, []() {
|
|
dumpAllWindows();
|
|
});
|
|
|
|
// 3 秒后再次枚举,检测是否有延迟创建的窗口
|
|
QTimer::singleShot(3000, []() {
|
|
LOG_INFO("WindowEnum", "===== 延迟 3 秒后再次枚举 =====");
|
|
dumpAllWindows();
|
|
});
|
|
#endif
|
|
|
|
return app.exec();
|
|
}
|