mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Handle toast activations by COM activator.
This commit is contained in:
parent
86e07518ad
commit
e7cf560da0
16 changed files with 511 additions and 135 deletions
|
@ -42,7 +42,10 @@ include(cmake/generate_appdata_changelog.cmake)
|
||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
include(cmake/generate_midl.cmake)
|
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)
|
nuget_add_winrt(Telegram)
|
||||||
endif()
|
endif()
|
||||||
|
@ -950,6 +953,8 @@ PRIVATE
|
||||||
platform/win/windows_dlls.h
|
platform/win/windows_dlls.h
|
||||||
platform/win/windows_event_filter.cpp
|
platform/win/windows_event_filter.cpp
|
||||||
platform/win/windows_event_filter.h
|
platform/win/windows_event_filter.h
|
||||||
|
platform/win/windows_toast_activator.cpp
|
||||||
|
platform/win/windows_toast_activator.h
|
||||||
platform/platform_audio.h
|
platform/platform_audio.h
|
||||||
platform/platform_file_utilities.h
|
platform/platform_file_utilities.h
|
||||||
platform/platform_launcher.h
|
platform/platform_launcher.h
|
||||||
|
@ -1384,6 +1389,7 @@ if (WIN32)
|
||||||
/DELAYLOAD:netapi32.dll
|
/DELAYLOAD:netapi32.dll
|
||||||
/DELAYLOAD:userenv.dll
|
/DELAYLOAD:userenv.dll
|
||||||
/DELAYLOAD:wtsapi32.dll
|
/DELAYLOAD:wtsapi32.dll
|
||||||
|
/DELAYLOAD:propsys.dll
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
@ -390,11 +390,12 @@ void MainWindow::initHook() {
|
||||||
using namespace base::Platform;
|
using namespace base::Platform;
|
||||||
auto factory = ComPtr<IUIViewSettingsInterop>();
|
auto factory = ComPtr<IUIViewSettingsInterop>();
|
||||||
if (SupportsWRL()) {
|
if (SupportsWRL()) {
|
||||||
GetActivationFactory(
|
ABI::Windows::Foundation::GetActivationFactory(
|
||||||
StringReferenceWrapper(
|
StringReferenceWrapper(
|
||||||
RuntimeClass_Windows_UI_ViewManagement_UIViewSettings).Get(),
|
RuntimeClass_Windows_UI_ViewManagement_UIViewSettings).Get(),
|
||||||
&factory);
|
&factory);
|
||||||
if (factory) {
|
if (factory) {
|
||||||
|
// NB! No such method (or IUIViewSettingsInterop) in C++/WinRT :(
|
||||||
factory->GetForWindow(
|
factory->GetForWindow(
|
||||||
ps_hWnd,
|
ps_hWnd,
|
||||||
IID_PPV_ARGS(&_private->viewSettings));
|
IID_PPV_ARGS(&_private->viewSettings));
|
||||||
|
|
|
@ -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_co_task_mem.h"
|
||||||
#include "base/platform/win/base_windows_winrt.h"
|
#include "base/platform/win/base_windows_winrt.h"
|
||||||
#include "base/platform/base_platform_info.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_app_user_model_id.h"
|
||||||
|
#include "platform/win/windows_toast_activator.h"
|
||||||
#include "platform/win/windows_event_filter.h"
|
#include "platform/win/windows_event_filter.h"
|
||||||
#include "platform/win/windows_dlls.h"
|
#include "platform/win/windows_dlls.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
|
@ -48,8 +51,10 @@ namespace Notifications {
|
||||||
#ifndef __MINGW32__
|
#ifndef __MINGW32__
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kNotificationTemplate = LR"(
|
[[nodiscard]] std::wstring NotificationTemplate(QString id) {
|
||||||
<toast launch="action=open">
|
const auto wid = id.replace('&', "&").toStdWString();
|
||||||
|
return LR"(
|
||||||
|
<toast launch="action=open&)" + wid + LR"(">
|
||||||
<visual>
|
<visual>
|
||||||
<binding template="ToastGeneric">
|
<binding template="ToastGeneric">
|
||||||
<image placement="appLogoOverride" hint-crop="circle" src=""/>
|
<image placement="appLogoOverride" hint-crop="circle" src=""/>
|
||||||
|
@ -62,17 +67,24 @@ constexpr auto kNotificationTemplate = LR"(
|
||||||
<input id="fastReply" type="text" placeHolderContent=""/>
|
<input id="fastReply" type="text" placeHolderContent=""/>
|
||||||
<action
|
<action
|
||||||
content="Send"
|
content="Send"
|
||||||
arguments="action=reply"
|
arguments="action=reply&)" + wid + LR"("
|
||||||
activationType="background"
|
activationType="background"
|
||||||
imageUri=""
|
imageUri=""
|
||||||
hint-inputId="fastReply"/>
|
hint-inputId="fastReply"/>
|
||||||
|
<action
|
||||||
|
content=""
|
||||||
|
arguments="action=mark&)" + wid + LR"("
|
||||||
|
activationType="background"/>
|
||||||
</actions>
|
</actions>
|
||||||
<audio silent="true"/>
|
<audio silent="true"/>
|
||||||
</toast>
|
</toast>
|
||||||
)";
|
)";
|
||||||
|
}
|
||||||
|
|
||||||
constexpr auto kNotificationTemplateSmall = LR"(
|
[[nodiscard]] std::wstring NotificationTemplateSmall(QString id) {
|
||||||
<toast launch="action=open">
|
const auto wid = id.replace('&', "&").toStdWString();
|
||||||
|
return LR"(
|
||||||
|
<toast launch="action=open&)" + wid + LR"(">
|
||||||
<visual>
|
<visual>
|
||||||
<binding template="ToastGeneric">
|
<binding template="ToastGeneric">
|
||||||
<image placement="appLogoOverride" hint-crop="circle" src=""/>
|
<image placement="appLogoOverride" hint-crop="circle" src=""/>
|
||||||
|
@ -84,17 +96,21 @@ constexpr auto kNotificationTemplateSmall = LR"(
|
||||||
<audio silent="true"/>
|
<audio silent="true"/>
|
||||||
</toast>
|
</toast>
|
||||||
)";
|
)";
|
||||||
|
}
|
||||||
|
|
||||||
bool init() {
|
bool init() {
|
||||||
if (!IsWindows8OrGreater()) {
|
if (!IsWindows8OrGreater()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ((Dlls::SetCurrentProcessExplicitAppUserModelID == nullptr)
|
if ((Dlls::SetCurrentProcessExplicitAppUserModelID == nullptr)
|
||||||
|| (Dlls::PropVariantToString == nullptr)
|
|
||||||
|| !base::WinRT::Supported()) {
|
|| !base::WinRT::Supported()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
using namespace Microsoft::WRL;
|
||||||
|
Module<OutOfProc>::GetModule().RegisterObjects();
|
||||||
|
}
|
||||||
if (!AppUserModelId::validateShortcut()) {
|
if (!AppUserModelId::validateShortcut()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -159,6 +175,26 @@ void SetReplyPlaceholder(
|
||||||
placeholder);
|
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 Checked = false;
|
||||||
auto InitSucceeded = false;
|
auto InitSucceeded = false;
|
||||||
|
|
||||||
|
@ -387,6 +423,8 @@ public:
|
||||||
not_null<Window::SessionController*> window);
|
not_null<Window::SessionController*> window);
|
||||||
void clearNotification(NotificationId id);
|
void clearNotification(NotificationId id);
|
||||||
|
|
||||||
|
void handleActivation(const ToastActivation &activation);
|
||||||
|
|
||||||
~Private();
|
~Private();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -410,12 +448,17 @@ private:
|
||||||
base::flat_map<
|
base::flat_map<
|
||||||
FullPeer,
|
FullPeer,
|
||||||
base::flat_map<MsgId, ToastNotification>> _notifications;
|
base::flat_map<MsgId, ToastNotification>> _notifications;
|
||||||
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Manager::Private::Private(Manager *instance, Type type)
|
Manager::Private::Private(Manager *instance, Type type)
|
||||||
: _cachedUserpics(type)
|
: _cachedUserpics(type)
|
||||||
, _guarded(std::make_shared<Manager*>(instance)) {
|
, _guarded(std::make_shared<Manager*>(instance)) {
|
||||||
|
ToastActivations(
|
||||||
|
) | rpl::start_with_next([=](const ToastActivation &activation) {
|
||||||
|
handleActivation(activation);
|
||||||
|
}, _lifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Manager::Private::init() {
|
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(
|
bool Manager::Private::showNotification(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
std::shared_ptr<Data::CloudImageView> &userpicView,
|
std::shared_ptr<Data::CloudImageView> &userpicView,
|
||||||
|
@ -549,17 +626,32 @@ bool Manager::Private::showNotificationInTryCatch(
|
||||||
bool hideReplyButton) {
|
bool hideReplyButton) {
|
||||||
const auto withSubtitle = !subtitle.isEmpty();
|
const auto withSubtitle = !subtitle.isEmpty();
|
||||||
auto toastXml = XmlDocument();
|
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();
|
const auto modern = Platform::IsWindows10OrGreater();
|
||||||
if (modern) {
|
if (modern) {
|
||||||
toastXml.LoadXml(hideReplyButton
|
toastXml.LoadXml(hideReplyButton
|
||||||
? kNotificationTemplateSmall
|
? NotificationTemplateSmall(idString)
|
||||||
: kNotificationTemplate);
|
: NotificationTemplate(idString));
|
||||||
} else {
|
} else {
|
||||||
toastXml = ToastNotificationManager::GetTemplateContent(
|
toastXml = ToastNotificationManager::GetTemplateContent(
|
||||||
(withSubtitle
|
(withSubtitle
|
||||||
? ToastTemplateType::ToastImageAndText04
|
? ToastTemplateType::ToastImageAndText04
|
||||||
: ToastTemplateType::ToastImageAndText02));
|
: ToastTemplateType::ToastImageAndText02));
|
||||||
SetAudioSilent(toastXml);
|
SetAudioSilent(toastXml);
|
||||||
|
SetAction(toastXml, idString);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto userpicKey = hideNameAndPhoto
|
const auto userpicKey = hideNameAndPhoto
|
||||||
|
@ -576,6 +668,9 @@ bool Manager::Private::showNotificationInTryCatch(
|
||||||
SetReplyPlaceholder(
|
SetReplyPlaceholder(
|
||||||
toastXml,
|
toastXml,
|
||||||
tr::lng_message_ph(tr::now).toStdWString());
|
tr::lng_message_ph(tr::now).toStdWString());
|
||||||
|
SetMarkAsReadText(
|
||||||
|
toastXml,
|
||||||
|
tr::lng_context_mark_read(tr::now).toStdWString());
|
||||||
}
|
}
|
||||||
|
|
||||||
SetImageSrc(toastXml, userpicPathWide);
|
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);
|
auto toast = ToastNotification(toastXml);
|
||||||
const auto token1 = toast.Activated([=](
|
const auto token1 = toast.Activated([=](
|
||||||
const ToastNotification &sender,
|
const ToastNotification &sender,
|
||||||
const winrt::Windows::Foundation::IInspectable &object) {
|
const winrt::Windows::Foundation::IInspectable &object) {
|
||||||
|
auto activation = ToastActivation();
|
||||||
|
const auto string = &ToastActivation::String;
|
||||||
if (const auto args = object.try_as<ToastActivatedEventArgs>()) {
|
if (const auto args = object.try_as<ToastActivatedEventArgs>()) {
|
||||||
const auto arguments = args.Arguments();
|
activation.args = string(args.Arguments().c_str());
|
||||||
const auto userInput = args.UserInput();
|
const auto reply = args.UserInput().TryLookup(L"fastReply");
|
||||||
if (arguments == L"action=reply") {
|
const auto data = reply.try_as<IReference<winrt::hstring>>();
|
||||||
const auto reply = userInput.TryLookup(L"fastReply");
|
if (data) {
|
||||||
const auto data = reply.try_as<IReference<winrt::hstring>>();
|
activation.input.push_back({
|
||||||
auto text = data
|
.key = u"fastReply"_q,
|
||||||
? QString::fromWCharArray(data.GetString().c_str())
|
.value = string(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);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
performOnMainQueue([notificationId](Manager *manager) {
|
activation.args = "action=open&" + idString;
|
||||||
manager->notificationActivated(notificationId);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
crl::on_main([=, activation = std::move(activation)]() mutable {
|
||||||
|
if (const auto strong = weak.lock()) {
|
||||||
|
(*strong)->handleActivation(activation);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
const auto token2 = toast.Dismissed([=](
|
const auto token2 = toast.Dismissed([=](
|
||||||
const ToastNotification &sender,
|
const ToastNotification &sender,
|
||||||
|
@ -705,6 +786,10 @@ void Manager::clearNotification(NotificationId id) {
|
||||||
_private->clearNotification(id);
|
_private->clearNotification(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Manager::handleActivation(const ToastActivation &activation) {
|
||||||
|
_private->handleActivation(activation);
|
||||||
|
}
|
||||||
|
|
||||||
Manager::~Manager() = default;
|
Manager::~Manager() = default;
|
||||||
|
|
||||||
void Manager::doShowNativeNotification(
|
void Manager::doShowNativeNotification(
|
||||||
|
|
|
@ -9,6 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "platform/platform_notifications_manager.h"
|
#include "platform/platform_notifications_manager.h"
|
||||||
|
|
||||||
|
struct ToastActivation;
|
||||||
|
|
||||||
namespace Platform {
|
namespace Platform {
|
||||||
namespace Notifications {
|
namespace Notifications {
|
||||||
|
|
||||||
|
@ -22,6 +24,8 @@ public:
|
||||||
bool init();
|
bool init();
|
||||||
void clearNotification(NotificationId id);
|
void clearNotification(NotificationId id);
|
||||||
|
|
||||||
|
void handleActivation(const ToastActivation &activation);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void doShowNativeNotification(
|
void doShowNativeNotification(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
|
|
|
@ -33,9 +33,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include <ShObjIdl_core.h>
|
#include <ShObjIdl_core.h>
|
||||||
#include <shellapi.h>
|
#include <shellapi.h>
|
||||||
|
|
||||||
#include <roapi.h>
|
|
||||||
#include <wrl/client.h>
|
|
||||||
|
|
||||||
#include <openssl/conf.h>
|
#include <openssl/conf.h>
|
||||||
#include <openssl/engine.h>
|
#include <openssl/engine.h>
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
|
@ -68,7 +65,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#define WM_NCPOINTERUP 0x0243
|
#define WM_NCPOINTERUP 0x0243
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
using namespace Microsoft::WRL;
|
|
||||||
using namespace Platform;
|
using namespace Platform;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -422,17 +418,17 @@ namespace {
|
||||||
namespace Platform {
|
namespace Platform {
|
||||||
|
|
||||||
PermissionStatus GetPermissionStatus(PermissionType type) {
|
PermissionStatus GetPermissionStatus(PermissionType type) {
|
||||||
if (type==PermissionType::Microphone) {
|
if (type == PermissionType::Microphone) {
|
||||||
PermissionStatus result=PermissionStatus::Granted;
|
PermissionStatus result = PermissionStatus::Granted;
|
||||||
HKEY hKey;
|
HKEY hKey;
|
||||||
LSTATUS res=RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\CapabilityAccessManager\\ConsentStore\\microphone", 0, KEY_QUERY_VALUE, &hKey);
|
LSTATUS res = RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\CapabilityAccessManager\\ConsentStore\\microphone", 0, KEY_QUERY_VALUE, &hKey);
|
||||||
if(res==ERROR_SUCCESS) {
|
if (res == ERROR_SUCCESS) {
|
||||||
wchar_t buf[20];
|
wchar_t buf[20];
|
||||||
DWORD length=sizeof(buf);
|
DWORD length = sizeof(buf);
|
||||||
res=RegQueryValueEx(hKey, L"Value", NULL, NULL, (LPBYTE)buf, &length);
|
res = RegQueryValueEx(hKey, L"Value", NULL, NULL, (LPBYTE)buf, &length);
|
||||||
if(res==ERROR_SUCCESS) {
|
if (res == ERROR_SUCCESS) {
|
||||||
if(wcscmp(buf, L"Deny")==0) {
|
if (wcscmp(buf, L"Deny") == 0) {
|
||||||
result=PermissionStatus::Denied;
|
result = PermissionStatus::Denied;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RegCloseKey(hKey);
|
RegCloseKey(hKey);
|
||||||
|
@ -498,20 +494,17 @@ void _manageAppLnk(bool create, bool silent, int path_csidl, const wchar_t *args
|
||||||
if (SUCCEEDED(hr)) {
|
if (SUCCEEDED(hr)) {
|
||||||
QString lnk = QString::fromWCharArray(startupFolder) + '\\' + AppFile.utf16() + qsl(".lnk");
|
QString lnk = QString::fromWCharArray(startupFolder) + '\\' + AppFile.utf16() + qsl(".lnk");
|
||||||
if (create) {
|
if (create) {
|
||||||
ComPtr<IShellLink> shellLink;
|
const auto shellLink = base::WinRT::TryCreateInstance<IShellLink>(
|
||||||
hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink));
|
CLSID_ShellLink,
|
||||||
if (SUCCEEDED(hr)) {
|
CLSCTX_INPROC_SERVER);
|
||||||
ComPtr<IPersistFile> persistFile;
|
if (shellLink) {
|
||||||
|
|
||||||
QString exe = QDir::toNativeSeparators(cExeDir() + cExeName()), dir = QDir::toNativeSeparators(QDir(cWorkingDir()).absolutePath());
|
QString exe = QDir::toNativeSeparators(cExeDir() + cExeName()), dir = QDir::toNativeSeparators(QDir(cWorkingDir()).absolutePath());
|
||||||
shellLink->SetArguments(args);
|
shellLink->SetArguments(args);
|
||||||
shellLink->SetPath(exe.toStdWString().c_str());
|
shellLink->SetPath(exe.toStdWString().c_str());
|
||||||
shellLink->SetWorkingDirectory(dir.toStdWString().c_str());
|
shellLink->SetWorkingDirectory(dir.toStdWString().c_str());
|
||||||
shellLink->SetDescription(description);
|
shellLink->SetDescription(description);
|
||||||
|
|
||||||
ComPtr<IPropertyStore> propertyStore;
|
if (const auto propertyStore = shellLink.try_as<IPropertyStore>()) {
|
||||||
hr = shellLink.As(&propertyStore);
|
|
||||||
if (SUCCEEDED(hr)) {
|
|
||||||
PROPVARIANT appIdPropVar;
|
PROPVARIANT appIdPropVar;
|
||||||
hr = InitPropVariantFromString(AppUserModelId::getId(), &appIdPropVar);
|
hr = InitPropVariantFromString(AppUserModelId::getId(), &appIdPropVar);
|
||||||
if (SUCCEEDED(hr)) {
|
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 (const auto persistFile = shellLink.try_as<IPersistFile>()) {
|
||||||
if (SUCCEEDED(hr)) {
|
|
||||||
hr = persistFile->Save(lnk.toStdWString().c_str(), TRUE);
|
hr = persistFile->Save(lnk.toStdWString().c_str(), TRUE);
|
||||||
} else {
|
} else {
|
||||||
if (!silent) LOG(("App Error: could not create interface IID_IPersistFile %1").arg(hr));
|
if (!silent) LOG(("App Error: could not create interface IID_IPersistFile %1").arg(hr));
|
||||||
|
|
|
@ -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_app_user_model_id.h"
|
||||||
|
|
||||||
#include "platform/win/windows_dlls.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 <propvarutil.h>
|
||||||
#include <propkey.h>
|
#include <propkey.h>
|
||||||
|
|
||||||
#include <roapi.h>
|
|
||||||
#include <wrl/client.h>
|
|
||||||
|
|
||||||
using namespace Microsoft::WRL;
|
using namespace Microsoft::WRL;
|
||||||
|
|
||||||
namespace Platform {
|
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_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_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
|
#ifdef OS_WIN_STORE
|
||||||
const WCHAR AppUserModelIdRelease[] = L"Telegram.TelegramDesktop.Store";
|
const WCHAR AppUserModelIdRelease[] = L"Telegram.TelegramDesktop.Store";
|
||||||
|
@ -36,15 +37,14 @@ QString pinnedPath() {
|
||||||
static const int maxFileLen = MAX_PATH * 10;
|
static const int maxFileLen = MAX_PATH * 10;
|
||||||
WCHAR wstrPath[maxFileLen];
|
WCHAR wstrPath[maxFileLen];
|
||||||
if (GetEnvironmentVariable(L"APPDATA", wstrPath, maxFileLen)) {
|
if (GetEnvironmentVariable(L"APPDATA", wstrPath, maxFileLen)) {
|
||||||
QDir appData(QString::fromStdWString(std::wstring(wstrPath)));
|
auto appData = QDir(QString::fromStdWString(std::wstring(wstrPath)));
|
||||||
return appData.absolutePath() + qsl("/Microsoft/Internet Explorer/Quick Launch/User Pinned/TaskBar/");
|
return appData.absolutePath()
|
||||||
|
+ u"/Microsoft/Internet Explorer/Quick Launch/User Pinned/TaskBar/"_q;
|
||||||
}
|
}
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
void checkPinned() {
|
void checkPinned() {
|
||||||
if (!Dlls::PropVariantToString) return;
|
|
||||||
|
|
||||||
static const int maxFileLen = MAX_PATH * 10;
|
static const int maxFileLen = MAX_PATH * 10;
|
||||||
|
|
||||||
HRESULT hr = CoInitialize(0);
|
HRESULT hr = CoInitialize(0);
|
||||||
|
@ -56,14 +56,27 @@ void checkPinned() {
|
||||||
WCHAR src[MAX_PATH];
|
WCHAR src[MAX_PATH];
|
||||||
GetModuleFileName(GetModuleHandle(0), src, MAX_PATH);
|
GetModuleFileName(GetModuleHandle(0), src, MAX_PATH);
|
||||||
BY_HANDLE_FILE_INFORMATION srcinfo = { 0 };
|
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;
|
if (srcfile == INVALID_HANDLE_VALUE) return;
|
||||||
BOOL srcres = GetFileInformationByHandle(srcfile, &srcinfo);
|
BOOL srcres = GetFileInformationByHandle(srcfile, &srcinfo);
|
||||||
CloseHandle(srcfile);
|
CloseHandle(srcfile);
|
||||||
if (!srcres) return;
|
if (!srcres) return;
|
||||||
LOG(("Checking..."));
|
LOG(("Checking..."));
|
||||||
WIN32_FIND_DATA findData;
|
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) {
|
if (findHandle == INVALID_HANDLE_VALUE) {
|
||||||
LOG(("Init Error: could not find files in pinned folder"));
|
LOG(("Init Error: could not find files in pinned folder"));
|
||||||
return;
|
return;
|
||||||
|
@ -78,7 +91,11 @@ void checkPinned() {
|
||||||
if (attributes >= 0xFFFFFFF) continue; // file does not exist
|
if (attributes >= 0xFFFFFFF) continue; // file does not exist
|
||||||
|
|
||||||
ComPtr<IShellLink> shellLink;
|
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;
|
if (!SUCCEEDED(hr)) continue;
|
||||||
|
|
||||||
ComPtr<IPersistFile> persistFile;
|
ComPtr<IPersistFile> persistFile;
|
||||||
|
@ -93,13 +110,22 @@ void checkPinned() {
|
||||||
if (!SUCCEEDED(hr)) continue;
|
if (!SUCCEEDED(hr)) continue;
|
||||||
|
|
||||||
BY_HANDLE_FILE_INFORMATION dstinfo = { 0 };
|
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;
|
if (dstfile == INVALID_HANDLE_VALUE) continue;
|
||||||
BOOL dstres = GetFileInformationByHandle(dstfile, &dstinfo);
|
BOOL dstres = GetFileInformationByHandle(dstfile, &dstinfo);
|
||||||
CloseHandle(dstfile);
|
CloseHandle(dstfile);
|
||||||
if (!dstres) continue;
|
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;
|
ComPtr<IPropertyStore> propertyStore;
|
||||||
hr = shellLink.As(&propertyStore);
|
hr = shellLink.As(&propertyStore);
|
||||||
if (!SUCCEEDED(hr)) return;
|
if (!SUCCEEDED(hr)) return;
|
||||||
|
@ -109,7 +135,7 @@ void checkPinned() {
|
||||||
if (!SUCCEEDED(hr)) return;
|
if (!SUCCEEDED(hr)) return;
|
||||||
LOG(("Reading..."));
|
LOG(("Reading..."));
|
||||||
WCHAR already[MAX_PATH];
|
WCHAR already[MAX_PATH];
|
||||||
hr = Dlls::PropVariantToString(appIdPropVar, already, MAX_PATH);
|
hr = PropVariantToString(appIdPropVar, already, MAX_PATH);
|
||||||
if (SUCCEEDED(hr)) {
|
if (SUCCEEDED(hr)) {
|
||||||
if (std::wstring(getId()) == already) {
|
if (std::wstring(getId()) == already) {
|
||||||
LOG(("Already!"));
|
LOG(("Already!"));
|
||||||
|
@ -141,7 +167,7 @@ void checkPinned() {
|
||||||
}
|
}
|
||||||
} while (FindNextFile(findHandle, &findData));
|
} while (FindNextFile(findHandle, &findData));
|
||||||
DWORD errorCode = GetLastError();
|
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"));
|
LOG(("Init Error: could not find some files in pinned folder"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -152,8 +178,9 @@ QString systemShortcutPath() {
|
||||||
static const int maxFileLen = MAX_PATH * 10;
|
static const int maxFileLen = MAX_PATH * 10;
|
||||||
WCHAR wstrPath[maxFileLen];
|
WCHAR wstrPath[maxFileLen];
|
||||||
if (GetEnvironmentVariable(L"APPDATA", wstrPath, maxFileLen)) {
|
if (GetEnvironmentVariable(L"APPDATA", wstrPath, maxFileLen)) {
|
||||||
QDir appData(QString::fromStdWString(std::wstring(wstrPath)));
|
auto appData = QDir(QString::fromStdWString(std::wstring(wstrPath)));
|
||||||
return appData.absolutePath() + qsl("/Microsoft/Windows/Start Menu/Programs/");
|
const auto path = appData.absolutePath();
|
||||||
|
return path + u"/Microsoft/Windows/Start Menu/Programs/"_q;
|
||||||
}
|
}
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
@ -168,7 +195,11 @@ void cleanupShortcut() {
|
||||||
if (attributes >= 0xFFFFFFF) return; // file does not exist
|
if (attributes >= 0xFFFFFFF) return; // file does not exist
|
||||||
|
|
||||||
ComPtr<IShellLink> shellLink;
|
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;
|
if (!SUCCEEDED(hr)) return;
|
||||||
|
|
||||||
ComPtr<IPersistFile> persistFile;
|
ComPtr<IPersistFile> persistFile;
|
||||||
|
@ -180,10 +211,15 @@ void cleanupShortcut() {
|
||||||
|
|
||||||
WCHAR szGotPath[MAX_PATH];
|
WCHAR szGotPath[MAX_PATH];
|
||||||
WIN32_FIND_DATA wfd;
|
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 (!SUCCEEDED(hr)) return;
|
||||||
|
|
||||||
if (QDir::toNativeSeparators(cExeDir() + cExeName()).toStdWString() == szGotPath) {
|
const auto full = cExeDir() + cExeName();
|
||||||
|
if (QDir::toNativeSeparators(full).toStdWString() == szGotPath) {
|
||||||
QFile().remove(path);
|
QFile().remove(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -197,7 +233,11 @@ bool validateShortcutAt(const QString &path) {
|
||||||
if (attributes >= 0xFFFFFFF) return false; // file does not exist
|
if (attributes >= 0xFFFFFFF) return false; // file does not exist
|
||||||
|
|
||||||
ComPtr<IShellLink> shellLink;
|
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;
|
if (!SUCCEEDED(hr)) return false;
|
||||||
|
|
||||||
ComPtr<IPersistFile> persistFile;
|
ComPtr<IPersistFile> persistFile;
|
||||||
|
@ -212,22 +252,31 @@ bool validateShortcutAt(const QString &path) {
|
||||||
if (!SUCCEEDED(hr)) return false;
|
if (!SUCCEEDED(hr)) return false;
|
||||||
|
|
||||||
PROPVARIANT appIdPropVar;
|
PROPVARIANT appIdPropVar;
|
||||||
|
PROPVARIANT toastActivatorPropVar;
|
||||||
hr = propertyStore->GetValue(getKey(), &appIdPropVar);
|
hr = propertyStore->GetValue(getKey(), &appIdPropVar);
|
||||||
if (!SUCCEEDED(hr)) return false;
|
if (!SUCCEEDED(hr)) return false;
|
||||||
|
|
||||||
|
hr = propertyStore->GetValue(
|
||||||
|
pkey_AppUserModel_ToastActivator,
|
||||||
|
&toastActivatorPropVar);
|
||||||
|
if (!SUCCEEDED(hr)) return false;
|
||||||
|
|
||||||
WCHAR already[MAX_PATH];
|
WCHAR already[MAX_PATH];
|
||||||
hr = Dlls::PropVariantToString(appIdPropVar, already, MAX_PATH);
|
hr = PropVariantToString(appIdPropVar, already, MAX_PATH);
|
||||||
if (SUCCEEDED(hr)) {
|
const auto good1 = SUCCEEDED(hr) && (std::wstring(getId()) == already);
|
||||||
if (std::wstring(getId()) == already) {
|
const auto bad1 = !good1 && (appIdPropVar.vt != VT_EMPTY);
|
||||||
PropVariantClear(&appIdPropVar);
|
PropVariantClear(&appIdPropVar);
|
||||||
return true;
|
|
||||||
}
|
auto clsid = CLSID();
|
||||||
}
|
hr = PropVariantToCLSID(toastActivatorPropVar, &clsid);
|
||||||
if (appIdPropVar.vt != VT_EMPTY) {
|
const auto good2 = SUCCEEDED(hr) && (clsid == __uuidof(ToastActivator));
|
||||||
PropVariantClear(&appIdPropVar);
|
const auto bad2 = !good2 && (toastActivatorPropVar.vt != VT_EMPTY);
|
||||||
|
PropVariantClear(&toastActivatorPropVar);
|
||||||
|
if (good1 && good2) {
|
||||||
|
return true;
|
||||||
|
} else if (bad1 || bad2) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
PropVariantClear(&appIdPropVar);
|
|
||||||
|
|
||||||
hr = InitPropVariantFromString(getId(), &appIdPropVar);
|
hr = InitPropVariantFromString(getId(), &appIdPropVar);
|
||||||
if (!SUCCEEDED(hr)) return false;
|
if (!SUCCEEDED(hr)) return false;
|
||||||
|
@ -236,6 +285,17 @@ bool validateShortcutAt(const QString &path) {
|
||||||
PropVariantClear(&appIdPropVar);
|
PropVariantClear(&appIdPropVar);
|
||||||
if (!SUCCEEDED(hr)) return false;
|
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();
|
hr = propertyStore->Commit();
|
||||||
if (!SUCCEEDED(hr)) return false;
|
if (!SUCCEEDED(hr)) return false;
|
||||||
|
|
||||||
|
@ -248,30 +308,48 @@ bool validateShortcutAt(const QString &path) {
|
||||||
|
|
||||||
bool validateShortcut() {
|
bool validateShortcut() {
|
||||||
QString path = systemShortcutPath();
|
QString path = systemShortcutPath();
|
||||||
if (path.isEmpty() || cExeName().isEmpty()) return false;
|
if (path.isEmpty() || cExeName().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (cAlphaVersion()) {
|
if (cAlphaVersion()) {
|
||||||
path += qsl("TelegramAlpha.lnk");
|
path += u"TelegramAlpha.lnk"_q;
|
||||||
if (validateShortcutAt(path)) return true;
|
if (validateShortcutAt(path)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (validateShortcutAt(path + qsl("Telegram Desktop/Telegram.lnk"))) return true;
|
const auto installed = u"Telegram Desktop/Telegram.lnk"_q;
|
||||||
if (validateShortcutAt(path + qsl("Telegram Win (Unofficial)/Telegram.lnk"))) return true;
|
const auto old = u"Telegram Win (Unofficial)/Telegram.lnk"_q;
|
||||||
|
if (validateShortcutAt(path + installed)
|
||||||
|
|| validateShortcutAt(path + old)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
path += qsl("Telegram.lnk");
|
path += u"Telegram.lnk"_q;
|
||||||
if (validateShortcutAt(path)) return true;
|
if (validateShortcutAt(path)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ComPtr<IShellLink> shellLink;
|
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;
|
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;
|
if (!SUCCEEDED(hr)) return false;
|
||||||
|
|
||||||
hr = shellLink->SetArguments(L"");
|
hr = shellLink->SetArguments(L"");
|
||||||
if (!SUCCEEDED(hr)) return false;
|
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;
|
if (!SUCCEEDED(hr)) return false;
|
||||||
|
|
||||||
ComPtr<IPropertyStore> propertyStore;
|
ComPtr<IPropertyStore> propertyStore;
|
||||||
|
@ -286,15 +364,29 @@ bool validateShortcut() {
|
||||||
PropVariantClear(&appIdPropVar);
|
PropVariantClear(&appIdPropVar);
|
||||||
if (!SUCCEEDED(hr)) return false;
|
if (!SUCCEEDED(hr)) return false;
|
||||||
|
|
||||||
#if WINVER >= 0x602
|
|
||||||
PROPVARIANT startPinPropVar;
|
PROPVARIANT startPinPropVar;
|
||||||
hr = InitPropVariantFromUInt32(APPUSERMODEL_STARTPINOPTION_NOPINONINSTALL, &startPinPropVar);
|
hr = InitPropVariantFromUInt32(
|
||||||
|
APPUSERMODEL_STARTPINOPTION_NOPINONINSTALL,
|
||||||
|
&startPinPropVar);
|
||||||
if (!SUCCEEDED(hr)) return false;
|
if (!SUCCEEDED(hr)) return false;
|
||||||
|
|
||||||
hr = propertyStore->SetValue(pkey_AppUserModel_StartPinOption, startPinPropVar);
|
hr = propertyStore->SetValue(
|
||||||
|
pkey_AppUserModel_StartPinOption,
|
||||||
|
startPinPropVar);
|
||||||
PropVariantClear(&startPinPropVar);
|
PropVariantClear(&startPinPropVar);
|
||||||
if (!SUCCEEDED(hr)) return false;
|
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();
|
hr = propertyStore->Commit();
|
||||||
if (!SUCCEEDED(hr)) return false;
|
if (!SUCCEEDED(hr)) return false;
|
||||||
|
@ -303,7 +395,9 @@ bool validateShortcut() {
|
||||||
hr = shellLink.As(&persistFile);
|
hr = shellLink.As(&persistFile);
|
||||||
if (!SUCCEEDED(hr)) return false;
|
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;
|
if (!SUCCEEDED(hr)) return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -58,7 +58,6 @@ SafeIniter::SafeIniter() {
|
||||||
LOAD_SYMBOL(LibWtsApi32, WTSUnRegisterSessionNotification);
|
LOAD_SYMBOL(LibWtsApi32, WTSUnRegisterSessionNotification);
|
||||||
|
|
||||||
const auto LibPropSys = LoadLibrary(L"propsys.dll");
|
const auto LibPropSys = LoadLibrary(L"propsys.dll");
|
||||||
LOAD_SYMBOL(LibPropSys, PropVariantToString);
|
|
||||||
LOAD_SYMBOL(LibPropSys, PSStringFromPropertyKey);
|
LOAD_SYMBOL(LibPropSys, PSStringFromPropertyKey);
|
||||||
|
|
||||||
const auto LibPsApi = LoadLibrary(L"psapi.dll");
|
const auto LibPsApi = LoadLibrary(L"psapi.dll");
|
||||||
|
|
|
@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "base/platform/win/base_windows_h.h"
|
#include "base/platform/win/base_windows_h.h"
|
||||||
|
|
||||||
#include <shlobj.h>
|
#include <shlobj.h>
|
||||||
#include <roapi.h>
|
|
||||||
#include <dwmapi.h>
|
#include <dwmapi.h>
|
||||||
#include <RestartManager.h>
|
#include <RestartManager.h>
|
||||||
#include <psapi.h>
|
#include <psapi.h>
|
||||||
|
@ -79,10 +78,6 @@ inline BOOL(__stdcall *WTSUnRegisterSessionNotification)(
|
||||||
|
|
||||||
// PROPSYS.DLL
|
// PROPSYS.DLL
|
||||||
|
|
||||||
inline HRESULT(__stdcall *PropVariantToString)(
|
|
||||||
_In_ REFPROPVARIANT propvar,
|
|
||||||
_Out_writes_(cch) PWSTR psz,
|
|
||||||
_In_ UINT cch);
|
|
||||||
inline HRESULT(__stdcall *PSStringFromPropertyKey)(
|
inline HRESULT(__stdcall *PSStringFromPropertyKey)(
|
||||||
_In_ REFPROPERTYKEY pkey,
|
_In_ REFPROPERTYKEY pkey,
|
||||||
_Out_writes_(cch) LPWSTR psz,
|
_Out_writes_(cch) LPWSTR psz,
|
||||||
|
|
|
@ -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);
|
50
Telegram/SourceFiles/platform/win/windows_toast_activator.h
Normal file
50
Telegram/SourceFiles/platform/win/windows_toast_activator.h
Normal 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();
|
29
Telegram/SourceFiles/platform/win/windows_toastactivator.idl
Normal file
29
Telegram/SourceFiles/platform/win/windows_toastactivator.idl
Normal 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);
|
||||||
|
};
|
|
@ -628,7 +628,9 @@ QString Manager::accountNameSeparator() {
|
||||||
return QString::fromUtf8(" \xE2\x9E\x9C ");
|
return QString::fromUtf8(" \xE2\x9E\x9C ");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::notificationActivated(NotificationId id) {
|
void Manager::notificationActivated(
|
||||||
|
NotificationId id,
|
||||||
|
const TextWithTags &reply) {
|
||||||
onBeforeNotificationActivated(id);
|
onBeforeNotificationActivated(id);
|
||||||
if (const auto session = system()->findSession(id.full.sessionId)) {
|
if (const auto session = system()->findSession(id.full.sessionId)) {
|
||||||
if (session->windows().empty()) {
|
if (session->windows().empty()) {
|
||||||
|
@ -637,6 +639,22 @@ void Manager::notificationActivated(NotificationId id) {
|
||||||
if (!session->windows().empty()) {
|
if (!session->windows().empty()) {
|
||||||
const auto window = session->windows().front();
|
const auto window = session->windows().front();
|
||||||
const auto history = session->data().history(id.full.peerId);
|
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()->showFromTray();
|
||||||
window->widget()->reActivateWindow();
|
window->widget()->reActivateWindow();
|
||||||
if (Core::App().passcodeLocked()) {
|
if (Core::App().passcodeLocked()) {
|
||||||
|
|
|
@ -191,7 +191,9 @@ public:
|
||||||
doClearFromSession(session);
|
doClearFromSession(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
void notificationActivated(NotificationId id);
|
void notificationActivated(
|
||||||
|
NotificationId id,
|
||||||
|
const TextWithTags &draft = {});
|
||||||
void notificationReplied(NotificationId id, const TextWithTags &reply);
|
void notificationReplied(NotificationId id, const TextWithTags &reply);
|
||||||
|
|
||||||
struct DisplayOptions {
|
struct DisplayOptions {
|
||||||
|
|
|
@ -4,40 +4,52 @@
|
||||||
# For license and copyright information please follow this link:
|
# For license and copyright information please follow this link:
|
||||||
# https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
# 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)
|
set(gen_dst ${CMAKE_CURRENT_BINARY_DIR}/gen)
|
||||||
file(MAKE_DIRECTORY ${gen_dst})
|
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)
|
if (build_win64)
|
||||||
set(env x64)
|
set(env x64)
|
||||||
else()
|
else()
|
||||||
set(env win32)
|
set(env win32)
|
||||||
endif()
|
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(
|
add_custom_command(
|
||||||
OUTPUT
|
OUTPUT
|
||||||
${gen_timestamp}
|
${gen_timestamp}
|
||||||
BYPRODUCTS
|
BYPRODUCTS
|
||||||
${gen_files}
|
${gen_files}
|
||||||
COMMAND
|
${gen_commands}
|
||||||
midl
|
|
||||||
/out ${gen_dst}
|
|
||||||
/h ${idl_file_name}_h.h
|
|
||||||
/env ${env}
|
|
||||||
/notlb
|
|
||||||
${idl_file}
|
|
||||||
COMMAND
|
COMMAND
|
||||||
echo 1> ${gen_timestamp}
|
echo 1> ${gen_timestamp}
|
||||||
COMMENT "Generating header from IDL (${target_name})"
|
COMMENT "Generating headers from IDLs (${target_name})"
|
||||||
DEPENDS
|
DEPENDS
|
||||||
${idl_file}
|
${full_generation_sources}
|
||||||
)
|
)
|
||||||
generate_target(${target_name} midl ${gen_timestamp} "${gen_files}" ${gen_dst})
|
generate_target(${target_name} midl ${gen_timestamp} "${gen_files}" ${gen_dst})
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit fc8b0dcd75453c7a136ee2e6bc17eccba77e5fec
|
Subproject commit e4b41e9409def2f65a021571e67e8ec3ec34f90e
|
2
cmake
2
cmake
|
@ -1 +1 @@
|
||||||
Subproject commit 5c32bf152f7b83d4a8e53366a1531a7305bc6d40
|
Subproject commit 2b7b92f30bd5c723562e1002375981b455700487
|
Loading…
Add table
Reference in a new issue