Handle toast activations by COM activator.

This commit is contained in:
John Preston 2021-10-05 22:37:34 +04:00
parent 86e07518ad
commit e7cf560da0
16 changed files with 511 additions and 135 deletions

View file

@ -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()

View file

@ -390,11 +390,12 @@ void MainWindow::initHook() {
using namespace base::Platform;
auto factory = ComPtr<IUIViewSettingsInterop>();
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));

View file

@ -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"(
<toast launch="action=open">
[[nodiscard]] std::wstring NotificationTemplate(QString id) {
const auto wid = id.replace('&', "&amp;").toStdWString();
return LR"(
<toast launch="action=open&amp;)" + wid + LR"(">
<visual>
<binding template="ToastGeneric">
<image placement="appLogoOverride" hint-crop="circle" src=""/>
@ -62,17 +67,24 @@ constexpr auto kNotificationTemplate = LR"(
<input id="fastReply" type="text" placeHolderContent=""/>
<action
content="Send"
arguments="action=reply"
arguments="action=reply&amp;)" + wid + LR"("
activationType="background"
imageUri=""
hint-inputId="fastReply"/>
<action
content=""
arguments="action=mark&amp;)" + wid + LR"("
activationType="background"/>
</actions>
<audio silent="true"/>
</toast>
)";
}
constexpr auto kNotificationTemplateSmall = LR"(
<toast launch="action=open">
[[nodiscard]] std::wstring NotificationTemplateSmall(QString id) {
const auto wid = id.replace('&', "&amp;").toStdWString();
return LR"(
<toast launch="action=open&amp;)" + wid + LR"(">
<visual>
<binding template="ToastGeneric">
<image placement="appLogoOverride" hint-crop="circle" src=""/>
@ -84,17 +96,21 @@ constexpr auto kNotificationTemplateSmall = LR"(
<audio silent="true"/>
</toast>
)";
}
bool init() {
if (!IsWindows8OrGreater()) {
return false;
}
if ((Dlls::SetCurrentProcessExplicitAppUserModelID == nullptr)
|| (Dlls::PropVariantToString == nullptr)
|| !base::WinRT::Supported()) {
return false;
}
{
using namespace Microsoft::WRL;
Module<OutOfProc>::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<XmlElement>()) {
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::SessionController*> window);
void clearNotification(NotificationId id);
void handleActivation(const ToastActivation &activation);
~Private();
private:
@ -410,12 +448,17 @@ private:
base::flat_map<
FullPeer,
base::flat_map<MsgId, ToastNotification>> _notifications;
rpl::lifetime _lifetime;
};
Manager::Private::Private(Manager *instance, Type type)
: _cachedUserpics(type)
, _guarded(std::make_shared<Manager*>(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<PeerData*> peer,
std::shared_ptr<Data::CloudImageView> &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<ToastActivatedEventArgs>()) {
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<IReference<winrt::hstring>>();
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<IReference<winrt::hstring>>();
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(

View file

@ -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<PeerData*> peer,

View file

@ -33,9 +33,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <ShObjIdl_core.h>
#include <shellapi.h>
#include <roapi.h>
#include <wrl/client.h>
#include <openssl/conf.h>
#include <openssl/engine.h>
#include <openssl/err.h>
@ -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<IShellLink> shellLink;
hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink));
if (SUCCEEDED(hr)) {
ComPtr<IPersistFile> persistFile;
const auto shellLink = base::WinRT::TryCreateInstance<IShellLink>(
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<IPropertyStore> propertyStore;
hr = shellLink.As(&propertyStore);
if (SUCCEEDED(hr)) {
if (const auto propertyStore = shellLink.try_as<IPropertyStore>()) {
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<IPersistFile>()) {
hr = persistFile->Save(lnk.toStdWString().c_str(), TRUE);
} else {
if (!silent) LOG(("App Error: could not create interface IID_IPersistFile %1").arg(hr));

View file

@ -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 <propvarutil.h>
#include <propkey.h>
#include <roapi.h>
#include <wrl/client.h>
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<IShellLink> 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<IPersistFile> 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<IPropertyStore> 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<IShellLink> 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<IPersistFile> 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<IShellLink> 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<IPersistFile> 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<IShellLink> 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<IPropertyStore> 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;

View file

@ -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");

View file

@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/platform/win/base_windows_h.h"
#include <shlobj.h>
#include <roapi.h>
#include <dwmapi.h>
#include <RestartManager.h>
#include <psapi.h>
@ -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,

View file

@ -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 <wrl/module.h>
#pragma warning(pop)
namespace {
rpl::event_stream<ToastActivation> 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<ToastActivation::UserInput>();
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<INotificationActivationCallback*>(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<ToastActivation> ToastActivations() {
return GlobalToastActivations.events();
}
CoCreatableClass(ToastActivator);

View file

@ -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<UserInput> input;
[[nodiscard]] static QString String(LPCWSTR value);
};
[[nodiscard]] rpl::producer<ToastActivation> ToastActivations();

View file

@ -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);
};

View file

@ -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<Data::Draft>(
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()) {

View file

@ -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 {

View file

@ -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()

@ -1 +1 @@
Subproject commit fc8b0dcd75453c7a136ee2e6bc17eccba77e5fec
Subproject commit e4b41e9409def2f65a021571e67e8ec3ec34f90e

2
cmake

@ -1 +1 @@
Subproject commit 5c32bf152f7b83d4a8e53366a1531a7305bc6d40
Subproject commit 2b7b92f30bd5c723562e1002375981b455700487