336 lines
11 KiB
C++
336 lines
11 KiB
C++
// Copyright 2007-2023 The Mumble Developers. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the LICENSE file at the root of the
|
|
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
|
|
|
|
#include "Overlay.h"
|
|
|
|
#include "Channel.h"
|
|
#include "MainWindow.h"
|
|
#include "MumbleApplication.h"
|
|
#include "OverlayConfig.h"
|
|
#include "User.h"
|
|
#include "Global.h"
|
|
|
|
#include "Overlay_win.h"
|
|
|
|
#include "../../overlay/overlay_exe/overlay_exe.h"
|
|
|
|
// Used by the overlay to detect whether we injected into ourselves.
|
|
extern "C" __declspec(dllexport) void mumbleSelfDetection(){};
|
|
|
|
// Determine if the current Mumble client is able to host
|
|
// x64 programs.
|
|
//
|
|
// If we're on x86, we use use the IsWoW64Process function
|
|
// to determine this. If we're on x64, we already know we're
|
|
// capable, so we simply return TRUE.
|
|
static bool canRun64BitPrograms() {
|
|
#if defined(_M_X64)
|
|
return TRUE;
|
|
#elif defined(_M_IX86)
|
|
typedef BOOL(WINAPI * IsWow64ProcessPtr)(HANDLE, BOOL *);
|
|
IsWow64ProcessPtr wow64check = (IsWow64ProcessPtr) GetProcAddress(GetModuleHandle(L"kernel32"), "IsWow64Process");
|
|
if (wow64check) {
|
|
BOOL isWoW64 = FALSE;
|
|
wow64check(GetCurrentProcess(), &isWoW64);
|
|
return isWoW64;
|
|
}
|
|
return FALSE;
|
|
#endif
|
|
}
|
|
|
|
OverlayPrivateWin::OverlayPrivateWin(QObject *p)
|
|
: OverlayPrivate(p), m_helper_enabled(true), m_helper64_enabled(true), m_mumble_handle(0), m_active(false) {
|
|
// Acquire a handle to ourselves and duplicate it. We duplicate it because
|
|
// want it to be inheritable by our helper processes, and the handle returned
|
|
// by GetCurrentProcess is not inheritable. Duplicating it makes it inheritable.
|
|
// This allows our helper processes to access the handle.
|
|
//
|
|
// The helper processes need a handle to us, their parent, to be able to listen
|
|
// detect when our process dies.
|
|
//
|
|
// The value of the handle is passed as an argument to the helper processes via
|
|
// the command line as a number. The HANDLE type in Windows is typedef'd to LPVOID,
|
|
// but for handles that are supposed to be shared between processes (like a process
|
|
// handle that we are using), only the lower 32-bits of the HANDLE are considered:
|
|
//
|
|
// "When sharing a handle between 32-bit and 64-bit applications, only the lower
|
|
// 32 bits are significant [...]"
|
|
//
|
|
// from https://msdn.microsoft.com/en-us/library/aa384203.aspx
|
|
HANDLE curproc = GetCurrentProcess();
|
|
if (!DuplicateHandle(curproc, curproc, curproc, &m_mumble_handle, 0, TRUE, DUPLICATE_SAME_ACCESS)) {
|
|
qFatal("OverlayPrivateWin: unable to duplicate handle to the Mumble process.");
|
|
return;
|
|
}
|
|
|
|
m_helper_exe_path =
|
|
QString::fromLatin1("%1/mumble_ol_helper.exe").arg(MumbleApplication::instance()->applicationVersionRootPath());
|
|
m_helper_exe_args << QString::number(OVERLAY_MAGIC_NUMBER)
|
|
<< QString::number(reinterpret_cast< quintptr >(m_mumble_handle));
|
|
m_helper_process = new QProcess(this);
|
|
|
|
connect(m_helper_process, SIGNAL(started()), this, SLOT(onHelperProcessStarted()));
|
|
|
|
connect(m_helper_process, SIGNAL(error(QProcess::ProcessError)), this,
|
|
SLOT(onHelperProcessError(QProcess::ProcessError)));
|
|
|
|
connect(m_helper_process, SIGNAL(finished(int, QProcess::ExitStatus)), this,
|
|
SLOT(onHelperProcessExited(int, QProcess::ExitStatus)));
|
|
|
|
m_helper_restart_timer = new QTimer(this);
|
|
m_helper_restart_timer->setSingleShot(true);
|
|
connect(m_helper_restart_timer, SIGNAL(timeout()), this, SLOT(onDelayedRestartTimerTriggered()));
|
|
|
|
if (!Global::get().s.bOverlayWinHelperX86Enable) {
|
|
qWarning("OverlayPrivateWin: mumble_ol_helper.exe (32-bit overlay helper) disabled via "
|
|
"'overlay_win/enable_x86_helper' config option.");
|
|
m_helper_enabled = false;
|
|
}
|
|
|
|
m_helper64_exe_path = QString::fromLatin1("%1/mumble_ol_helper_x64.exe")
|
|
.arg(MumbleApplication::instance()->applicationVersionRootPath());
|
|
m_helper64_exe_args = m_helper_exe_args;
|
|
m_helper64_process = new QProcess(this);
|
|
|
|
connect(m_helper64_process, SIGNAL(started()), this, SLOT(onHelperProcessStarted()));
|
|
|
|
connect(m_helper64_process, SIGNAL(error(QProcess::ProcessError)), this,
|
|
SLOT(onHelperProcessError(QProcess::ProcessError)));
|
|
|
|
connect(m_helper64_process, SIGNAL(finished(int, QProcess::ExitStatus)), this,
|
|
SLOT(onHelperProcessExited(int, QProcess::ExitStatus)));
|
|
|
|
m_helper64_restart_timer = new QTimer(this);
|
|
m_helper64_restart_timer->setSingleShot(true);
|
|
connect(m_helper64_restart_timer, SIGNAL(timeout()), this, SLOT(onDelayedRestartTimerTriggered()));
|
|
|
|
if (!canRun64BitPrograms()) {
|
|
qWarning("OverlayPrivateWin: mumble_ol_helper_x64.exe (64-bit overlay helper) disabled because the host is not "
|
|
"x64 capable.");
|
|
m_helper64_enabled = false;
|
|
} else if (!Global::get().s.bOverlayWinHelperX64Enable) {
|
|
qWarning("OverlayPrivateWin: mumble_ol_helper_x64.exe (64-bit overlay helper) disabled via "
|
|
"'overlay_win/enable_x64_helper' config option.");
|
|
m_helper64_enabled = false;
|
|
}
|
|
}
|
|
|
|
OverlayPrivateWin::~OverlayPrivateWin() {
|
|
m_active = false;
|
|
|
|
if (!CloseHandle(m_mumble_handle)) {
|
|
qFatal("OverlayPrivateWin: unable to close Mumble process handle.");
|
|
return;
|
|
}
|
|
|
|
// Remove all signals, so they don't
|
|
// interfere with our calls to waitForFinished
|
|
// below.
|
|
m_helper_process->disconnect();
|
|
m_helper64_process->disconnect();
|
|
|
|
m_helper_process->terminate();
|
|
m_helper64_process->terminate();
|
|
|
|
m_helper_process->waitForFinished();
|
|
m_helper64_process->waitForFinished();
|
|
}
|
|
|
|
void OverlayPrivateWin::startHelper(QProcess *helper) {
|
|
if (helper->state() == QProcess::NotRunning) {
|
|
if (helper == m_helper_process) {
|
|
helper->start(m_helper_exe_path, m_helper_exe_args);
|
|
m_helper_start_time.restart();
|
|
} else if (helper == m_helper64_process) {
|
|
helper->start(m_helper64_exe_path, m_helper64_exe_args);
|
|
m_helper64_start_time.restart();
|
|
} else {
|
|
qFatal("OverlayPrivateWin: invalid helper passed to startHelper().");
|
|
}
|
|
} else {
|
|
qWarning("OverlayPrivateWin: startHelper() called while process is already running. skipping.");
|
|
}
|
|
}
|
|
|
|
void OverlayPrivateWin::setActive(bool active) {
|
|
if (m_active != active) {
|
|
m_active = active;
|
|
|
|
if (m_active) {
|
|
if (m_helper_enabled) {
|
|
startHelper(m_helper_process);
|
|
}
|
|
if (m_helper64_enabled) {
|
|
startHelper(m_helper64_process);
|
|
}
|
|
} else {
|
|
if (m_helper_enabled) {
|
|
m_helper_process->terminate();
|
|
}
|
|
if (m_helper64_enabled) {
|
|
m_helper64_process->terminate();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static const char *exitStatusString(QProcess::ExitStatus exitStatus) {
|
|
switch (exitStatus) {
|
|
case QProcess::NormalExit:
|
|
return "normal exit";
|
|
case QProcess::CrashExit:
|
|
return "crash";
|
|
}
|
|
|
|
return "unknown";
|
|
}
|
|
|
|
static const char *processErrorString(QProcess::ProcessError processError) {
|
|
switch (processError) {
|
|
case QProcess::FailedToStart:
|
|
return "process failed to start";
|
|
case QProcess::Crashed:
|
|
return "process crashed";
|
|
case QProcess::Timedout:
|
|
return "process wait operation timed out";
|
|
case QProcess::WriteError:
|
|
return "an error occurred when attempting to write to the process";
|
|
case QProcess::ReadError:
|
|
return "an error occurred when attempting to read from the process";
|
|
case QProcess::UnknownError:
|
|
return "an unknown error occurred";
|
|
}
|
|
|
|
return "unknown";
|
|
}
|
|
|
|
void OverlayPrivateWin::onHelperProcessStarted() {
|
|
QProcess *helper = qobject_cast< QProcess * >(sender());
|
|
QString path;
|
|
if (helper == m_helper_process) {
|
|
path = m_helper_exe_path;
|
|
} else if (helper == m_helper64_process) {
|
|
path = m_helper64_exe_path;
|
|
} else {
|
|
qFatal("OverlayPrivateWin: unknown QProcess found in onHelperProcessStarted().");
|
|
}
|
|
|
|
std::uint64_t processID = helper->processId();
|
|
qWarning("OverlayPrivateWin: overlay helper process '%s' started with PID %llu.", qPrintable(path), processID);
|
|
}
|
|
|
|
void OverlayPrivateWin::onHelperProcessError(QProcess::ProcessError processError) {
|
|
QProcess *helper = qobject_cast< QProcess * >(sender());
|
|
QString path;
|
|
if (helper == m_helper_process) {
|
|
path = m_helper_exe_path;
|
|
} else if (helper == m_helper64_process) {
|
|
path = m_helper64_exe_path;
|
|
} else {
|
|
qFatal("OverlayPrivateWin: unknown QProcess found in onHelperProcessError().");
|
|
}
|
|
|
|
qWarning("OverlayPrivateWin: an error occurred for overlay helper process '%s': %s", qPrintable(path),
|
|
processErrorString(processError));
|
|
}
|
|
|
|
void OverlayPrivateWin::onHelperProcessExited(int exitCode, QProcess::ExitStatus exitStatus) {
|
|
QProcess *helper = qobject_cast< QProcess * >(sender());
|
|
|
|
QString path;
|
|
qint64 elapsedMsec = 0;
|
|
QTimer *restartTimer = nullptr;
|
|
if (helper == m_helper_process) {
|
|
path = m_helper_exe_path;
|
|
elapsedMsec = m_helper_start_time.elapsed();
|
|
restartTimer = m_helper_restart_timer;
|
|
} else if (helper == m_helper64_process) {
|
|
path = m_helper64_exe_path;
|
|
elapsedMsec = m_helper64_start_time.elapsed();
|
|
restartTimer = m_helper64_restart_timer;
|
|
} else {
|
|
qFatal("OverlayPrivateWin: unknown QProcess found in onHelperProcessExited().");
|
|
}
|
|
|
|
const char *helperErrString = OverlayHelperErrorToString(static_cast< OverlayHelperError >(exitCode));
|
|
qWarning("OverlayPrivateWin: overlay helper process '%s' exited (%s) with status code %s.", qPrintable(path),
|
|
exitStatusString(exitStatus), helperErrString ? helperErrString : qPrintable(QString::number(exitCode)));
|
|
|
|
// If the helper process exited while we're in 'active'
|
|
// mode, restart it.
|
|
if (m_active) {
|
|
// If the helper was only recently started, be
|
|
// a little more patient with restarting it.
|
|
// We could be hitting a crash bug in the helper,
|
|
// and we don't want to do too much harm in that
|
|
// case by spawning thousands of processes.
|
|
qint64 cooldownMsec = (qint64) Global::get().s.iOverlayWinHelperRestartCooldownMsec;
|
|
if (elapsedMsec < cooldownMsec) {
|
|
qint64 delayMsec = cooldownMsec - elapsedMsec;
|
|
qWarning("OverlayPrivateWin: waiting %llu seconds until restarting helper process '%s'. last restart was "
|
|
"%llu seconds ago.",
|
|
(unsigned long long) delayMsec / 1000ULL, qPrintable(path),
|
|
(unsigned long long) elapsedMsec / 1000ULL);
|
|
if (!restartTimer->isActive()) {
|
|
restartTimer->start(delayMsec);
|
|
}
|
|
} else {
|
|
startHelper(helper);
|
|
}
|
|
}
|
|
}
|
|
|
|
void OverlayPrivateWin::onDelayedRestartTimerTriggered() {
|
|
if (!m_active) {
|
|
return;
|
|
}
|
|
|
|
QTimer *timer = qobject_cast< QTimer * >(sender());
|
|
|
|
QProcess *helper = nullptr;
|
|
if (timer == m_helper_restart_timer) {
|
|
helper = m_helper_process;
|
|
} else if (timer == m_helper64_restart_timer) {
|
|
helper = m_helper64_process;
|
|
} else {
|
|
qFatal("OverlayPrivateWin: unknown timer found in onDelayedRestartTimerTriggered().");
|
|
}
|
|
|
|
if (helper->state() == QProcess::NotRunning) {
|
|
startHelper(helper);
|
|
}
|
|
}
|
|
|
|
void Overlay::platformInit() {
|
|
d = new OverlayPrivateWin(this);
|
|
}
|
|
|
|
void Overlay::setActiveInternal(bool act) {
|
|
if (d) {
|
|
// Only act if the private instance has been created already
|
|
static_cast< OverlayPrivateWin * >(d)->setActive(act);
|
|
}
|
|
}
|
|
|
|
bool OverlayConfig::supportsInstallableOverlay() {
|
|
return false;
|
|
}
|
|
|
|
bool OverlayConfig::isInstalled() {
|
|
return true;
|
|
}
|
|
|
|
bool OverlayConfig::needsUpgrade() {
|
|
return false;
|
|
}
|
|
|
|
bool OverlayConfig::installFiles() {
|
|
return false;
|
|
}
|
|
|
|
bool OverlayConfig::uninstallFiles() {
|
|
return false;
|
|
}
|