diff --git a/Telegram/SourceFiles/core/crash_report_window.cpp b/Telegram/SourceFiles/core/crash_report_window.cpp index e848b41bf..3c32fb038 100644 --- a/Telegram/SourceFiles/core/crash_report_window.cpp +++ b/Telegram/SourceFiles/core/crash_report_window.cpp @@ -48,7 +48,32 @@ PreLaunchWindow::PreLaunchWindow(QString title) { int paddingVertical = (_size / 2); int paddingHorizontal = _size; int borderRadius = (_size / 5); - setStyleSheet(u"QPushButton { padding: %1px %2px; background-color: #ffffff; border-radius: %3px; }\nQPushButton#confirm:hover, QPushButton#cancel:hover { background-color: #e3f1fa; color: #2f9fea; }\nQPushButton#confirm { color: #2f9fea; }\nQPushButton#cancel { color: #aeaeae; }\nQLineEdit { border: 1px solid #e0e0e0; padding: 5px; }\nQLineEdit:focus { border: 2px solid #37a1de; padding: 4px; }"_q.arg(paddingVertical).arg(paddingHorizontal).arg(borderRadius)); + setStyleSheet(uR"( +QPushButton { + padding: %1px %2px; + background-color: #ffffff; + border-radius: %3px; +} +QPushButton#confirm:hover, +QPushButton#cancel:hover { + background-color: #e3f1fa; + color: #2f9fea; +} +QPushButton#confirm { + color: #2f9fea; +} +QPushButton#cancel { + color: #aeaeae; +} +QLineEdit { + border: 1px solid #e0e0e0; + padding: 5px; +} +QLineEdit:focus { + border: 2px solid #37a1de; + padding: 4px; +} +)"_q.arg(paddingVertical).arg(paddingHorizontal).arg(borderRadius)); if (!PreLaunchWindowInstance) { PreLaunchWindowInstance = this; } @@ -57,7 +82,7 @@ PreLaunchWindow::PreLaunchWindow(QString title) { void PreLaunchWindow::activate() { setWindowState(windowState() & ~Qt::WindowMinimized); setVisible(true); - psActivateProcess(); + Platform::ActivateThisProcess(); raise(); activateWindow(); } diff --git a/Telegram/SourceFiles/core/sandbox.cpp b/Telegram/SourceFiles/core/sandbox.cpp index ef93d97b5..ac8941087 100644 --- a/Telegram/SourceFiles/core/sandbox.cpp +++ b/Telegram/SourceFiles/core/sandbox.cpp @@ -36,8 +36,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Core { namespace { -constexpr auto kEmptyPidForCommandResponse = 0ULL; - QChar _toHex(ushort v) { v = v & 0x000F; return QChar::fromLatin1((v >= 10) ? ('a' + (v - 10)) : ('0' + v)); @@ -304,17 +302,21 @@ void Sandbox::socketReading() { return; } _localSocketReadData.append(_localSocket.readAll()); - if (QRegularExpression("RES:(\\d+);").match(_localSocketReadData).hasMatch()) { - uint64 pid = base::StringViewMid( - _localSocketReadData, - 4, - _localSocketReadData.length() - 5).toULongLong(); - if (pid != kEmptyPidForCommandResponse) { - psActivateProcess(pid); - } - LOG(("Show command response received, pid = %1, activating and quitting...").arg(pid)); - return Quit(); + const auto m = QRegularExpression(u"RES:(\\d+)_(\\d+);"_q).match( + _localSocketReadData); + if (!m.hasMatch()) { + return; } + const auto processId = m.capturedView(1).toULongLong(); + const auto windowId = m.capturedView(2).toULongLong(); + if (windowId) { + Platform::ActivateOtherProcess(processId, windowId); + } + LOG(("Show command response received, processId = %1, windowId = %2, " + "activating and quitting..." + ).arg(processId + ).arg(windowId)); + return Quit(); } void Sandbox::socketError(QLocalSocket::LocalSocketError e) { @@ -432,8 +434,9 @@ void Sandbox::readClients() { for (int32 to = cmds.indexOf(QChar(';'), from); to >= from; to = (from < l) ? cmds.indexOf(QChar(';'), from) : -1) { auto cmd = base::StringViewMid(cmds, from, to - from); if (cmd.startsWith(u"CMD:"_q)) { - execExternal(cmds.mid(from + 4, to - from - 4)); - const auto response = u"RES:%1;"_q.arg(QApplication::applicationPid()).toLatin1(); + const auto processId = QApplication::applicationPid(); + const auto windowId = execExternal(cmds.mid(from + 4, to - from - 4)); + const auto response = u"RES:%1_%2;"_q.arg(processId).arg(windowId).toLatin1(); i->first->write(response.data(), response.size()); } else if (cmd.startsWith(u"SEND:"_q)) { if (cSendPaths().isEmpty()) { @@ -443,14 +446,12 @@ void Sandbox::readClients() { qputenv("XDG_ACTIVATION_TOKEN", _escapeFrom7bit(cmds.mid(from + 21, to - from - 21)).toUtf8()); } else if (cmd.startsWith(u"OPEN:"_q)) { startUrl = _escapeFrom7bit(cmds.mid(from + 5, to - from - 5)).mid(0, 8192); - auto activateRequired = StartUrlRequiresActivate(startUrl); - if (activateRequired) { - execExternal("show"); - } - const auto responsePid = activateRequired - ? QApplication::applicationPid() - : kEmptyPidForCommandResponse; - const auto response = u"RES:%1;"_q.arg(responsePid).toLatin1(); + const auto activationRequired = StartUrlRequiresActivate(startUrl); + const auto processId = QApplication::applicationPid(); + const auto windowId = activationRequired + ? execExternal("show") + : 0; + const auto response = u"RES:%1_%2;"_q.arg(processId).arg(windowId).toLatin1(); i->first->write(response.data(), response.size()); } else { LOG(("Sandbox Error: unknown command %1 passed in local socket").arg(cmd.toString())); @@ -649,17 +650,21 @@ void Sandbox::closeApplication() { _updateChecker = nullptr; } -void Sandbox::execExternal(const QString &cmd) { +uint64 Sandbox::execExternal(const QString &cmd) { DEBUG_LOG(("Sandbox Info: executing external command '%1'").arg(cmd)); if (cmd == "show") { if (Core::IsAppLaunched() && Core::App().primaryWindow()) { - Core::App().primaryWindow()->activate(); - } else if (PreLaunchWindow::instance()) { - PreLaunchWindow::instance()->activate(); + const auto window = Core::App().primaryWindow(); + window->activate(); + return Platform::ActivationWindowId(window->widget()); + } else if (const auto window = PreLaunchWindow::instance()) { + window->activate(); + return Platform::ActivationWindowId(window); } } else if (cmd == "quit") { Quit(); } + return 0; } } // namespace Core diff --git a/Telegram/SourceFiles/core/sandbox.h b/Telegram/SourceFiles/core/sandbox.h index 098ab2c5f..bcf83982a 100644 --- a/Telegram/SourceFiles/core/sandbox.h +++ b/Telegram/SourceFiles/core/sandbox.h @@ -95,7 +95,9 @@ private: void singleInstanceChecked(); void launchApplication(); void setupScreenScale(); - void execExternal(const QString &cmd); + + // Return window id for activation. + uint64 execExternal(const QString &cmd); // Single instance application void socketConnected(); diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 980bdcd43..b71ab2794 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -684,10 +684,6 @@ bool SkipTaskbarSupported() { } // namespace Platform -void psActivateProcess(uint64 pid) { -// objc_activateProgram(); -} - QString psAppDataPath() { // Previously we used ~/.TelegramDesktop, so look there first. // If we find data there, we should still use it. diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.h b/Telegram/SourceFiles/platform/linux/specific_linux.h index 002bd0234..8b58cc8d3 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.h +++ b/Telegram/SourceFiles/platform/linux/specific_linux.h @@ -24,6 +24,16 @@ inline void WriteCrashDumpDetails() { inline void AutostartRequestStateFromSystem(Fn callback) { } +inline void ActivateThisProcess() { +} + +inline uint64 ActivationWindowId(not_null window) { + return 1; +} + +inline void ActivateOtherProcess(uint64 processId, uint64 windowId) { +} + } // namespace Platform inline void psCheckLocalSocket(const QString &serverName) { @@ -33,7 +43,6 @@ inline void psCheckLocalSocket(const QString &serverName) { } } -void psActivateProcess(uint64 pid = 0); QString psAppDataPath(); void psSendToMenu(bool send, bool silent = false); diff --git a/Telegram/SourceFiles/platform/mac/specific_mac.h b/Telegram/SourceFiles/platform/mac/specific_mac.h index e3a9fc2e1..e2ed5addc 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac.h +++ b/Telegram/SourceFiles/platform/mac/specific_mac.h @@ -36,6 +36,15 @@ inline bool SkipTaskbarSupported() { inline void InstallLauncher(bool force) { } +void ActivateThisProcess(); + +inline uint64 ActivationWindowId(not_null window) { + return 1; +} + +inline void ActivateOtherProcess(uint64 processId, uint64 windowId) { +} + namespace ThirdParty { inline void start() { @@ -54,7 +63,6 @@ inline void psCheckLocalSocket(const QString &serverName) { } } -void psActivateProcess(uint64 pid = 0); QString psAppDataPath(); void psSendToMenu(bool send, bool silent = false); diff --git a/Telegram/SourceFiles/platform/mac/specific_mac.mm b/Telegram/SourceFiles/platform/mac/specific_mac.mm index 2a528bb2e..64592db02 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac.mm +++ b/Telegram/SourceFiles/platform/mac/specific_mac.mm @@ -35,13 +35,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include -void psActivateProcess(uint64 pid) { - if (!pid) { - const auto window = Core::App().activeWindow(); - objc_activateProgram(window ? window->widget()->winId() : 0); - } -} - QString psAppDataPath() { return objc_appDataPath(); } @@ -194,6 +187,11 @@ bool AutostartSkip() { void NewVersionLaunched(int oldVersion) { } +void ActivateThisProcess() { + const auto window = Core::App().activeWindow(); + objc_activateProgram(window ? window->widget()->winId() : 0); +} + } // namespace Platform void psSendToMenu(bool send, bool silent) { diff --git a/Telegram/SourceFiles/platform/win/main_window_win.cpp b/Telegram/SourceFiles/platform/win/main_window_win.cpp index eb69acc4f..e83517446 100644 --- a/Telegram/SourceFiles/platform/win/main_window_win.cpp +++ b/Telegram/SourceFiles/platform/win/main_window_win.cpp @@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/popup_menu.h" #include "ui/ui_utility.h" #include "window/themes/window_theme.h" +#include "window/window_controller.h" #include "history/history.h" #include @@ -171,6 +172,8 @@ MainWindow::MainWindow(not_null controller) setupNativeWindowFrame(); + SetWindowPriority(this, controller->isPrimary() ? 2 : 1); + using namespace rpl::mappers; Core::App().appDeactivatedValue( ) | rpl::distinct_until_changed( diff --git a/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp b/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp index 53106d661..beb262be2 100644 --- a/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp +++ b/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp @@ -615,7 +615,9 @@ void Manager::Private::handleActivation(const ToastActivation &activation) { ).arg(activation.args ).arg(my ).arg(pid)); - psActivateProcess(pid); + const auto processId = pid; + const auto windowId = 0; // Activate some window. + Platform::ActivateOtherProcess(processId, windowId); return; } const auto action = parsed.value("action"); diff --git a/Telegram/SourceFiles/platform/win/specific_win.cpp b/Telegram/SourceFiles/platform/win/specific_win.cpp index 43acb0934..df97e31da 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.cpp +++ b/Telegram/SourceFiles/platform/win/specific_win.cpp @@ -68,7 +68,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #define WM_NCPOINTERUP 0x0243 #endif -using namespace Platform; +using namespace ::Platform; namespace { @@ -77,24 +77,39 @@ bool finished = true; QMargins simpleMargins, margins; HICON bigIcon = 0, smallIcon = 0, overlayIcon = 0; -BOOL CALLBACK ActivateProcessByPid(HWND hWnd, LPARAM lParam) { - uint64 &processId(*(uint64*)lParam); +[[nodiscard]] uint64 WindowIdFromHWND(HWND value) { + return (reinterpret_cast(value) & 0xFFFFFFFFULL); +} + +struct FindToActivateRequest { + uint64 processId = 0; + uint64 windowId = 0; + HWND result = nullptr; + uint32 resultLevel = 0; // Larger is better. +}; + +BOOL CALLBACK FindToActivate(HWND hwnd, LPARAM lParam) { + const auto request = reinterpret_cast(lParam); DWORD dwProcessId; - ::GetWindowThreadProcessId(hWnd, &dwProcessId); + ::GetWindowThreadProcessId(hwnd, &dwProcessId); - if ((uint64)dwProcessId == processId) { // found top-level window - static const int32 nameBufSize = 1024; - WCHAR nameBuf[nameBufSize]; - int32 len = GetWindowText(hWnd, nameBuf, nameBufSize); - if (len && len < nameBufSize) { - if (QRegularExpression(u"^Telegram(\\s*\\(\\d+\\))?$"_q).match(QString::fromStdWString(nameBuf)).hasMatch()) { - BOOL res = ::SetForegroundWindow(hWnd); - ::SetFocus(hWnd); - return FALSE; - } - } + if ((uint64)dwProcessId != request->processId) { + return TRUE; } + // Found a Top-Level window. + auto level = 0; + if (WindowIdFromHWND(hwnd) == request->windowId) { + request->result = hwnd; + request->resultLevel = 3; + return FALSE; + } + const auto data = static_cast(GetWindowLong(hwnd, GWL_USERDATA)); + if ((data != 1 && data != 2) || (data <= request->resultLevel)) { + return TRUE; + } + request->result = hwnd; + request->resultLevel = data; return TRUE; } @@ -218,12 +233,6 @@ bool ManageAppLink( } // namespace -void psActivateProcess(uint64 pid) { - if (pid) { - ::EnumWindows((WNDENUMPROC)ActivateProcessByPid, (LPARAM)&pid); - } -} - QString psAppDataPath() { static const int maxFileLen = MAX_PATH * 10; WCHAR wstrPath[maxFileLen]; @@ -488,6 +497,29 @@ void WriteCrashDumpDetails() { #endif // DESKTOP_APP_DISABLE_CRASH_REPORTS } +void SetWindowPriority(not_null window, uint32 priority) { + const auto hwnd = reinterpret_cast(window->winId()); + Assert(hwnd != nullptr); + + SetWindowLong(hwnd, GWL_USERDATA, static_cast(priority)); +} + +uint64 ActivationWindowId(not_null window) { + return WindowIdFromHWND(reinterpret_cast(window->winId())); +} + +void ActivateOtherProcess(uint64 processId, uint64 windowId) { + auto request = FindToActivateRequest{ + .processId = processId, + .windowId = windowId, + }; + ::EnumWindows((WNDENUMPROC)FindToActivate, (LPARAM)&request); + if (const auto hwnd = request.result) { + ::SetForegroundWindow(hwnd); + ::SetFocus(hwnd); + } +} + } // namespace Platform namespace { diff --git a/Telegram/SourceFiles/platform/win/specific_win.h b/Telegram/SourceFiles/platform/win/specific_win.h index b8fccad89..6859a345b 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.h +++ b/Telegram/SourceFiles/platform/win/specific_win.h @@ -30,6 +30,17 @@ inline bool SkipTaskbarSupported() { inline void InstallLauncher(bool force) { } +inline void ActivateThisProcess() { +} + +// 1 - secondary, 2 - primary. +void SetWindowPriority(not_null window, uint32 priority); + +[[nodiscard]] uint64 ActivationWindowId(not_null window); + +// Activate window with windowId (if found) or the largest priority. +void ActivateOtherProcess(uint64 processId, uint64 windowId); + namespace ThirdParty { void start(); @@ -43,7 +54,6 @@ inline void finish() { inline void psCheckLocalSocket(const QString &) { } -void psActivateProcess(uint64 pid = 0); QString psAppDataPath(); QString psAppDataPathOld(); void psSendToMenu(bool send, bool silent = false); diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp index a52317f3c..b2b19fad1 100644 --- a/Telegram/SourceFiles/window/main_window.cpp +++ b/Telegram/SourceFiles/window/main_window.cpp @@ -533,7 +533,7 @@ void MainWindow::activate() { bool wasHidden = !isVisible(); setWindowState(windowState() & ~Qt::WindowMinimized); setVisible(true); - psActivateProcess(); + Platform::ActivateThisProcess(); raise(); activateWindow(); controller().updateIsActiveFocus();