#include #include #include #include #include #include #include #include #ifdef _WIN32 #include #include #endif #include "mainwindow.h" #include "../db/db_manager.h" #include "../transport/node_config.h" #include "../transport/utun_node.h" #include "../../lib/debug_config.h" #ifdef _WIN32 static UtunNode* g_ctrl_node = nullptr; static MainWindow* g_mainWindow = nullptr; static DWORD count_threads(void) { DWORD pid = GetCurrentProcessId(), count = 0; HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (snap == INVALID_HANDLE_VALUE) return 0; THREADENTRY32 te = { sizeof(te) }; if (Thread32First(snap, &te)) { do { if (te.th32OwnerProcessID == pid) count++; } while (Thread32Next(snap, &te)); } CloseHandle(snap); return count; } static BOOL WINAPI console_ctrl_handler(DWORD dwCtrlType) { if (dwCtrlType == CTRL_C_EVENT || dwCtrlType == CTRL_BREAK_EVENT) { if (g_mainWindow) QMetaObject::invokeMethod(g_mainWindow, [g_mainWindow] { g_mainWindow->requestShutdown(); }, Qt::QueuedConnection); else if (g_ctrl_node) g_ctrl_node->requestStop(); return TRUE; } return FALSE; } #endif static struct ShutdownSentinel { ~ShutdownSentinel() { DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "main: static destructors done"); #ifdef _WIN32 DWORD threads = count_threads(); DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "main: threads at exit=%lu", (unsigned long)threads); if (threads > 4) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "main: %lu hanging threads, TerminateProcess", (unsigned long)threads); fflush(nullptr); TerminateProcess(GetCurrentProcess(), 0); } #endif } } g_shutdown_sentinel; int main(int argc, char *argv[]) { #ifdef Q_OS_LINUX /* На Wayland композитор владеет позицией окна (move() игнорируется) — * для полноценного ручного move/resize с пропорцией запускаемся через XWayland. */ if (!qEnvironmentVariableIsSet("QT_QPA_PLATFORM")) qputenv("QT_QPA_PLATFORM", "xcb"); #endif QApplication app(argc, argv); app.setApplicationName("vibechat"); app.setQuitOnLastWindowClosed(false); /* single-instance: лог/БД/сокеты конфликтуют при втором запуске */ QLockFile lock(QCoreApplication::applicationDirPath() + "/vibechat.lock"); if (!lock.tryLock()) { QMessageBox::warning(nullptr, "vibechat", "vibechat is already running."); return 1; } app.setStyle(QStyleFactory::create("Fusion")); QPalette p; p.setColor(QPalette::Window, QColor(53, 53, 53)); p.setColor(QPalette::WindowText, QColor(220, 220, 220)); p.setColor(QPalette::Base, QColor(42, 42, 42)); p.setColor(QPalette::AlternateBase, QColor(66, 66, 66)); p.setColor(QPalette::ToolTipBase, QColor(25, 25, 25)); p.setColor(QPalette::ToolTipText, QColor(220, 220, 220)); p.setColor(QPalette::Text, QColor(220, 220, 220)); p.setColor(QPalette::Button, QColor(53, 53, 53)); p.setColor(QPalette::ButtonText, QColor(220, 220, 220)); p.setColor(QPalette::BrightText, Qt::red); p.setColor(QPalette::Link, QColor(42, 130, 218)); p.setColor(QPalette::Highlight, QColor(42, 130, 218)); p.setColor(QPalette::HighlightedText, Qt::white); p.setColor(QPalette::Disabled, QPalette::Text, QColor(127, 127, 127)); p.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(127, 127, 127)); app.setPalette(p); QString cfgDir = QCoreApplication::applicationDirPath(); QString cfgPath = cfgDir + "/vibechat.cfg"; /* миграция: старый конфиг chatgui.cfg → vibechat.cfg */ if (!QFile::exists(cfgPath) && QFile::exists(cfgDir + "/chatgui.cfg")) QFile::rename(cfgDir + "/chatgui.cfg", cfgPath); NodeConfig cfg(cfgPath); bool needSave = false; if (!cfg.exists()) { if (!cfg.create("", 1333)) { qCritical("Failed to create config: %s", qPrintable(cfgPath)); return 1; } } else { if (!cfg.load()) { qCritical("Cannot open/read config file: %s", qPrintable(cfgPath)); return 1; } if (cfg.hasErrors()) { for (const auto& err : cfg.errors()) fprintf(stderr, "CONFIG ERROR: %s\n", qPrintable(err)); return 1; } if (cfg.privKey().length() != 64 || cfg.pubKey().length() != 64) { qWarning("Config: keys missing/invalid, regenerating identity"); cfg.generateIdentity(); needSave = true; } if (cfg.nodeId().length() != 16) { qWarning("Config: my_node_id missing/invalid, regenerating identity"); cfg.generateIdentity(); needSave = true; } if (needSave) cfg.save(); } QString dbPath = cfg.dbPath(); if (dbPath.isEmpty()) dbPath = "chat_data"; if (!QDir::isAbsolutePath(dbPath)) dbPath = QCoreApplication::applicationDirPath() + "/" + dbPath; QDir(dbPath).mkpath("."); QString debugFile = cfg.debugFile(); if (debugFile.isEmpty()) debugFile = "chatgui.log"; if (!QDir::isAbsolutePath(debugFile)) debugFile = QCoreApplication::applicationDirPath() + "/" + debugFile; DbManager db; MainWindow w(nullptr, &db, cfgPath, dbPath, debugFile, cfg.debugLevel(), cfg.debugCategories()); w.show(); #ifdef _WIN32 g_ctrl_node = w.utunNode(); g_mainWindow = &w; SetConsoleCtrlHandler(console_ctrl_handler, TRUE); #endif int ret = app.exec(); DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "main: app.exec() returned %d", ret); return ret; }