Enable VibeChat UTUN service and package the Wintun driver
Add a settings button to open the current config in an external editor. Wait for core, UTUN and chat startup before reporting readiness; verify startup failures and restarts. Include Wintun DLL and license notices in Windows builds and installers.
This commit is contained in:
1 parent
bdb6bf80a4
commit
ffe563bb9f
13 files changed
+253
-26
No files matched your search
@@ -350,6 +350,16 @@ if(WIN32)
|
||||
set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/vibechat.rc" PROPERTIES
|
||||
OBJECT_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/resources/icons/app_icon.ico")
|
||||
message(STATUS "Windows executable icon: resources/icons/app_icon.ico")
|
||||
# Wintun загружается через LoadLibrary и не попадает в обход импортов EXE.
|
||||
add_custom_command(TARGET vibechat POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/../../lib/wintun.dll"
|
||||
"$<TARGET_FILE_DIR:vibechat>/wintun.dll"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:vibechat>/licenses/wintun"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/../../lib/wintun-LICENSE.txt"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/../../lib/wintun-api-LICENSE.txt"
|
||||
"$<TARGET_FILE_DIR:vibechat>/licenses/wintun"
|
||||
COMMENT "Deploying Wintun driver DLL and licenses" VERBATIM)
|
||||
endif()
|
||||
|
||||
target_include_directories(vibechat PRIVATE ${CMAKE_SOURCE_DIR}/../../lib ${CMAKE_SOURCE_DIR}/../../src ${CMAKE_SOURCE_DIR}/db ${CMAKE_SOURCE_DIR}/../../lib/libopus/include ${FFMPEG_INCLUDE_DIRS})
|
||||
@@ -594,6 +604,11 @@ target_include_directories(test_radio_mute_bridge PRIVATE ${UTUN_INCLUDE_DIRS})
|
||||
target_link_libraries(test_radio_mute_bridge PRIVATE ${QT_LIBS} ${QT_TEST_LIB} utun_voice utun pthread)
|
||||
add_test(NAME test_radio_mute_bridge COMMAND test_radio_mute_bridge)
|
||||
set_tests_properties(test_radio_mute_bridge PROPERTIES TIMEOUT 15 ENVIRONMENT "QT_QPA_PLATFORM=offscreen")
|
||||
add_executable(test_utun_node tests/test_utun_node.cpp transport/utun_node.cpp transport/gui_bridge_impl.cpp transport/stream_bridge.cpp)
|
||||
target_include_directories(test_utun_node PRIVATE ${UTUN_INCLUDE_DIRS})
|
||||
target_link_libraries(test_utun_node PRIVATE ${QT_LIBS} ${QT_TEST_LIB} utun_voice utun pthread)
|
||||
add_test(NAME test_utun_node COMMAND test_utun_node)
|
||||
set_tests_properties(test_utun_node PROPERTIES TIMEOUT 30 ENVIRONMENT "QT_QPA_PLATFORM=offscreen")
|
||||
set_tests_properties(test_radio_member_mute PROPERTIES TIMEOUT 20)
|
||||
if(UNIX AND NOT APPLE)
|
||||
target_compile_definitions(test_radio_member_mute PRIVATE UTUN_TEST_WRAP_VAD)
|
||||
|
||||
@@ -72,6 +72,11 @@ test ${PIPESTATUS[0]} -eq 0 || { log "Build FAILED"; exit 1; }
|
||||
|
||||
log ""
|
||||
log "[3/4] Deploying runtime DLLs..."
|
||||
# Повторная сборка восстанавливает DLL, даже если EXE не перелинковывался.
|
||||
cp "$SRC_DIR/../../lib/wintun.dll" "$BUILD_DIR/wintun.dll" || {
|
||||
log " ERROR: cannot deploy bundled wintun.dll to $BUILD_DIR"; exit 1;
|
||||
}
|
||||
log " OK: wintun.dll (bundled x64 TUN driver)"
|
||||
RLOTTIE_DLL="$SRC_DIR/rlottie_build/librlottie.dll"
|
||||
[ -f "$RLOTTIE_DLL" ] || { log " ERROR: librlottie.dll not found: $RLOTTIE_DLL"; exit 1; }
|
||||
cp "$RLOTTIE_DLL" "$BUILD_DIR/librlottie.dll" || {
|
||||
@@ -96,7 +101,7 @@ fi
|
||||
MAIN_DLLS=(
|
||||
"Qt${QT_MAJOR}Core.dll" "Qt${QT_MAJOR}Gui.dll" "Qt${QT_MAJOR}Network.dll" "Qt${QT_MAJOR}Widgets.dll"
|
||||
"platforms/qwindows.dll" "imageformats/qjpeg.dll" "imageformats/qwebp.dll"
|
||||
"libstdc++-6.dll" "libgcc_s_*.dll" "libwinpthread-1.dll" "librlottie.dll" "zlib1.dll" "libcrypto-*.dll"
|
||||
"libstdc++-6.dll" "libgcc_s_*.dll" "libwinpthread-1.dll" "librlottie.dll" "wintun.dll" "zlib1.dll" "libcrypto-*.dll"
|
||||
)
|
||||
if [ -d "$FFMPEG_BIN" ]; then
|
||||
MAIN_DLLS+=("avcodec-*.dll" "avformat-*.dll" "avutil-*.dll" "swscale-*.dll" "swresample-*.dll")
|
||||
|
||||
@@ -50,7 +50,7 @@ if [[ -f "$BUILD_DIR/chatgui_capture_enabled.txt" && "$(tr -d '\r\n' < "$BUILD_D
|
||||
CAPTURE_RUNTIME=(Qt6Multimedia.dll Qt6MultimediaWidgets.dll multimedia/ffmpegmediaplugin.dll)
|
||||
fi
|
||||
for file in "${QT_PACKAGE}Core.dll" "${QT_PACKAGE}Gui.dll" "${QT_PACKAGE}Network.dll" "${QT_PACKAGE}Widgets.dll" \
|
||||
librlottie.dll platforms/qwindows.dll imageformats/qjpeg.dll imageformats/qwebp.dll "${CAPTURE_RUNTIME[@]}"; do
|
||||
librlottie.dll wintun.dll platforms/qwindows.dll imageformats/qjpeg.dll imageformats/qwebp.dll "${CAPTURE_RUNTIME[@]}"; do
|
||||
[[ -f "$BUILD_DIR/$file" ]] || { echo "ERROR: missing runtime file: $BUILD_DIR/$file" >&2; exit 1; }
|
||||
done
|
||||
|
||||
@@ -64,13 +64,15 @@ done
|
||||
mkdir -p "$PACKAGE_DIR/licenses/rlottie"
|
||||
cp "$SRC_DIR/rlottie_src/COPYING" "$PACKAGE_DIR/licenses/rlottie/"
|
||||
cp -R "$SRC_DIR/rlottie_src/licenses" "$PACKAGE_DIR/licenses/rlottie/"
|
||||
mkdir -p "$PACKAGE_DIR/licenses/wintun"
|
||||
cp "$SRC_DIR/../../lib/wintun-LICENSE.txt" "$SRC_DIR/../../lib/wintun-api-LICENSE.txt" "$PACKAGE_DIR/licenses/wintun/"
|
||||
|
||||
# Проверяем архитектуру/полноту уже собранного комплекта, включая динамические Qt-плагины.
|
||||
SYSTEM_DIR=$(cygpath -u "${WINDIR:-C:\\Windows}/System32")
|
||||
source "$SRC_DIR/compiler_runtime.sh"
|
||||
declare -A LICENSE_PACKAGES=()
|
||||
declare -A INCLUDED=()
|
||||
FILES=(vibechat.exe platforms/qwindows.dll imageformats/qjpeg.dll imageformats/qwebp.dll)
|
||||
FILES=(vibechat.exe wintun.dll platforms/qwindows.dll imageformats/qjpeg.dll imageformats/qwebp.dll)
|
||||
FILES+=("${CAPTURE_RUNTIME[@]}")
|
||||
HAS_FFMPEG=0
|
||||
for ((i = 0; i < ${#FILES[@]}; i++)); do
|
||||
@@ -101,7 +103,7 @@ for ((i = 0; i < ${#FILES[@]}; i++)); do
|
||||
echo "INFO: verified installer runtime: ${file#"$PACKAGE_DIR/"}"
|
||||
|
||||
# Лицензии внешних библиотек берём из пакетов той же UCRT64-сборки.
|
||||
[[ "$name" != vibechat.exe && "$name" != librlottie.dll ]] || continue
|
||||
[[ "$name" != vibechat.exe && "$name" != librlottie.dll && "$name" != wintun.dll ]] || continue
|
||||
if [[ -f "$SRC_DIR/ffmpeg/ffmpeg_build/install/bin/$name" ]] && ! chatgui_is_compiler_runtime_dll "$name"; then
|
||||
HAS_FFMPEG=1
|
||||
continue
|
||||
|
||||
@@ -37,6 +37,10 @@ extern "C" {
|
||||
#include <QScrollArea>
|
||||
#include <QSpinBox>
|
||||
#include <QSettings>
|
||||
#include <QDesktopServices>
|
||||
#include <QFileInfo>
|
||||
#include <QProcess>
|
||||
#include <QUrl>
|
||||
|
||||
SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, UtunNode* node,
|
||||
AudioRecorder* recorder, QWidget* parent)
|
||||
@@ -246,6 +250,10 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, UtunNod
|
||||
// --- Bottom buttons ---
|
||||
auto* btnLayout = new QHBoxLayout();
|
||||
btnLayout->setSpacing(8);
|
||||
auto* editConfigBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Edit config"), rightPanel);
|
||||
editConfigBtn->setObjectName(QStringLiteral("editConfig"));
|
||||
connect(editConfigBtn, &QPushButton::clicked, this, &SettingsDialog::onEditConfig);
|
||||
btnLayout->addWidget(editConfigBtn);
|
||||
btnLayout->addStretch();
|
||||
|
||||
auto* cancelBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Cancel"), rightPanel);
|
||||
@@ -494,6 +502,30 @@ void SettingsDialog::onSave() {
|
||||
accept();
|
||||
}
|
||||
|
||||
void SettingsDialog::onEditConfig() {
|
||||
const QString path = QFileInfo(m_configPath).absoluteFilePath();
|
||||
#ifdef Q_OS_WIN
|
||||
QProcess editor;
|
||||
editor.setProgram(QStringLiteral("notepad.exe"));
|
||||
editor.setArguments({path});
|
||||
const bool opened = editor.startDetached();
|
||||
if (!opened) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Settings: cannot start Notepad path=%s error=%s", qPrintable(path), qPrintable(editor.errorString()));
|
||||
}
|
||||
#else
|
||||
const bool opened = QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
||||
if (!opened) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Settings: desktop service could not open config path=%s", qPrintable(path));
|
||||
}
|
||||
#endif
|
||||
if (!opened) {
|
||||
QMessageBox::warning(this, QCoreApplication::translate("ChatGui", "Edit config"),
|
||||
QCoreApplication::translate("ChatGui", "Could not open the configuration file in a text editor:\n%1").arg(path));
|
||||
return;
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Settings: config editor launched path=%s", qPrintable(path));
|
||||
}
|
||||
|
||||
void SettingsDialog::onOpenConnMonitor() {
|
||||
if (m_connMonitorWindow) {
|
||||
m_connMonitorWindow->raise();
|
||||
|
||||
@@ -33,6 +33,8 @@ public:
|
||||
private slots:
|
||||
void onCategoryChanged(int row);
|
||||
void onSave();
|
||||
// Открывает текущий конфиг во внешнем текстовом редакторе; ошибки показывает пользователю.
|
||||
void onEditConfig();
|
||||
void onOpenConnMonitor();
|
||||
|
||||
private:
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/* Desktop запускает UTUN вместе с чатом; ошибка TUN откатывает запуск до сигнала started. */
|
||||
#include "../transport/utun_node.h"
|
||||
#include "../transport/gui_bridge.h"
|
||||
#include <QtTest>
|
||||
#include <QFile>
|
||||
#include <QTemporaryDir>
|
||||
|
||||
extern "C" {
|
||||
#include "utun_instance.h"
|
||||
#include "tun_if.h"
|
||||
#include "routing_layer/topo_group.h"
|
||||
#include "u_async.h"
|
||||
#include "debug_config.h"
|
||||
}
|
||||
|
||||
static const QByteArray identity =
|
||||
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
|
||||
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n";
|
||||
|
||||
class UtunNodeTest : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void initTestCase() { gui_bridge_init(); }
|
||||
void cleanup() { debug_disable_file_output(); }
|
||||
|
||||
void services_data() {
|
||||
QTest::addColumn<QByteArray>("settings");
|
||||
QTest::addColumn<bool>("withTun");
|
||||
QTest::newRow("chat-default") << QByteArray() << false;
|
||||
QTest::newRow("tun-disabled") << QByteArray("tun_enabled=no\ntun_ifname=unused\ntun_ip=198.18.0.1\n") << false;
|
||||
#ifndef Q_OS_WIN
|
||||
QTest::newRow("tun-queues") << QByteArray("tun_enabled=yes\ntun_test_mode=1\ntun_ifname=vibetest\ntun_ip=198.18.0.1\n") << true;
|
||||
#endif
|
||||
if (qEnvironmentVariableIsSet("UTUN_TEST_REAL_TUN"))
|
||||
QTest::newRow("tun-real") << QByteArray("tun_enabled=yes\ntun_ifname=vibetest\ntun_ip=198.18.0.1\n") << true;
|
||||
}
|
||||
|
||||
void services() {
|
||||
QFETCH(QByteArray, settings); QFETCH(bool, withTun);
|
||||
QTemporaryDir directory; QVERIFY(directory.isValid());
|
||||
const QString path = directory.filePath("node.cfg");
|
||||
QFile config(path); QVERIFY(config.open(QIODevice::WriteOnly));
|
||||
const QByteArray text = "[global]\n" + identity + settings + "[server:local]\naddr=127.0.0.1:0\ntype=private\n"
|
||||
"[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\n[ntp]\nenabled=no\n";
|
||||
QCOMPARE(config.write(text), text.size()); config.close();
|
||||
UtunNode node;
|
||||
node.setDbPath(directory.filePath("db")); node.setDebugFile(directory.filePath("node.log"));
|
||||
node.setDebugCategories("sys=info,tun=debug,general=info");
|
||||
QSignalSpy started(&node, &UtunNode::started);
|
||||
for (int attempt = 0; attempt < 2; ++attempt) {
|
||||
const bool running = node.start(path);
|
||||
if (!running) {
|
||||
directory.setAutoRemove(false);
|
||||
qWarning() << "Startup diagnostics:" << directory.filePath("node.log");
|
||||
}
|
||||
QVERIFY(running); QCOMPARE(started.count(), attempt + 1);
|
||||
QVERIFY(node.isRunning() && gui_bridge_is_uasync_ready());
|
||||
bool ready = false, valid = false;
|
||||
struct Probe { QObject* receiver; bool* ready; bool* valid; bool tun; } probe{this, &ready, &valid, withTun};
|
||||
gui_bridge_post_uasync_fn([](void* arg) {
|
||||
auto* p = static_cast<Probe*>(arg);
|
||||
auto* inst = gui_bridge_get_inst();
|
||||
const bool ok = inst && inst->core_started && inst->utun_started && inst->chat_started &&
|
||||
uasync_is_running(inst->ua) && topo_groups_get_default(inst->topo_groups) &&
|
||||
(inst->tun != nullptr) == p->tun;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_TUN, "desktop startup test: services=%d expected_tun=%d", ok, p->tun);
|
||||
QMetaObject::invokeMethod(p->receiver, [p, ok] { *p->valid = ok; *p->ready = true; }, Qt::QueuedConnection);
|
||||
}, &probe);
|
||||
QTRY_VERIFY_WITH_TIMEOUT(ready, 3000); QVERIFY(valid);
|
||||
node.stop();
|
||||
QVERIFY(!node.isRunning() && !node.instance() && !gui_bridge_get_inst() && !gui_bridge_is_uasync_ready());
|
||||
}
|
||||
}
|
||||
|
||||
void tunFailureRejectsStartup() {
|
||||
QTemporaryDir directory; QVERIFY(directory.isValid());
|
||||
const QString path = directory.filePath("node.cfg");
|
||||
QFile config(path); QVERIFY(config.open(QIODevice::WriteOnly));
|
||||
// Нет tun_ip: отказ до обращения к ОС воспроизводится и на Windows без установленного драйвера.
|
||||
const QByteArray text = "[global]\n" + identity +
|
||||
"tun_enabled=yes\n[server:local]\naddr=127.0.0.1:0\ntype=private\n[ntp]\nenabled=no\n";
|
||||
QCOMPARE(config.write(text), text.size()); config.close();
|
||||
UtunNode node;
|
||||
node.setDbPath(directory.filePath("db")); node.setDebugFile(directory.filePath("node.log"));
|
||||
QSignalSpy started(&node, &UtunNode::started), errors(&node, &UtunNode::error);
|
||||
QVERIFY(!node.start(path)); QCOMPARE(started.count(), 0);
|
||||
QVERIFY(!node.isRunning() && !node.instance() && !gui_bridge_get_inst() && !gui_bridge_is_uasync_ready());
|
||||
QTRY_COMPARE(errors.count(), 1);
|
||||
const QString error = errors.first().first().toString();
|
||||
QVERIFY2(error.contains("utun_service_start"), qPrintable(error));
|
||||
QVERIFY(config.open(QIODevice::WriteOnly | QIODevice::Truncate));
|
||||
QByteArray disabled = text; disabled.replace("tun_enabled=yes", "tun_enabled=no");
|
||||
QCOMPARE(config.write(disabled), disabled.size()); config.close();
|
||||
QVERIFY(node.start(path)); QCOMPARE(started.count(), 1);
|
||||
node.stop(); QVERIFY(!node.isRunning() && !gui_bridge_is_uasync_ready());
|
||||
}
|
||||
};
|
||||
QTEST_MAIN(UtunNodeTest)
|
||||
#include "test_utun_node.moc"
|
||||
@@ -52,6 +52,9 @@ try:
|
||||
shutil.copyfile(source / name, target)
|
||||
for name in ('LICENSE', 'LICENSING', 'THIRD_PARTY_NOTICES'):
|
||||
write(case / name, 'fixture license\n')
|
||||
for name in ('wintun-LICENSE.txt', 'wintun-api-LICENSE.txt'):
|
||||
write(case / 'lib' / name, 'Wintun notice\n')
|
||||
write(case / 'lib/wintun.dll', 'DLL Name: KERNEL32.dll\n')
|
||||
write(gui / 'rlottie_src/COPYING', 'rlottie notice\n')
|
||||
write(gui / 'rlottie_src/licenses/COPYING.MIT', 'MIT notice\n')
|
||||
write(prefix / 'share/licenses/fixture/COPYING', 'runtime notice\n')
|
||||
@@ -91,7 +94,7 @@ files = sorted(str(p.relative_to(package)) for p in package.rglob('*') if p.is_f
|
||||
'Qt6Widgets.dll': ['Qt6Core.dll', 'Qt6Gui.dll'],
|
||||
'Qt6Core.dll': ['KERNEL32.dll', 'api-ms-win-core-test.dll'],
|
||||
'Qt6Gui.dll': ['helper.dll'], 'Qt6Network.dll': ['Qt6Core.dll'],
|
||||
'helper.dll': ['Qt6Core.dll'], 'librlottie.dll': ['KERNEL32.dll'],
|
||||
'helper.dll': ['Qt6Core.dll'], 'librlottie.dll': ['KERNEL32.dll'], 'wintun.dll': ['KERNEL32.dll'],
|
||||
'platforms/qwindows.dll': ['platform-helper.dll'],
|
||||
'imageformats/qjpeg.dll': ['jpeg-helper.dll'], 'imageformats/qwebp.dll': ['webp-helper.dll'],
|
||||
'platform-helper.dll': ['KERNEL32.dll'], 'jpeg-helper.dll': ['KERNEL32.dll'], 'webp-helper.dll': ['KERNEL32.dll'],
|
||||
@@ -103,7 +106,8 @@ files = sorted(str(p.relative_to(package)) for p in package.rglob('*') if p.is_f
|
||||
runtime = prefix / 'share/qt6/plugins' / name
|
||||
else:
|
||||
runtime = prefix / 'bin' / name
|
||||
write(runtime, content)
|
||||
if name != 'wintun.dll':
|
||||
write(runtime, content)
|
||||
for name in ('vibechat.cfg', 'chatgui.cfg', 'chat_data/chats.db', 'chat_data/media/private.bin',
|
||||
'chatgui.log', 'vibechat.lock', 'test_gui.exe', 'CMakeCache.txt', 'Qt5Core.dll', 'obsolete.dll'):
|
||||
write(build / name, 'PRIVATE OR STALE FILE\n')
|
||||
@@ -112,6 +116,7 @@ files = sorted(str(p.relative_to(package)) for p in package.rglob('*') if p.is_f
|
||||
assert not (build / 'installer').exists(), 'Preflight created build artifacts'
|
||||
result = package()
|
||||
expected = {'vibechat.exe', *libraries, 'licenses/LICENSE', 'licenses/LICENSING', 'licenses/THIRD_PARTY_NOTICES',
|
||||
'licenses/wintun/wintun-LICENSE.txt', 'licenses/wintun/wintun-api-LICENSE.txt',
|
||||
'licenses/rlottie/COPYING', 'licenses/rlottie/licenses/COPYING.MIT', 'licenses/runtime/fixture/COPYING'}
|
||||
assert set(result['files']) == expected, result['files']
|
||||
print('PASS: complete dependency graph, Qt plugin helpers and license notices; private files and stale DLLs excluded')
|
||||
@@ -120,6 +125,17 @@ files = sorted(str(p.relative_to(package)) for p in package.rglob('*') if p.is_f
|
||||
assert result2['files'] == result['files']
|
||||
print('PASS: repeated packaging and paths containing spaces')
|
||||
|
||||
driver = build / 'wintun.dll'
|
||||
saved_driver = driver.read_text()
|
||||
driver.unlink()
|
||||
failed = run(ok=False)
|
||||
assert 'missing runtime file:' in failed.stderr and 'wintun.dll' in failed.stderr
|
||||
write(driver, 'wrong-architecture\n')
|
||||
failed = run(ok=False)
|
||||
assert 'wintun.dll is not a Windows x64 binary' in failed.stderr
|
||||
write(driver, saved_driver)
|
||||
print('PASS: dynamically loaded Wintun and its licenses are included; missing/wrong architecture DLL is rejected')
|
||||
|
||||
capture = {'Qt6Multimedia.dll': ['Qt6Core.dll'], 'Qt6MultimediaWidgets.dll': ['Qt6Multimedia.dll'],
|
||||
'multimedia/ffmpegmediaplugin.dll': ['capture-helper.dll', 'MFPlat.dll'], 'capture-helper.dll': ['KERNEL32.dll']}
|
||||
write(root / 'Windows/System32/MFPlat.dll', 'system multimedia file\n')
|
||||
@@ -207,11 +223,13 @@ printf '%s\\n' "$*" >> "$CMAKE_CALLS"
|
||||
original_exe += f'DLL Name: {name}\n'
|
||||
write(build / 'vibechat.exe', original_exe)
|
||||
calls = root / 'cmake-calls.log'
|
||||
(build / 'wintun.dll').unlink()
|
||||
built = subprocess.run(['bash', str(gui / 'build.sh'), '--installer', '-j2'], cwd=gui,
|
||||
env={**environment, 'CMAKE_CALLS': str(calls)}, text=True, capture_output=True)
|
||||
assert built.returncode == 0, built.stdout + built.stderr
|
||||
assert '-DCMAKE_BUILD_TYPE=Release' in calls.read_text()
|
||||
assert 'installer created:' in built.stdout and 'Build SUCCESS' in built.stdout
|
||||
assert (build / 'wintun.dll').read_bytes() == (case / 'lib/wintun.dll').read_bytes()
|
||||
failed = subprocess.run(['bash', str(gui / 'build.sh'), '--installer'], cwd=gui,
|
||||
env={**environment, 'CMAKE_CALLS': str(calls), 'COMPILER_FAIL': '1'}, text=True, capture_output=True)
|
||||
assert failed.returncode != 0 and 'Build SUCCESS' not in failed.stdout
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
<TS version="2.1" language="en_US" sourcelanguage="en_US">
|
||||
<context>
|
||||
<name>ChatGui</name>
|
||||
<message><source>Edit config</source><translation>Edit config</translation></message>
|
||||
<message><source>Could not open the configuration file in a text editor:
|
||||
%1</source><translation>Could not open the configuration file in a text editor:
|
||||
%1</translation></message>
|
||||
<message><source>Maximum QP</source><translation>Maximum QP</translation></message>
|
||||
<message><source>Adapt QP to the target bitrate</source><translation>Adapt QP to the target bitrate</translation></message>
|
||||
<message><source>Current QP ceiling: %1</source><translation>Current QP ceiling: %1</translation></message>
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
<TS version="2.1" language="ru_RU" sourcelanguage="en_US">
|
||||
<context>
|
||||
<name>ChatGui</name>
|
||||
<message><source>Edit config</source><translation>Редактировать конфиг</translation></message>
|
||||
<message><source>Could not open the configuration file in a text editor:
|
||||
%1</source><translation>Не удалось открыть конфиг в текстовом редакторе:
|
||||
%1</translation></message>
|
||||
<message><source>Maximum QP</source><translation>Верхний предел QP</translation></message>
|
||||
<message><source>Adapt QP to the target bitrate</source><translation>Подстраивать QP под заданный битрейт</translation></message>
|
||||
<message><source>Current QP ceiling: %1</source><translation>Текущий предел QP: %1</translation></message>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "utun_node.h"
|
||||
#include "stream_bridge.h"
|
||||
#include <cinttypes>
|
||||
#include <utility>
|
||||
#include <QDir>
|
||||
#include "../../lib/socket_compat.h"
|
||||
|
||||
@@ -47,15 +48,12 @@ bool UtunNode::start(const QString& configPath) {
|
||||
m_configPath = configPath;
|
||||
m_stop = false;
|
||||
m_running = true;
|
||||
m_thread = std::thread(&UtunNode::runLoop, this);
|
||||
|
||||
for (int i = 0; i < 50 && m_running && !m_instance; ++i)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
|
||||
if (!m_instance) {
|
||||
m_stop = true;
|
||||
std::promise<bool> startup;
|
||||
auto ready = startup.get_future();
|
||||
m_thread = std::thread(&UtunNode::runLoop, this, std::move(startup));
|
||||
if (!ready.get()) {
|
||||
if (m_thread.joinable()) m_thread.join();
|
||||
m_running = false;
|
||||
finalize();
|
||||
return false;
|
||||
}
|
||||
emit started();
|
||||
@@ -125,7 +123,7 @@ bool UtunNode::ntpEnabled() const {
|
||||
return m_instance ? m_instance->ntp.enabled : false;
|
||||
}
|
||||
|
||||
void UtunNode::runLoop() {
|
||||
void UtunNode::runLoop(std::promise<bool> startup) {
|
||||
debug_config_init();
|
||||
debug_enable_function_name(0);
|
||||
|
||||
@@ -180,11 +178,13 @@ void UtunNode::runLoop() {
|
||||
g_debug_config.thread_marker = 2;
|
||||
}
|
||||
|
||||
utun_instance_set_tun_init_enabled(0);
|
||||
utun_instance_set_tun_init_enabled(1);
|
||||
|
||||
struct UASYNC* ua = uasync_create();
|
||||
if (!ua) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "desktop startup: uasync_create failed");
|
||||
QMetaObject::invokeMethod(this, [this] { emit error("uasync_create failed"); });
|
||||
startup.set_value(false);
|
||||
return;
|
||||
}
|
||||
m_ua = ua;
|
||||
@@ -194,9 +194,11 @@ void UtunNode::runLoop() {
|
||||
* иначе auto_socket не сможет сохранить/переиспользовать порты (БД ещё не открыта). */
|
||||
struct utun_config* cfg = parse_config(m_configPath.toUtf8().constData());
|
||||
if (!cfg) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "desktop startup: parse_config failed path=%s", qPrintable(m_configPath));
|
||||
QMetaObject::invokeMethod(this, [this] { emit error("parse_config failed"); });
|
||||
uasync_destroy(ua, 0);
|
||||
m_ua = nullptr;
|
||||
startup.set_value(false);
|
||||
return;
|
||||
}
|
||||
cfg->global.db_sync_enabled = 1;
|
||||
@@ -207,25 +209,35 @@ void UtunNode::runLoop() {
|
||||
|
||||
m_instance = utun_instance_create_from_config(ua, cfg);
|
||||
if (!m_instance) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "desktop startup: instance creation failed");
|
||||
free_config(cfg);
|
||||
QMetaObject::invokeMethod(this, [this] { emit error("utun_instance_create_from_config failed"); });
|
||||
uasync_destroy(ua, 0);
|
||||
m_ua = nullptr;
|
||||
startup.set_value(false);
|
||||
return;
|
||||
}
|
||||
|
||||
m_instance->test_user_ptr = this;
|
||||
|
||||
/* set ua for gui_bridge before init, so GUI can post */
|
||||
gui_bridge_set_uasync(ua);
|
||||
gui_bridge_set_inst(m_instance);
|
||||
|
||||
if (utun_core_start(m_instance) != 0) {
|
||||
QMetaObject::invokeMethod(this, [this] { emit error("utun_instance_init failed"); });
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "desktop startup: utun_core_start failed");
|
||||
QMetaObject::invokeMethod(this, [this] { emit error("utun_core_start failed"); });
|
||||
utun_instance_destroy(m_instance);
|
||||
m_instance = nullptr;
|
||||
uasync_destroy(ua, 0);
|
||||
m_ua = nullptr;
|
||||
startup.set_value(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (utun_service_start(m_instance) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "desktop startup: UTUN service failed tun_enabled=%d interface=%s",
|
||||
cfg->global.tun_enabled, cfg->global.tun_ifname);
|
||||
QMetaObject::invokeMethod(this, [this] { emit error("utun_service_start failed; check TUN configuration, driver and permissions"); });
|
||||
utun_instance_destroy(m_instance); m_instance = nullptr;
|
||||
uasync_destroy(ua, 0); m_ua = nullptr;
|
||||
startup.set_value(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -242,9 +254,12 @@ void UtunNode::runLoop() {
|
||||
});
|
||||
|
||||
if (chat_service_start(m_instance) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "desktop startup: chat_service_start failed");
|
||||
QMetaObject::invokeMethod(this, [this] { emit error("chat_service_start failed"); });
|
||||
utun_instance_destroy(m_instance); m_instance = nullptr;
|
||||
uasync_destroy(ua, 0); m_ua = nullptr; return;
|
||||
uasync_destroy(ua, 0); m_ua = nullptr;
|
||||
startup.set_value(false);
|
||||
return;
|
||||
}
|
||||
call_audio_init(m_instance);
|
||||
call_video_port_attach(m_instance);
|
||||
@@ -257,7 +272,13 @@ void UtunNode::runLoop() {
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "chat_core + chat_sync initialized");
|
||||
uasync_mark_running(ua);
|
||||
gui_bridge_set_uasync(ua);
|
||||
gui_bridge_set_inst(m_instance);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "utun_node: poll started ua=%p running=%d", (void*)ua, uasync_is_running(ua));
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SYS, "desktop services ready: core=%d utun=%d chat=%d tun=%d interface=%s",
|
||||
m_instance->core_started, m_instance->utun_started, m_instance->chat_started,
|
||||
cfg->global.tun_enabled, cfg->global.tun_ifname);
|
||||
startup.set_value(true);
|
||||
|
||||
while (!m_stop) {
|
||||
uasync_poll(ua, 100);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* Desktop-владелец встроенного ядра: читает INI, создаёт uasync и запускает core/chat в std::thread.
|
||||
/* Desktop-владелец встроенного ядра: читает INI, создаёт uasync и запускает core/UTUN/chat в std::thread.
|
||||
* TUN создаётся по конфигу; start подтверждает запуск только после готовности сервисов и poll.
|
||||
* requestStop сигнализирует завершение, stop ждёт поток; worker уничтожает instance,
|
||||
* finalize после завершения worker освобождает uasync и сбрасывает ссылку GUI-моста.
|
||||
* instance возвращает заимствованный указатель: операции ядра отправлять в uasync через gui_bridge,
|
||||
@@ -11,6 +12,7 @@
|
||||
#include <QString>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <future>
|
||||
#include <thread>
|
||||
|
||||
struct UTUN_INSTANCE;
|
||||
@@ -21,6 +23,7 @@ public:
|
||||
explicit UtunNode(QObject* parent = nullptr);
|
||||
~UtunNode();
|
||||
|
||||
// Запускает сервисы по INI configPath; ждёт готовности, возвращает false после отката ошибки.
|
||||
bool start(const QString& configPath);
|
||||
void requestStop();
|
||||
void stop();
|
||||
@@ -46,7 +49,7 @@ signals:
|
||||
void error(const QString& msg);
|
||||
|
||||
private:
|
||||
void runLoop();
|
||||
void runLoop(std::promise<bool> startup);
|
||||
|
||||
UTUN_INSTANCE* m_instance = nullptr;
|
||||
struct UASYNC* m_ua = nullptr;
|
||||
|
||||
Reference in new issue
Block a user