Add minimized Windows autostart and preserve terminal output
This commit is contained in:
1 parent
abdca1acc7
commit
7c5b76b482
5 files changed
+69
-4
No files matched your search
@@ -216,7 +216,7 @@ set_source_files_properties(${CMAKE_SOURCE_DIR}/../../src/radio/radio_audio.c PR
|
|||||||
set_target_properties(utun_voice PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
set_target_properties(utun_voice PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||||
|
|
||||||
|
|
||||||
add_executable(vibechat
|
add_executable(vibechat WIN32
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/mainwindow.cpp
|
src/mainwindow.cpp
|
||||||
src/channellist.cpp
|
src/channellist.cpp
|
||||||
|
|||||||
@@ -51,6 +51,18 @@ BAT-скрипты автоматически включают --installer. От
|
|||||||
запускается от исходного пользователя. Входящие подключения разрешаются
|
запускается от исходного пользователя. Входящие подключения разрешаются
|
||||||
правилом "VibeChat (installer)" только для установленного EXE.
|
правилом "VibeChat (installer)" только для установленного EXE.
|
||||||
|
|
||||||
|
На странице дополнительных задач включена по умолчанию опция автозапуска
|
||||||
|
при входе в Windows для всех пользователей. Ярлык автозагрузки передаёт
|
||||||
|
--minimized: главное окно запускается свёрнутым, обычные ярлыки открывают его
|
||||||
|
как раньше. Выбор сохраняется при обновлении; снятие галочки удаляет прежний
|
||||||
|
ярлык автозагрузки. При удалении приложения этот ярлык также удаляется.
|
||||||
|
|
||||||
|
EXE собирается как GUI-приложение: запуск через Проводник, установщик или
|
||||||
|
автозагрузку не создаёт терминал. Запуск из терминала подключает родительскую
|
||||||
|
консоль и сохраняет перенаправление stdout/stderr, в том числе в файл или pipe.
|
||||||
|
Лог приложения показывает режим окна и наличие консоли, лог установки —
|
||||||
|
выбранный режим автозапуска и путь ярлыка.
|
||||||
|
|
||||||
При обновлении установщик предлагает закрыть приложение, использующее EXE/DLL.
|
При обновлении установщик предлагает закрыть приложение, использующее EXE/DLL.
|
||||||
Обновление и удаление сохраняют конфиг, ключи, историю и медиа пользователя.
|
Обновление и удаление сохраняют конфиг, ключи, историю и медиа пользователя.
|
||||||
Деинсталлятор удаляет своё правило firewall и установленные файлы программы.
|
Деинсталлятор удаляет своё правило firewall и установленные файлы программы.
|
||||||
@@ -68,3 +80,7 @@ BAT-скрипты автоматически включают --installer. От
|
|||||||
На чистой Windows без MSYS2/Qt: установка, запуск обычным пользователем,
|
На чистой Windows без MSYS2/Qt: установка, запуск обычным пользователем,
|
||||||
сообщения/звуки/медиа, обновление с сохранением идентичности, удаление,
|
сообщения/звуки/медиа, обновление с сохранением идентичности, удаление,
|
||||||
повторная установка. Проверить пути с пробелами и имя пользователя с кириллицей.
|
повторная установка. Проверить пути с пробелами и имя пользователя с кириллицей.
|
||||||
|
Проверить автозапуск после входа в Windows, свёрнутость при ранее развёрнутом
|
||||||
|
окне, восстановление из трея, сохранение выбора и отключение при обновлении.
|
||||||
|
При запуске из Проводника не должно быть консоли; из терминала проверить
|
||||||
|
вывод, перенаправление и Ctrl+C. Дочерний процесс камеры также не создаёт консоль.
|
||||||
@@ -14,6 +14,7 @@ AppPublisher=uTun
|
|||||||
DefaultDirName={autopf}\VibeChat
|
DefaultDirName={autopf}\VibeChat
|
||||||
DisableDirPage=no
|
DisableDirPage=no
|
||||||
DisableProgramGroupPage=yes
|
DisableProgramGroupPage=yes
|
||||||
|
UsePreviousTasks=yes
|
||||||
ArchitecturesAllowed=x64compatible
|
ArchitecturesAllowed=x64compatible
|
||||||
ArchitecturesInstallIn64BitMode=x64compatible
|
ArchitecturesInstallIn64BitMode=x64compatible
|
||||||
MinVersion=10.0
|
MinVersion=10.0
|
||||||
@@ -36,6 +37,11 @@ Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl"
|
|||||||
|
|
||||||
[Tasks]
|
[Tasks]
|
||||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||||
|
Name: "autostart"; Description: "{cm:AutoStart}"; GroupDescription: "{cm:AutoStartGroup}"
|
||||||
|
|
||||||
|
[InstallDelete]
|
||||||
|
; Снятая при обновлении галочка удаляет только наш прежний ярлык автозагрузки.
|
||||||
|
Type: files; Name: "{autostartup}\VibeChat.lnk"; Tasks: not autostart
|
||||||
|
|
||||||
[Files]
|
[Files]
|
||||||
Source: "{#PackageDir}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
Source: "{#PackageDir}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
@@ -43,11 +49,16 @@ Source: "{#PackageDir}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs
|
|||||||
[Icons]
|
[Icons]
|
||||||
Name: "{autoprograms}\VibeChat"; Filename: "{app}\vibechat.exe"; WorkingDir: "{app}"
|
Name: "{autoprograms}\VibeChat"; Filename: "{app}\vibechat.exe"; WorkingDir: "{app}"
|
||||||
Name: "{autodesktop}\VibeChat"; Filename: "{app}\vibechat.exe"; WorkingDir: "{app}"; Tasks: desktopicon
|
Name: "{autodesktop}\VibeChat"; Filename: "{app}\vibechat.exe"; WorkingDir: "{app}"; Tasks: desktopicon
|
||||||
|
Name: "{autostartup}\VibeChat"; Filename: "{app}\vibechat.exe"; Parameters: "--minimized"; WorkingDir: "{app}"; Tasks: autostart
|
||||||
|
|
||||||
[Run]
|
[Run]
|
||||||
Filename: "{app}\vibechat.exe"; Description: "{cm:LaunchProgram,VibeChat}"; WorkingDir: "{app}"; Flags: nowait postinstall skipifsilent runasoriginaluser
|
Filename: "{app}\vibechat.exe"; Description: "{cm:LaunchProgram,VibeChat}"; WorkingDir: "{app}"; Flags: nowait postinstall skipifsilent runasoriginaluser
|
||||||
|
|
||||||
[CustomMessages]
|
[CustomMessages]
|
||||||
|
english.AutoStartGroup=Startup:
|
||||||
|
russian.AutoStartGroup=Автозагрузка:
|
||||||
|
english.AutoStart=Start VibeChat minimized when signing in to Windows (for all users)
|
||||||
|
russian.AutoStart=Запускать VibeChat свёрнутым при входе в Windows (для всех пользователей)
|
||||||
english.FirewallFailed=Could not configure the VibeChat firewall rule. See the installation log for details.
|
english.FirewallFailed=Could not configure the VibeChat firewall rule. See the installation log for details.
|
||||||
russian.FirewallFailed=Не удалось настроить правило брандмауэра VibeChat. Подробности в журнале установки.
|
russian.FirewallFailed=Не удалось настроить правило брандмауэра VibeChat. Подробности в журнале установки.
|
||||||
|
|
||||||
@@ -75,6 +86,10 @@ var
|
|||||||
begin
|
begin
|
||||||
if CurStep <> ssPostInstall then Exit;
|
if CurStep <> ssPostInstall then Exit;
|
||||||
Log('VibeChat: installed to ' + ExpandConstant('{app}') + '; user config, keys and history are preserved');
|
Log('VibeChat: installed to ' + ExpandConstant('{app}') + '; user config, keys and history are preserved');
|
||||||
|
if WizardIsTaskSelected('autostart') then
|
||||||
|
Log('Startup: enabled with --minimized; shortcut=' + ExpandConstant('{autostartup}\VibeChat.lnk'))
|
||||||
|
else
|
||||||
|
Log('Startup: disabled; shortcut=' + ExpandConstant('{autostartup}\VibeChat.lnk'));
|
||||||
RemoveFirewallRule;
|
RemoveFirewallRule;
|
||||||
Parameters := 'advfirewall firewall add rule name="VibeChat (installer)" dir=in action=allow program=' +
|
Parameters := 'advfirewall firewall add rule name="VibeChat (installer)" dir=in action=allow program=' +
|
||||||
AddQuotes(ExpandConstant('{app}\vibechat.exe')) + ' enable=yes profile=any';
|
AddQuotes(ExpandConstant('{app}\vibechat.exe')) + ' enable=yes profile=any';
|
||||||
|
|||||||
@@ -15,10 +15,12 @@
|
|||||||
#include "call_camera_capture.h"
|
#include "call_camera_capture.h"
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <cerrno>
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <tlhelp32.h>
|
#include <tlhelp32.h>
|
||||||
|
#include <io.h>
|
||||||
#endif
|
#endif
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "../db/db_manager.h"
|
#include "../db/db_manager.h"
|
||||||
@@ -30,6 +32,28 @@
|
|||||||
static UtunNode* g_ctrl_node = nullptr;
|
static UtunNode* g_ctrl_node = nullptr;
|
||||||
static MainWindow* g_mainWindow = nullptr;
|
static MainWindow* g_mainWindow = nullptr;
|
||||||
|
|
||||||
|
// GUI EXE не создаёт консоль; подключаем только родительскую, сохраняя перенаправленные потоки.
|
||||||
|
static bool attach_parent_console(void) {
|
||||||
|
if (!AttachConsole(ATTACH_PARENT_PROCESS)) {
|
||||||
|
const DWORD error = GetLastError();
|
||||||
|
if (error != ERROR_ACCESS_DENIED) {
|
||||||
|
if (error != ERROR_INVALID_HANDLE && error != ERROR_INVALID_PARAMETER)
|
||||||
|
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "Windows parent console attach failed: error=%lu", (unsigned long)error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FILE* streams[] = {stdin, stdout, stderr};
|
||||||
|
const char* devices[] = {"CONIN$", "CONOUT$", "CONOUT$"};
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
const int fd = _fileno(streams[i]);
|
||||||
|
const HANDLE handle = fd < 0 ? INVALID_HANDLE_VALUE : (HANDLE)_get_osfhandle(fd);
|
||||||
|
if (handle && handle != INVALID_HANDLE_VALUE && GetFileType(handle) != FILE_TYPE_UNKNOWN) continue;
|
||||||
|
if (!freopen(devices[i], i == 0 ? "r" : "w", streams[i]))
|
||||||
|
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "Windows console stream reopen failed: stream=%d errno=%d", i, errno);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static DWORD count_threads(void) {
|
static DWORD count_threads(void) {
|
||||||
DWORD pid = GetCurrentProcessId(), count = 0;
|
DWORD pid = GetCurrentProcessId(), count = 0;
|
||||||
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
|
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
|
||||||
@@ -75,6 +99,10 @@ static struct ShutdownSentinel {
|
|||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
if (argc >= 2 && std::strcmp(argv[1], "--call-camera-worker") == 0) return call_camera_worker_main(argc, argv);
|
if (argc >= 2 && std::strcmp(argv[1], "--call-camera-worker") == 0) return call_camera_worker_main(argc, argv);
|
||||||
|
debug_config_init();
|
||||||
|
#ifdef _WIN32
|
||||||
|
const bool consoleAttached = attach_parent_console();
|
||||||
|
#endif
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
/* На Wayland композитор владеет позицией окна (move() игнорируется) —
|
/* На Wayland композитор владеет позицией окна (move() игнорируется) —
|
||||||
* для полноценного ручного move/resize с пропорцией запускаемся через XWayland. */
|
* для полноценного ручного move/resize с пропорцией запускаемся через XWayland. */
|
||||||
@@ -87,7 +115,7 @@ int main(int argc, char *argv[]) {
|
|||||||
app.setDesktopFileName("org.utun.vibechat");
|
app.setDesktopFileName("org.utun.vibechat");
|
||||||
app.setWindowIcon(QIcon(":/icons/app_icon.png"));
|
app.setWindowIcon(QIcon(":/icons/app_icon.png"));
|
||||||
app.setQuitOnLastWindowClosed(false);
|
app.setQuitOnLastWindowClosed(false);
|
||||||
debug_config_init();
|
const bool minimized = app.arguments().contains(QStringLiteral("--minimized"));
|
||||||
|
|
||||||
QString cfgDir = QCoreApplication::applicationDirPath();
|
QString cfgDir = QCoreApplication::applicationDirPath();
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@@ -218,6 +246,7 @@ int main(int argc, char *argv[]) {
|
|||||||
debug_apply_global_level(lvl.toUtf8().constData());
|
debug_apply_global_level(lvl.toUtf8().constData());
|
||||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "vibechat: version=%s", qPrintable(app.applicationVersion()));
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "vibechat: version=%s", qPrintable(app.applicationVersion()));
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "Windows console: attached=%d", consoleAttached);
|
||||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Windows storage: reason=%s config=%s db=%s log=%s lock=%s",
|
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Windows storage: reason=%s config=%s db=%s log=%s lock=%s",
|
||||||
qPrintable(storageReason), qPrintable(cfgPath), qPrintable(dbPath), qPrintable(debugFile),
|
qPrintable(storageReason), qPrintable(cfgPath), qPrintable(dbPath), qPrintable(debugFile),
|
||||||
qPrintable(cfgDir + "/vibechat.lock"));
|
qPrintable(cfgDir + "/vibechat.lock"));
|
||||||
@@ -228,7 +257,8 @@ int main(int argc, char *argv[]) {
|
|||||||
DbManager db;
|
DbManager db;
|
||||||
MainWindow w(nullptr, &db, cfgPath, dbPath, debugFile,
|
MainWindow w(nullptr, &db, cfgPath, dbPath, debugFile,
|
||||||
cfg.debugLevel(), cfg.debugCategories());
|
cfg.debugLevel(), cfg.debugCategories());
|
||||||
w.show();
|
if (minimized) w.showMinimized(); else w.show();
|
||||||
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "GUI startup: minimized_requested=%d minimized=%d", minimized, w.isMinimized());
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
g_ctrl_node = w.utunNode();
|
g_ctrl_node = w.utunNode();
|
||||||
|
|||||||
@@ -301,8 +301,10 @@ void MainWindow::restoreWindowGeometry() {
|
|||||||
if (!visible) target.moveTo(0, 0);
|
if (!visible) target.moveTo(0, 0);
|
||||||
|
|
||||||
setGeometry(target);
|
setGeometry(target);
|
||||||
|
// Поздний ответ БД сохраняет свёрнутость при --minimized и не показывает скрытое в tray окно.
|
||||||
if (m_db->getUiStateInt("window_maximized", 0))
|
if (m_db->getUiStateInt("window_maximized", 0))
|
||||||
showMaximized();
|
setWindowState(windowState() | Qt::WindowMaximized);
|
||||||
|
GUI_DEBUG("window geometry restored: maximized=%d minimized=%d visible=%d", isMaximized(), isMinimized(), isVisible());
|
||||||
|
|
||||||
if (m_splitter) {
|
if (m_splitter) {
|
||||||
const QStringList sp = m_db->getUiState("window_splitter").split(',');
|
const QStringList sp = m_db->getUiState("window_splitter").split(',');
|
||||||
@@ -933,6 +935,7 @@ void MainWindow::setupTray() {
|
|||||||
|
|
||||||
QAction *showAction = m_trayMenu->addAction(QCoreApplication::translate("ChatGui", "Show"));
|
QAction *showAction = m_trayMenu->addAction(QCoreApplication::translate("ChatGui", "Show"));
|
||||||
connect(showAction, &QAction::triggered, this, [this]() {
|
connect(showAction, &QAction::triggered, this, [this]() {
|
||||||
|
if (isMinimized()) setWindowState(windowState() & ~Qt::WindowMinimized);
|
||||||
show();
|
show();
|
||||||
raise();
|
raise();
|
||||||
activateWindow();
|
activateWindow();
|
||||||
@@ -999,6 +1002,7 @@ void MainWindow::onShutdownCheck() {
|
|||||||
void MainWindow::onTrayActivated(QSystemTrayIcon::ActivationReason reason) {
|
void MainWindow::onTrayActivated(QSystemTrayIcon::ActivationReason reason) {
|
||||||
if (reason == QSystemTrayIcon::Trigger ||
|
if (reason == QSystemTrayIcon::Trigger ||
|
||||||
reason == QSystemTrayIcon::DoubleClick) {
|
reason == QSystemTrayIcon::DoubleClick) {
|
||||||
|
if (isMinimized()) setWindowState(windowState() & ~Qt::WindowMinimized);
|
||||||
show();
|
show();
|
||||||
raise();
|
raise();
|
||||||
activateWindow();
|
activateWindow();
|
||||||
|
|||||||
Reference in new issue
Block a user