From e7cf560da07f84def25cf0020555c668a3697f5b Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 5 Oct 2021 22:37:34 +0400 Subject: [PATCH] Handle toast activations by COM activator. --- Telegram/CMakeLists.txt | 8 +- .../platform/win/main_window_win.cpp | 3 +- .../win/notifications_manager_win.cpp | 159 +++++++++++---- .../platform/win/notifications_manager_win.h | 4 + .../SourceFiles/platform/win/specific_win.cpp | 38 ++-- .../win/windows_app_user_model_id.cpp | 184 +++++++++++++----- .../SourceFiles/platform/win/windows_dlls.cpp | 1 - .../SourceFiles/platform/win/windows_dlls.h | 5 - .../platform/win/windows_toast_activator.cpp | 89 +++++++++ .../platform/win/windows_toast_activator.h | 50 +++++ .../platform/win/windows_toastactivator.idl | 29 +++ .../window/notifications_manager.cpp | 20 +- .../window/notifications_manager.h | 4 +- Telegram/cmake/generate_midl.cmake | 48 +++-- Telegram/lib_base | 2 +- cmake | 2 +- 16 files changed, 511 insertions(+), 135 deletions(-) create mode 100644 Telegram/SourceFiles/platform/win/windows_toast_activator.cpp create mode 100644 Telegram/SourceFiles/platform/win/windows_toast_activator.h create mode 100644 Telegram/SourceFiles/platform/win/windows_toastactivator.idl diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index fbc893d8d..c3548cb96 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -42,7 +42,10 @@ include(cmake/generate_appdata_changelog.cmake) if (WIN32) include(cmake/generate_midl.cmake) - generate_midl(Telegram ${src_loc}/platform/win/windows_quiethours.idl) + generate_midl(Telegram ${src_loc} + platform/win/windows_quiethours.idl + platform/win/windows_toastactivator.idl + ) nuget_add_winrt(Telegram) endif() @@ -950,6 +953,8 @@ PRIVATE platform/win/windows_dlls.h platform/win/windows_event_filter.cpp platform/win/windows_event_filter.h + platform/win/windows_toast_activator.cpp + platform/win/windows_toast_activator.h platform/platform_audio.h platform/platform_file_utilities.h platform/platform_launcher.h @@ -1384,6 +1389,7 @@ if (WIN32) /DELAYLOAD:netapi32.dll /DELAYLOAD:userenv.dll /DELAYLOAD:wtsapi32.dll + /DELAYLOAD:propsys.dll ) endif() diff --git a/Telegram/SourceFiles/platform/win/main_window_win.cpp b/Telegram/SourceFiles/platform/win/main_window_win.cpp index 2a952cd1b..24764233d 100644 --- a/Telegram/SourceFiles/platform/win/main_window_win.cpp +++ b/Telegram/SourceFiles/platform/win/main_window_win.cpp @@ -390,11 +390,12 @@ void MainWindow::initHook() { using namespace base::Platform; auto factory = ComPtr(); if (SupportsWRL()) { - GetActivationFactory( + ABI::Windows::Foundation::GetActivationFactory( StringReferenceWrapper( RuntimeClass_Windows_UI_ViewManagement_UIViewSettings).Get(), &factory); if (factory) { + // NB! No such method (or IUIViewSettingsInterop) in C++/WinRT :( factory->GetForWindow( ps_hWnd, IID_PPV_ARGS(&_private->viewSettings)); diff --git a/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp b/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp index 69ffb8223..dd521ebf5 100644 --- a/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp +++ b/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp @@ -12,7 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/platform/win/base_windows_co_task_mem.h" #include "base/platform/win/base_windows_winrt.h" #include "base/platform/base_platform_info.h" +#include "base/platform/win/wrl/wrl_module_h.h" +#include "base/qthelp_url.h" #include "platform/win/windows_app_user_model_id.h" +#include "platform/win/windows_toast_activator.h" #include "platform/win/windows_event_filter.h" #include "platform/win/windows_dlls.h" #include "history/history.h" @@ -48,8 +51,10 @@ namespace Notifications { #ifndef __MINGW32__ namespace { -constexpr auto kNotificationTemplate = LR"( - +[[nodiscard]] std::wstring NotificationTemplate(QString id) { + const auto wid = id.replace('&', "&").toStdWString(); + return LR"( + @@ -62,17 +67,24 @@ constexpr auto kNotificationTemplate = LR"( + )"; +} -constexpr auto kNotificationTemplateSmall = LR"( - +[[nodiscard]] std::wstring NotificationTemplateSmall(QString id) { + const auto wid = id.replace('&', "&").toStdWString(); + return LR"( + @@ -84,17 +96,21 @@ constexpr auto kNotificationTemplateSmall = LR"( )"; +} bool init() { if (!IsWindows8OrGreater()) { return false; } if ((Dlls::SetCurrentProcessExplicitAppUserModelID == nullptr) - || (Dlls::PropVariantToString == nullptr) || !base::WinRT::Supported()) { return false; } + { + using namespace Microsoft::WRL; + Module::GetModule().RegisterObjects(); + } if (!AppUserModelId::validateShortcut()) { return false; } @@ -159,6 +175,26 @@ void SetReplyPlaceholder( placeholder); } +// Throws. +void SetAction(const XmlDocument &toastXml, const QString &id) { + auto nodeList = toastXml.GetElementsByTagName(L"toast"); + if (const auto toast = nodeList.Item(0).try_as()) { + toast.SetAttribute(L"launch", L"action=open&" + id.toStdWString()); + } +} + +// Throws. +void SetMarkAsReadText( + const XmlDocument &toastXml, + const std::wstring &text) { + const auto nodeList = toastXml.GetElementsByTagName(L"action"); + const auto attributes = nodeList.Item(1).Attributes(); + return SetNodeValueString( + toastXml, + attributes.GetNamedItem(L"content"), + text); +} + auto Checked = false; auto InitSucceeded = false; @@ -387,6 +423,8 @@ public: not_null window); void clearNotification(NotificationId id); + void handleActivation(const ToastActivation &activation); + ~Private(); private: @@ -410,12 +448,17 @@ private: base::flat_map< FullPeer, base::flat_map> _notifications; + rpl::lifetime _lifetime; }; Manager::Private::Private(Manager *instance, Type type) : _cachedUserpics(type) , _guarded(std::make_shared(instance)) { + ToastActivations( + ) | rpl::start_with_next([=](const ToastActivation &activation) { + handleActivation(activation); + }, _lifetime); } bool Manager::Private::init() { @@ -503,6 +546,40 @@ void Manager::Private::clearNotification(NotificationId id) { } } +void Manager::Private::handleActivation(const ToastActivation &activation) { + const auto parsed = qthelp::url_parse_params(activation.args); + const auto action = parsed.value("action"); + const auto id = NotificationId{ + .full = FullPeer{ + .sessionId = parsed.value("s").toULongLong(), + .peerId = PeerId(parsed.value("p").toULongLong()), + }, + .msgId = MsgId(parsed.value("m").toLongLong()), + }; + if (!id.full.sessionId || !id.full.peerId || !id.msgId) { + return; + } + auto text = TextWithTags(); + for (const auto &entry : activation.input) { + if (entry.key == "fastReply") { + text.text = entry.value; + } + } + const auto i = _notifications.find(id.full); + if (i == _notifications.cend() || !i->second.contains(id.msgId)) { + return; + } + + const auto manager = *_guarded; + if (action == "reply") { + manager->notificationReplied(id, text); + } else if (action == "mark") { + manager->notificationReplied(id, TextWithTags()); + } else { + manager->notificationActivated(id, text); + } +} + bool Manager::Private::showNotification( not_null peer, std::shared_ptr &userpicView, @@ -549,17 +626,32 @@ bool Manager::Private::showNotificationInTryCatch( bool hideReplyButton) { const auto withSubtitle = !subtitle.isEmpty(); auto toastXml = XmlDocument(); + + const auto key = FullPeer{ + .sessionId = peer->session().uniqueId(), + .peerId = peer->id, + }; + const auto notificationId = NotificationId{ + .full = key, + .msgId = msgId + }; + const auto idString = u"s=%1&p=%2&m=%3"_q + .arg(key.sessionId) + .arg(key.peerId.value) + .arg(msgId.bare); + const auto modern = Platform::IsWindows10OrGreater(); if (modern) { toastXml.LoadXml(hideReplyButton - ? kNotificationTemplateSmall - : kNotificationTemplate); + ? NotificationTemplateSmall(idString) + : NotificationTemplate(idString)); } else { toastXml = ToastNotificationManager::GetTemplateContent( (withSubtitle ? ToastTemplateType::ToastImageAndText04 : ToastTemplateType::ToastImageAndText02)); SetAudioSilent(toastXml); + SetAction(toastXml, idString); } const auto userpicKey = hideNameAndPhoto @@ -576,6 +668,9 @@ bool Manager::Private::showNotificationInTryCatch( SetReplyPlaceholder( toastXml, tr::lng_message_ph(tr::now).toStdWString()); + SetMarkAsReadText( + toastXml, + tr::lng_context_mark_read(tr::now).toStdWString()); } SetImageSrc(toastXml, userpicPathWide); @@ -604,44 +699,30 @@ bool Manager::Private::showNotificationInTryCatch( }); }; - const auto key = FullPeer{ - .sessionId = peer->session().uniqueId(), - .peerId = peer->id, - }; - const auto notificationId = NotificationId{ - .full = key, - .msgId = msgId - }; - auto toast = ToastNotification(toastXml); const auto token1 = toast.Activated([=]( const ToastNotification &sender, const winrt::Windows::Foundation::IInspectable &object) { + auto activation = ToastActivation(); + const auto string = &ToastActivation::String; if (const auto args = object.try_as()) { - const auto arguments = args.Arguments(); - const auto userInput = args.UserInput(); - if (arguments == L"action=reply") { - const auto reply = userInput.TryLookup(L"fastReply"); - const auto data = reply.try_as>(); - auto text = data - ? QString::fromWCharArray(data.GetString().c_str()) - : QString(); - if (text.indexOf(QChar('\n')) < 0) { - text.replace(QChar('\r'), QChar('\n')); - } - performOnMainQueue([notificationId, text](Manager *manager) { - manager->notificationReplied(notificationId, { text }); - }); - } else { - performOnMainQueue([notificationId](Manager *manager) { - manager->notificationActivated(notificationId); + activation.args = string(args.Arguments().c_str()); + const auto reply = args.UserInput().TryLookup(L"fastReply"); + const auto data = reply.try_as>(); + if (data) { + activation.input.push_back({ + .key = u"fastReply"_q, + .value = string(data.GetString().c_str()), }); } } else { - performOnMainQueue([notificationId](Manager *manager) { - manager->notificationActivated(notificationId); - }); + activation.args = "action=open&" + idString; } + crl::on_main([=, activation = std::move(activation)]() mutable { + if (const auto strong = weak.lock()) { + (*strong)->handleActivation(activation); + } + }); }); const auto token2 = toast.Dismissed([=]( const ToastNotification &sender, @@ -705,6 +786,10 @@ void Manager::clearNotification(NotificationId id) { _private->clearNotification(id); } +void Manager::handleActivation(const ToastActivation &activation) { + _private->handleActivation(activation); +} + Manager::~Manager() = default; void Manager::doShowNativeNotification( diff --git a/Telegram/SourceFiles/platform/win/notifications_manager_win.h b/Telegram/SourceFiles/platform/win/notifications_manager_win.h index be37f9ff4..f9d5de0b6 100644 --- a/Telegram/SourceFiles/platform/win/notifications_manager_win.h +++ b/Telegram/SourceFiles/platform/win/notifications_manager_win.h @@ -9,6 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/platform_notifications_manager.h" +struct ToastActivation; + namespace Platform { namespace Notifications { @@ -22,6 +24,8 @@ public: bool init(); void clearNotification(NotificationId id); + void handleActivation(const ToastActivation &activation); + protected: void doShowNativeNotification( not_null peer, diff --git a/Telegram/SourceFiles/platform/win/specific_win.cpp b/Telegram/SourceFiles/platform/win/specific_win.cpp index 3143e91fc..8104f6468 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.cpp +++ b/Telegram/SourceFiles/platform/win/specific_win.cpp @@ -33,9 +33,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include -#include -#include - #include #include #include @@ -68,7 +65,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #define WM_NCPOINTERUP 0x0243 #endif -using namespace Microsoft::WRL; using namespace Platform; namespace { @@ -422,17 +418,17 @@ namespace { namespace Platform { PermissionStatus GetPermissionStatus(PermissionType type) { - if (type==PermissionType::Microphone) { - PermissionStatus result=PermissionStatus::Granted; + if (type == PermissionType::Microphone) { + PermissionStatus result = PermissionStatus::Granted; HKEY hKey; - LSTATUS res=RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\CapabilityAccessManager\\ConsentStore\\microphone", 0, KEY_QUERY_VALUE, &hKey); - if(res==ERROR_SUCCESS) { + LSTATUS res = RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\CapabilityAccessManager\\ConsentStore\\microphone", 0, KEY_QUERY_VALUE, &hKey); + if (res == ERROR_SUCCESS) { wchar_t buf[20]; - DWORD length=sizeof(buf); - res=RegQueryValueEx(hKey, L"Value", NULL, NULL, (LPBYTE)buf, &length); - if(res==ERROR_SUCCESS) { - if(wcscmp(buf, L"Deny")==0) { - result=PermissionStatus::Denied; + DWORD length = sizeof(buf); + res = RegQueryValueEx(hKey, L"Value", NULL, NULL, (LPBYTE)buf, &length); + if (res == ERROR_SUCCESS) { + if (wcscmp(buf, L"Deny") == 0) { + result = PermissionStatus::Denied; } } RegCloseKey(hKey); @@ -498,20 +494,17 @@ void _manageAppLnk(bool create, bool silent, int path_csidl, const wchar_t *args if (SUCCEEDED(hr)) { QString lnk = QString::fromWCharArray(startupFolder) + '\\' + AppFile.utf16() + qsl(".lnk"); if (create) { - ComPtr shellLink; - hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink)); - if (SUCCEEDED(hr)) { - ComPtr persistFile; - + const auto shellLink = base::WinRT::TryCreateInstance( + CLSID_ShellLink, + CLSCTX_INPROC_SERVER); + if (shellLink) { QString exe = QDir::toNativeSeparators(cExeDir() + cExeName()), dir = QDir::toNativeSeparators(QDir(cWorkingDir()).absolutePath()); shellLink->SetArguments(args); shellLink->SetPath(exe.toStdWString().c_str()); shellLink->SetWorkingDirectory(dir.toStdWString().c_str()); shellLink->SetDescription(description); - ComPtr propertyStore; - hr = shellLink.As(&propertyStore); - if (SUCCEEDED(hr)) { + if (const auto propertyStore = shellLink.try_as()) { PROPVARIANT appIdPropVar; hr = InitPropVariantFromString(AppUserModelId::getId(), &appIdPropVar); if (SUCCEEDED(hr)) { @@ -523,8 +516,7 @@ void _manageAppLnk(bool create, bool silent, int path_csidl, const wchar_t *args } } - hr = shellLink.As(&persistFile); - if (SUCCEEDED(hr)) { + if (const auto persistFile = shellLink.try_as()) { hr = persistFile->Save(lnk.toStdWString().c_str(), TRUE); } else { if (!silent) LOG(("App Error: could not create interface IID_IPersistFile %1").arg(hr)); diff --git a/Telegram/SourceFiles/platform/win/windows_app_user_model_id.cpp b/Telegram/SourceFiles/platform/win/windows_app_user_model_id.cpp index 9e4c7f6cf..6645ef8ca 100644 --- a/Telegram/SourceFiles/platform/win/windows_app_user_model_id.cpp +++ b/Telegram/SourceFiles/platform/win/windows_app_user_model_id.cpp @@ -8,12 +8,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/win/windows_app_user_model_id.h" #include "platform/win/windows_dlls.h" +#include "platform/win/windows_toast_activator.h" +#include "base/platform/win/base_windows_wrl.h" + #include #include -#include -#include - using namespace Microsoft::WRL; namespace Platform { @@ -22,6 +22,7 @@ namespace { const PROPERTYKEY pkey_AppUserModel_ID = { { 0x9F4C2855, 0x9F79, 0x4B39, { 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3 } }, 5 }; const PROPERTYKEY pkey_AppUserModel_StartPinOption = { { 0x9F4C2855, 0x9F79, 0x4B39, { 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3 } }, 12 }; +const PROPERTYKEY pkey_AppUserModel_ToastActivator = { { 0x9F4C2855, 0x9F79, 0x4B39, { 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3 } }, 26 }; #ifdef OS_WIN_STORE const WCHAR AppUserModelIdRelease[] = L"Telegram.TelegramDesktop.Store"; @@ -36,15 +37,14 @@ QString pinnedPath() { static const int maxFileLen = MAX_PATH * 10; WCHAR wstrPath[maxFileLen]; if (GetEnvironmentVariable(L"APPDATA", wstrPath, maxFileLen)) { - QDir appData(QString::fromStdWString(std::wstring(wstrPath))); - return appData.absolutePath() + qsl("/Microsoft/Internet Explorer/Quick Launch/User Pinned/TaskBar/"); + auto appData = QDir(QString::fromStdWString(std::wstring(wstrPath))); + return appData.absolutePath() + + u"/Microsoft/Internet Explorer/Quick Launch/User Pinned/TaskBar/"_q; } return QString(); } void checkPinned() { - if (!Dlls::PropVariantToString) return; - static const int maxFileLen = MAX_PATH * 10; HRESULT hr = CoInitialize(0); @@ -56,14 +56,27 @@ void checkPinned() { WCHAR src[MAX_PATH]; GetModuleFileName(GetModuleHandle(0), src, MAX_PATH); BY_HANDLE_FILE_INFORMATION srcinfo = { 0 }; - HANDLE srcfile = CreateFile(src, 0x00, 0x00, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + HANDLE srcfile = CreateFile( + src, + 0x00, + 0x00, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); if (srcfile == INVALID_HANDLE_VALUE) return; BOOL srcres = GetFileInformationByHandle(srcfile, &srcinfo); CloseHandle(srcfile); if (!srcres) return; LOG(("Checking...")); WIN32_FIND_DATA findData; - HANDLE findHandle = FindFirstFileEx((p + L"*").c_str(), FindExInfoStandard, &findData, FindExSearchNameMatch, 0, 0); + HANDLE findHandle = FindFirstFileEx( + (p + L"*").c_str(), + FindExInfoStandard, + &findData, + FindExSearchNameMatch, + 0, + 0); if (findHandle == INVALID_HANDLE_VALUE) { LOG(("Init Error: could not find files in pinned folder")); return; @@ -78,7 +91,11 @@ void checkPinned() { if (attributes >= 0xFFFFFFF) continue; // file does not exist ComPtr shellLink; - HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink)); + HRESULT hr = CoCreateInstance( + CLSID_ShellLink, + nullptr, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&shellLink)); if (!SUCCEEDED(hr)) continue; ComPtr persistFile; @@ -93,13 +110,22 @@ void checkPinned() { if (!SUCCEEDED(hr)) continue; BY_HANDLE_FILE_INFORMATION dstinfo = { 0 }; - HANDLE dstfile = CreateFile(dst, 0x00, 0x00, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + HANDLE dstfile = CreateFile( + dst, + 0x00, + 0x00, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); if (dstfile == INVALID_HANDLE_VALUE) continue; BOOL dstres = GetFileInformationByHandle(dstfile, &dstinfo); CloseHandle(dstfile); if (!dstres) continue; - if (srcinfo.dwVolumeSerialNumber == dstinfo.dwVolumeSerialNumber && srcinfo.nFileIndexLow == dstinfo.nFileIndexLow && srcinfo.nFileIndexHigh == dstinfo.nFileIndexHigh) { + if (srcinfo.dwVolumeSerialNumber == dstinfo.dwVolumeSerialNumber + && srcinfo.nFileIndexLow == dstinfo.nFileIndexLow + && srcinfo.nFileIndexHigh == dstinfo.nFileIndexHigh) { ComPtr propertyStore; hr = shellLink.As(&propertyStore); if (!SUCCEEDED(hr)) return; @@ -109,7 +135,7 @@ void checkPinned() { if (!SUCCEEDED(hr)) return; LOG(("Reading...")); WCHAR already[MAX_PATH]; - hr = Dlls::PropVariantToString(appIdPropVar, already, MAX_PATH); + hr = PropVariantToString(appIdPropVar, already, MAX_PATH); if (SUCCEEDED(hr)) { if (std::wstring(getId()) == already) { LOG(("Already!")); @@ -141,7 +167,7 @@ void checkPinned() { } } while (FindNextFile(findHandle, &findData)); DWORD errorCode = GetLastError(); - if (errorCode && errorCode != ERROR_NO_MORE_FILES) { // everything is found + if (errorCode && errorCode != ERROR_NO_MORE_FILES) { LOG(("Init Error: could not find some files in pinned folder")); return; } @@ -152,8 +178,9 @@ QString systemShortcutPath() { static const int maxFileLen = MAX_PATH * 10; WCHAR wstrPath[maxFileLen]; if (GetEnvironmentVariable(L"APPDATA", wstrPath, maxFileLen)) { - QDir appData(QString::fromStdWString(std::wstring(wstrPath))); - return appData.absolutePath() + qsl("/Microsoft/Windows/Start Menu/Programs/"); + auto appData = QDir(QString::fromStdWString(std::wstring(wstrPath))); + const auto path = appData.absolutePath(); + return path + u"/Microsoft/Windows/Start Menu/Programs/"_q; } return QString(); } @@ -168,7 +195,11 @@ void cleanupShortcut() { if (attributes >= 0xFFFFFFF) return; // file does not exist ComPtr shellLink; - HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink)); + HRESULT hr = CoCreateInstance( + CLSID_ShellLink, + nullptr, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&shellLink)); if (!SUCCEEDED(hr)) return; ComPtr persistFile; @@ -180,10 +211,15 @@ void cleanupShortcut() { WCHAR szGotPath[MAX_PATH]; WIN32_FIND_DATA wfd; - hr = shellLink->GetPath(szGotPath, MAX_PATH, (WIN32_FIND_DATA*)&wfd, SLGP_SHORTPATH); + hr = shellLink->GetPath( + szGotPath, + MAX_PATH, + (WIN32_FIND_DATA*)&wfd, + SLGP_SHORTPATH); if (!SUCCEEDED(hr)) return; - if (QDir::toNativeSeparators(cExeDir() + cExeName()).toStdWString() == szGotPath) { + const auto full = cExeDir() + cExeName(); + if (QDir::toNativeSeparators(full).toStdWString() == szGotPath) { QFile().remove(path); } } @@ -197,7 +233,11 @@ bool validateShortcutAt(const QString &path) { if (attributes >= 0xFFFFFFF) return false; // file does not exist ComPtr shellLink; - HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink)); + HRESULT hr = CoCreateInstance( + CLSID_ShellLink, + nullptr, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&shellLink)); if (!SUCCEEDED(hr)) return false; ComPtr persistFile; @@ -212,22 +252,31 @@ bool validateShortcutAt(const QString &path) { if (!SUCCEEDED(hr)) return false; PROPVARIANT appIdPropVar; + PROPVARIANT toastActivatorPropVar; hr = propertyStore->GetValue(getKey(), &appIdPropVar); if (!SUCCEEDED(hr)) return false; + hr = propertyStore->GetValue( + pkey_AppUserModel_ToastActivator, + &toastActivatorPropVar); + if (!SUCCEEDED(hr)) return false; + WCHAR already[MAX_PATH]; - hr = Dlls::PropVariantToString(appIdPropVar, already, MAX_PATH); - if (SUCCEEDED(hr)) { - if (std::wstring(getId()) == already) { - PropVariantClear(&appIdPropVar); - return true; - } - } - if (appIdPropVar.vt != VT_EMPTY) { - PropVariantClear(&appIdPropVar); + hr = PropVariantToString(appIdPropVar, already, MAX_PATH); + const auto good1 = SUCCEEDED(hr) && (std::wstring(getId()) == already); + const auto bad1 = !good1 && (appIdPropVar.vt != VT_EMPTY); + PropVariantClear(&appIdPropVar); + + auto clsid = CLSID(); + hr = PropVariantToCLSID(toastActivatorPropVar, &clsid); + const auto good2 = SUCCEEDED(hr) && (clsid == __uuidof(ToastActivator)); + const auto bad2 = !good2 && (toastActivatorPropVar.vt != VT_EMPTY); + PropVariantClear(&toastActivatorPropVar); + if (good1 && good2) { + return true; + } else if (bad1 || bad2) { return false; } - PropVariantClear(&appIdPropVar); hr = InitPropVariantFromString(getId(), &appIdPropVar); if (!SUCCEEDED(hr)) return false; @@ -236,6 +285,17 @@ bool validateShortcutAt(const QString &path) { PropVariantClear(&appIdPropVar); if (!SUCCEEDED(hr)) return false; + hr = InitPropVariantFromCLSID( + __uuidof(ToastActivator), + &toastActivatorPropVar); + if (!SUCCEEDED(hr)) return false; + + hr = propertyStore->SetValue( + pkey_AppUserModel_ToastActivator, + toastActivatorPropVar); + PropVariantClear(&toastActivatorPropVar); + if (!SUCCEEDED(hr)) return false; + hr = propertyStore->Commit(); if (!SUCCEEDED(hr)) return false; @@ -248,30 +308,48 @@ bool validateShortcutAt(const QString &path) { bool validateShortcut() { QString path = systemShortcutPath(); - if (path.isEmpty() || cExeName().isEmpty()) return false; + if (path.isEmpty() || cExeName().isEmpty()) { + return false; + } if (cAlphaVersion()) { - path += qsl("TelegramAlpha.lnk"); - if (validateShortcutAt(path)) return true; + path += u"TelegramAlpha.lnk"_q; + if (validateShortcutAt(path)) { + return true; + } } else { - if (validateShortcutAt(path + qsl("Telegram Desktop/Telegram.lnk"))) return true; - if (validateShortcutAt(path + qsl("Telegram Win (Unofficial)/Telegram.lnk"))) return true; + const auto installed = u"Telegram Desktop/Telegram.lnk"_q; + const auto old = u"Telegram Win (Unofficial)/Telegram.lnk"_q; + if (validateShortcutAt(path + installed) + || validateShortcutAt(path + old)) { + return true; + } - path += qsl("Telegram.lnk"); - if (validateShortcutAt(path)) return true; + path += u"Telegram.lnk"_q; + if (validateShortcutAt(path)) { + return true; + } } ComPtr shellLink; - HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink)); + HRESULT hr = CoCreateInstance( + CLSID_ShellLink, + nullptr, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&shellLink)); if (!SUCCEEDED(hr)) return false; - hr = shellLink->SetPath(QDir::toNativeSeparators(cExeDir() + cExeName()).toStdWString().c_str()); + hr = shellLink->SetPath( + QDir::toNativeSeparators( + cExeDir() + cExeName()).toStdWString().c_str()); if (!SUCCEEDED(hr)) return false; hr = shellLink->SetArguments(L""); if (!SUCCEEDED(hr)) return false; - hr = shellLink->SetWorkingDirectory(QDir::toNativeSeparators(QDir(cWorkingDir()).absolutePath()).toStdWString().c_str()); + hr = shellLink->SetWorkingDirectory( + QDir::toNativeSeparators( + QDir(cWorkingDir()).absolutePath()).toStdWString().c_str()); if (!SUCCEEDED(hr)) return false; ComPtr propertyStore; @@ -286,15 +364,29 @@ bool validateShortcut() { PropVariantClear(&appIdPropVar); if (!SUCCEEDED(hr)) return false; -#if WINVER >= 0x602 PROPVARIANT startPinPropVar; - hr = InitPropVariantFromUInt32(APPUSERMODEL_STARTPINOPTION_NOPINONINSTALL, &startPinPropVar); + hr = InitPropVariantFromUInt32( + APPUSERMODEL_STARTPINOPTION_NOPINONINSTALL, + &startPinPropVar); if (!SUCCEEDED(hr)) return false; - hr = propertyStore->SetValue(pkey_AppUserModel_StartPinOption, startPinPropVar); + hr = propertyStore->SetValue( + pkey_AppUserModel_StartPinOption, + startPinPropVar); PropVariantClear(&startPinPropVar); if (!SUCCEEDED(hr)) return false; -#endif // WINVER >= 0x602 + + PROPVARIANT toastActivatorPropVar{}; + hr = InitPropVariantFromCLSID( + __uuidof(ToastActivator), + &toastActivatorPropVar); + if (!SUCCEEDED(hr)) return false; + + hr = propertyStore->SetValue( + pkey_AppUserModel_ToastActivator, + toastActivatorPropVar); + PropVariantClear(&toastActivatorPropVar); + if (!SUCCEEDED(hr)) return false; hr = propertyStore->Commit(); if (!SUCCEEDED(hr)) return false; @@ -303,7 +395,9 @@ bool validateShortcut() { hr = shellLink.As(&persistFile); if (!SUCCEEDED(hr)) return false; - hr = persistFile->Save(QDir::toNativeSeparators(path).toStdWString().c_str(), TRUE); + hr = persistFile->Save( + QDir::toNativeSeparators(path).toStdWString().c_str(), + TRUE); if (!SUCCEEDED(hr)) return false; return true; diff --git a/Telegram/SourceFiles/platform/win/windows_dlls.cpp b/Telegram/SourceFiles/platform/win/windows_dlls.cpp index d114115ab..4d2e75309 100644 --- a/Telegram/SourceFiles/platform/win/windows_dlls.cpp +++ b/Telegram/SourceFiles/platform/win/windows_dlls.cpp @@ -58,7 +58,6 @@ SafeIniter::SafeIniter() { LOAD_SYMBOL(LibWtsApi32, WTSUnRegisterSessionNotification); const auto LibPropSys = LoadLibrary(L"propsys.dll"); - LOAD_SYMBOL(LibPropSys, PropVariantToString); LOAD_SYMBOL(LibPropSys, PSStringFromPropertyKey); const auto LibPsApi = LoadLibrary(L"psapi.dll"); diff --git a/Telegram/SourceFiles/platform/win/windows_dlls.h b/Telegram/SourceFiles/platform/win/windows_dlls.h index f23e7ff04..4579c8ce9 100644 --- a/Telegram/SourceFiles/platform/win/windows_dlls.h +++ b/Telegram/SourceFiles/platform/win/windows_dlls.h @@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/platform/win/base_windows_h.h" #include -#include #include #include #include @@ -79,10 +78,6 @@ inline BOOL(__stdcall *WTSUnRegisterSessionNotification)( // PROPSYS.DLL -inline HRESULT(__stdcall *PropVariantToString)( - _In_ REFPROPVARIANT propvar, - _Out_writes_(cch) PWSTR psz, - _In_ UINT cch); inline HRESULT(__stdcall *PSStringFromPropertyKey)( _In_ REFPROPERTYKEY pkey, _Out_writes_(cch) LPWSTR psz, diff --git a/Telegram/SourceFiles/platform/win/windows_toast_activator.cpp b/Telegram/SourceFiles/platform/win/windows_toast_activator.cpp new file mode 100644 index 000000000..44ac8f553 --- /dev/null +++ b/Telegram/SourceFiles/platform/win/windows_toast_activator.cpp @@ -0,0 +1,89 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "platform/win/windows_toast_activator.h" + +#pragma warning(push) +// class has virtual functions, but destructor is not virtual +#pragma warning(disable:4265) +#pragma warning(disable:5104) +#include +#pragma warning(pop) + +namespace { + +rpl::event_stream GlobalToastActivations; + +} // namespace + +QString ToastActivation::String(LPCWSTR value) { + const auto length = int(wcslen(value)); + auto result = value + ? QString::fromWCharArray(value, std::min(length, 16384)) + : QString(); + if (result.indexOf(QChar('\n')) < 0) { + result.replace(QChar('\r'), QChar('\n')); + } + return result; +} + +HRESULT ToastActivator::Activate( + _In_ LPCWSTR appUserModelId, + _In_ LPCWSTR invokedArgs, + _In_reads_(dataCount) const NOTIFICATION_USER_INPUT_DATA *data, + ULONG dataCount) { + const auto string = &ToastActivation::String; + auto input = std::vector(); + input.reserve(dataCount); + for (auto i = 0; i != dataCount; ++i) { + input.push_back({ + .key = string(data[i].Key), + .value = string(data[i].Value), + }); + } + auto activation = ToastActivation{ + .args = string(invokedArgs), + .input = std::move(input), + }; + crl::on_main([activation = std::move(activation)]() mutable { + GlobalToastActivations.fire(std::move(activation)); + }); + return S_OK; +} + +HRESULT ToastActivator::QueryInterface( + REFIID riid, + void **ppObj) { + if (riid == IID_IUnknown + || riid == IID_INotificationActivationCallback) { + *ppObj = static_cast(this); + AddRef(); + return S_OK; + } + + *ppObj = NULL; + return E_NOINTERFACE; +} + +ULONG ToastActivator::AddRef() { + return InterlockedIncrement(&_ref); +} + +ULONG ToastActivator::Release() { + long ref = 0; + ref = InterlockedDecrement(&_ref); + if (!ref) { + delete this; + } + return ref; +} + +rpl::producer ToastActivations() { + return GlobalToastActivations.events(); +} + +CoCreatableClass(ToastActivator); diff --git a/Telegram/SourceFiles/platform/win/windows_toast_activator.h b/Telegram/SourceFiles/platform/win/windows_toast_activator.h new file mode 100644 index 000000000..ba6c2b32c --- /dev/null +++ b/Telegram/SourceFiles/platform/win/windows_toast_activator.h @@ -0,0 +1,50 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "windows_toastactivator_h.h" +#include "base/platform/win/wrl/wrl_implements_h.h" + +// {F11932D3-6110-4BBC-9B02-B2EC07A1BD19} +class DECLSPEC_UUID("F11932D3-6110-4BBC-9B02-B2EC07A1BD19") ToastActivator + : public ::Microsoft::WRL::RuntimeClass< + ::Microsoft::WRL::RuntimeClassFlags<::Microsoft::WRL::ClassicCom>, + INotificationActivationCallback, + ::Microsoft::WRL::FtmBase> { +public: + ToastActivator() = default; + ~ToastActivator() = default; + + HRESULT STDMETHODCALLTYPE Activate( + _In_ LPCWSTR appUserModelId, + _In_ LPCWSTR invokedArgs, + _In_reads_(dataCount) const NOTIFICATION_USER_INPUT_DATA *data, + ULONG dataCount) override; + + HRESULT STDMETHODCALLTYPE QueryInterface( + REFIID riid, + void **ppObj); + ULONG STDMETHODCALLTYPE AddRef(); + ULONG STDMETHODCALLTYPE Release(); + +private: + long _ref = 1; + +}; + +struct ToastActivation { + struct UserInput { + QString key; + QString value; + }; + QString args; + std::vector input; + + [[nodiscard]] static QString String(LPCWSTR value); +}; +[[nodiscard]] rpl::producer ToastActivations(); diff --git a/Telegram/SourceFiles/platform/win/windows_toastactivator.idl b/Telegram/SourceFiles/platform/win/windows_toastactivator.idl new file mode 100644 index 000000000..7d9dbd6b4 --- /dev/null +++ b/Telegram/SourceFiles/platform/win/windows_toastactivator.idl @@ -0,0 +1,29 @@ +// ToastActivator.idl : IDL source for ToastActivator +// + +// This file will be processed by the MIDL tool to +// produce the type library (ToastActivator.tlb) and marshalling code. + +import "oaidl.idl"; +import "ocidl.idl"; + +typedef struct _NOTIFICATION_USER_INPUT_DATA +{ + LPCWSTR Key; + LPCWSTR Value; +} NOTIFICATION_USER_INPUT_DATA; + +[ + object, + uuid("53E31837-6600-4A81-9395-75CFFE746F94"), + pointer_default(ref) +] + +interface INotificationActivationCallback : IUnknown +{ + HRESULT Activate( + [in, string] LPCWSTR appUserModelId, + [in, string] LPCWSTR arguments, // arugments from the invoked button + [in, size_is(count), unique] const NOTIFICATION_USER_INPUT_DATA* data, // data from all the input elements in the XML + [in] ULONG count); +}; diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp index 7a720bfca..9d8a652ed 100644 --- a/Telegram/SourceFiles/window/notifications_manager.cpp +++ b/Telegram/SourceFiles/window/notifications_manager.cpp @@ -628,7 +628,9 @@ QString Manager::accountNameSeparator() { return QString::fromUtf8(" \xE2\x9E\x9C "); } -void Manager::notificationActivated(NotificationId id) { +void Manager::notificationActivated( + NotificationId id, + const TextWithTags &reply) { onBeforeNotificationActivated(id); if (const auto session = system()->findSession(id.full.sessionId)) { if (session->windows().empty()) { @@ -637,6 +639,22 @@ void Manager::notificationActivated(NotificationId id) { if (!session->windows().empty()) { const auto window = session->windows().front(); const auto history = session->data().history(id.full.peerId); + if (!reply.text.isEmpty()) { + const auto replyToId = (id.msgId > 0 + && !history->peer->isUser()) + ? id.msgId + : 0; + auto draft = std::make_unique( + reply, + replyToId, + MessageCursor{ + reply.text.size(), + reply.text.size(), + QFIXED_MAX, + }, + Data::PreviewState::Allowed); + history->setLocalDraft(std::move(draft)); + } window->widget()->showFromTray(); window->widget()->reActivateWindow(); if (Core::App().passcodeLocked()) { diff --git a/Telegram/SourceFiles/window/notifications_manager.h b/Telegram/SourceFiles/window/notifications_manager.h index 006e233b2..408eeb8af 100644 --- a/Telegram/SourceFiles/window/notifications_manager.h +++ b/Telegram/SourceFiles/window/notifications_manager.h @@ -191,7 +191,9 @@ public: doClearFromSession(session); } - void notificationActivated(NotificationId id); + void notificationActivated( + NotificationId id, + const TextWithTags &draft = {}); void notificationReplied(NotificationId id, const TextWithTags &reply); struct DisplayOptions { diff --git a/Telegram/cmake/generate_midl.cmake b/Telegram/cmake/generate_midl.cmake index d40477f75..6bbf128e6 100644 --- a/Telegram/cmake/generate_midl.cmake +++ b/Telegram/cmake/generate_midl.cmake @@ -4,40 +4,52 @@ # For license and copyright information please follow this link: # https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL -function(generate_midl target_name idl_file) +function(generate_midl target_name src_loc) + set(list ${ARGV}) + list(REMOVE_AT list 0 1) + set(gen_dst ${CMAKE_CURRENT_BINARY_DIR}/gen) file(MAKE_DIRECTORY ${gen_dst}) - get_filename_component(idl_file_name ${idl_file} NAME_WLE) - - set(gen_timestamp ${gen_dst}/midl_${idl_file_name}.timestamp) - set(gen_files - ${gen_dst}/${idl_file_name}_i.c - ${gen_dst}/${idl_file_name}_h.h - ) - if (build_win64) set(env x64) else() set(env win32) endif() + + set(gen_timestamp ${gen_dst}/${target_name}_midl.timestamp) + set(gen_files "") + set(full_generation_sources "") + set(full_dependencies_list "") + foreach (file ${list}) + list(APPEND full_generation_sources ${src_loc}/${file}) + get_filename_component(file_name ${file} NAME_WLE) + list(APPEND gen_files + ${gen_dst}/${file_name}_i.c + ${gen_dst}/${file_name}_h.h + ) + list(APPEND gen_commands + COMMAND + midl + /out ${gen_dst} + /h ${file_name}_h.h + /env ${env} + /notlb + ${src_loc}/${file} + ) + endforeach() + add_custom_command( OUTPUT ${gen_timestamp} BYPRODUCTS ${gen_files} - COMMAND - midl - /out ${gen_dst} - /h ${idl_file_name}_h.h - /env ${env} - /notlb - ${idl_file} + ${gen_commands} COMMAND echo 1> ${gen_timestamp} - COMMENT "Generating header from IDL (${target_name})" + COMMENT "Generating headers from IDLs (${target_name})" DEPENDS - ${idl_file} + ${full_generation_sources} ) generate_target(${target_name} midl ${gen_timestamp} "${gen_files}" ${gen_dst}) endfunction() diff --git a/Telegram/lib_base b/Telegram/lib_base index fc8b0dcd7..e4b41e940 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit fc8b0dcd75453c7a136ee2e6bc17eccba77e5fec +Subproject commit e4b41e9409def2f65a021571e67e8ec3ec34f90e diff --git a/cmake b/cmake index 5c32bf152..2b7b92f30 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 5c32bf152f7b83d4a8e53366a1531a7305bc6d40 +Subproject commit 2b7b92f30bd5c723562e1002375981b455700487