From 62da586201dacce0ecfd02c2efa728e6a0572ff8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 20 Jul 2026 22:48:15 +0400 Subject: [PATCH] Stop Telegram before Windows uninstallation --- Telegram/SourceFiles/core/launcher.cpp | 2 + Telegram/SourceFiles/core/sandbox.cpp | 60 +++++++++++++++++-- Telegram/SourceFiles/core/sandbox.h | 1 + .../platform/linux/specific_linux.h | 4 ++ .../SourceFiles/platform/mac/specific_mac.h | 4 ++ .../SourceFiles/platform/win/specific_win.cpp | 12 ++++ .../SourceFiles/platform/win/specific_win.h | 2 + Telegram/SourceFiles/settings.h | 1 + Telegram/build/setup.iss | 2 +- 9 files changed, 83 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/core/launcher.cpp b/Telegram/SourceFiles/core/launcher.cpp index 8223996d21..f6c9431b32 100644 --- a/Telegram/SourceFiles/core/launcher.cpp +++ b/Telegram/SourceFiles/core/launcher.cpp @@ -555,6 +555,7 @@ void Launcher::processArguments() { { "-autostart" , KeyFormat::NoValues }, { "-fixprevious" , KeyFormat::NoValues }, { "-cleanup" , KeyFormat::NoValues }, + { "-uninstall" , KeyFormat::NoValues }, { "-noupdate" , KeyFormat::NoValues }, { "-tosettings" , KeyFormat::NoValues }, { "-startintray" , KeyFormat::NoValues }, @@ -603,6 +604,7 @@ void Launcher::processArguments() { gLaunchMode = parseResult.contains("-autostart") ? LaunchModeAutoStart : parseResult.contains("-fixprevious") ? LaunchModeFixPrevious : parseResult.contains("-cleanup") ? LaunchModeCleanup + : parseResult.contains("-uninstall") ? LaunchModeUninstall : LaunchModeNormal; gNoStartUpdate = parseResult.contains("-noupdate"); gStartToSettings = parseResult.contains("-tosettings"); diff --git a/Telegram/SourceFiles/core/sandbox.cpp b/Telegram/SourceFiles/core/sandbox.cpp index 7fc9a80e65..aae889ddf3 100644 --- a/Telegram/SourceFiles/core/sandbox.cpp +++ b/Telegram/SourceFiles/core/sandbox.cpp @@ -49,6 +49,9 @@ base::options::toggle OptionDeadlockDetector({ .description = "Check once every 30 seconds that main thread is still responsive.", }); +constexpr auto kUninstallIpcTimeout = 10 * crl::time(1000); +constexpr auto kUninstallQuitTimeout = 30 * crl::time(1000); + } // namespace const char kOptionDeadlockDetector[] = "deadlock-detector"; @@ -65,10 +68,6 @@ Sandbox::Sandbox(int &argc, char **argv) } int Sandbox::start() { - if (!Core::UpdaterDisabled()) { - _updateChecker = std::make_unique(); - } - { const auto d = QFile::encodeName(QDir(cWorkingDir()).absolutePath()); char h[33] = { 0 }; @@ -76,6 +75,17 @@ int Sandbox::start() { _localServerName = Platform::SingleInstanceLocalServerName(h); } + if (cLaunchMode() == LaunchModeUninstall) { + const auto result = stopRunningInstance(); + psCleanup(); + closeApplication(); + return result; + } + + if (!Core::UpdaterDisabled()) { + _updateChecker = std::make_unique(); + } + { const auto d = QFile::encodeName(cExeDir() + cExeName()); QByteArray h; @@ -171,6 +181,48 @@ int Sandbox::start() { return exec(); } +int Sandbox::stopRunningInstance() { + LOG(("Uninstall: connecting to %1...").arg(_localServerName)); + _localSocket.connectToServer(_localServerName); + if (!_localSocket.waitForConnected(int(kUninstallIpcTimeout))) { + if (_localSocket.error() == QLocalSocket::ServerNotFoundError) { + LOG(("Uninstall: no running instance found.")); + return 0; + } + LOG(("Uninstall: connect error %1.").arg(_localSocket.error())); + return 1; + } + _localSocket.write("CMD:quit;"); + if (!_localSocket.waitForBytesWritten(int(kUninstallIpcTimeout))) { + LOG(("Uninstall: could not send the quit command.")); + return 1; + } + auto response = QByteArray(); + const auto deadline = crl::now() + kUninstallIpcTimeout; + while (!response.contains(';')) { + const auto timeout = deadline - crl::now(); + if (timeout <= 0 || !_localSocket.waitForReadyRead(int(timeout))) { + LOG(("Uninstall: no response to the quit command.")); + return 1; + } + response.append(_localSocket.readAll()); + } + const auto match = QRegularExpression(u"RES:(\\d+)_(\\d+);"_q).match( + QString::fromLatin1(response)); + if (!match.hasMatch()) { + LOG(("Uninstall: bad response to the quit command.")); + return 1; + } + const auto processId = match.capturedView(1).toULongLong(); + LOG(("Uninstall: waiting for process %1 to quit...").arg(processId)); + if (!Platform::WaitForProcessExit(processId, kUninstallQuitTimeout)) { + LOG(("Uninstall: the process did not quit in time.")); + return 1; + } + LOG(("Uninstall: the running instance quit.")); + return 0; +} + void Sandbox::NotifySystemShuttingDown() { SystemShuttingDown = true; } diff --git a/Telegram/SourceFiles/core/sandbox.h b/Telegram/SourceFiles/core/sandbox.h index 3a43b889de..271ee41163 100644 --- a/Telegram/SourceFiles/core/sandbox.h +++ b/Telegram/SourceFiles/core/sandbox.h @@ -103,6 +103,7 @@ private: void socketWritten(qint64 bytes); void socketReading(); void newInstanceConnected(); + int stopRunningInstance(); void readClients(); void removeClients(); diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.h b/Telegram/SourceFiles/platform/linux/specific_linux.h index ae3c4670c0..a13800c83e 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.h +++ b/Telegram/SourceFiles/platform/linux/specific_linux.h @@ -34,6 +34,10 @@ inline uint64 ActivationWindowId(not_null window) { inline void ActivateOtherProcess(uint64 processId, uint64 windowId) { } +inline bool WaitForProcessExit(uint64 processId, crl::time timeout) { + return true; +} + } // namespace Platform inline void psCheckLocalSocket(const QString &serverName) { diff --git a/Telegram/SourceFiles/platform/mac/specific_mac.h b/Telegram/SourceFiles/platform/mac/specific_mac.h index 3c14de2af4..2d050ec7be 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac.h +++ b/Telegram/SourceFiles/platform/mac/specific_mac.h @@ -36,6 +36,10 @@ inline uint64 ActivationWindowId(not_null window) { inline void ActivateOtherProcess(uint64 processId, uint64 windowId) { } +inline bool WaitForProcessExit(uint64 processId, crl::time timeout) { + return true; +} + inline QString ApplicationIconName() { return {}; } diff --git a/Telegram/SourceFiles/platform/win/specific_win.cpp b/Telegram/SourceFiles/platform/win/specific_win.cpp index 89e8a3ec48..ac8c5f2785 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.cpp +++ b/Telegram/SourceFiles/platform/win/specific_win.cpp @@ -540,6 +540,18 @@ void ActivateOtherProcess(uint64 processId, uint64 windowId) { } } +bool WaitForProcessExit(uint64 processId, crl::time timeout) { + const auto process = ::OpenProcess( + SYNCHRONIZE, + FALSE, + DWORD(processId)); + if (!process) { + return (::GetLastError() == ERROR_INVALID_PARAMETER); + } + const auto guard = gsl::finally([&] { ::CloseHandle(process); }); + return (::WaitForSingleObject(process, DWORD(timeout)) == WAIT_OBJECT_0); +} + } // namespace Platform namespace { diff --git a/Telegram/SourceFiles/platform/win/specific_win.h b/Telegram/SourceFiles/platform/win/specific_win.h index 39a4103e34..6414a4f014 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.h +++ b/Telegram/SourceFiles/platform/win/specific_win.h @@ -39,6 +39,8 @@ void SetWindowPriority(not_null window, uint32 priority); // Activate window with windowId (if found) or the largest priority. void ActivateOtherProcess(uint64 processId, uint64 windowId); +[[nodiscard]] bool WaitForProcessExit(uint64 processId, crl::time timeout); + inline QString ApplicationIconName() { return {}; } diff --git a/Telegram/SourceFiles/settings.h b/Telegram/SourceFiles/settings.h index a6993209a7..f69aea35ac 100644 --- a/Telegram/SourceFiles/settings.h +++ b/Telegram/SourceFiles/settings.h @@ -44,6 +44,7 @@ enum LaunchMode { LaunchModeAutoStart, LaunchModeFixPrevious, LaunchModeCleanup, + LaunchModeUninstall, }; DeclareReadSetting(LaunchMode, LaunchMode); DeclareSetting(QString, WorkingDir); diff --git a/Telegram/build/setup.iss b/Telegram/build/setup.iss index 5c304b5729..b54b9cd891 100644 --- a/Telegram/build/setup.iss +++ b/Telegram/build/setup.iss @@ -113,7 +113,7 @@ var ResultCode: Integer; begin if CurUninstallStep = usUninstall then begin - ShellExec('', ExpandConstant('{app}\{#MyAppExeName}'), '-cleanup', '', SW_SHOW, ewWaitUntilTerminated, ResultCode); + Exec(ExpandConstant('{app}\{#MyAppExeName}'), '-uninstall', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); end; end;