init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled

This commit is contained in:
allhaileris
2026-02-16 15:50:16 +03:00
commit afb81b8278
13816 changed files with 3689732 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
/*
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/current_geo_location_win.h"
#include "base/platform/win/base_windows_winrt.h"
#include "core/current_geo_location.h"
#include <winrt/Windows.Devices.Geolocation.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Services.Maps.h>
#include <winrt/Windows.Foundation.Collections.h>
namespace Platform {
void ResolveCurrentExactLocation(Fn<void(Core::GeoLocation)> callback) {
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Devices::Geolocation;
const auto success = base::WinRT::Try([&] {
Geolocator geolocator;
geolocator.DesiredAccuracy(PositionAccuracy::High);
if (geolocator.LocationStatus() == PositionStatus::NotAvailable) {
callback({});
return;
}
geolocator.GetGeopositionAsync().Completed([=](
IAsyncOperation<Geoposition> that,
AsyncStatus status) {
if (status != AsyncStatus::Completed) {
crl::on_main([=] {
callback({});
});
return;
}
const auto point = base::WinRT::Try([&] {
const auto coordinate = that.GetResults().Coordinate();
return coordinate.Point().Position();
});
crl::on_main([=] {
if (!point) {
callback({});
} else {
callback({
.point = { point->Latitude, point->Longitude },
.accuracy = Core::GeoLocationAccuracy::Exact,
});
}
});
});
});
if (!success) {
callback({});
}
}
void ResolveLocationAddress(
const Core::GeoLocation &location,
const QString &language,
Fn<void(Core::GeoAddress)> callback) {
callback({});
}
} // namespace Platform

View File

@@ -0,0 +1,10 @@
/*
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 "platform/platform_current_geo_location.h"

View File

@@ -0,0 +1,473 @@
/*
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/file_utilities_win.h"
#include "mainwindow.h"
#include "storage/localstorage.h"
#include "platform/win/windows_dlls.h"
#include "lang/lang_keys.h"
#include "core/application.h"
#include "core/crash_reports.h"
#include "window/window_controller.h"
#include "ui/ui_utility.h"
#include <QtWidgets/QFileDialog>
#include <QtGui/QDesktopServices>
#include <QtCore/QSettings>
#include <QtCore/QStandardPaths>
#include <Shlwapi.h>
#include <Windowsx.h>
HBITMAP qt_pixmapToWinHBITMAP(const QPixmap &, int hbitmapFormat);
namespace Platform {
namespace File {
namespace {
class OpenWithApp {
public:
OpenWithApp(const QString &name, IAssocHandler *handler, HBITMAP icon = nullptr)
: _name(name)
, _handler(handler)
, _icon(icon) {
}
OpenWithApp(OpenWithApp &&other)
: _name(base::take(other._name))
, _handler(base::take(other._handler))
, _icon(base::take(other._icon)) {
}
OpenWithApp &operator=(OpenWithApp &&other) {
_name = base::take(other._name);
_icon = base::take(other._icon);
_handler = base::take(other._handler);
return (*this);
}
OpenWithApp(const OpenWithApp &other) = delete;
OpenWithApp &operator=(const OpenWithApp &other) = delete;
~OpenWithApp() {
if (_icon) {
DeleteBitmap(_icon);
}
if (_handler) {
_handler->Release();
}
}
const QString &name() const {
return _name;
}
HBITMAP icon() const {
return _icon;
}
IAssocHandler *handler() const {
return _handler;
}
private:
QString _name;
IAssocHandler *_handler = nullptr;
HBITMAP _icon = nullptr;
};
HBITMAP IconToBitmap(LPWSTR icon, int iconindex) {
if (!icon) return 0;
WCHAR tmpIcon[4096];
if (icon[0] == L'@' && SUCCEEDED(SHLoadIndirectString(icon, tmpIcon, 4096, 0))) {
icon = tmpIcon;
}
int32 w = GetSystemMetrics(SM_CXSMICON), h = GetSystemMetrics(SM_CYSMICON);
HICON ico = ExtractIcon(0, icon, iconindex);
if (!ico) {
if (!iconindex) { // try to read image
QImage img(QString::fromWCharArray(icon));
if (!img.isNull()) {
return qt_pixmapToWinHBITMAP(
Ui::PixmapFromImage(
img.scaled(
w,
h,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation)),
/* HBitmapAlpha */ 2);
}
}
return 0;
}
HDC screenDC = GetDC(0), hdc = CreateCompatibleDC(screenDC);
HBITMAP result = CreateCompatibleBitmap(screenDC, w, h);
HGDIOBJ was = SelectObject(hdc, result);
DrawIconEx(hdc, 0, 0, ico, w, h, 0, NULL, DI_NORMAL);
SelectObject(hdc, was);
DeleteDC(hdc);
ReleaseDC(0, screenDC);
DestroyIcon(ico);
return (HBITMAP)CopyImage(result, IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE | LR_CREATEDIBSECTION);
}
bool ShouldSaveZoneInformation() {
// Check if the "Do not preserve zone information in file attachments" policy is enabled.
const auto keyName = L"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Attachments";
const auto valueName = L"SaveZoneInformation";
auto key = HKEY();
auto result = RegOpenKeyEx(HKEY_CURRENT_USER, keyName, 0, KEY_READ, &key);
if (result != ERROR_SUCCESS) {
// If the registry key cannot be opened, assume the default behavior:
// Windows preserves zone information for downloaded files.
return true;
}
DWORD value = 0, type = 0, size = sizeof(value);
result = RegQueryValueEx(key, valueName, 0, &type, (LPBYTE)&value, &size);
RegCloseKey(key);
if (result != ERROR_SUCCESS || type != REG_DWORD) {
return true;
}
return (value != 1);
}
} // namespace
void UnsafeOpenEmailLink(const QString &email) {
auto url = QUrl(qstr("mailto:") + email);
if (!QDesktopServices::openUrl(url)) {
auto wstringUrl = url.toString(QUrl::FullyEncoded).toStdWString();
if (Dlls::SHOpenWithDialog) {
OPENASINFO info;
info.oaifInFlags = OAIF_ALLOW_REGISTRATION
| OAIF_REGISTER_EXT
| OAIF_EXEC
#if WINVER >= 0x0602
| OAIF_FILE_IS_URI
#endif // WINVER >= 0x602
| OAIF_URL_PROTOCOL;
info.pcszClass = NULL;
info.pcszFile = wstringUrl.c_str();
Dlls::SHOpenWithDialog(0, &info);
} else if (Dlls::OpenAs_RunDLL) {
Dlls::OpenAs_RunDLL(0, 0, wstringUrl.c_str(), SW_SHOWNORMAL);
} else {
ShellExecute(0, L"open", wstringUrl.c_str(), 0, 0, SW_SHOWNORMAL);
}
}
}
bool UnsafeShowOpenWithDropdown(const QString &filepath) {
if (!Dlls::SHAssocEnumHandlers || !Dlls::SHCreateItemFromParsingName) {
return false;
}
auto window = Core::App().activeWindow();
if (!window) {
return false;
}
auto parentHWND = window->widget()->psHwnd();
auto wstringPath = QDir::toNativeSeparators(filepath).toStdWString();
auto result = false;
std::vector<OpenWithApp> handlers;
IShellItem* pItem = nullptr;
if (SUCCEEDED(Dlls::SHCreateItemFromParsingName(wstringPath.c_str(), nullptr, IID_PPV_ARGS(&pItem)))) {
IEnumAssocHandlers *assocHandlers = nullptr;
if (SUCCEEDED(pItem->BindToHandler(nullptr, BHID_EnumAssocHandlers, IID_PPV_ARGS(&assocHandlers)))) {
HRESULT hr = S_FALSE;
do {
IAssocHandler *handler = nullptr;
ULONG ulFetched = 0;
hr = assocHandlers->Next(1, &handler, &ulFetched);
if (FAILED(hr) || hr == S_FALSE || !ulFetched) break;
LPWSTR name = 0;
if (SUCCEEDED(handler->GetUIName(&name))) {
LPWSTR icon = 0;
int iconindex = 0;
if (SUCCEEDED(handler->GetIconLocation(&icon, &iconindex)) && icon) {
handlers.push_back(OpenWithApp(QString::fromWCharArray(name), handler, IconToBitmap(icon, iconindex)));
CoTaskMemFree(icon);
} else {
handlers.push_back(OpenWithApp(QString::fromWCharArray(name), handler));
}
CoTaskMemFree(name);
} else {
handler->Release();
}
} while (hr != S_FALSE);
assocHandlers->Release();
}
if (!handlers.empty()) {
HMENU menu = CreatePopupMenu();
ranges::sort(handlers, [](const OpenWithApp &a, auto &b) {
return a.name() < b.name();
});
for (int32 i = 0, l = handlers.size(); i < l; ++i) {
MENUITEMINFO menuInfo = { 0 };
menuInfo.cbSize = sizeof(menuInfo);
menuInfo.fMask = MIIM_STRING | MIIM_DATA | MIIM_ID;
menuInfo.fType = MFT_STRING;
menuInfo.wID = i + 1;
if (auto icon = handlers[i].icon()) {
menuInfo.fMask |= MIIM_BITMAP;
menuInfo.hbmpItem = icon;
}
auto name = handlers[i].name();
if (name.size() > 512) name = name.mid(0, 512);
WCHAR nameArr[1024];
name.toWCharArray(nameArr);
nameArr[name.size()] = 0;
menuInfo.dwTypeData = nameArr;
InsertMenuItem(menu, GetMenuItemCount(menu), TRUE, &menuInfo);
}
MENUITEMINFO sepInfo = { 0 };
sepInfo.cbSize = sizeof(sepInfo);
sepInfo.fMask = MIIM_STRING | MIIM_DATA;
sepInfo.fType = MFT_SEPARATOR;
InsertMenuItem(menu, GetMenuItemCount(menu), true, &sepInfo);
MENUITEMINFO menuInfo = { 0 };
menuInfo.cbSize = sizeof(menuInfo);
menuInfo.fMask = MIIM_STRING | MIIM_DATA | MIIM_ID;
menuInfo.fType = MFT_STRING;
menuInfo.wID = handlers.size() + 1;
QString name = tr::lng_wnd_choose_program_menu(tr::now);
if (name.size() > 512) name = name.mid(0, 512);
WCHAR nameArr[1024];
name.toWCharArray(nameArr);
nameArr[name.size()] = 0;
menuInfo.dwTypeData = nameArr;
InsertMenuItem(menu, GetMenuItemCount(menu), TRUE, &menuInfo);
POINT position;
GetCursorPos(&position);
int sel = TrackPopupMenu(menu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON | TPM_RETURNCMD, position.x, position.y, 0, parentHWND, 0);
DestroyMenu(menu);
if (sel > 0) {
if (sel <= handlers.size()) {
IDataObject *dataObj = 0;
if (SUCCEEDED(pItem->BindToHandler(nullptr, BHID_DataObject, IID_PPV_ARGS(&dataObj))) && dataObj) {
handlers[sel - 1].handler()->Invoke(dataObj);
dataObj->Release();
result = true;
}
}
} else {
result = true;
}
}
pItem->Release();
}
return result;
}
bool UnsafeShowOpenWith(const QString &filepath) {
auto wstringPath = QDir::toNativeSeparators(filepath).toStdWString();
if (Dlls::SHOpenWithDialog) {
OPENASINFO info;
info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_REGISTER_EXT | OAIF_EXEC;
info.pcszClass = NULL;
info.pcszFile = wstringPath.c_str();
Dlls::SHOpenWithDialog(0, &info);
return true;
} else if (Dlls::OpenAs_RunDLL) {
Dlls::OpenAs_RunDLL(0, 0, wstringPath.c_str(), SW_SHOWNORMAL);
return true;
}
return false;
}
void UnsafeLaunch(const QString &filepath) {
auto wstringPath = QDir::toNativeSeparators(filepath).toStdWString();
ShellExecute(0, L"open", wstringPath.c_str(), 0, 0, SW_SHOWNORMAL);
}
void PostprocessDownloaded(const QString &filepath) {
// Mark file saved to the NTFS file system as originating from the Internet security zone
// unless this feature is disabled by Group Policy.
if (!ShouldSaveZoneInformation()) {
return;
}
auto wstringZoneFile = QDir::toNativeSeparators(filepath).toStdWString() + L":Zone.Identifier";
auto f = CreateFile(wstringZoneFile.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (f == INVALID_HANDLE_VALUE) { // :(
return;
}
const char data[] = "[ZoneTransfer]\r\nZoneId=3\r\n";
DWORD written = 0;
BOOL result = WriteFile(f, data, sizeof(data), &written, NULL);
CloseHandle(f);
if (!result || written != sizeof(data)) { // :(
return;
}
}
} // namespace File
namespace FileDialog {
namespace {
using Type = ::FileDialog::internal::Type;
} // namespace
void InitLastPath() {
// hack to restore previous dir without hurting performance
QSettings settings(QSettings::UserScope, qstr("QtProject"));
settings.beginGroup(qstr("Qt"));
QByteArray sd = settings.value(qstr("filedialog")).toByteArray();
QDataStream stream(&sd, QIODevice::ReadOnly);
if (!stream.atEnd()) {
int version = 3, _QFileDialogMagic = 190;
QByteArray splitterState;
QByteArray headerData;
QList<QUrl> bookmarks;
QStringList history;
QString currentDirectory;
qint32 marker;
qint32 v;
qint32 viewMode;
stream >> marker;
stream >> v;
if (marker == _QFileDialogMagic && v == version) {
stream >> splitterState
>> bookmarks
>> history
>> currentDirectory
>> headerData
>> viewMode;
cSetDialogLastPath(currentDirectory);
}
}
if (cDialogHelperPath().isEmpty()) {
QDir temppath(cWorkingDir() + "tdata/tdummy/");
if (!temppath.exists()) {
temppath.mkpath(temppath.absolutePath());
}
if (temppath.exists()) {
cSetDialogHelperPath(temppath.absolutePath());
}
}
}
bool Get(
QPointer<QWidget> parent,
QStringList &files,
QByteArray &remoteContent,
const QString &caption,
const QString &filter,
::FileDialog::internal::Type type,
QString startFile) {
if (cDialogLastPath().isEmpty()) {
Platform::FileDialog::InitLastPath();
}
// A hack for fast dialog create. There was some huge performance problem
// if we open a file dialog in some folder with a large amount of files.
// Some internal Qt watcher iterated over all of them, querying some information
// that forced file icon and maybe other properties being resolved and this was
// a blocking operation.
auto helperPath = cDialogHelperPathFinal();
QFileDialog dialog(parent, caption, helperPath, filter);
dialog.setModal(true);
if (type == Type::ReadFile || type == Type::ReadFiles) {
dialog.setFileMode((type == Type::ReadFiles) ? QFileDialog::ExistingFiles : QFileDialog::ExistingFile);
dialog.setAcceptMode(QFileDialog::AcceptOpen);
} else if (type == Type::ReadFolder) { // save dir
dialog.setAcceptMode(QFileDialog::AcceptOpen);
dialog.setFileMode(QFileDialog::Directory);
dialog.setOption(QFileDialog::ShowDirsOnly);
} else { // save file
dialog.setFileMode(QFileDialog::AnyFile);
dialog.setAcceptMode(QFileDialog::AcceptSave);
}
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
dialog.show();
#endif // Qt < 6.0.0
auto realLastPath = [=] {
// If we're given some non empty path containing a folder - use it.
if (!startFile.isEmpty() && (startFile.indexOf('/') >= 0 || startFile.indexOf('\\') >= 0)) {
return QFileInfo(startFile).dir().absolutePath();
}
return cDialogLastPath();
}();
if (realLastPath.isEmpty() || realLastPath.endsWith(qstr("/tdummy"))) {
realLastPath = QStandardPaths::writableLocation(
QStandardPaths::DownloadLocation);
}
dialog.setDirectory(realLastPath);
auto toSelect = startFile;
if (type == Type::WriteFile) {
const auto lastSlash = toSelect.lastIndexOf('/');
if (lastSlash >= 0) {
toSelect = toSelect.mid(lastSlash + 1);
}
const auto lastBackSlash = toSelect.lastIndexOf('\\');
if (lastBackSlash >= 0) {
toSelect = toSelect.mid(lastBackSlash + 1);
}
dialog.selectFile(toSelect);
}
CrashReports::SetAnnotation(
"file_dialog",
QString("caption:%1;helper:%2;filter:%3;real:%4;select:%5"
).arg(caption
).arg(helperPath
).arg(filter
).arg(realLastPath
).arg(toSelect));
const auto result = dialog.exec();
CrashReports::ClearAnnotation("file_dialog");
if (type != Type::ReadFolder) {
// Save last used directory for all queries except directory choosing.
const auto path = dialog.directory().absolutePath();
if (path != cDialogLastPath()) {
cSetDialogLastPath(path);
Local::writeSettings();
}
}
if (result == QDialog::Accepted) {
if (type == Type::ReadFiles) {
files = dialog.selectedFiles();
} else {
files = dialog.selectedFiles().mid(0, 1);
}
//if (type == Type::ReadFile || type == Type::ReadFiles) {
// remoteContent = dialog.selectedRemoteContent();
//}
return true;
}
files = QStringList();
remoteContent = QByteArray();
return false;
}
} // namespace FileDialog
} // namespace Platform

View File

@@ -0,0 +1,24 @@
/*
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 "platform/platform_file_utilities.h"
namespace Platform {
namespace File {
inline QString UrlToLocal(const QUrl &url) {
return ::File::internal::UrlToLocalDefault(url);
}
inline void UnsafeOpenUrl(const QString &url) {
return ::File::internal::UnsafeOpenUrlDefault(url);
}
} // namespace File
} // namespace Platform

View File

@@ -0,0 +1,189 @@
/*
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/integration_win.h"
#include "base/platform/win/base_windows_winrt.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "core/sandbox.h"
#include "lang/lang_keys.h"
#include "platform/win/windows_app_user_model_id.h"
#include "platform/win/tray_win.h"
#include "platform/platform_integration.h"
#include "platform/platform_specific.h"
#include "tray.h"
#include "styles/style_window.h"
#include <QtCore/QAbstractNativeEventFilter>
#include <private/qguiapplication_p.h>
#include <propvarutil.h>
#include <propkey.h>
namespace Platform {
void WindowsIntegration::init() {
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
using namespace QNativeInterface::Private;
const auto native = qApp->nativeInterface<QWindowsApplication>();
if (native) {
native->setHasBorderInFullScreenDefault(true);
}
#endif // Qt >= 6.5.0
QCoreApplication::instance()->installNativeEventFilter(this);
_taskbarCreatedMsgId = RegisterWindowMessage(L"TaskbarButtonCreated");
}
ITaskbarList3 *WindowsIntegration::taskbarList() const {
return _taskbarList.get();
}
WindowsIntegration &WindowsIntegration::Instance() {
return static_cast<WindowsIntegration&>(Integration::Instance());
}
bool WindowsIntegration::nativeEventFilter(
const QByteArray &eventType,
void *message,
native_event_filter_result *result) {
return Core::Sandbox::Instance().customEnterFromEventLoop([&] {
const auto msg = static_cast<MSG*>(message);
return processEvent(
msg->hwnd,
msg->message,
msg->wParam,
msg->lParam,
(LRESULT*)result);
});
}
void WindowsIntegration::createCustomJumpList() {
_jumpList = base::WinRT::TryCreateInstance<ICustomDestinationList>(
CLSID_DestinationList);
if (_jumpList) {
refreshCustomJumpList();
}
}
void WindowsIntegration::refreshCustomJumpList() {
auto added = false;
auto maxSlots = UINT();
auto removed = (IObjectArray*)nullptr;
auto hr = _jumpList->BeginList(&maxSlots, IID_PPV_ARGS(&removed));
if (!SUCCEEDED(hr)) {
return;
}
const auto guard = gsl::finally([&] {
if (added) {
_jumpList->CommitList();
} else {
_jumpList->AbortList();
}
});
auto shellLink = base::WinRT::TryCreateInstance<IShellLink>(
CLSID_ShellLink);
if (!shellLink) {
return;
}
// Set the path to your application and the command-line argument for quitting
const auto exe = QDir::toNativeSeparators(cExeDir() + cExeName());
const auto dir = QDir::toNativeSeparators(QDir(cWorkingDir()).absolutePath());
const auto icon = Tray::QuitJumpListIconPath();
shellLink->SetArguments(L"-quit");
shellLink->SetPath(exe.toStdWString().c_str());
shellLink->SetWorkingDirectory(dir.toStdWString().c_str());
shellLink->SetIconLocation(icon.toStdWString().c_str(), 0);
if (const auto propertyStore = shellLink.try_as<IPropertyStore>()) {
auto appIdPropVar = PROPVARIANT();
hr = InitPropVariantFromString(
AppUserModelId::Id().c_str(),
&appIdPropVar);
if (SUCCEEDED(hr)) {
hr = propertyStore->SetValue(
AppUserModelId::Key(),
appIdPropVar);
PropVariantClear(&appIdPropVar);
}
auto titlePropVar = PROPVARIANT();
hr = InitPropVariantFromString(
tr::lng_quit_from_tray(tr::now).toStdWString().c_str(),
&titlePropVar);
if (SUCCEEDED(hr)) {
hr = propertyStore->SetValue(PKEY_Title, titlePropVar);
PropVariantClear(&titlePropVar);
}
propertyStore->Commit();
}
auto collection = base::WinRT::TryCreateInstance<IObjectCollection>(
CLSID_EnumerableObjectCollection);
if (!collection) {
return;
}
collection->AddObject(shellLink.get());
_jumpList->AddUserTasks(collection.get());
added = true;
}
bool WindowsIntegration::processEvent(
HWND hWnd,
UINT msg,
WPARAM wParam,
LPARAM lParam,
LRESULT *result) {
if (msg && msg == _taskbarCreatedMsgId && !_taskbarList) {
_taskbarList = base::WinRT::TryCreateInstance<ITaskbarList3>(
CLSID_TaskbarList,
CLSCTX_ALL);
if (_taskbarList) {
createCustomJumpList();
}
}
switch (msg) {
case WM_ENDSESSION:
Core::Quit();
break;
case WM_TIMECHANGE:
Core::App().checkAutoLockIn(100);
break;
case WM_WTSSESSION_CHANGE:
if (wParam == WTS_SESSION_LOGOFF
|| wParam == WTS_SESSION_LOCK) {
Core::App().setScreenIsLocked(true);
} else if (wParam == WTS_SESSION_LOGON
|| wParam == WTS_SESSION_UNLOCK) {
Core::App().setScreenIsLocked(false);
}
break;
case WM_SETTINGCHANGE:
RefreshTaskbarThemeValue();
#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0)
Core::App().settings().setSystemDarkMode(Platform::IsDarkMode());
#endif // Qt < 6.5.0
Core::App().tray().updateIconCounters();
if (_jumpList) {
refreshCustomJumpList();
}
break;
}
return false;
}
std::unique_ptr<Integration> CreateIntegration() {
return std::make_unique<WindowsIntegration>();
}
} // namespace Platform

View File

@@ -0,0 +1,51 @@
/*
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 "base/platform/win/base_windows_shlobj_h.h"
#include "base/platform/win/base_windows_winrt.h"
#include "platform/platform_integration.h"
#include <QAbstractNativeEventFilter>
namespace Platform {
class WindowsIntegration final
: public Integration
, public QAbstractNativeEventFilter {
public:
void init() override;
[[nodiscard]] ITaskbarList3 *taskbarList() const;
[[nodiscard]] static WindowsIntegration &Instance();
private:
bool nativeEventFilter(
const QByteArray &eventType,
void *message,
native_event_filter_result *result) override;
bool processEvent(
HWND hWnd,
UINT msg,
WPARAM wParam,
LPARAM lParam,
LRESULT *result);
void createCustomJumpList();
void refreshCustomJumpList();
uint32 _taskbarCreatedMsgId = 0;
winrt::com_ptr<ITaskbarList3> _taskbarList;
winrt::com_ptr<ICustomDestinationList> _jumpList;
};
[[nodiscard]] std::unique_ptr<Integration> CreateIntegration();
} // namespace Platform

View File

@@ -0,0 +1,134 @@
/*
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/launcher_win.h"
#include "core/crash_reports.h"
#include "core/update_checker.h"
#include <windows.h>
#include <shellapi.h>
#include <VersionHelpers.h>
namespace Platform {
Launcher::Launcher(int argc, char *argv[])
: Core::Launcher(argc, argv) {
}
std::optional<QStringList> Launcher::readArgumentsHook(
int argc,
char *argv[]) const {
auto count = 0;
if (const auto list = CommandLineToArgvW(GetCommandLine(), &count)) {
const auto guard = gsl::finally([&] { LocalFree(list); });
if (count > 0) {
auto result = QStringList();
result.reserve(count);
for (auto i = 0; i != count; ++i) {
result.push_back(QString::fromWCharArray(list[i]));
}
return result;
}
}
return std::nullopt;
}
bool Launcher::launchUpdater(UpdaterLaunch action) {
if (cExeName().isEmpty()) {
return false;
}
const auto operation = (action == UpdaterLaunch::JustRelaunch)
? QString()
: (cWriteProtected()
? u"runas"_q
: QString());
const auto binaryPath = (action == UpdaterLaunch::JustRelaunch)
? (cExeDir() + cExeName())
: (cWriteProtected()
? (cWorkingDir() + u"tupdates/temp/Updater.exe"_q)
: (cExeDir() + u"Updater.exe"_q));
auto argumentsList = QStringList();
const auto pushArgument = [&](const QString &argument) {
argumentsList.push_back(argument.trimmed());
};
if (cLaunchMode() == LaunchModeAutoStart) {
pushArgument(u"-autostart"_q);
}
if (Logs::DebugEnabled()) {
pushArgument(u"-debug"_q);
}
if (cStartInTray()) {
pushArgument(u"-startintray"_q);
}
if (customWorkingDir()) {
pushArgument(u"-workdir"_q);
pushArgument('"' + cWorkingDir() + '"');
}
if (cDataFile() != u"data"_q) {
pushArgument(u"-key"_q);
pushArgument('"' + cDataFile() + '"');
}
if (action == UpdaterLaunch::JustRelaunch) {
pushArgument(u"-noupdate"_q);
if (cRestartingToSettings()) {
pushArgument(u"-tosettings"_q);
}
} else {
pushArgument(u"-update"_q);
pushArgument(u"-exename"_q);
pushArgument('"' + cExeName() + '"');
if (cWriteProtected()) {
pushArgument(u"-writeprotected"_q);
pushArgument('"' + cExeDir() + '"');
}
}
return launch(operation, binaryPath, argumentsList);
}
bool Launcher::launch(
const QString &operation,
const QString &binaryPath,
const QStringList &argumentsList) {
const auto convertPath = [](const QString &path) {
return QDir::toNativeSeparators(path).toStdWString();
};
const auto nativeBinaryPath = convertPath(binaryPath);
const auto nativeWorkingDir = convertPath(cWorkingDir());
const auto arguments = argumentsList.join(' ');
DEBUG_LOG(("Application Info: executing %1 %2"
).arg(binaryPath
).arg(arguments
));
Logs::closeMain();
CrashReports::Finish();
const auto hwnd = HWND(0);
const auto result = ShellExecute(
hwnd,
operation.isEmpty() ? nullptr : operation.toStdWString().c_str(),
nativeBinaryPath.c_str(),
arguments.toStdWString().c_str(),
nativeWorkingDir.empty() ? nullptr : nativeWorkingDir.c_str(),
SW_SHOWNORMAL);
if (int64(result) < 32) {
DEBUG_LOG(("Application Error: failed to execute %1, working directory: '%2', result: %3"
).arg(binaryPath
).arg(cWorkingDir()
).arg(int64(result)
));
return false;
}
return true;
}
} // namespace Platform

View File

@@ -0,0 +1,32 @@
/*
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 "core/launcher.h"
namespace Platform {
class Launcher : public Core::Launcher {
public:
Launcher(int argc, char *argv[]);
private:
std::optional<QStringList> readArgumentsHook(
int argc,
char *argv[]) const override;
bool launchUpdater(UpdaterLaunch action) override;
bool launch(
const QString &operation,
const QString &binaryPath,
const QStringList &argumentsList);
};
} // namespace Platform

View File

@@ -0,0 +1,849 @@
/*
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/main_window_win.h"
#include "styles/style_window.h"
#include "platform/platform_specific.h"
#include "platform/platform_notifications_manager.h"
#include "platform/win/tray_win.h"
#include "platform/win/windows_dlls.h"
#include "platform/win/integration_win.h"
#include "window/notifications_manager.h"
#include "window/window_session_controller.h"
#include "mainwindow.h"
#include "main/main_session.h"
#include "base/crc32hash.h"
#include "base/platform/win/base_windows_wrl.h"
#include "base/platform/base_platform_info.h"
#include "core/application.h"
#include "core/sandbox.h"
#include "lang/lang_keys.h"
#include "storage/localstorage.h"
#include "ui/widgets/popup_menu.h"
#include "ui/ui_utility.h"
#include "window/themes/window_theme.h"
#include "window/window_controller.h"
#include "history/history.h"
#include <QtWidgets/QStyleFactory>
#include <QtWidgets/QApplication>
#include <QtGui/QWindow>
#include <QtGui/QScreen>
#include <QtCore/QOperatingSystemVersion>
#include <Shobjidl.h>
#include <shellapi.h>
#include <WtsApi32.h>
#include <dwmapi.h>
#include <windows.ui.viewmanagement.h>
#include <UIViewSettingsInterop.h>
#include <Windowsx.h>
#include <VersionHelpers.h>
// Taken from qtbase/src/gui/image/qpixmap_win.cpp
HICON qt_pixmapToWinHICON(const QPixmap &);
HBITMAP qt_imageToWinHBITMAP(const QImage &, int hbitmapFormat);
namespace ViewManagement = ABI::Windows::UI::ViewManagement;
namespace Platform {
namespace {
// Mouse down on tray icon deactivates the application.
// So there is no way to know for sure if the tray icon was clicked from
// active application or from inactive application. So we assume that
// if the application was deactivated less than 0.5s ago, then the tray
// icon click (both left or right button) was made from the active app.
constexpr auto kKeepActiveForTrayIcon = crl::time(500);
using namespace Microsoft::WRL;
// Taken from qtbase/src/gui/image/qpixmap_win.cpp
enum HBitmapFormat {
HBitmapNoAlpha,
HBitmapPremultipliedAlpha,
HBitmapAlpha
};
class EventFilter final : public QAbstractNativeEventFilter {
public:
explicit EventFilter(not_null<MainWindow*> window);
private:
bool nativeEventFilter(
const QByteArray &eventType,
void *message,
native_event_filter_result *result) override;
bool mainWindowEvent(
HWND hWnd,
UINT msg,
WPARAM wParam,
LPARAM lParam,
LRESULT *result);
const not_null<MainWindow*> _window;
};
[[nodiscard]] HICON NativeIcon(const QIcon &icon, QSize size) {
if (!icon.isNull()) {
const auto pixmap = icon.pixmap(icon.actualSize(size));
if (!pixmap.isNull()) {
return qt_pixmapToWinHICON(pixmap);
}
}
return nullptr;
}
struct RealSize {
QSize value;
bool maximized = false;
};
[[nodiscard]] RealSize DetectRealSize(HWND hwnd) {
auto result = RECT();
auto placement = WINDOWPLACEMENT();
if (!GetWindowPlacement(hwnd, &placement)) {
return {};
} else if (placement.flags & WPF_RESTORETOMAXIMIZED) {
const auto monitor = MonitorFromRect(
&placement.rcNormalPosition,
MONITOR_DEFAULTTONULL);
if (!monitor) {
return {};
}
auto info = MONITORINFO{ .cbSize = sizeof(MONITORINFO) };
if (!GetMonitorInfo(monitor, &info)) {
return {};
}
result = info.rcWork;
} else {
CopyRect(&result, &placement.rcNormalPosition);
}
return {
{ int(result.right - result.left), int(result.bottom - result.top) },
((placement.flags & WPF_RESTORETOMAXIMIZED) != 0)
};
}
[[nodiscard]] QImage PrepareLogoPreview(
QSize size,
QImage::Format format,
int radius = 0) {
auto result = QImage(size, QImage::Format_RGB32);
result.fill(st::windowBg->c);
const auto logo = Window::Logo();
const auto width = size.width();
const auto height = size.height();
const auto side = logo.width();
const auto skip = width / 8;
const auto use = std::min({ width - skip, height - skip, side });
auto p = QPainter(&result);
if (use == side) {
p.drawImage((width - side) / 2, (height - side) / 2, logo);
} else {
const auto scaled = logo.scaled(
use,
use,
Qt::KeepAspectRatio,
Qt::SmoothTransformation);
p.drawImage((width - use) / 2, (height - use) / 2, scaled);
}
p.end();
return radius
? Images::Round(std::move(result), Images::CornersMask(radius))
: result;
}
EventFilter::EventFilter(not_null<MainWindow*> window) : _window(window) {
}
bool EventFilter::nativeEventFilter(
const QByteArray &eventType,
void *message,
native_event_filter_result *result) {
return Core::Sandbox::Instance().customEnterFromEventLoop([&] {
const auto msg = static_cast<MSG*>(message);
if (msg->hwnd == _window->psHwnd()
|| msg->hwnd && !_window->psHwnd()) {
return mainWindowEvent(
msg->hwnd,
msg->message,
msg->wParam,
msg->lParam,
(LRESULT*)result);
}
return false;
});
}
bool EventFilter::mainWindowEvent(
HWND hWnd,
UINT msg,
WPARAM wParam,
LPARAM lParam,
LRESULT *result) {
switch (msg) {
case WM_DESTROY: {
_window->destroyedFromSystem();
} return false;
case WM_ACTIVATE: {
if (LOWORD(wParam) != WA_INACTIVE) {
_window->shadowsActivate();
} else {
_window->shadowsDeactivate();
}
} return false;
case WM_SIZE: {
if (wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED || wParam == SIZE_MINIMIZED) {
if (wParam == SIZE_RESTORED && _window->windowState() == Qt::WindowNoState) {
_window->positionUpdated();
}
}
} return false;
case WM_MOVE: {
_window->positionUpdated();
} return false;
case WM_DWMSENDICONICTHUMBNAIL: {
if (!Core::App().passcodeLocked()) {
return false;
}
const auto size = QSize(int(HIWORD(lParam)), int(LOWORD(lParam)));
return _window->setDwmThumbnail(size);
}
case WM_DWMSENDICONICLIVEPREVIEWBITMAP: {
if (!Core::App().passcodeLocked()) {
return false;
}
const auto size = DetectRealSize(hWnd);
const auto radius = size.maximized ? 0 : style::ConvertScale(8);
return _window->setDwmPreview(size.value, radius);
}
}
return false;
}
[[nodiscard]] QString HumanReadableDisplayName(HMONITOR hMonitor) {
// https://github.com/qt/qtbase/commit/6136b92f540c15835e0c7eb7e01ab7b58fbea685
auto monitorInfo = MONITORINFOEX{};
monitorInfo.cbSize = sizeof(MONITORINFOEX);
if (!GetMonitorInfo(hMonitor, &monitorInfo)) {
return QString();
}
// Try Display Configuration API first (Windows 7+).
auto numPathArrayElements = UINT32(0);
auto numModeInfoArrayElements = UINT32(0);
if (GetDisplayConfigBufferSizes(
QDC_ONLY_ACTIVE_PATHS,
&numPathArrayElements,
&numModeInfoArrayElements) != ERROR_SUCCESS) {
return QString();
}
auto pathInfos = std::vector<DISPLAYCONFIG_PATH_INFO>(
numPathArrayElements);
auto modeInfos = std::vector<DISPLAYCONFIG_MODE_INFO>(
numModeInfoArrayElements);
if (QueryDisplayConfig(
QDC_ONLY_ACTIVE_PATHS,
&numPathArrayElements,
pathInfos.data(),
&numModeInfoArrayElements,
modeInfos.data(),
nullptr) != ERROR_SUCCESS) {
return QString();
}
// Find matching path.
for (const auto &path : pathInfos) {
auto deviceName = DISPLAYCONFIG_SOURCE_DEVICE_NAME{};
deviceName.header.type = static_cast<DISPLAYCONFIG_DEVICE_INFO_TYPE>(
DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME);
deviceName.header.size = sizeof(DISPLAYCONFIG_SOURCE_DEVICE_NAME);
deviceName.header.adapterId = path.sourceInfo.adapterId;
deviceName.header.id = path.sourceInfo.id;
if (DisplayConfigGetDeviceInfo(&deviceName.header) != ERROR_SUCCESS
|| wcscmp(
monitorInfo.szDevice,
deviceName.viewGdiDeviceName) != 0) {
continue;
}
auto targetName = DISPLAYCONFIG_TARGET_DEVICE_NAME{};
targetName.header.type = static_cast<DISPLAYCONFIG_DEVICE_INFO_TYPE>(
DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME);
targetName.header.size = sizeof(DISPLAYCONFIG_TARGET_DEVICE_NAME);
targetName.header.adapterId = path.targetInfo.adapterId;
targetName.header.id = path.targetInfo.id;
if (DisplayConfigGetDeviceInfo(&targetName.header) == ERROR_SUCCESS) {
const auto friendlyName = QString::fromWCharArray(
targetName.monitorFriendlyDeviceName);
if (!friendlyName.isEmpty()) {
return friendlyName;
}
}
}
// Fallback to legacy method.
auto displayDevice = DISPLAY_DEVICE{};
displayDevice.cb = sizeof(DISPLAY_DEVICE);
for (auto deviceIndex = DWORD(0);
EnumDisplayDevices(
monitorInfo.szDevice,
deviceIndex,
&displayDevice,
0);
deviceIndex++) {
if (!(displayDevice.StateFlags & DISPLAY_DEVICE_ACTIVE)) {
continue;
}
const auto deviceName = QString::fromWCharArray(
displayDevice.DeviceString);
if (!deviceName.isEmpty()
&& deviceName != u"Generic PnP Monitor"_q) {
return deviceName;
}
break;
}
return QString();
}
} // namespace
struct MainWindow::Private {
explicit Private(not_null<MainWindow*> window) : filter(window) {
}
EventFilter filter;
ComPtr<ViewManagement::IUIViewSettings> viewSettings;
};
MainWindow::BitmapPointer::BitmapPointer(HBITMAP value) : _value(value) {
}
MainWindow::BitmapPointer::BitmapPointer(BitmapPointer &&other)
: _value(base::take(other._value)) {
}
MainWindow::BitmapPointer &MainWindow::BitmapPointer::operator=(
BitmapPointer &&other) {
if (_value != other._value) {
reset();
_value = base::take(other._value);
}
return *this;
}
MainWindow::BitmapPointer::~BitmapPointer() {
reset();
}
HBITMAP MainWindow::BitmapPointer::get() const {
return _value;
}
MainWindow::BitmapPointer::operator bool() const {
return _value != nullptr;
}
void MainWindow::BitmapPointer::release() {
_value = nullptr;
}
void MainWindow::BitmapPointer::reset(HBITMAP value) {
if (_value != value) {
if (const auto old = std::exchange(_value, value)) {
DeleteObject(old);
}
}
}
MainWindow::MainWindow(not_null<Window::Controller*> controller)
: Window::MainWindow(controller)
, _private(std::make_unique<Private>(this))
, _taskbarHiderWindow(std::make_unique<QWindow>()) {
qApp->installNativeEventFilter(&_private->filter);
setupNativeWindowFrame();
SetWindowPriority(this, controller->isPrimary() ? 2 : 1);
using namespace rpl::mappers;
Core::App().appDeactivatedValue(
) | rpl::distinct_until_changed(
) | rpl::filter(_1) | rpl::on_next([=] {
_lastDeactivateTime = crl::now();
}, lifetime());
setupPreviewPasscodeLock();
}
void MainWindow::setupPreviewPasscodeLock() {
Core::App().passcodeLockValue(
) | rpl::on_next([=](bool locked) {
// Use iconic bitmap instead of the window content if passcoded.
BOOL fForceIconic = locked ? TRUE : FALSE;
BOOL fHasIconicBitmap = fForceIconic;
DwmSetWindowAttribute(
_hWnd,
DWMWA_FORCE_ICONIC_REPRESENTATION,
&fForceIconic,
sizeof(fForceIconic));
DwmSetWindowAttribute(
_hWnd,
DWMWA_HAS_ICONIC_BITMAP,
&fHasIconicBitmap,
sizeof(fHasIconicBitmap));
}, lifetime());
}
void MainWindow::setupNativeWindowFrame() {
auto nativeFrame = rpl::single(
Core::App().settings().nativeWindowFrame()
) | rpl::then(
Core::App().settings().nativeWindowFrameChanges()
);
rpl::combine(
std::move(nativeFrame),
Window::Theme::IsNightModeValue()
) | rpl::skip(1) | rpl::on_next([=](bool native, bool night) {
validateWindowTheme(native, night);
}, lifetime());
}
void MainWindow::shadowsActivate() {
_hasActiveFrame = true;
}
void MainWindow::shadowsDeactivate() {
_hasActiveFrame = false;
}
void MainWindow::destroyedFromSystem() {
if (!Core::App().closeNonLastAsync(&controller())) {
Core::Quit();
}
}
bool MainWindow::setDwmThumbnail(QSize size) {
validateDwmPreviewColors();
if (size.isEmpty()) {
return false;
} else if (!_dwmThumbnail || _dwmThumbnailSize != size) {
const auto result = PrepareLogoPreview(size, QImage::Format_RGB32);
const auto bitmap = qt_imageToWinHBITMAP(result, HBitmapNoAlpha);
if (!bitmap) {
return false;
}
_dwmThumbnail.reset(bitmap);
_dwmThumbnailSize = size;
}
DwmSetIconicThumbnail(_hWnd, _dwmThumbnail.get(), NULL);
return true;
}
bool MainWindow::setDwmPreview(QSize size, int radius) {
Expects(radius >= 0);
validateDwmPreviewColors();
if (size.isEmpty()) {
return false;
} else if (!_dwmPreview
|| _dwmPreviewSize != size
|| _dwmPreviewRadius != radius) {
const auto format = (radius > 0)
? QImage::Format_ARGB32_Premultiplied
: QImage::Format_RGB32;
const auto result = PrepareLogoPreview(size, format, radius);
const auto bitmap = qt_imageToWinHBITMAP(
result,
(radius > 0) ? HBitmapPremultipliedAlpha : HBitmapNoAlpha);
if (!bitmap) {
return false;
}
_dwmPreview.reset(bitmap);
_dwmPreviewRadius = radius;
_dwmPreviewSize = size;
}
const auto flags = 0;
DwmSetIconicLivePreviewBitmap(_hWnd, _dwmPreview.get(), NULL, flags);
return true;
}
void MainWindow::validateDwmPreviewColors() {
if (_dwmBackground == st::windowBg->c) {
return;
}
_dwmBackground = st::windowBg->c;
_dwmThumbnail.reset();
_dwmPreview.reset();
}
void MainWindow::forceIconRefresh() {
const auto refresher = std::make_unique<QWidget>(this);
refresher->setWindowFlags(
static_cast<Qt::WindowFlags>(Qt::Tool) | Qt::FramelessWindowHint);
refresher->setGeometry(x() + 1, y() + 1, 1, 1);
auto palette = refresher->palette();
palette.setColor(
QPalette::Window,
(isActiveWindow() ? st::titleBgActive : st::titleBg)->c);
refresher->setPalette(palette);
refresher->show();
refresher->raise();
refresher->activateWindow();
updateTaskbarAndIconCounters();
}
void MainWindow::workmodeUpdated(Core::Settings::WorkMode mode) {
using WorkMode = Core::Settings::WorkMode;
switch (mode) {
case WorkMode::WindowAndTray: {
HWND psOwner = (HWND)GetWindowLongPtr(_hWnd, GWLP_HWNDPARENT);
if (psOwner) {
SetWindowLongPtr(_hWnd, GWLP_HWNDPARENT, 0);
windowHandle()->setTransientParent(nullptr);
forceIconRefresh();
}
} break;
case WorkMode::TrayOnly: {
HWND psOwner = (HWND)GetWindowLongPtr(_hWnd, GWLP_HWNDPARENT);
if (!psOwner) {
const auto hwnd = _taskbarHiderWindow->winId();
SetWindowLongPtr(_hWnd, GWLP_HWNDPARENT, (LONG_PTR)hwnd);
windowHandle()->setTransientParent(_taskbarHiderWindow.get());
}
} break;
case WorkMode::WindowOnly: {
HWND psOwner = (HWND)GetWindowLongPtr(_hWnd, GWLP_HWNDPARENT);
if (psOwner) {
SetWindowLongPtr(_hWnd, GWLP_HWNDPARENT, 0);
windowHandle()->setTransientParent(nullptr);
forceIconRefresh();
}
} break;
}
}
bool MainWindow::hasTabletView() const {
if (!_private->viewSettings) {
return false;
}
auto mode = ViewManagement::UserInteractionMode();
_private->viewSettings->get_UserInteractionMode(&mode);
return (mode == ViewManagement::UserInteractionMode_Touch);
}
bool MainWindow::initGeometryFromSystem() {
if (!hasTabletView() || !screen()) {
return false;
}
Ui::RpWidget::setGeometry(screen()->availableGeometry());
return true;
}
bool MainWindow::nativeEvent(
const QByteArray &eventType,
void *message,
native_event_filter_result *result) {
if (message) {
const auto msg = static_cast<MSG*>(message);
if (msg->message == WM_IME_STARTCOMPOSITION) {
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
imeCompositionStartReceived();
});
}
}
return false;
}
void MainWindow::updateWindowIcon() {
updateTaskbarAndIconCounters();
}
bool MainWindow::isActiveForTrayMenu() {
return !_lastDeactivateTime
|| (_lastDeactivateTime + kKeepActiveForTrayIcon >= crl::now());
}
void MainWindow::unreadCounterChangedHook() {
updateTaskbarAndIconCounters();
}
void MainWindow::updateTaskbarAndIconCounters() {
const auto counter = Core::App().unreadBadge();
const auto muted = Core::App().unreadBadgeMuted();
const auto controller = sessionController();
const auto session = controller ? &controller->session() : nullptr;
const auto iconSizeSmall = QSize(
GetSystemMetrics(SM_CXSMICON),
GetSystemMetrics(SM_CYSMICON));
const auto iconSizeBig = QSize(
GetSystemMetrics(SM_CXICON),
GetSystemMetrics(SM_CYICON));
const auto supportMode = session && session->supportMode();
auto iconSmallPixmap16 = Tray::IconWithCounter(
Tray::CounterLayerArgs(16, counter, muted),
true,
false,
supportMode);
auto iconSmallPixmap32 = Tray::IconWithCounter(
Tray::CounterLayerArgs(32, counter, muted),
true,
false,
supportMode);
QIcon iconSmall, iconBig;
iconSmall.addPixmap(iconSmallPixmap16);
iconSmall.addPixmap(iconSmallPixmap32);
const auto integration = &Platform::WindowsIntegration::Instance();
const auto taskbarList = integration->taskbarList();
const auto bigCounter = taskbarList ? 0 : counter;
iconBig.addPixmap(Tray::IconWithCounter(
Tray::CounterLayerArgs(32, bigCounter, muted),
false,
false,
supportMode));
iconBig.addPixmap(Tray::IconWithCounter(
Tray::CounterLayerArgs(64, bigCounter, muted),
false,
false,
supportMode));
destroyCachedIcons();
_iconSmall = NativeIcon(iconSmall, iconSizeSmall);
_iconBig = NativeIcon(iconBig, iconSizeBig);
SendMessage(_hWnd, WM_SETICON, ICON_SMALL, (LPARAM)_iconSmall);
SendMessage(_hWnd, WM_SETICON, ICON_BIG, (LPARAM)(_iconBig ? _iconBig : _iconSmall));
if (taskbarList) {
if (counter > 0) {
const auto pixmap = [&](int size) {
return Ui::PixmapFromImage(Window::GenerateCounterLayer(
Tray::CounterLayerArgs(size, counter, muted)));
};
QIcon iconOverlay;
iconOverlay.addPixmap(pixmap(16));
iconOverlay.addPixmap(pixmap(32));
_iconOverlay = NativeIcon(iconOverlay, iconSizeSmall);
}
const auto description = (counter > 0)
? tr::lng_unread_bar(tr::now, lt_count, counter).toStdWString()
: std::wstring();
taskbarList->SetOverlayIcon(_hWnd, _iconOverlay, description.c_str());
}
SetWindowPos(_hWnd, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
}
void MainWindow::initHook() {
_hWnd = reinterpret_cast<HWND>(winId());
if (!_hWnd) {
return;
}
WTSRegisterSessionNotification(_hWnd, NOTIFY_FOR_THIS_SESSION);
using namespace base::Platform;
auto factory = ComPtr<IUIViewSettingsInterop>();
if (SupportsWRL()) {
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(
_hWnd,
IID_PPV_ARGS(&_private->viewSettings));
}
}
validateWindowTheme(
Core::App().settings().nativeWindowFrame(),
Window::Theme::IsNightMode());
}
void MainWindow::validateWindowTheme(bool native, bool night) {
if (!IsWindows8OrGreater()) {
const auto empty = native ? nullptr : L" ";
SetWindowTheme(_hWnd, empty, empty);
QApplication::setStyle(QStyleFactory::create(u"Windows"_q));
#if 0
} else if (!Core::App().settings().systemDarkMode().has_value()/*
|| (!Dlls::AllowDarkModeForApp && !Dlls::SetPreferredAppMode)
|| !Dlls::AllowDarkModeForWindow
|| !Dlls::RefreshImmersiveColorPolicyState
|| !Dlls::FlushMenuThemes*/) {
return;
#endif
} else if (!native) {
SetWindowTheme(_hWnd, nullptr, nullptr);
return;
}
// See "https://github.com/microsoft/terminal/blob/"
// "eb480b6bbbd83a2aafbe62992d360838e0ab9da5/"
// "src/interactivity/win32/windowtheme.cpp#L43-L63"
auto darkValue = BOOL(night ? TRUE : FALSE);
const auto updateStyle = [&] {
static const auto kSystemVersion = QOperatingSystemVersion::current();
if (kSystemVersion.microVersion() >= 18875 && Dlls::SetWindowCompositionAttribute) {
Dlls::WINDOWCOMPOSITIONATTRIBDATA data = {
Dlls::WINDOWCOMPOSITIONATTRIB::WCA_USEDARKMODECOLORS,
&darkValue,
sizeof(darkValue)
};
Dlls::SetWindowCompositionAttribute(_hWnd, &data);
} else if (kSystemVersion.microVersion() >= 17763) {
static const auto kDWMWA_USE_IMMERSIVE_DARK_MODE = (kSystemVersion.microVersion() >= 18985)
? DWORD(20)
: DWORD(19);
DwmSetWindowAttribute(
_hWnd,
kDWMWA_USE_IMMERSIVE_DARK_MODE,
&darkValue,
sizeof(darkValue));
}
};
updateStyle();
// See "https://osdn.net/projects/tortoisesvn/scm/svn/blobs/28812/"
// "trunk/src/TortoiseIDiff/MainWindow.cpp"
//
// But for now it works event with a small part of that.
//
//const auto updateWindowTheme = [&] {
// const auto set = [&](LPCWSTR name) {
// return SetWindowTheme(_hWnd, name, nullptr);
// };
// if (!night || FAILED(set(L"DarkMode_Explorer"))) {
// set(L"Explorer");
// }
//};
//
//if (night) {
// if (Dlls::SetPreferredAppMode) {
// Dlls::SetPreferredAppMode(Dlls::PreferredAppMode::AllowDark);
// } else {
// Dlls::AllowDarkModeForApp(TRUE);
// }
// Dlls::AllowDarkModeForWindow(_hWnd, TRUE);
// updateWindowTheme();
// updateStyle();
// Dlls::FlushMenuThemes();
// Dlls::RefreshImmersiveColorPolicyState();
//} else {
// updateWindowTheme();
// Dlls::AllowDarkModeForWindow(_hWnd, FALSE);
// updateStyle();
// Dlls::FlushMenuThemes();
// Dlls::RefreshImmersiveColorPolicyState();
// if (Dlls::SetPreferredAppMode) {
// Dlls::SetPreferredAppMode(Dlls::PreferredAppMode::Default);
// } else {
// Dlls::AllowDarkModeForApp(FALSE);
// }
//}
// Didn't find any other way to definitely repaint with the new style.
SendMessage(_hWnd, WM_NCACTIVATE, _hasActiveFrame ? 0 : 1, 0);
SendMessage(_hWnd, WM_NCACTIVATE, _hasActiveFrame ? 1 : 0, 0);
}
HWND MainWindow::psHwnd() const {
return _hWnd;
}
void MainWindow::destroyCachedIcons() {
const auto destroy = [](HICON &icon) {
if (icon) {
DestroyIcon(icon);
icon = nullptr;
}
};
destroy(_iconBig);
destroy(_iconSmall);
destroy(_iconOverlay);
}
MainWindow::~MainWindow() {
WTSUnRegisterSessionNotification(_hWnd);
_private->viewSettings.Reset();
destroyCachedIcons();
}
int32 ScreenNameChecksum(const QString &name) {
constexpr int DeviceNameSize = base::array_size(MONITORINFOEX().szDevice);
wchar_t buffer[DeviceNameSize] = { 0 };
if (name.size() < DeviceNameSize) {
name.toWCharArray(buffer);
} else {
memcpy(buffer, name.toStdWString().data(), sizeof(buffer));
}
return base::crc32(buffer, sizeof(buffer));
}
int32 ScreenNameChecksum(const QScreen *screen) {
return ScreenNameChecksum(screen->name());
}
QString ScreenDisplayLabel(const QScreen *screen) {
if (!screen) {
return QString();
}
const auto geometry = screen->geometry();
const auto hMonitor = MonitorFromPoint(
POINT{
geometry.x() + geometry.width() / 2,
geometry.y() + geometry.height() / 2,
},
MONITOR_DEFAULTTONEAREST);
const auto displayName = HumanReadableDisplayName(hMonitor);
if (!displayName.isEmpty()) {
return displayName;
}
const auto name = screen->name();
const auto genericName = u"\\\\.\\DISPLAY"_q;
if (name.startsWith(genericName)) {
const auto displayNum = name.mid(genericName.size());
return u"Display %1"_q.arg(displayNum);
}
return name;
}
} // namespace Platform

View File

@@ -0,0 +1,113 @@
/*
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 "platform/platform_main_window.h"
#include "base/flags.h"
#include <windows.h>
namespace Ui {
class PopupMenu;
} // namespace Ui
namespace Platform {
class MainWindow : public Window::MainWindow {
public:
explicit MainWindow(not_null<Window::Controller*> controller);
HWND psHwnd() const;
void updateWindowIcon() override;
bool isActiveForTrayMenu() override;
// Custom shadows.
void shadowsActivate();
void shadowsDeactivate();
[[nodiscard]] bool hasTabletView() const;
void destroyedFromSystem();
bool setDwmThumbnail(QSize size);
bool setDwmPreview(QSize size, int radius);
~MainWindow();
protected:
void initHook() override;
void unreadCounterChangedHook() override;
void workmodeUpdated(Core::Settings::WorkMode mode) override;
bool initGeometryFromSystem() override;
bool nativeEvent(
const QByteArray &eventType,
void *message,
native_event_filter_result *result) override;
private:
struct Private;
class BitmapPointer {
public:
BitmapPointer(HBITMAP value = nullptr);
BitmapPointer(BitmapPointer &&other);
BitmapPointer& operator=(BitmapPointer &&other);
~BitmapPointer();
[[nodiscard]] HBITMAP get() const;
[[nodiscard]] explicit operator bool() const;
void release();
void reset(HBITMAP value = nullptr);
private:
HBITMAP _value = nullptr;
};
void setupNativeWindowFrame();
void setupPreviewPasscodeLock();
void updateTaskbarAndIconCounters();
void validateWindowTheme(bool native, bool night);
void forceIconRefresh();
void destroyCachedIcons();
void validateDwmPreviewColors();
const std::unique_ptr<Private> _private;
const std::unique_ptr<QWindow> _taskbarHiderWindow;
HWND _hWnd = nullptr;
HICON _iconBig = nullptr;
HICON _iconSmall = nullptr;
HICON _iconOverlay = nullptr;
BitmapPointer _dwmThumbnail;
BitmapPointer _dwmPreview;
QSize _dwmThumbnailSize;
QSize _dwmPreviewSize;
QColor _dwmBackground;
int _dwmPreviewRadius = 0;
// Workarounds for activation from tray icon.
crl::time _lastDeactivateTime = 0;
bool _hasActiveFrame = false;
};
[[nodiscard]] int32 ScreenNameChecksum(const QString &name);
[[nodiscard]] int32 ScreenNameChecksum(const QScreen *screen);
[[nodiscard]] QString ScreenDisplayLabel(const QScreen *screen);
} // namespace Platform

View File

@@ -0,0 +1,978 @@
/*
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/notifications_manager_win.h"
#include "window/notifications_utilities.h"
#include "window/window_session_controller.h"
#include "base/platform/win/base_windows_co_task_mem.h"
#include "base/platform/win/base_windows_rpcndr_h.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_dlls.h"
#include "platform/win/specific_win.h"
#include "data/data_forum_topic.h"
#include "data/data_saved_sublist.h"
#include "data/data_peer.h"
#include "history/history.h"
#include "history/history_item.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "mainwindow.h"
#include "windows_quiethours_h.h"
#include "styles/style_chat.h"
#include "styles/style_chat_helpers.h"
#include <QtCore/QOperatingSystemVersion>
#include <Shobjidl.h>
#include <shellapi.h>
#include <strsafe.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Data.Xml.Dom.h>
#include <winrt/Windows.UI.Notifications.h>
HICON qt_pixmapToWinHICON(const QPixmap &);
using namespace winrt::Windows::UI::Notifications;
using namespace winrt::Windows::Data::Xml::Dom;
using namespace winrt::Windows::Foundation;
using winrt::com_ptr;
namespace Platform {
namespace Notifications {
namespace {
constexpr auto kQuerySettingsEachMs = 1000;
crl::time LastSettingsQueryMs/* = 0*/;
[[nodiscard]] bool ShouldQuerySettings() {
const auto now = crl::now();
if (LastSettingsQueryMs > 0 && now <= LastSettingsQueryMs + kQuerySettingsEachMs) {
return false;
}
LastSettingsQueryMs = now;
return true;
}
[[nodiscard]] std::wstring NotificationTemplate(
QString id,
Window::Notifications::Manager::DisplayOptions options) {
const auto wid = id.replace('&', "&amp;").toStdWString();
const auto fastReply = LR"(
<input id="fastReply" type="text" placeHolderContent=""/>
<action
content="Send"
arguments="action=reply&amp;)" + wid + LR"("
activationType="background"
imageUri=""
hint-inputId="fastReply"/>
)";
const auto markAsRead = LR"(
<action
content=""
arguments="action=mark&amp;)" + wid + LR"("
activationType="background"/>
)";
const auto actions = (options.hideReplyButton ? L"" : fastReply)
+ (options.hideMarkAsRead ? L"" : markAsRead);
return LR"(
<toast launch="action=open&amp;)" + wid + LR"(">
<visual>
<binding template="ToastGeneric">
<image placement="appLogoOverride" hint-crop="circle" src=""/>
<text hint-maxLines="1"></text>
<text></text>
<text></text>
</binding>
</visual>
)" + (actions.empty()
? L""
: (L"<actions>" + actions + L"</actions>")) + LR"(
<audio silent="true"/>
</toast>
)";
}
bool init() {
if (!IsWindows8OrGreater() || !base::WinRT::Supported()) {
return false;
}
{
using namespace Microsoft::WRL;
const auto hr = Module<OutOfProc>::GetModule().RegisterObjects();
if (!SUCCEEDED(hr)) {
LOG(("App Error: Object registration failed."));
}
}
if (!AppUserModelId::ValidateShortcut()) {
LOG(("App Error: Shortcut validation failed."));
return false;
}
PWSTR appUserModelId = {};
if (!SUCCEEDED(GetCurrentProcessExplicitAppUserModelID(&appUserModelId))) {
return false;
}
const auto appUserModelIdGuard = gsl::finally([&] {
CoTaskMemFree(appUserModelId);
});
if (AppUserModelId::Id() != appUserModelId) {
return false;
}
return true;
}
// Throws.
void SetNodeValueString(
const XmlDocument &xml,
const IXmlNode &node,
const std::wstring &text) {
node.AppendChild(xml.CreateTextNode(text).as<IXmlNode>());
}
// Throws.
void SetAudioSilent(const XmlDocument &toastXml) {
const auto nodeList = toastXml.GetElementsByTagName(L"audio");
if (const auto audioNode = nodeList.Item(0)) {
audioNode.as<IXmlElement>().SetAttribute(L"silent", L"true");
} else {
auto audioElement = toastXml.CreateElement(L"audio");
audioElement.SetAttribute(L"silent", L"true");
auto nodeList = toastXml.GetElementsByTagName(L"toast");
nodeList.Item(0).AppendChild(audioElement.as<IXmlNode>());
}
}
// Throws.
void SetImageSrc(const XmlDocument &toastXml, const std::wstring &path) {
const auto nodeList = toastXml.GetElementsByTagName(L"image");
const auto attributes = nodeList.Item(0).Attributes();
return SetNodeValueString(
toastXml,
attributes.GetNamedItem(L"src"),
L"file:///" + path);
}
// Throws.
void SetReplyIconSrc(const XmlDocument &toastXml, const std::wstring &path) {
const auto nodeList = toastXml.GetElementsByTagName(L"action");
const auto length = int(nodeList.Length());
for (auto i = 0; i != length; ++i) {
const auto attributes = nodeList.Item(i).Attributes();
if (const auto uri = attributes.GetNamedItem(L"imageUri")) {
return SetNodeValueString(toastXml, uri, L"file:///" + path);
}
}
}
// Throws.
void SetReplyPlaceholder(
const XmlDocument &toastXml,
const std::wstring &placeholder) {
const auto nodeList = toastXml.GetElementsByTagName(L"input");
const auto attributes = nodeList.Item(0).Attributes();
return SetNodeValueString(
toastXml,
attributes.GetNamedItem(L"placeHolderContent"),
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 length = int(nodeList.Length());
for (auto i = 0; i != length; ++i) {
const auto attributes = nodeList.Item(i).Attributes();
if (!attributes.GetNamedItem(L"imageUri")) {
return SetNodeValueString(
toastXml,
attributes.GetNamedItem(L"content"),
text);
}
}
}
auto Checked = false;
auto InitSucceeded = false;
void Check() {
InitSucceeded = init();
}
bool QuietHoursEnabled = false;
DWORD QuietHoursValue = 0;
[[nodiscard]] bool UseQuietHoursRegistryEntry() {
static const bool result = [] {
const auto version = QOperatingSystemVersion::current();
// At build 17134 (Redstone 4) the "Quiet hours" was replaced
// by "Focus assist" and it looks like it doesn't use registry.
return (version.majorVersion() == 10)
&& (version.minorVersion() == 0)
&& (version.microVersion() < 17134);
}();
return result;
}
// Thanks https://stackoverflow.com/questions/35600128/get-windows-quiet-hours-from-win32-or-c-sharp-api
void QueryQuietHours() {
if (!UseQuietHoursRegistryEntry()) {
// There are quiet hours in Windows starting from Windows 8.1
// But there were several reports about the notifications being shut
// down according to the registry while no quiet hours were enabled.
// So we try this method only starting with Windows 10.
return;
}
LPCWSTR lpKeyName = L"Software\\Microsoft\\Windows\\CurrentVersion\\Notifications\\Settings";
LPCWSTR lpValueName = L"NOC_GLOBAL_SETTING_TOASTS_ENABLED";
HKEY key;
auto result = RegOpenKeyEx(HKEY_CURRENT_USER, lpKeyName, 0, KEY_READ, &key);
if (result != ERROR_SUCCESS) {
return;
}
DWORD value = 0, type = 0, size = sizeof(value);
result = RegQueryValueEx(key, lpValueName, 0, &type, (LPBYTE)&value, &size);
RegCloseKey(key);
auto quietHoursEnabled = (result == ERROR_SUCCESS) && (value == 0);
if (QuietHoursEnabled != quietHoursEnabled) {
QuietHoursEnabled = quietHoursEnabled;
QuietHoursValue = value;
LOG(("Quiet hours changed, entry value: %1").arg(value));
} else if (QuietHoursValue != value) {
QuietHoursValue = value;
LOG(("Quiet hours value changed, was value: %1, entry value: %2").arg(QuietHoursValue).arg(value));
}
}
bool FocusAssistBlocks = false;
// Thanks https://www.withinrafael.com/2019/09/19/determine-if-your-app-is-in-a-focus-assist-profiles-priority-list/
void QueryFocusAssist() {
const auto quietHoursSettings = base::WinRT::TryCreateInstance<
IQuietHoursSettings
>(CLSID_QuietHoursSettings, CLSCTX_LOCAL_SERVER);
if (!quietHoursSettings) {
return;
}
auto profileId = base::CoTaskMemString();
auto hr = quietHoursSettings->get_UserSelectedProfile(profileId.put());
if (FAILED(hr) || !profileId) {
return;
}
const auto profileName = QString::fromWCharArray(profileId.data());
if (profileName.endsWith(".alarmsonly", Qt::CaseInsensitive)) {
if (!FocusAssistBlocks) {
LOG(("Focus Assist: Alarms Only."));
FocusAssistBlocks = true;
}
return;
} else if (!profileName.endsWith(".priorityonly", Qt::CaseInsensitive)) {
if (!profileName.endsWith(".unrestricted", Qt::CaseInsensitive)) {
LOG(("Focus Assist Warning: Unknown profile '%1'"
).arg(profileName));
}
if (FocusAssistBlocks) {
LOG(("Focus Assist: Unrestricted."));
FocusAssistBlocks = false;
}
return;
}
const auto appUserModelId = AppUserModelId::Id();
auto blocked = true;
const auto guard = gsl::finally([&] {
if (FocusAssistBlocks != blocked) {
LOG(("Focus Assist: %1, AppUserModelId: %2, Blocks: %3"
).arg(profileName
).arg(QString::fromStdWString(appUserModelId)
).arg(Logs::b(blocked)));
FocusAssistBlocks = blocked;
}
});
com_ptr<IQuietHoursProfile> profile;
hr = quietHoursSettings->GetProfile(profileId.data(), profile.put());
if (FAILED(hr) || !profile) {
return;
}
auto apps = base::CoTaskMemStringArray();
hr = profile->GetAllowedApps(apps.put_size(), apps.put());
if (FAILED(hr) || !apps) {
return;
}
for (const auto &app : apps) {
if (app && app.data() == appUserModelId) {
blocked = false;
break;
}
}
}
QUERY_USER_NOTIFICATION_STATE UserNotificationState
= QUNS_ACCEPTS_NOTIFICATIONS;
void QueryUserNotificationState() {
if (Dlls::SHQueryUserNotificationState != nullptr) {
QUERY_USER_NOTIFICATION_STATE state;
if (SUCCEEDED(Dlls::SHQueryUserNotificationState(&state))) {
UserNotificationState = state;
}
}
}
void QuerySystemNotificationSettings() {
if (!ShouldQuerySettings()) {
return;
}
QueryQuietHours();
QueryFocusAssist();
QueryUserNotificationState();
}
bool SkipSoundForCustom() {
QuerySystemNotificationSettings();
return (UserNotificationState == QUNS_NOT_PRESENT)
|| (UserNotificationState == QUNS_PRESENTATION_MODE)
|| (FocusAssistBlocks && Core::App().settings().skipToastsInFocus())
|| Core::App().screenIsLocked();
}
bool SkipFlashBounceForCustom() {
return SkipToastForCustom();
}
} // namespace
void MaybePlaySoundForCustom(Fn<void()> playSound) {
if (!SkipSoundForCustom()) {
playSound();
}
}
bool SkipToastForCustom() {
QuerySystemNotificationSettings();
return (UserNotificationState == QUNS_PRESENTATION_MODE)
|| (UserNotificationState == QUNS_RUNNING_D3D_FULL_SCREEN)
|| (FocusAssistBlocks && Core::App().settings().skipToastsInFocus());
}
void MaybeFlashBounceForCustom(Fn<void()> flashBounce) {
if (!SkipFlashBounceForCustom()) {
flashBounce();
}
}
bool WaitForInputForCustom() {
QuerySystemNotificationSettings();
return UserNotificationState != QUNS_BUSY;
}
bool Supported() {
if (!Checked) {
Checked = true;
Check();
}
return InitSucceeded;
}
bool Enforced() {
return false;
}
bool ByDefault() {
return false;
}
bool VolumeSupported() {
return true;
}
void Create(Window::Notifications::System *system) {
system->setManager([=] {
auto result = std::make_unique<Manager>(system);
return result->init() ? std::move(result) : nullptr;
});
}
class Manager::Private {
public:
using Info = Window::Notifications::NativeManager::NotificationInfo;
explicit Private(Manager *instance);
bool init();
bool showNotification(Info &&info, Ui::PeerUserpicView &userpicView);
void clearAll();
void clearFromItem(not_null<HistoryItem*> item);
void clearFromTopic(not_null<Data::ForumTopic*> topic);
void clearFromSublist(not_null<Data::SavedSublist*> sublist);
void clearFromHistory(not_null<History*> history);
void clearFromSession(not_null<Main::Session*> session);
void beforeNotificationActivated(NotificationId id);
void afterNotificationActivated(
NotificationId id,
not_null<Window::SessionController*> window);
void clearNotification(NotificationId id);
void handleActivation(const ToastActivation &activation);
~Private();
private:
bool showNotificationInTryCatch(
NotificationInfo &&info,
Ui::PeerUserpicView &userpicView);
void tryHide(const ToastNotification &notification);
[[nodiscard]] std::wstring ensureSendButtonIcon();
Window::Notifications::CachedUserpics _cachedUserpics;
std::wstring _sendButtonIconPath;
std::shared_ptr<Manager*> _guarded;
ToastNotifier _notifier = nullptr;
base::flat_map<
ContextId,
base::flat_map<MsgId, ToastNotification>> _notifications;
rpl::lifetime _lifetime;
};
Manager::Private::Private(Manager *instance)
: _guarded(std::make_shared<Manager*>(instance)) {
ToastActivations(
) | rpl::on_next([=](const ToastActivation &activation) {
handleActivation(activation);
}, _lifetime);
}
bool Manager::Private::init() {
return base::WinRT::Try([&] {
_notifier = ToastNotificationManager::CreateToastNotifier(
AppUserModelId::Id());
});
}
Manager::Private::~Private() {
clearAll();
_notifications.clear();
_notifier = nullptr;
}
void Manager::Private::clearAll() {
if (!_notifier) {
return;
}
for (const auto &[key, notifications] : base::take(_notifications)) {
for (const auto &[msgId, notification] : notifications) {
tryHide(notification);
}
}
}
void Manager::Private::clearFromItem(not_null<HistoryItem*> item) {
if (!_notifier) {
return;
}
auto i = _notifications.find(ContextId{
.sessionId = item->history()->session().uniqueId(),
.peerId = item->history()->peer->id,
.topicRootId = item->topicRootId(),
.monoforumPeerId = item->sublistPeerId(),
});
if (i == _notifications.cend()) {
return;
}
const auto j = i->second.find(item->id);
if (j == end(i->second)) {
return;
}
const auto taken = std::exchange(j->second, nullptr);
i->second.erase(j);
if (i->second.empty()) {
_notifications.erase(i);
}
tryHide(taken);
}
void Manager::Private::clearFromTopic(not_null<Data::ForumTopic*> topic) {
if (!_notifier) {
return;
}
const auto i = _notifications.find(ContextId{
.sessionId = topic->session().uniqueId(),
.peerId = topic->history()->peer->id,
.topicRootId = topic->rootId(),
});
if (i != _notifications.cend()) {
const auto temp = base::take(i->second);
_notifications.erase(i);
for (const auto &[msgId, notification] : temp) {
tryHide(notification);
}
}
}
void Manager::Private::clearFromSublist(
not_null<Data::SavedSublist*> sublist) {
if (!_notifier) {
return;
}
const auto i = _notifications.find(ContextId{
.sessionId = sublist->session().uniqueId(),
.peerId = sublist->owningHistory()->peer->id,
.monoforumPeerId = sublist->sublistPeer()->id,
});
if (i != _notifications.cend()) {
const auto temp = base::take(i->second);
_notifications.erase(i);
for (const auto &[msgId, notification] : temp) {
tryHide(notification);
}
}
}
void Manager::Private::clearFromHistory(not_null<History*> history) {
if (!_notifier) {
return;
}
const auto sessionId = history->session().uniqueId();
const auto peerId = history->peer->id;
auto i = _notifications.lower_bound(ContextId{
.sessionId = sessionId,
.peerId = peerId,
});
while (i != _notifications.cend()
&& i->first.sessionId == sessionId
&& i->first.peerId == peerId) {
const auto temp = base::take(i->second);
i = _notifications.erase(i);
for (const auto &[msgId, notification] : temp) {
tryHide(notification);
}
}
}
void Manager::Private::clearFromSession(not_null<Main::Session*> session) {
if (!_notifier) {
return;
}
const auto sessionId = session->uniqueId();
auto i = _notifications.lower_bound(ContextId{
.sessionId = sessionId,
});
while (i != _notifications.cend() && i->first.sessionId == sessionId) {
const auto temp = base::take(i->second);
i = _notifications.erase(i);
for (const auto &[msgId, notification] : temp) {
tryHide(notification);
}
}
}
void Manager::Private::beforeNotificationActivated(NotificationId id) {
clearNotification(id);
}
void Manager::Private::afterNotificationActivated(
NotificationId id,
not_null<Window::SessionController*> window) {
SetForegroundWindow(window->widget()->psHwnd());
}
void Manager::Private::clearNotification(NotificationId id) {
auto i = _notifications.find(id.contextId);
if (i != _notifications.cend()) {
i->second.remove(id.msgId);
if (i->second.empty()) {
_notifications.erase(i);
}
}
}
void Manager::Private::handleActivation(const ToastActivation &activation) {
const auto parsed = qthelp::url_parse_params(activation.args);
const auto pid = parsed.value("pid").toULong();
const auto my = GetCurrentProcessId();
if (pid != my) {
DEBUG_LOG(("Toast Info: "
"Got activation \"%1\", my %2, activating %3."
).arg(activation.args
).arg(my
).arg(pid));
const auto processId = pid;
const auto windowId = 0; // Activate some window.
Platform::ActivateOtherProcess(processId, windowId);
return;
}
const auto action = parsed.value("action");
const auto id = NotificationId{
.contextId = ContextId{
.sessionId = parsed.value("session").toULongLong(),
.peerId = PeerId(parsed.value("peer").toULongLong()),
.topicRootId = MsgId(parsed.value("topic").toLongLong()),
.monoforumPeerId = PeerId(
parsed.value("monoforumpeer").toULongLong()),
},
.msgId = MsgId(parsed.value("msg").toLongLong()),
};
if (!id.contextId.sessionId || !id.contextId.peerId || !id.msgId) {
DEBUG_LOG(("Toast Info: Got activation \"%1\", my %1, skipping."
).arg(activation.args
).arg(pid));
return;
}
DEBUG_LOG(("Toast Info: Got activation \"%1\", my %1, handling."
).arg(activation.args
).arg(pid));
auto text = TextWithTags();
for (const auto &entry : activation.input) {
if (entry.key == "fastReply") {
text.text = entry.value;
}
}
const auto i = _notifications.find(id.contextId);
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, {
.draft = std::move(text),
});
}
}
bool Manager::Private::showNotification(
Info &&info,
Ui::PeerUserpicView &userpicView) {
if (!_notifier) {
return false;
}
return base::WinRT::Try([&] {
return showNotificationInTryCatch(std::move(info), userpicView);
}).value_or(false);
}
std::wstring Manager::Private::ensureSendButtonIcon() {
if (_sendButtonIconPath.empty()) {
const auto path = cWorkingDir() + u"tdata/temp/fast_reply.png"_q;
st::historySendIcon.instance(Qt::white, 300).save(path, "PNG");
_sendButtonIconPath = path.toStdWString();
}
return _sendButtonIconPath;
}
bool Manager::Private::showNotificationInTryCatch(
NotificationInfo &&info,
Ui::PeerUserpicView &userpicView) {
const auto withSubtitle = !info.subtitle.isEmpty();
const auto peer = info.peer;
auto toastXml = XmlDocument();
const auto key = ContextId{
.sessionId = peer->session().uniqueId(),
.peerId = peer->id,
.topicRootId = info.topicRootId,
.monoforumPeerId = info.monoforumPeerId,
};
const auto notificationId = NotificationId{
.contextId = key,
.msgId = info.itemId,
};
const auto idString = u"pid=%1&session=%2&peer=%3&topic=%4&monoforumpeer=%5&msg=%6"_q
.arg(GetCurrentProcessId())
.arg(key.sessionId)
.arg(key.peerId.value)
.arg(info.topicRootId.bare)
.arg(info.monoforumPeerId.value)
.arg(info.itemId.bare);
const auto modern = Platform::IsWindows10OrGreater();
if (modern) {
toastXml.LoadXml(NotificationTemplate(idString, info.options));
} else {
toastXml = ToastNotificationManager::GetTemplateContent(
(withSubtitle
? ToastTemplateType::ToastImageAndText04
: ToastTemplateType::ToastImageAndText02));
SetAudioSilent(toastXml);
SetAction(toastXml, idString);
}
const auto userpicKey = info.options.hideNameAndPhoto
? InMemoryKey()
: peer->userpicUniqueKey(userpicView);
const auto userpicPath = _cachedUserpics.get(
userpicKey,
peer,
userpicView);
const auto userpicPathWide = QDir::toNativeSeparators(
userpicPath).toStdWString();
if (modern && !info.options.hideReplyButton) {
SetReplyIconSrc(toastXml, ensureSendButtonIcon());
SetReplyPlaceholder(
toastXml,
tr::lng_message_ph(tr::now).toStdWString());
}
if (modern && !info.options.hideMarkAsRead) {
SetMarkAsReadText(
toastXml,
tr::lng_context_mark_read(tr::now).toStdWString());
}
SetImageSrc(toastXml, userpicPathWide);
const auto nodeList = toastXml.GetElementsByTagName(L"text");
if (nodeList.Length() < (withSubtitle ? 3U : 2U)) {
return false;
}
SetNodeValueString(
toastXml,
nodeList.Item(0),
info.title.toStdWString());
if (withSubtitle) {
SetNodeValueString(
toastXml,
nodeList.Item(1),
info.subtitle.toStdWString());
}
SetNodeValueString(
toastXml,
nodeList.Item(withSubtitle ? 2 : 1),
info.message.toStdWString());
const auto weak = std::weak_ptr(_guarded);
const auto performOnMainQueue = [=](FnMut<void(Manager *manager)> task) {
crl::on_main(weak, [=, task = std::move(task)]() mutable {
task(*weak.lock());
});
};
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>()) {
activation.args = string(args.Arguments().c_str());
const auto args2 = args.try_as<IToastActivatedEventArgs2>();
if (!args2 && activation.args.startsWith("action=reply&")) {
LOG(("WinRT Error: "
"FastReply without IToastActivatedEventArgs2 support."));
return;
}
const auto input = args2 ? args2.UserInput() : nullptr;
const auto reply = input
? input.TryLookup(L"fastReply")
: nullptr;
const auto data = reply
? reply.try_as<IReference<winrt::hstring>>()
: nullptr;
if (data) {
activation.input.push_back({
.key = u"fastReply"_q,
.value = string(data.GetString().c_str()),
});
}
} else {
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,
const ToastDismissedEventArgs &args) {
const auto reason = args.Reason();
switch (reason) {
case ToastDismissalReason::ApplicationHidden:
case ToastDismissalReason::TimedOut: // Went to Action Center.
break;
case ToastDismissalReason::UserCanceled:
default:
performOnMainQueue([notificationId](Manager *manager) {
manager->clearNotification(notificationId);
});
break;
}
});
const auto token3 = toast.Failed([=](
const ToastNotification &sender,
const ToastFailedEventArgs &args) {
performOnMainQueue([notificationId](Manager *manager) {
manager->clearNotification(notificationId);
});
});
auto i = _notifications.find(key);
if (i != _notifications.cend()) {
auto j = i->second.find(info.itemId);
if (j != i->second.end()) {
const auto existing = j->second;
i->second.erase(j);
tryHide(existing);
i = _notifications.find(key);
}
}
if (i == _notifications.cend()) {
i = _notifications.emplace(
key,
base::flat_map<MsgId, ToastNotification>()).first;
}
if (!base::WinRT::Try([&] { _notifier.Show(toast); })) {
i = _notifications.find(key);
if (i != _notifications.cend() && i->second.empty()) {
_notifications.erase(i);
}
return false;
}
i->second.emplace(info.itemId, toast);
return true;
}
void Manager::Private::tryHide(const ToastNotification &notification) {
base::WinRT::Try([&] {
_notifier.Hide(notification);
});
}
Manager::Manager(Window::Notifications::System *system)
: NativeManager(system)
, _private(std::make_unique<Private>(this)) {
}
bool Manager::init() {
return _private->init();
}
void Manager::clearNotification(NotificationId id) {
_private->clearNotification(id);
}
void Manager::handleActivation(const ToastActivation &activation) {
_private->handleActivation(activation);
}
Manager::~Manager() = default;
void Manager::doShowNativeNotification(
NotificationInfo &&info,
Ui::PeerUserpicView &userpicView) {
_private->showNotification(std::move(info), userpicView);
}
void Manager::doClearAllFast() {
_private->clearAll();
}
void Manager::doClearFromItem(not_null<HistoryItem*> item) {
_private->clearFromItem(item);
}
void Manager::doClearFromTopic(not_null<Data::ForumTopic*> topic) {
_private->clearFromTopic(topic);
}
void Manager::doClearFromSublist(not_null<Data::SavedSublist*> sublist) {
_private->clearFromSublist(sublist);
}
void Manager::doClearFromHistory(not_null<History*> history) {
_private->clearFromHistory(history);
}
void Manager::doClearFromSession(not_null<Main::Session*> session) {
_private->clearFromSession(session);
}
void Manager::onBeforeNotificationActivated(NotificationId id) {
_private->beforeNotificationActivated(id);
}
void Manager::onAfterNotificationActivated(
NotificationId id,
not_null<Window::SessionController*> window) {
_private->afterNotificationActivated(id, window);
}
bool Manager::doSkipToast() const {
return false;
}
void Manager::doMaybePlaySound(Fn<void()> playSound) {
const auto skip = SkipSoundForCustom()
|| QuietHoursEnabled
|| FocusAssistBlocks;
if (!skip) {
playSound();
}
}
void Manager::doMaybeFlashBounce(Fn<void()> flashBounce) {
const auto skip = SkipFlashBounceForCustom()
|| QuietHoursEnabled
|| FocusAssistBlocks;
if (!skip) {
flashBounce();
}
}
} // namespace Notifications
} // namespace Platform

View File

@@ -0,0 +1,52 @@
/*
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 "platform/platform_notifications_manager.h"
struct ToastActivation;
namespace Platform {
namespace Notifications {
class Manager : public Window::Notifications::NativeManager {
public:
Manager(Window::Notifications::System *system);
~Manager();
bool init();
void clearNotification(NotificationId id);
void handleActivation(const ToastActivation &activation);
protected:
void doShowNativeNotification(
NotificationInfo &&info,
Ui::PeerUserpicView &userpicView) override;
void doClearAllFast() override;
void doClearFromItem(not_null<HistoryItem*> item) override;
void doClearFromTopic(not_null<Data::ForumTopic*> topic) override;
void doClearFromSublist(not_null<Data::SavedSublist*> sublist) override;
void doClearFromHistory(not_null<History*> history) override;
void doClearFromSession(not_null<Main::Session*> session) override;
void onBeforeNotificationActivated(NotificationId id) override;
void onAfterNotificationActivated(
NotificationId id,
not_null<Window::SessionController*> window) override;
bool doSkipToast() const override;
void doMaybePlaySound(Fn<void()> playSound) override;
void doMaybeFlashBounce(Fn<void()> flashBounce) override;
private:
class Private;
const std::unique_ptr<Private> _private;
};
} // namespace Notifications
} // namespace Platform

View File

@@ -0,0 +1,20 @@
/*
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
namespace Platform {
inline std::unique_ptr<OverlayWidgetHelper> CreateOverlayWidgetHelper(
not_null<Ui::RpWindow*> window,
Fn<void(bool)> maximize) {
return std::make_unique<DefaultOverlayWidgetHelper>(
window,
std::move(maximize));
}
} // namespace Platform

View File

@@ -0,0 +1,728 @@
/*
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/specific_win.h"
#include "platform/win/main_window_win.h"
#include "platform/win/notifications_manager_win.h"
#include "platform/win/windows_app_user_model_id.h"
#include "platform/win/windows_dlls.h"
#include "platform/win/windows_autostart_task.h"
#include "base/platform/base_platform_info.h"
#include "base/platform/win/base_windows_co_task_mem.h"
#include "base/platform/win/base_windows_shlobj_h.h"
#include "base/platform/win/base_windows_winrt.h"
#include "base/call_delayed.h"
#include "ui/boxes/confirm_box.h"
#include "lang/lang_keys.h"
#include "mainwindow.h"
#include "mainwidget.h"
#include "history/history_location_manager.h"
#include "storage/localstorage.h"
#include "core/application.h"
#include "window/window_controller.h"
#include "core/crash_reports.h"
#include <QtCore/QOperatingSystemVersion>
#include <QtWidgets/QApplication>
#include <QtGui/QDesktopServices>
#include <QtGui/QWindow>
#include <Shobjidl.h>
#include <ShObjIdl_core.h>
#include <shellapi.h>
#include <openssl/conf.h>
#include <openssl/engine.h>
#include <openssl/err.h>
#include <dbghelp.h>
#include <Shlwapi.h>
#include <Strsafe.h>
#include <Windowsx.h>
#include <WtsApi32.h>
#include <SDKDDKVer.h>
#include <sal.h>
#include <Psapi.h>
#include <strsafe.h>
#include <ObjBase.h>
#include <propvarutil.h>
#include <functiondiscoverykeys.h>
#include <intsafe.h>
#include <guiddef.h>
#include <locale.h>
#include <ShellScalingApi.h>
#ifndef DCX_USESTYLE
#define DCX_USESTYLE 0x00010000
#endif
#ifndef WM_NCPOINTERUPDATE
#define WM_NCPOINTERUPDATE 0x0241
#define WM_NCPOINTERDOWN 0x0242
#define WM_NCPOINTERUP 0x0243
#endif
using namespace ::Platform;
namespace {
bool themeInited = false;
bool finished = true;
QMargins simpleMargins, margins;
HICON bigIcon = 0, smallIcon = 0, overlayIcon = 0;
[[nodiscard]] uint64 WindowIdFromHWND(HWND value) {
return (reinterpret_cast<uint64>(value) & 0xFFFFFFFFULL);
}
struct FindToActivateRequest {
uint64 processId = 0;
uint64 windowId = 0;
HWND result = nullptr;
uint32 resultLevel = 0; // Larger is better.
};
BOOL CALLBACK FindToActivate(HWND hwnd, LPARAM lParam) {
const auto request = reinterpret_cast<FindToActivateRequest*>(lParam);
DWORD dwProcessId;
::GetWindowThreadProcessId(hwnd, &dwProcessId);
if ((uint64)dwProcessId != request->processId) {
return TRUE;
}
// Found a Top-Level window.
if (WindowIdFromHWND(hwnd) == request->windowId) {
request->result = hwnd;
request->resultLevel = 3;
return FALSE;
}
const auto data = static_cast<uint32>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
if ((data != 1 && data != 2) || (data <= request->resultLevel)) {
return TRUE;
}
request->result = hwnd;
request->resultLevel = data;
return TRUE;
}
void DeleteMyModules() {
constexpr auto kMaxPathLong = 32767;
auto exePath = std::array<WCHAR, kMaxPathLong + 1>{ 0 };
const auto exeLength = GetModuleFileName(
nullptr,
exePath.data(),
kMaxPathLong + 1);
if (!exeLength || exeLength >= kMaxPathLong + 1) {
return;
}
const auto exe = std::wstring(exePath.data());
const auto last1 = exe.find_last_of('\\');
const auto last2 = exe.find_last_of('/');
const auto last = std::max(
(last1 == std::wstring::npos) ? -1 : int(last1),
(last2 == std::wstring::npos) ? -1 : int(last2));
if (last < 0) {
return;
}
const auto modules = exe.substr(0, last + 1) + L"modules";
const auto deleteOne = [&](const wchar_t *name, const wchar_t *arch) {
const auto path = modules + L'\\' + arch + L'\\' + name;
DeleteFile(path.c_str());
};
const auto deleteBoth = [&](const wchar_t *name) {
deleteOne(name, L"x86");
deleteOne(name, L"x64");
};
const auto removeOne = [&](const std::wstring &name) {
const auto path = modules + L'\\' + name;
RemoveDirectory(path.c_str());
};
const auto removeBoth = [&](const std::wstring &name) {
removeOne(L"x86\\" + name);
removeOne(L"x64\\" + name);
};
deleteBoth(L"d3d\\d3dcompiler_47.dll");
removeBoth(L"d3d");
removeOne(L"x86");
removeOne(L"x64");
RemoveDirectory(modules.c_str());
}
bool ManageAppLink(
bool create,
bool silent,
const GUID &folderId,
const wchar_t *args,
const wchar_t *description) {
if (cExeName().isEmpty()) {
return false;
}
PWSTR startupFolder;
HRESULT hr = SHGetKnownFolderPath(
folderId,
KF_FLAG_CREATE,
nullptr,
&startupFolder);
const auto guard = gsl::finally([&] {
CoTaskMemFree(startupFolder);
});
if (!SUCCEEDED(hr)) {
WCHAR buffer[64];
const auto size = base::array_size(buffer) - 1;
const auto length = StringFromGUID2(folderId, buffer, size);
if (length > 0 && length <= size) {
buffer[length] = 0;
if (!silent) LOG(("App Error: could not get %1 folder: %2").arg(buffer).arg(hr));
}
return false;
}
const auto lnk = QString::fromWCharArray(startupFolder)
+ '\\'
+ AppFile.utf16()
+ u".lnk"_q;
if (!create) {
QFile::remove(lnk);
return true;
}
const auto shellLink = base::WinRT::TryCreateInstance<IShellLink>(
CLSID_ShellLink);
if (!shellLink) {
if (!silent) LOG(("App Error: could not create instance of IID_IShellLink %1").arg(hr));
return false;
}
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);
if (const auto propertyStore = shellLink.try_as<IPropertyStore>()) {
PROPVARIANT appIdPropVar;
hr = InitPropVariantFromString(AppUserModelId::Id().c_str(), &appIdPropVar);
if (SUCCEEDED(hr)) {
hr = propertyStore->SetValue(AppUserModelId::Key(), appIdPropVar);
PropVariantClear(&appIdPropVar);
if (SUCCEEDED(hr)) {
hr = propertyStore->Commit();
}
}
}
const auto persistFile = shellLink.try_as<IPersistFile>();
if (!persistFile) {
if (!silent) LOG(("App Error: could not create interface IID_IPersistFile %1").arg(hr));
return false;
}
hr = persistFile->Save(lnk.toStdWString().c_str(), TRUE);
if (!SUCCEEDED(hr)) {
if (!silent) LOG(("App Error: could not save IPersistFile to path %1").arg(lnk));
return false;
}
return true;
}
} // namespace
QString psAppDataPath() {
static const int maxFileLen = MAX_PATH * 10;
WCHAR wstrPath[maxFileLen];
if (GetEnvironmentVariable(L"APPDATA", wstrPath, maxFileLen)) {
QDir appData(QString::fromStdWString(std::wstring(wstrPath)));
#ifdef OS_WIN_STORE
return appData.absolutePath() + u"/Telegram Desktop UWP/"_q;
#else // OS_WIN_STORE
return appData.absolutePath() + '/' + AppName.utf16() + '/';
#endif // OS_WIN_STORE
}
return QString();
}
QString psAppDataPathOld() {
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() + '/' + AppNameOld.utf16() + '/';
}
return QString();
}
void psDoCleanup() {
try {
Platform::AutostartToggle(false);
psSendToMenu(false, true);
AppUserModelId::CleanupShortcut();
DeleteMyModules();
} catch (...) {
}
}
int psCleanup() {
__try
{
psDoCleanup();
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
return 0;
}
return 0;
}
void psDoFixPrevious() {
try {
static const int bufSize = 4096;
DWORD checkType = 0;
DWORD checkSize = bufSize * 2;
WCHAR checkStr[bufSize] = { 0 };
HKEY newKey1 = nullptr;
HKEY newKey2 = nullptr;
HKEY oldKey1 = nullptr;
HKEY oldKey2 = nullptr;
const auto appId = AppId.utf16();
const auto newKeyStr1 = QString("Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%1_is1").arg(appId).toStdWString();
const auto newKeyStr2 = QString("Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%1_is1").arg(appId).toStdWString();
const auto oldKeyStr1 = QString("SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%1_is1").arg(appId).toStdWString();
const auto oldKeyStr2 = QString("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%1_is1").arg(appId).toStdWString();
const auto newKeyRes1 = RegOpenKeyEx(HKEY_CURRENT_USER, newKeyStr1.c_str(), 0, KEY_READ, &newKey1);
const auto newKeyRes2 = RegOpenKeyEx(HKEY_CURRENT_USER, newKeyStr2.c_str(), 0, KEY_READ, &newKey2);
const auto oldKeyRes1 = RegOpenKeyEx(HKEY_LOCAL_MACHINE, oldKeyStr1.c_str(), 0, KEY_READ, &oldKey1);
const auto oldKeyRes2 = RegOpenKeyEx(HKEY_LOCAL_MACHINE, oldKeyStr2.c_str(), 0, KEY_READ, &oldKey2);
const auto existNew1 = (newKeyRes1 == ERROR_SUCCESS) && (RegQueryValueEx(newKey1, L"InstallDate", 0, &checkType, (BYTE*)checkStr, &checkSize) == ERROR_SUCCESS); checkSize = bufSize * 2;
const auto existNew2 = (newKeyRes2 == ERROR_SUCCESS) && (RegQueryValueEx(newKey2, L"InstallDate", 0, &checkType, (BYTE*)checkStr, &checkSize) == ERROR_SUCCESS); checkSize = bufSize * 2;
const auto existOld1 = (oldKeyRes1 == ERROR_SUCCESS) && (RegQueryValueEx(oldKey1, L"InstallDate", 0, &checkType, (BYTE*)checkStr, &checkSize) == ERROR_SUCCESS); checkSize = bufSize * 2;
const auto existOld2 = (oldKeyRes2 == ERROR_SUCCESS) && (RegQueryValueEx(oldKey2, L"InstallDate", 0, &checkType, (BYTE*)checkStr, &checkSize) == ERROR_SUCCESS); checkSize = bufSize * 2;
if (newKeyRes1 == ERROR_SUCCESS) RegCloseKey(newKey1);
if (newKeyRes2 == ERROR_SUCCESS) RegCloseKey(newKey2);
if (oldKeyRes1 == ERROR_SUCCESS) RegCloseKey(oldKey1);
if (oldKeyRes2 == ERROR_SUCCESS) RegCloseKey(oldKey2);
if (existNew1 || existNew2) {
if (existOld1) RegDeleteKey(HKEY_LOCAL_MACHINE, oldKeyStr1.c_str());
if (existOld2) RegDeleteKey(HKEY_LOCAL_MACHINE, oldKeyStr2.c_str());
}
QString userDesktopLnk, commonDesktopLnk;
WCHAR userDesktopFolder[MAX_PATH], commonDesktopFolder[MAX_PATH];
HRESULT userDesktopRes = SHGetFolderPath(0, CSIDL_DESKTOPDIRECTORY, 0, SHGFP_TYPE_CURRENT, userDesktopFolder);
HRESULT commonDesktopRes = SHGetFolderPath(0, CSIDL_COMMON_DESKTOPDIRECTORY, 0, SHGFP_TYPE_CURRENT, commonDesktopFolder);
if (SUCCEEDED(userDesktopRes)) {
userDesktopLnk = QString::fromWCharArray(userDesktopFolder) + "\\Telegram.lnk";
}
if (SUCCEEDED(commonDesktopRes)) {
commonDesktopLnk = QString::fromWCharArray(commonDesktopFolder) + "\\Telegram.lnk";
}
QFile userDesktopFile(userDesktopLnk), commonDesktopFile(commonDesktopLnk);
if (QFile::exists(userDesktopLnk) && QFile::exists(commonDesktopLnk) && userDesktopLnk != commonDesktopLnk) {
QFile::remove(commonDesktopLnk);
}
} catch (...) {
}
}
int psFixPrevious() {
__try
{
psDoFixPrevious();
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
return 0;
}
return 0;
}
namespace Platform {
namespace ThirdParty {
namespace {
void StartOpenSSL() {
// Don't use dynamic OpenSSL config, it can load unwanted DLLs.
OPENSSL_load_builtin_modules();
ENGINE_load_builtin_engines();
ERR_clear_error();
OPENSSL_no_config();
}
} // namespace
void start() {
StartOpenSSL();
Dlls::CheckLoadedModules();
}
} // namespace ThirdParty
void start() {
const auto supported = base::WinRT::Supported();
LOG(("WinRT Supported: %1").arg(Logs::b(supported)));
// https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/setlocale-wsetlocale#utf-8-support
setlocale(LC_ALL, ".UTF8");
const auto appUserModelId = AppUserModelId::Id();
SetCurrentProcessExplicitAppUserModelID(appUserModelId.c_str());
LOG(("AppUserModelID: %1").arg(appUserModelId));
}
void finish() {
}
void SetApplicationIcon(const QIcon &icon) {
QApplication::setWindowIcon(icon);
}
QString SingleInstanceLocalServerName(const QString &hash) {
return u"Global\\"_q + hash + '-' + cGUIDStr();
}
#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0)
std::optional<bool> IsDarkMode() {
static const auto kSystemVersion = QOperatingSystemVersion::current();
static const auto kDarkModeAddedVersion = QOperatingSystemVersion(
QOperatingSystemVersion::Windows,
10,
0,
17763);
static const auto kSupported = (kSystemVersion >= kDarkModeAddedVersion);
if (!kSupported) {
return std::nullopt;
}
HIGHCONTRAST hcf = {};
hcf.cbSize = static_cast<UINT>(sizeof(HIGHCONTRAST));
if (SystemParametersInfo(SPI_GETHIGHCONTRAST, hcf.cbSize, &hcf, FALSE)
&& (hcf.dwFlags & HCF_HIGHCONTRASTON)) {
return std::nullopt;
}
const auto keyName = L""
"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
const auto valueName = L"AppsUseLightTheme";
auto key = HKEY();
auto result = RegOpenKeyEx(HKEY_CURRENT_USER, keyName, 0, KEY_READ, &key);
if (result != ERROR_SUCCESS) {
return std::nullopt;
}
DWORD value = 0, type = 0, size = sizeof(value);
result = RegQueryValueEx(key, valueName, 0, &type, (LPBYTE)&value, &size);
RegCloseKey(key);
if (result != ERROR_SUCCESS) {
return std::nullopt;
}
return (value == 0);
}
#endif // Qt < 6.5.0
bool AutostartSupported() {
return true;
}
void AutostartRequestStateFromSystem(Fn<void(bool)> callback) {
#ifdef OS_WIN_STORE
AutostartTask::RequestState([=](bool enabled) {
crl::on_main([=] {
callback(enabled);
});
});
#endif // OS_WIN_STORE
}
void AutostartToggle(bool enabled, Fn<void(bool)> done) {
#ifdef OS_WIN_STORE
const auto requested = enabled;
const auto callback = [=](bool enabled) { crl::on_main([=] {
if (!Core::IsAppLaunched()) {
return;
}
done(enabled);
if (!requested || enabled) {
return;
} else if (const auto window = Core::App().activeWindow()) {
window->show(Ui::MakeConfirmBox({
.text = tr::lng_settings_auto_start_disabled_uwp(),
.confirmed = [](Fn<void()> close) {
AutostartTask::OpenSettings();
close();
},
.confirmText = tr::lng_settings_open_system_settings(),
}));
}
}); };
AutostartTask::Toggle(
enabled,
done ? Fn<void(bool)>(callback) : nullptr);
#else // OS_WIN_STORE
const auto silent = !done;
const auto success = ManageAppLink(
enabled,
silent,
FOLDERID_Startup,
L"-autostart",
L"Telegram autorun link.\n"
"You can disable autorun in Telegram settings.");
if (done) {
done(enabled && success);
}
#endif // OS_WIN_STORE
}
bool AutostartSkip() {
#ifdef OS_WIN_STORE
return false;
#else // OS_WIN_STORE
return !cAutoStart();
#endif // OS_WIN_STORE
}
void WriteCrashDumpDetails() {
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
PROCESS_MEMORY_COUNTERS data = { 0 };
if (Dlls::GetProcessMemoryInfo
&& Dlls::GetProcessMemoryInfo(
GetCurrentProcess(),
&data,
sizeof(data))) {
const auto mb = 1024 * 1024;
CrashReports::dump()
<< "Memory-usage: "
<< (data.PeakWorkingSetSize / mb)
<< " MB (peak), "
<< (data.WorkingSetSize / mb)
<< " MB (current)\n";
CrashReports::dump()
<< "Pagefile-usage: "
<< (data.PeakPagefileUsage / mb)
<< " MB (peak), "
<< (data.PagefileUsage / mb)
<< " MB (current)\n";
}
#endif // TDESKTOP_DISABLE_CRASH_REPORTS
}
void SetWindowPriority(not_null<QWidget*> window, uint32 priority) {
const auto hwnd = reinterpret_cast<HWND>(window->winId());
Assert(hwnd != nullptr);
SetWindowLongPtr(hwnd, GWLP_USERDATA, static_cast<LONG_PTR>(priority));
}
uint64 ActivationWindowId(not_null<QWidget*> window) {
return WindowIdFromHWND(reinterpret_cast<HWND>(window->winId()));
}
void ActivateOtherProcess(uint64 processId, uint64 windowId) {
auto request = FindToActivateRequest{
.processId = processId,
.windowId = windowId,
};
::EnumWindows((WNDENUMPROC)FindToActivate, (LPARAM)&request);
if (const auto hwnd = request.result) {
::SetForegroundWindow(hwnd);
::SetFocus(hwnd);
}
}
} // namespace Platform
namespace {
void _psLogError(const char *str, LSTATUS code) {
LPWSTR errorTextFormatted = nullptr;
auto formatFlags = FORMAT_MESSAGE_FROM_SYSTEM
| FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_IGNORE_INSERTS;
FormatMessage(
formatFlags,
NULL,
code,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&errorTextFormatted,
0,
0);
auto errorText = errorTextFormatted
? errorTextFormatted
: L"(Unknown error)";
LOG((str).arg(code).arg(QString::fromStdWString(errorText)));
LocalFree(errorTextFormatted);
}
bool _psOpenRegKey(LPCWSTR key, PHKEY rkey) {
DEBUG_LOG(("App Info: opening reg key %1...").arg(QString::fromStdWString(key)));
LSTATUS status = RegOpenKeyEx(HKEY_CURRENT_USER, key, 0, KEY_QUERY_VALUE | KEY_WRITE, rkey);
if (status != ERROR_SUCCESS) {
if (status == ERROR_FILE_NOT_FOUND) {
status = RegCreateKeyEx(HKEY_CURRENT_USER, key, 0, 0, REG_OPTION_NON_VOLATILE, KEY_QUERY_VALUE | KEY_WRITE, 0, rkey, 0);
if (status != ERROR_SUCCESS) {
QString msg = u"App Error: could not create '%1' registry key, error %2"_q.arg(QString::fromStdWString(key)).arg(u"%1: %2"_q);
_psLogError(msg.toUtf8().constData(), status);
return false;
}
} else {
QString msg = u"App Error: could not open '%1' registry key, error %2"_q.arg(QString::fromStdWString(key)).arg(u"%1: %2"_q);
_psLogError(msg.toUtf8().constData(), status);
return false;
}
}
return true;
}
bool _psSetKeyValue(HKEY rkey, LPCWSTR value, QString v) {
static const int bufSize = 4096;
DWORD defaultType, defaultSize = bufSize * 2;
WCHAR defaultStr[bufSize] = { 0 };
if (RegQueryValueEx(rkey, value, 0, &defaultType, (BYTE*)defaultStr, &defaultSize) != ERROR_SUCCESS || defaultType != REG_SZ || defaultSize != (v.size() + 1) * 2 || QString::fromStdWString(defaultStr) != v) {
WCHAR tmp[bufSize] = { 0 };
if (!v.isEmpty()) StringCbPrintf(tmp, bufSize, v.replace(QChar('%'), u"%%"_q).toStdWString().c_str());
LSTATUS status = RegSetValueEx(rkey, value, 0, REG_SZ, (BYTE*)tmp, (wcslen(tmp) + 1) * sizeof(WCHAR));
if (status != ERROR_SUCCESS) {
QString msg = u"App Error: could not set %1, error %2"_q.arg(value ? ('\'' + QString::fromStdWString(value) + '\'') : u"(Default)"_q).arg("%1: %2");
_psLogError(msg.toUtf8().constData(), status);
return false;
}
}
return true;
}
}
namespace Platform {
PermissionStatus GetPermissionStatus(PermissionType type) {
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) {
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;
}
}
RegCloseKey(hKey);
}
return result;
}
return PermissionStatus::Granted;
}
void RequestPermission(PermissionType type, Fn<void(PermissionStatus)> resultCallback) {
resultCallback(PermissionStatus::Granted);
}
void OpenSystemSettingsForPermission(PermissionType type) {
if (type == PermissionType::Microphone) {
crl::on_main([] {
ShellExecute(
nullptr,
L"open",
L"ms-settings:privacy-microphone",
nullptr,
nullptr,
SW_SHOWDEFAULT);
});
}
}
bool OpenSystemSettings(SystemSettingsType type) {
if (type == SystemSettingsType::Audio) {
crl::on_main([] {
WinExec("control.exe mmsys.cpl", SW_SHOW);
//QDesktopServices::openUrl(QUrl("ms-settings:sound"));
});
}
return true;
}
void NewVersionLaunched(int oldVersion) {
if (oldVersion <= 4009009) {
AppUserModelId::CheckPinned();
}
if (oldVersion > 0 && oldVersion < 2008012) {
// Reset icons cache, because we've changed the application icon.
if (Dlls::SHChangeNotify) {
Dlls::SHChangeNotify(
SHCNE_ASSOCCHANGED,
SHCNF_IDLIST,
nullptr,
nullptr);
}
}
}
QImage DefaultApplicationIcon() {
return Window::Logo();
}
void LaunchMaps(const Data::LocationPoint &point, Fn<void()> fail) {
const auto aar = base::WinRT::TryCreateInstance<
IApplicationAssociationRegistration
>(CLSID_ApplicationAssociationRegistration);
if (!aar) {
fail();
return;
}
auto handler = base::CoTaskMemString();
const auto result = aar->QueryCurrentDefault(
L"bingmaps",
AT_URLPROTOCOL,
AL_EFFECTIVE,
handler.put());
if (FAILED(result)
|| !handler
|| !handler.data()
|| std::wstring(handler.data()) == L"bingmaps") {
fail();
return;
}
const auto url = u"bingmaps:?lvl=16&collection=point.%1_%2_Point"_q;
if (!QDesktopServices::openUrl(
url.arg(point.latAsString(), point.lonAsString()))) {
fail();
}
}
} // namespace Platform
void psSendToMenu(bool send, bool silent) {
ManageAppLink(
send,
silent,
FOLDERID_SendTo,
L"--",
L"Telegram send to link.\n"
"You can disable send to menu item in Telegram settings.");
}
// Stub while we still support Windows 7.
extern "C" {
STDAPI GetDpiForMonitor(
_In_ HMONITOR hmonitor,
_In_ MONITOR_DPI_TYPE dpiType,
_Out_ UINT *dpiX,
_Out_ UINT *dpiY) {
return Dlls::GetDpiForMonitor
? Dlls::GetDpiForMonitor(hmonitor, dpiType, dpiX, dpiY)
: E_FAIL;
}
} // extern "C"

View File

@@ -0,0 +1,71 @@
/*
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 "platform/platform_specific.h"
#include <windows.h>
namespace Platform {
inline void IgnoreApplicationActivationRightNow() {
}
inline bool TrayIconSupported() {
return true;
}
inline bool SkipTaskbarSupported() {
return true;
}
inline bool PreventsQuit(Core::QuitReason reason) {
return false;
}
inline void ActivateThisProcess() {
}
// 1 - secondary, 2 - primary.
void SetWindowPriority(not_null<QWidget*> window, uint32 priority);
[[nodiscard]] uint64 ActivationWindowId(not_null<QWidget*> window);
// Activate window with windowId (if found) or the largest priority.
void ActivateOtherProcess(uint64 processId, uint64 windowId);
inline QString ApplicationIconName() {
return {};
}
inline QString ExecutablePathForShortcuts() {
return cExeDir() + cExeName();
}
namespace ThirdParty {
void start();
} // namespace ThirdParty
} // namespace Platform
inline void psCheckLocalSocket(const QString &) {
}
QString psAppDataPath();
QString psAppDataPathOld();
void psSendToMenu(bool send, bool silent = false);
int psCleanup();
int psFixPrevious();
inline QByteArray psDownloadPathBookmark(const QString &path) {
return QByteArray();
}
inline void psDownloadPathEnableAccess() {
}

View File

@@ -0,0 +1,24 @@
/*
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 "platform/platform_text_recognition.h"
namespace Platform {
namespace TextRecognition {
inline bool IsAvailable() {
return false;
}
inline Result RecognizeText(const QImage &image) {
return {};
}
} // namespace TextRecognition
} // namespace Platform

View File

@@ -0,0 +1,448 @@
/*
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/tray_win.h"
#include "base/invoke_queued.h"
#include "base/qt_signal_producer.h"
#include "core/application.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "storage/localstorage.h"
#include "ui/painter.h"
#include "ui/ui_utility.h"
#include "ui/widgets/popup_menu.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "styles/style_window.h"
#include <qpa/qplatformscreen.h>
#include <qpa/qplatformsystemtrayicon.h>
#include <qpa/qplatformtheme.h>
#include <private/qguiapplication_p.h>
#include <private/qhighdpiscaling_p.h>
#include <QSvgRenderer>
#include <QBuffer>
namespace Platform {
namespace {
constexpr auto kTooltipDelay = crl::time(10000);
std::optional<bool> DarkTaskbar;
bool DarkTasbarValueValid/* = false*/;
[[nodiscard]] std::optional<bool> ReadDarkTaskbarValue() {
const auto keyName = L""
"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
const auto valueName = L"SystemUsesLightTheme";
auto key = HKEY();
auto result = RegOpenKeyEx(HKEY_CURRENT_USER, keyName, 0, KEY_READ, &key);
if (result != ERROR_SUCCESS) {
return std::nullopt;
}
DWORD value = 0, type = 0, size = sizeof(value);
result = RegQueryValueEx(key, valueName, 0, &type, (LPBYTE)&value, &size);
RegCloseKey(key);
if (result != ERROR_SUCCESS) {
return std::nullopt;
}
return (value == 0);
}
[[nodiscard]] std::optional<bool> IsDarkTaskbar() {
static const auto kSystemVersion = QOperatingSystemVersion::current();
static const auto kDarkModeAddedVersion = QOperatingSystemVersion(
QOperatingSystemVersion::Windows,
10,
0,
18282);
static const auto kSupported = (kSystemVersion >= kDarkModeAddedVersion);
if (!kSupported) {
return std::nullopt;
} else if (!DarkTasbarValueValid) {
DarkTasbarValueValid = true;
DarkTaskbar = ReadDarkTaskbarValue();
}
return DarkTaskbar;
}
[[nodiscard]] QImage MonochromeIconFor(int size, bool darkMode) {
Expects(size > 0);
static const auto Content = [&] {
auto f = QFile(u":/gui/icons/tray/monochrome.svg"_q);
return f.open(QIODevice::ReadOnly) ? f.readAll() : QByteArray();
}();
static auto Mask = QImage();
static auto Size = 0;
if (Mask.isNull() || Size != size) {
Size = size;
Mask = QImage(size, size, QImage::Format_ARGB32_Premultiplied);
Mask.fill(Qt::transparent);
auto p = QPainter(&Mask);
QSvgRenderer(Content).render(&p, QRectF(0, 0, size, size));
}
static auto Colored = QImage();
static auto ColoredDark = QImage();
auto &use = darkMode ? ColoredDark : Colored;
if (use.size() != Mask.size()) {
const auto color = darkMode ? 255 : 0;
const auto alpha = darkMode ? 255 : 228;
use = style::colorizeImage(Mask, { color, color, color, alpha });
}
return use;
}
[[nodiscard]] QImage MonochromeWithDot(QImage image, style::color color) {
auto p = QPainter(&image);
auto hq = PainterHighQualityEnabler(p);
const auto xm = image.width() / 16.;
const auto ym = image.height() / 16.;
p.setBrush(color);
p.setPen(Qt::NoPen);
p.drawEllipse(QRectF( // cx=3.9, cy=12.7, r=2.2
1.7 * xm,
10.5 * ym,
4.4 * xm,
4.4 * ym));
return image;
}
[[nodiscard]] QImage ImageIconWithCounter(
Window::CounterLayerArgs &&args,
bool supportMode,
bool smallIcon,
bool monochrome) {
static auto ScaledLogo = base::flat_map<int, QImage>();
static auto ScaledLogoNoMargin = base::flat_map<int, QImage>();
static auto ScaledLogoDark = base::flat_map<int, QImage>();
static auto ScaledLogoLight = base::flat_map<int, QImage>();
const auto darkMode = IsDarkTaskbar();
auto &scaled = (monochrome && darkMode)
? (*darkMode
? ScaledLogoDark
: ScaledLogoLight)
: smallIcon
? ScaledLogoNoMargin
: ScaledLogo;
auto result = [&] {
if (const auto it = scaled.find(args.size); it != scaled.end()) {
return it->second;
} else if (monochrome && darkMode) {
return MonochromeIconFor(args.size, *darkMode);
}
return scaled.emplace(
args.size,
(smallIcon
? Window::LogoNoMargin()
: Window::Logo()
).scaledToWidth(args.size, Qt::SmoothTransformation)
).first->second;
}();
if ((!monochrome || !darkMode) && supportMode) {
Window::ConvertIconToBlack(result);
}
if (!args.count) {
return result;
} else if (smallIcon) {
if (monochrome && darkMode) {
return MonochromeWithDot(std::move(result), args.bg);
}
return Window::WithSmallCounter(std::move(result), std::move(args));
}
QPainter p(&result);
const auto half = args.size / 2;
args.size = half;
p.drawPixmap(
half,
half,
Ui::PixmapFromImage(Window::GenerateCounterLayer(std::move(args))));
return result;
}
} // namespace
Tray::Tray() {
}
void Tray::createIcon() {
if (!_icon) {
if (const auto theme = QGuiApplicationPrivate::platformTheme()) {
_icon.reset(theme->createPlatformSystemTrayIcon());
}
if (!_icon) {
return;
}
_icon->init();
updateIcon();
_icon->updateToolTip(AppName.utf16());
using Reason = QPlatformSystemTrayIcon::ActivationReason;
base::qt_signal_producer(
_icon.get(),
&QPlatformSystemTrayIcon::activated
) | rpl::filter(
rpl::mappers::_1 != Reason::Context
) | rpl::map_to(
rpl::empty
) | rpl::start_to_stream(_iconClicks, _lifetime);
base::qt_signal_producer(
_icon.get(),
&QPlatformSystemTrayIcon::contextMenuRequested
) | rpl::filter([=] {
return _menu != nullptr;
}) | rpl::on_next([=](
QPoint globalNativePosition,
const QPlatformScreen *screen) {
_aboutToShowRequests.fire({});
const auto position = QHighDpi::fromNativePixels(
globalNativePosition,
screen ? screen->screen() : nullptr);
InvokeQueued(_menu.get(), [=] {
_menu->popup(position);
});
}, _lifetime);
} else {
updateIcon();
}
}
void Tray::destroyIcon() {
_icon = nullptr;
}
void Tray::updateIcon() {
if (!_icon) {
return;
}
const auto controller = Core::App().activePrimaryWindow();
const auto session = !controller
? nullptr
: !controller->sessionController()
? nullptr
: &controller->sessionController()->session();
// Force Qt to use right icon size, not the larger one.
QIcon forTrayIcon;
forTrayIcon.addPixmap(
Tray::IconWithCounter(
CounterLayerArgs(
GetSystemMetrics(SM_CXSMICON),
Core::App().unreadBadge(),
Core::App().unreadBadgeMuted()),
true,
Core::App().settings().trayIconMonochrome(),
session && session->supportMode()));
_icon->updateIcon(forTrayIcon);
}
void Tray::createMenu() {
if (!_menu) {
_menu = base::make_unique_q<Ui::PopupMenu>(nullptr);
_menu->deleteOnHide(false);
}
}
void Tray::destroyMenu() {
_menu = nullptr;
_actionsLifetime.destroy();
}
void Tray::addAction(rpl::producer<QString> text, Fn<void()> &&callback) {
if (!_menu) {
return;
}
// If we try to activate() window before the _menu is hidden,
// then the window will be shown in semi-active state (Qt bug).
// It will receive input events, but it will be rendered as inactive.
auto callbackLater = crl::guard(_menu.get(), [=] {
using namespace rpl::mappers;
_callbackFromTrayLifetime = _menu->shownValue(
) | rpl::filter(!_1) | rpl::take(1) | rpl::on_next([=] {
callback();
});
});
const auto action = _menu->addAction(QString(), std::move(callbackLater));
std::move(
text
) | rpl::on_next([=](const QString &text) {
action->setText(text);
}, _actionsLifetime);
}
void Tray::showTrayMessage() const {
if (!cSeenTrayTooltip() && _icon) {
_icon->showMessage(
AppName.utf16(),
tr::lng_tray_icon_text(tr::now),
QIcon(),
QPlatformSystemTrayIcon::Information,
kTooltipDelay);
cSetSeenTrayTooltip(true);
Local::writeSettings();
}
}
bool Tray::hasTrayMessageSupport() const {
return !cSeenTrayTooltip();
}
rpl::producer<> Tray::aboutToShowRequests() const {
return _aboutToShowRequests.events();
}
rpl::producer<> Tray::showFromTrayRequests() const {
return rpl::never<>();
}
rpl::producer<> Tray::hideToTrayRequests() const {
return rpl::never<>();
}
rpl::producer<> Tray::iconClicks() const {
return _iconClicks.events();
}
bool Tray::hasIcon() const {
return _icon;
}
rpl::lifetime &Tray::lifetime() {
return _lifetime;
}
Window::CounterLayerArgs Tray::CounterLayerArgs(
int size,
int counter,
bool muted) {
return Window::CounterLayerArgs{
.size = size,
.count = counter,
.bg = muted ? st::trayCounterBgMute : st::trayCounterBg,
.fg = st::trayCounterFg,
};
}
QPixmap Tray::IconWithCounter(
Window::CounterLayerArgs &&args,
bool smallIcon,
bool monochrome,
bool supportMode) {
return Ui::PixmapFromImage(ImageIconWithCounter(
std::move(args),
supportMode,
smallIcon,
monochrome));
}
void WriteIco(const QString &path, std::vector<QImage> images) {
Expects(!images.empty());
auto buffer = QByteArray();
const auto write = [&](auto value) {
buffer.append(reinterpret_cast<const char*>(&value), sizeof(value));
};
const auto count = int(images.size());
auto full = 0;
auto pngs = std::vector<QByteArray>();
pngs.reserve(count);
for (const auto &image : images) {
pngs.emplace_back();
{
auto buffer = QBuffer(&pngs.back());
image.save(&buffer, "PNG");
}
full += pngs.back().size();
}
// Images directory
constexpr auto entry = sizeof(int8)
+ sizeof(int8)
+ sizeof(int8)
+ sizeof(int8)
+ sizeof(int16)
+ sizeof(int16)
+ sizeof(uint32)
+ sizeof(uint32);
static_assert(entry == 16);
auto offset = 3 * sizeof(int16) + count * entry;
full += offset;
buffer.reserve(full);
// Thanks https://stackoverflow.com/a/54289564/6509833
write(int16(0));
write(int16(1));
write(int16(count));
for (auto i = 0; i != count; ++i) {
const auto &image = images[i];
Assert(image.width() <= 256 && image.height() <= 256);
write(int8(image.width() == 256 ? 0 : image.width()));
write(int8(image.height() == 256 ? 0 : image.height()));
write(int8(0)); // palette size
write(int8(0)); // reserved
write(int16(1)); // color planes
write(int16(image.depth())); // bits-per-pixel
write(uint32(pngs[i].size())); // size of image in bytes
write(uint32(offset)); // offset
offset += pngs[i].size();
}
for (auto i = 0; i != count; ++i) {
buffer.append(pngs[i]);
}
const auto dir = QFileInfo(path).dir();
dir.mkpath(dir.absolutePath());
auto f = QFile(path);
if (f.open(QIODevice::WriteOnly)) {
f.write(buffer);
}
}
QString Tray::QuitJumpListIconPath() {
const auto dark = IsDarkTaskbar();
const auto key = !dark ? 0 : *dark ? 1 : 2;
const auto path = cWorkingDir() + u"tdata/temp/quit_%1.ico"_q.arg(key);
if (QFile::exists(path)) {
return path;
}
const auto color = !dark
? st::trayCounterBg->c
: *dark
? QColor(255, 255, 255)
: QColor(0, 0, 0, 228);
WriteIco(path, {
st::winQuitIcon.instance(color, 100, true),
st::winQuitIcon.instance(color, 200, true),
st::winQuitIcon.instance(color, 300, true),
});
return path;
}
bool HasMonochromeSetting() {
return IsDarkTaskbar().has_value();
}
void RefreshTaskbarThemeValue() {
DarkTasbarValueValid = false;
}
} // namespace Platform

View File

@@ -0,0 +1,79 @@
/*
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 "platform/platform_tray.h"
#include "base/unique_qptr.h"
namespace Window {
struct CounterLayerArgs;
} // namespace Window
namespace Ui {
class PopupMenu;
} // namespace Ui
class QPlatformSystemTrayIcon;
namespace Platform {
class Tray final {
public:
Tray();
[[nodiscard]] rpl::producer<> aboutToShowRequests() const;
[[nodiscard]] rpl::producer<> showFromTrayRequests() const;
[[nodiscard]] rpl::producer<> hideToTrayRequests() const;
[[nodiscard]] rpl::producer<> iconClicks() const;
[[nodiscard]] bool hasIcon() const;
void createIcon();
void destroyIcon();
void updateIcon();
void createMenu();
void destroyMenu();
void addAction(rpl::producer<QString> text, Fn<void()> &&callback);
void showTrayMessage() const;
[[nodiscard]] bool hasTrayMessageSupport() const;
[[nodiscard]] rpl::lifetime &lifetime();
// Windows only.
[[nodiscard]] static Window::CounterLayerArgs CounterLayerArgs(
int size,
int counter,
bool muted);
[[nodiscard]] static QPixmap IconWithCounter(
Window::CounterLayerArgs &&args,
bool smallIcon,
bool monochrome,
bool supportMode);
[[nodiscard]] static QString QuitJumpListIconPath();
private:
base::unique_qptr<QPlatformSystemTrayIcon> _icon;
base::unique_qptr<Ui::PopupMenu> _menu;
rpl::event_stream<> _iconClicks;
rpl::event_stream<> _aboutToShowRequests;
rpl::lifetime _callbackFromTrayLifetime;
rpl::lifetime _actionsLifetime;
rpl::lifetime _lifetime;
};
void RefreshTaskbarThemeValue();
} // namespace Platform

View File

@@ -0,0 +1,297 @@
/*
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/platform_webauthn.h"
#include "base/platform/win/base_windows_safe_library.h"
#include "data/data_passkey_deserialize.h"
#include <windows.h>
#include <combaseapi.h>
#include <webauthn.h>
#include <QWindow>
#include <QGuiApplication>
namespace Platform::WebAuthn {
namespace {
HRESULT(__stdcall *WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable)(
BOOL *pbIsUserVerifyingPlatformAuthenticatorAvailable);
HRESULT(__stdcall *WebAuthNAuthenticatorMakeCredential)(
HWND hWnd,
PCWEBAUTHN_RP_ENTITY_INFORMATION pRpInformation,
PCWEBAUTHN_USER_ENTITY_INFORMATION pUserInformation,
PCWEBAUTHN_COSE_CREDENTIAL_PARAMETERS pPubKeyCredParams,
PCWEBAUTHN_CLIENT_DATA pWebAuthNClientData,
PCWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS
pWebAuthNCredentialOptions,
PWEBAUTHN_CREDENTIAL_ATTESTATION *ppWebAuthNCredentialAttestation);
void(__stdcall *WebAuthNFreeCredentialAttestation)(
PWEBAUTHN_CREDENTIAL_ATTESTATION pWebAuthNCredentialAttestation);
HRESULT(__stdcall *WebAuthNAuthenticatorGetAssertion)(
HWND hWnd,
LPCWSTR pwszRpId,
PCWEBAUTHN_CLIENT_DATA pWebAuthNClientData,
PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS
pWebAuthNGetAssertionOptions,
PWEBAUTHN_ASSERTION *ppWebAuthNAssertion);
void(__stdcall *WebAuthNFreeAssertion)(
PWEBAUTHN_ASSERTION pWebAuthNAssertion);
[[nodiscard]] bool Resolve() {
const auto webauthn = base::Platform::SafeLoadLibrary(L"webauthn.dll");
if (!webauthn) {
return false;
}
auto total = 0, resolved = 0;
#define LOAD_SYMBOL(name) \
++total; \
if (base::Platform::LoadMethod(webauthn, #name, name)) ++resolved;
LOAD_SYMBOL(WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable);
LOAD_SYMBOL(WebAuthNAuthenticatorMakeCredential);
LOAD_SYMBOL(WebAuthNFreeCredentialAttestation);
LOAD_SYMBOL(WebAuthNAuthenticatorGetAssertion);
LOAD_SYMBOL(WebAuthNFreeAssertion);
#undef LOAD_SYMBOL
return (total == resolved);
}
[[nodiscard]] bool Supported() {
static const auto Result = Resolve();
return Result;
}
} // namespace
bool IsSupported() {
if (!Supported()) {
return false;
}
auto available = (BOOL)(FALSE);
return SUCCEEDED(
WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable(&available))
&& available;
}
void RegisterKey(
const Data::Passkey::RegisterData &data,
Fn<void(RegisterResult result)> callback) {
if (!Supported()) {
callback({});
return;
}
auto rpId = data.rp.id.toStdWString();
auto rpName = data.rp.name.toStdWString();
auto userName = data.user.name.toStdWString();
auto userDisplayName = data.user.displayName.toStdWString();
auto rpInfo = WEBAUTHN_RP_ENTITY_INFORMATION();
memset(&rpInfo, 0, sizeof(rpInfo));
rpInfo.dwVersion =
WEBAUTHN_RP_ENTITY_INFORMATION_CURRENT_VERSION;
rpInfo.pwszId = rpId.c_str();
rpInfo.pwszName = rpName.c_str();
auto userInfo = WEBAUTHN_USER_ENTITY_INFORMATION();
memset(&userInfo, 0, sizeof(userInfo));
userInfo.dwVersion =
WEBAUTHN_USER_ENTITY_INFORMATION_CURRENT_VERSION;
userInfo.cbId = data.user.id.size();
userInfo.pbId = (PBYTE)data.user.id.data();
userInfo.pwszName = userName.c_str();
userInfo.pwszDisplayName = userDisplayName.c_str();
auto credParams = std::vector<WEBAUTHN_COSE_CREDENTIAL_PARAMETER>();
for (const auto &param : data.pubKeyCredParams) {
auto cp = WEBAUTHN_COSE_CREDENTIAL_PARAMETER{};
cp.dwVersion =
WEBAUTHN_COSE_CREDENTIAL_PARAMETER_CURRENT_VERSION;
auto type = param.type.toStdWString();
cp.pwszCredentialType = type == L"public-key"
? WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY
: L"";
cp.lAlg = param.alg;
credParams.push_back(cp);
}
auto credParamsList = WEBAUTHN_COSE_CREDENTIAL_PARAMETERS();
memset(&credParamsList, 0, sizeof(credParamsList));
credParamsList.cCredentialParameters = credParams.size();
credParamsList.pCredentialParameters = credParams.data();
auto clientDataJson = Data::Passkey::SerializeClientDataCreate(
data.challenge);
auto clientData = WEBAUTHN_CLIENT_DATA();
memset(&clientData, 0, sizeof(clientData));
clientData.dwVersion = WEBAUTHN_CLIENT_DATA_CURRENT_VERSION;
clientData.cbClientDataJSON = clientDataJson.size();
clientData.pbClientDataJSON = (PBYTE)clientDataJson.data();
clientData.pwszHashAlgId = WEBAUTHN_HASH_ALGORITHM_SHA_256;
auto cancellationId = GUID();
CoCreateGuid(&cancellationId);
auto options = WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS();
memset(&options, 0, sizeof(options));
options.dwVersion =
WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_CURRENT_VERSION;
options.dwTimeoutMilliseconds = data.timeout;
options.dwAuthenticatorAttachment =
WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY;
options.bRequireResidentKey = FALSE;
options.dwUserVerificationRequirement =
WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED;
options.dwAttestationConveyancePreference =
WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE;
options.pCancellationId = &cancellationId;
#if defined(WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_4) \
|| defined(WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_5) \
|| defined(WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_6) \
|| defined(WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_7) \
|| defined(WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_8) \
|| defined(WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_9)
options.bPreferResidentKey = FALSE;
#endif
auto hwnd = (HWND)(nullptr);
if (auto window = QGuiApplication::topLevelWindows().value(0)) {
hwnd = (HWND)window->winId();
if (hwnd) {
SetForegroundWindow(hwnd);
SetFocus(hwnd);
}
}
auto attestation = (PWEBAUTHN_CREDENTIAL_ATTESTATION)(nullptr);
auto hr = (HRESULT)(WebAuthNAuthenticatorMakeCredential)(
hwnd,
&rpInfo,
&userInfo,
&credParamsList,
&clientData,
&options,
&attestation);
if (SUCCEEDED(hr) && attestation) {
auto result = RegisterResult();
result.success = true;
result.credentialId = QByteArray(
(char*)attestation->pbCredentialId,
attestation->cbCredentialId);
result.attestationObject = QByteArray(
(char*)attestation->pbAttestationObject,
attestation->cbAttestationObject);
result.clientDataJSON = QByteArray::fromStdString(clientDataJson);
WebAuthNFreeCredentialAttestation(attestation);
callback(result);
} else {
callback({});
}
}
void Login(
const Data::Passkey::LoginData &data,
Fn<void(LoginResult result)> callback) {
if (!Supported()) {
callback({});
return;
}
auto rpId = data.rpId.toStdWString();
auto clientDataJson = Data::Passkey::SerializeClientDataGet(
data.challenge);
auto clientData = WEBAUTHN_CLIENT_DATA();
memset(&clientData, 0, sizeof(clientData));
clientData.dwVersion = WEBAUTHN_CLIENT_DATA_CURRENT_VERSION;
clientData.cbClientDataJSON = clientDataJson.size();
clientData.pbClientDataJSON = (PBYTE)clientDataJson.data();
clientData.pwszHashAlgId = WEBAUTHN_HASH_ALGORITHM_SHA_256;
auto allowCredentials = std::vector<WEBAUTHN_CREDENTIAL>();
auto credentialIds = std::vector<QByteArray>();
for (const auto &cred : data.allowCredentials) {
credentialIds.push_back(cred.id);
auto credential = WEBAUTHN_CREDENTIAL();
memset(&credential, 0, sizeof(credential));
credential.dwVersion = WEBAUTHN_CREDENTIAL_CURRENT_VERSION;
credential.cbId = cred.id.size();
credential.pbId = (PBYTE)credentialIds.back().data();
credential.pwszCredentialType = WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY;
allowCredentials.push_back(credential);
}
auto cancellationId = GUID();
CoCreateGuid(&cancellationId);
auto options = WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS();
memset(&options, 0, sizeof(options));
options.dwVersion =
WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_CURRENT_VERSION;
options.dwTimeoutMilliseconds = data.timeout;
if (!allowCredentials.empty()) {
options.CredentialList.cCredentials = allowCredentials.size();
options.CredentialList.pCredentials = allowCredentials.data();
}
options.pCancellationId = &cancellationId;
if (data.userVerification == "required") {
options.dwUserVerificationRequirement =
WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED;
} else if (data.userVerification == "preferred") {
options.dwUserVerificationRequirement =
WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED;
} else {
options.dwUserVerificationRequirement =
WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED;
}
auto hwnd = (HWND)(nullptr);
if (auto window = QGuiApplication::topLevelWindows().value(0)) {
hwnd = (HWND)window->winId();
if (hwnd) {
SetForegroundWindow(hwnd);
SetFocus(hwnd);
}
}
auto assertion = (PWEBAUTHN_ASSERTION)(nullptr);
auto hr = (HRESULT)(WebAuthNAuthenticatorGetAssertion)(
hwnd,
rpId.c_str(),
&clientData,
&options,
&assertion);
if (SUCCEEDED(hr) && assertion) {
auto result = LoginResult();
result.clientDataJSON = QByteArray::fromStdString(clientDataJson);
result.credentialId = QByteArray(
(char*)assertion->Credential.pbId,
assertion->Credential.cbId);
result.authenticatorData = QByteArray(
(char*)assertion->pbAuthenticatorData,
assertion->cbAuthenticatorData);
result.signature = QByteArray(
(char*)assertion->pbSignature,
assertion->cbSignature);
result.userHandle = assertion->cbUserId > 0
? QByteArray((char*)assertion->pbUserId, assertion->cbUserId)
: QByteArray();
WebAuthNFreeAssertion(assertion);
callback(result);
} else {
callback({});
}
}
} // namespace Platform::WebAuthn

View File

@@ -0,0 +1,507 @@
/*
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_app_user_model_id.h"
#include "platform/win/windows_dlls.h"
#include "platform/win/windows_toast_activator.h"
#include "base/platform/win/base_windows_winrt.h"
#include "core/launcher.h"
#include <propvarutil.h>
#include <propkey.h>
namespace Platform {
namespace AppUserModelId {
namespace {
constexpr auto kMaxFileLen = MAX_PATH * 2;
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 AppUserModelIdBase[] = L"Telegram.TelegramDesktop.Store";
#else // OS_WIN_STORE
const WCHAR AppUserModelIdBase[] = L"Telegram.TelegramDesktop";
#endif // OS_WIN_STORE
[[nodiscard]] QString PinnedIconsPath() {
WCHAR wstrPath[kMaxFileLen] = {};
if (GetEnvironmentVariable(L"APPDATA", wstrPath, kMaxFileLen)) {
auto appData = QDir(QString::fromStdWString(std::wstring(wstrPath)));
return appData.absolutePath()
+ u"/Microsoft/Internet Explorer/Quick Launch/User Pinned/TaskBar/"_q;
}
return QString();
}
} // namespace
const std::wstring &MyExecutablePath() {
static const auto Path = [&] {
auto result = std::wstring(kMaxFileLen, 0);
const auto length = GetModuleFileName(
GetModuleHandle(nullptr),
result.data(),
kMaxFileLen);
if (!length || length == kMaxFileLen) {
result.clear();
} else {
result.resize(length + 1);
}
return result;
}();
return Path;
}
UniqueFileId MyExecutablePathId() {
return GetUniqueFileId(MyExecutablePath().c_str());
}
UniqueFileId GetUniqueFileId(LPCWSTR path) {
auto info = BY_HANDLE_FILE_INFORMATION{};
const auto file = CreateFile(
path,
0,
0,
nullptr,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (file == INVALID_HANDLE_VALUE) {
return {};
}
const auto result = GetFileInformationByHandle(file, &info);
CloseHandle(file);
if (!result) {
return {};
}
return {
.part1 = info.dwVolumeSerialNumber,
.part2 = ((std::uint64_t(info.nFileIndexLow) << 32)
| std::uint64_t(info.nFileIndexHigh)),
};
}
void CheckPinned() {
if (!SUCCEEDED(CoInitialize(0))) {
return;
}
const auto coGuard = gsl::finally([] {
CoUninitialize();
});
const auto path = PinnedIconsPath();
const auto native = QDir::toNativeSeparators(path).toStdWString();
const auto srcid = MyExecutablePathId();
if (!srcid) {
return;
}
LOG(("Checking..."));
WIN32_FIND_DATA findData;
HANDLE findHandle = FindFirstFileEx(
(native + L"*").c_str(),
FindExInfoStandard,
&findData,
FindExSearchNameMatch,
0,
0);
if (findHandle == INVALID_HANDLE_VALUE) {
LOG(("Init Error: could not find files in pinned folder"));
return;
}
do {
std::wstring fname = native + findData.cFileName;
LOG(("Checking %1").arg(QString::fromStdWString(fname)));
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
continue;
} else {
DWORD attributes = GetFileAttributes(fname.c_str());
if (attributes >= 0xFFFFFFF) {
continue; // file does not exist
}
auto shellLink = base::WinRT::TryCreateInstance<IShellLink>(
CLSID_ShellLink);
if (!shellLink) {
continue;
}
auto persistFile = shellLink.try_as<IPersistFile>();
if (!persistFile) {
continue;
}
auto hr = persistFile->Load(fname.c_str(), STGM_READWRITE);
if (!SUCCEEDED(hr)) continue;
WCHAR dst[MAX_PATH] = { 0 };
hr = shellLink->GetPath(dst, MAX_PATH, nullptr, 0);
if (!SUCCEEDED(hr)) continue;
if (GetUniqueFileId(dst) == srcid) {
auto propertyStore = shellLink.try_as<IPropertyStore>();
if (!propertyStore) {
return;
}
PROPVARIANT appIdPropVar;
hr = propertyStore->GetValue(Key(), &appIdPropVar);
if (!SUCCEEDED(hr)) return;
LOG(("Reading..."));
WCHAR already[MAX_PATH];
hr = PropVariantToString(appIdPropVar, already, MAX_PATH);
if (SUCCEEDED(hr)) {
if (Id() == already) {
LOG(("Already!"));
PropVariantClear(&appIdPropVar);
return;
}
}
if (appIdPropVar.vt != VT_EMPTY) {
PropVariantClear(&appIdPropVar);
return;
}
PropVariantClear(&appIdPropVar);
hr = InitPropVariantFromString(Id().c_str(), &appIdPropVar);
if (!SUCCEEDED(hr)) return;
hr = propertyStore->SetValue(Key(), appIdPropVar);
PropVariantClear(&appIdPropVar);
if (!SUCCEEDED(hr)) return;
hr = propertyStore->Commit();
if (!SUCCEEDED(hr)) return;
if (persistFile->IsDirty() == S_OK) {
persistFile->Save(fname.c_str(), TRUE);
}
return;
}
}
} while (FindNextFile(findHandle, &findData));
DWORD errorCode = GetLastError();
if (errorCode && errorCode != ERROR_NO_MORE_FILES) {
LOG(("Init Error: could not find some files in pinned folder"));
return;
}
FindClose(findHandle);
}
QString systemShortcutPath() {
WCHAR wstrPath[kMaxFileLen] = {};
if (GetEnvironmentVariable(L"APPDATA", wstrPath, kMaxFileLen)) {
auto appData = QDir(QString::fromStdWString(std::wstring(wstrPath)));
const auto path = appData.absolutePath();
return path + u"/Microsoft/Windows/Start Menu/Programs/"_q;
}
return QString();
}
void CleanupShortcut() {
const auto myid = MyExecutablePathId();
if (!myid) {
return;
}
QString path = systemShortcutPath() + u"Telegram.lnk"_q;
std::wstring p = QDir::toNativeSeparators(path).toStdWString();
DWORD attributes = GetFileAttributes(p.c_str());
if (attributes >= 0xFFFFFFF) return; // file does not exist
auto shellLink = base::WinRT::TryCreateInstance<IShellLink>(
CLSID_ShellLink);
if (!shellLink) {
return;
}
auto persistFile = shellLink.try_as<IPersistFile>();
if (!persistFile) {
return;
}
auto hr = persistFile->Load(p.c_str(), STGM_READWRITE);
if (!SUCCEEDED(hr)) return;
WCHAR szGotPath[MAX_PATH];
hr = shellLink->GetPath(szGotPath, MAX_PATH, nullptr, 0);
if (!SUCCEEDED(hr)) return;
if (GetUniqueFileId(szGotPath) == myid) {
QFile().remove(path);
}
}
bool validateShortcutAt(const QString &path) {
const auto native = QDir::toNativeSeparators(path).toStdWString();
DWORD attributes = GetFileAttributes(native.c_str());
if (attributes >= 0xFFFFFFF) {
return false; // file does not exist
}
auto shellLink = base::WinRT::TryCreateInstance<IShellLink>(
CLSID_ShellLink);
if (!shellLink) {
return false;
}
auto persistFile = shellLink.try_as<IPersistFile>();
if (!persistFile) {
return false;
}
auto hr = persistFile->Load(native.c_str(), STGM_READWRITE);
if (!SUCCEEDED(hr)) return false;
WCHAR szGotPath[kMaxFileLen] = { 0 };
hr = shellLink->GetPath(szGotPath, kMaxFileLen, nullptr, 0);
if (!SUCCEEDED(hr)) {
return false;
}
if (GetUniqueFileId(szGotPath) != MyExecutablePathId()) {
return false;
}
auto propertyStore = shellLink.try_as<IPropertyStore>();
if (!propertyStore) {
return false;
}
PROPVARIANT appIdPropVar;
PROPVARIANT toastActivatorPropVar;
hr = propertyStore->GetValue(Key(), &appIdPropVar);
if (!SUCCEEDED(hr)) return false;
hr = propertyStore->GetValue(
pkey_AppUserModel_ToastActivator,
&toastActivatorPropVar);
if (!SUCCEEDED(hr)) return false;
WCHAR already[MAX_PATH];
hr = PropVariantToString(appIdPropVar, already, MAX_PATH);
const auto good1 = SUCCEEDED(hr) && (Id() == 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) {
LOG(("App Info: Shortcut validated at \"%1\"").arg(path));
return true;
} else if (bad1 || bad2) {
return false;
}
hr = InitPropVariantFromString(Id().c_str(), &appIdPropVar);
if (!SUCCEEDED(hr)) return false;
hr = propertyStore->SetValue(Key(), appIdPropVar);
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;
if (persistFile->IsDirty() == S_OK) {
hr = persistFile->Save(native.c_str(), TRUE);
if (!SUCCEEDED(hr)) return false;
}
LOG(("App Info: Shortcut set and validated at \"%1\"").arg(path));
return true;
}
bool checkInstalled(QString path = {}) {
if (path.isEmpty()) {
path = systemShortcutPath();
if (path.isEmpty()) {
return false;
}
}
const auto installed = u"Telegram Desktop/Telegram.lnk"_q;
const auto old = u"Telegram Win (Unofficial)/Telegram.lnk"_q;
return validateShortcutAt(path + installed)
|| validateShortcutAt(path + old);
}
bool ValidateShortcut() {
QString path = systemShortcutPath();
if (path.isEmpty() || cExeName().isEmpty()) {
return false;
}
if (cAlphaVersion()) {
path += u"TelegramAlpha.lnk"_q;
if (validateShortcutAt(path)) {
return true;
}
} else {
if (checkInstalled(path)) {
return true;
}
path += u"Telegram.lnk"_q;
if (validateShortcutAt(path)) {
return true;
}
}
auto shellLink = base::WinRT::TryCreateInstance<IShellLink>(
CLSID_ShellLink);
if (!shellLink) {
return false;
}
auto hr = shellLink->SetPath(MyExecutablePath().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());
if (!SUCCEEDED(hr)) {
return false;
}
auto propertyStore = shellLink.try_as<IPropertyStore>();
if (!propertyStore) {
return false;
}
PROPVARIANT appIdPropVar;
hr = InitPropVariantFromString(Id().c_str(), &appIdPropVar);
if (!SUCCEEDED(hr)) {
return false;
}
hr = propertyStore->SetValue(Key(), appIdPropVar);
PropVariantClear(&appIdPropVar);
if (!SUCCEEDED(hr)) {
return false;
}
PROPVARIANT startPinPropVar;
hr = InitPropVariantFromUInt32(
APPUSERMODEL_STARTPINOPTION_NOPINONINSTALL,
&startPinPropVar);
if (!SUCCEEDED(hr)) {
return false;
}
hr = propertyStore->SetValue(
pkey_AppUserModel_StartPinOption,
startPinPropVar);
PropVariantClear(&startPinPropVar);
if (!SUCCEEDED(hr)) {
return false;
}
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;
}
auto persistFile = shellLink.try_as<IPersistFile>();
if (!persistFile) {
return false;
}
hr = persistFile->Save(
QDir::toNativeSeparators(path).toStdWString().c_str(),
TRUE);
if (!SUCCEEDED(hr)) {
return false;
}
LOG(("App Info: Shortcut created and validated at \"%1\"").arg(path));
return true;
}
const std::wstring &Id() {
static const auto BaseId = std::wstring(AppUserModelIdBase);
static auto CheckingInstalled = false;
if (CheckingInstalled) {
return BaseId;
}
static const auto Installed = [] {
#ifdef OS_WIN_STORE
return true;
#else // OS_WIN_STORE
CheckingInstalled = true;
const auto guard = gsl::finally([] {
CheckingInstalled = false;
});
if (!SUCCEEDED(CoInitialize(nullptr))) {
return false;
}
const auto coGuard = gsl::finally([] {
CoUninitialize();
});
return checkInstalled();
#endif
}();
if (Installed) {
return BaseId;
}
static const auto PortableId = [] {
const auto h = Core::Launcher::Instance().instanceHash();
return BaseId + L'.' + std::wstring(h.begin(), h.end());
}();
return PortableId;
}
const PROPERTYKEY &Key() {
return pkey_AppUserModel_ID;
}
} // namespace AppUserModelId
} // namespace Platform

View File

@@ -0,0 +1,48 @@
/*
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 <wtypes.h>
namespace Platform {
namespace AppUserModelId {
void CleanupShortcut();
void CheckPinned();
[[nodiscard]] const std::wstring &Id();
bool ValidateShortcut();
[[nodiscard]] const PROPERTYKEY &Key();
[[nodiscard]] const std::wstring &MyExecutablePath();
struct UniqueFileId {
std::uint64_t part1 = 0;
std::uint64_t part2 = 0;
[[nodiscard]] bool valid() const {
return part1 || part2;
}
[[nodiscard]] explicit operator bool() const {
return valid();
}
[[nodiscard]] friend inline auto operator<=>(
UniqueFileId a,
UniqueFileId b) = default;
[[nodiscard]] friend inline bool operator==(
UniqueFileId a,
UniqueFileId b) = default;
};
[[nodiscard]] UniqueFileId GetUniqueFileId(LPCWSTR path);
[[nodiscard]] UniqueFileId MyExecutablePathId();
} // namespace AppUserModelId
} // namespace Platform

View File

@@ -0,0 +1,111 @@
/*
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_autostart_task.h"
#include "base/platform/win/base_windows_winrt.h"
#include <winrt/Windows.ApplicationModel.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.System.h>
namespace Platform::AutostartTask {
namespace {
using namespace winrt::Windows::ApplicationModel;
using namespace winrt::Windows::System;
using namespace winrt::Windows::Foundation;
[[nodiscard]] bool IsEnabled(StartupTaskState state) {
switch (state) {
case StartupTaskState::Enabled:
case StartupTaskState::EnabledByPolicy:
return true;
case StartupTaskState::Disabled:
case StartupTaskState::DisabledByPolicy:
case StartupTaskState::DisabledByUser:
default:
return false;
}
}
} // namespace
void Toggle(bool enabled, Fn<void(bool)> done) {
if (!base::WinRT::Supported()) {
return;
}
const auto processEnableResult = [=](StartupTaskState state) {
LOG(("Startup Task: Enable finished, state: %1").arg(int(state)));
done(IsEnabled(state));
};
const auto processTask = [=](StartupTask task) {
LOG(("Startup Task: Got it, state: %1, requested: %2"
).arg(int(task.State())
).arg(Logs::b(enabled)));
if (IsEnabled(task.State()) == enabled) {
return;
}
if (!enabled) {
LOG(("Startup Task: Disabling."));
task.Disable();
return;
}
LOG(("Startup Task: Requesting enable."));
const auto asyncState = task.RequestEnableAsync();
if (!done) {
return;
}
asyncState.Completed([=](
IAsyncOperation<StartupTaskState> operation,
AsyncStatus status) {
base::WinRT::Try([&] {
processEnableResult(operation.GetResults());
});
});
};
base::WinRT::Try([&] {
StartupTask::GetAsync(L"TelegramStartupTask").Completed([=](
IAsyncOperation<StartupTask> operation,
AsyncStatus status) {
base::WinRT::Try([&] {
processTask(operation.GetResults());
});
});
});
}
void RequestState(Fn<void(bool)> callback) {
Expects(callback != nullptr);
if (!base::WinRT::Supported()) {
return;
}
const auto processTask = [=](StartupTask task) {
DEBUG_LOG(("Startup Task: Got value, state: %1"
).arg(int(task.State())));
callback(IsEnabled(task.State()));
};
base::WinRT::Try([&] {
StartupTask::GetAsync(L"TelegramStartupTask").Completed([=](
IAsyncOperation<StartupTask> operation,
AsyncStatus status) {
base::WinRT::Try([&] {
processTask(operation.GetResults());
});
});
});
}
void OpenSettings() {
Launcher::LaunchUriAsync(Uri(L"ms-settings:startupapps"));
}
} // namespace Platform::AutostartTask

View File

@@ -0,0 +1,16 @@
/*
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
namespace Platform::AutostartTask {
void Toggle(bool enabled, Fn<void(bool)> done);
void RequestState(Fn<void(bool)> callback);
void OpenSettings();
} // namespace Platform::AutostartTask

View File

@@ -0,0 +1,102 @@
/*
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_dlls.h"
#include "base/platform/win/base_windows_safe_library.h"
#include "ui/gl/gl_detection.h"
#include <VersionHelpers.h>
#include <QtCore/QSysInfo>
#define LOAD_SYMBOL(lib, name) ::base::Platform::LoadMethod(lib, #name, name)
#ifdef DESKTOP_APP_USE_ANGLE
bool DirectXResolveCompiler();
#endif // DESKTOP_APP_USE_ANGLE
namespace Platform {
namespace Dlls {
namespace {
struct SafeIniter {
SafeIniter();
};
SafeIniter::SafeIniter() {
base::Platform::InitDynamicLibraries();
const auto LibShell32 = LoadLibrary(L"shell32.dll");
LOAD_SYMBOL(LibShell32, SHAssocEnumHandlers);
LOAD_SYMBOL(LibShell32, SHCreateItemFromParsingName);
LOAD_SYMBOL(LibShell32, SHOpenWithDialog);
LOAD_SYMBOL(LibShell32, OpenAs_RunDLL);
LOAD_SYMBOL(LibShell32, SHQueryUserNotificationState);
LOAD_SYMBOL(LibShell32, SHChangeNotify);
//if (IsWindows10OrGreater()) {
// static const auto kSystemVersion = QOperatingSystemVersion::current();
// static const auto kMinor = kSystemVersion.minorVersion();
// static const auto kBuild = kSystemVersion.microVersion();
// if (kMinor > 0 || (kMinor == 0 && kBuild >= 17763)) {
// const auto LibUxTheme = LoadLibrary(L"uxtheme.dll");
// if (kBuild < 18362) {
// LOAD_SYMBOL(LibUxTheme, AllowDarkModeForApp, 135);
// } else {
// LOAD_SYMBOL(LibUxTheme, SetPreferredAppMode, 135);
// }
// LOAD_SYMBOL(LibUxTheme, AllowDarkModeForWindow, 133);
// LOAD_SYMBOL(LibUxTheme, RefreshImmersiveColorPolicyState, 104);
// LOAD_SYMBOL(LibUxTheme, FlushMenuThemes, 136);
// }
//}
const auto LibPropSys = LoadLibrary(L"propsys.dll");
LOAD_SYMBOL(LibPropSys, PSStringFromPropertyKey);
const auto LibPsApi = LoadLibrary(L"psapi.dll");
LOAD_SYMBOL(LibPsApi, GetProcessMemoryInfo);
const auto LibUser32 = LoadLibrary(L"user32.dll");
LOAD_SYMBOL(LibUser32, SetWindowCompositionAttribute);
const auto LibShCore = LoadLibrary(L"Shcore.dll");
LOAD_SYMBOL(LibShCore, GetDpiForMonitor);
}
SafeIniter kSafeIniter;
} // namespace
void CheckLoadedModules() {
#ifdef DESKTOP_APP_USE_ANGLE
if (DirectXResolveCompiler()) {
auto LibD3DCompiler = HMODULE();
if (GetModuleHandleEx(0, L"d3dcompiler_47.dll", &LibD3DCompiler)) {
constexpr auto kMaxPathLong = 32767;
auto path = std::array<WCHAR, kMaxPathLong + 1>{ 0 };
const auto length = GetModuleFileName(
LibD3DCompiler,
path.data(),
kMaxPathLong);
if (length > 0 && length < kMaxPathLong) {
LOG(("Using DirectX compiler '%1'."
).arg(QString::fromWCharArray(path.data())));
} else {
LOG(("Error: Could not resolve DirectX compiler path."));
}
} else {
LOG(("Error: Could not resolve DirectX compiler module."));
}
} else {
LOG(("Error: Could not resolve DirectX compiler library."));
}
#endif // DESKTOP_APP_USE_ANGLE
}
} // namespace Dlls
} // namespace Platform

View File

@@ -0,0 +1,132 @@
/*
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 "base/platform/win/base_windows_shlobj_h.h"
#include <windows.h>
#include <shellapi.h>
#include <ShellScalingApi.h>
#include <dwmapi.h>
#include <RestartManager.h>
#include <psapi.h>
namespace Platform {
namespace Dlls {
void CheckLoadedModules();
//inline void(__stdcall *RefreshImmersiveColorPolicyState)();
//
//inline BOOL(__stdcall *AllowDarkModeForApp)(BOOL allow);
//
//enum class PreferredAppMode {
// Default,
// AllowDark,
// ForceDark,
// ForceLight,
// Max
//};
//
//inline PreferredAppMode(__stdcall *SetPreferredAppMode)(
// PreferredAppMode appMode);
//inline BOOL(__stdcall *AllowDarkModeForWindow)(HWND hwnd, BOOL allow);
//inline void(__stdcall *FlushMenuThemes)();
// SHELL32.DLL
inline HRESULT(__stdcall *SHAssocEnumHandlers)(
PCWSTR pszExtra,
ASSOC_FILTER afFilter,
IEnumAssocHandlers **ppEnumHandler);
inline HRESULT(__stdcall *SHCreateItemFromParsingName)(
PCWSTR pszPath,
IBindCtx *pbc,
REFIID riid,
void **ppv);
inline HRESULT(__stdcall *SHOpenWithDialog)(
HWND hwndParent,
const OPENASINFO *poainfo);
inline HRESULT(__stdcall *OpenAs_RunDLL)(
HWND hWnd,
HINSTANCE hInstance,
LPCWSTR lpszCmdLine,
int nCmdShow);
inline HRESULT(__stdcall *SHQueryUserNotificationState)(
QUERY_USER_NOTIFICATION_STATE *pquns);
inline void(__stdcall *SHChangeNotify)(
LONG wEventId,
UINT uFlags,
__in_opt LPCVOID dwItem1,
__in_opt LPCVOID dwItem2);
// PROPSYS.DLL
inline HRESULT(__stdcall *PSStringFromPropertyKey)(
_In_ REFPROPERTYKEY pkey,
_Out_writes_(cch) LPWSTR psz,
_In_ UINT cch);
// PSAPI.DLL
inline BOOL(__stdcall *GetProcessMemoryInfo)(
HANDLE Process,
PPROCESS_MEMORY_COUNTERS ppsmemCounters,
DWORD cb);
// USER32.DLL
enum class WINDOWCOMPOSITIONATTRIB {
WCA_UNDEFINED = 0,
WCA_NCRENDERING_ENABLED = 1,
WCA_NCRENDERING_POLICY = 2,
WCA_TRANSITIONS_FORCEDISABLED = 3,
WCA_ALLOW_NCPAINT = 4,
WCA_CAPTION_BUTTON_BOUNDS = 5,
WCA_NONCLIENT_RTL_LAYOUT = 6,
WCA_FORCE_ICONIC_REPRESENTATION = 7,
WCA_EXTENDED_FRAME_BOUNDS = 8,
WCA_HAS_ICONIC_BITMAP = 9,
WCA_THEME_ATTRIBUTES = 10,
WCA_NCRENDERING_EXILED = 11,
WCA_NCADORNMENTINFO = 12,
WCA_EXCLUDED_FROM_LIVEPREVIEW = 13,
WCA_VIDEO_OVERLAY_ACTIVE = 14,
WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15,
WCA_DISALLOW_PEEK = 16,
WCA_CLOAK = 17,
WCA_CLOAKED = 18,
WCA_ACCENT_POLICY = 19,
WCA_FREEZE_REPRESENTATION = 20,
WCA_EVER_UNCLOAKED = 21,
WCA_VISUAL_OWNER = 22,
WCA_HOLOGRAPHIC = 23,
WCA_EXCLUDED_FROM_DDA = 24,
WCA_PASSIVEUPDATEMODE = 25,
WCA_USEDARKMODECOLORS = 26,
WCA_LAST = 27
};
struct WINDOWCOMPOSITIONATTRIBDATA {
WINDOWCOMPOSITIONATTRIB Attrib;
PVOID pvData;
SIZE_T cbData;
};
inline BOOL(__stdcall *SetWindowCompositionAttribute)(
HWND hWnd,
WINDOWCOMPOSITIONATTRIBDATA*);
// SHCORE.DLL
inline HRESULT(__stdcall *GetDpiForMonitor)(
_In_ HMONITOR hmonitor,
_In_ MONITOR_DPI_TYPE dpiType,
_Out_ UINT *dpiX,
_Out_ UINT *dpiY);
} // namespace Dlls
} // namespace Platform

View File

@@ -0,0 +1,79 @@
// © Rafael Rivera
// License: MIT
import "oaidl.idl";
[uuid(e0b5ef8b-a9b4-497a-8f71-08dd5c8ab2bf)]
library QuietHours
{
[uuid(f53321fa-34f8-4b7f-b9a3-361877cb94cf)]
coclass QuietHoursSettings
{
[default] interface IQuietHoursSettings;
}
[uuid(af86e2e0-b12d-4c6a-9c5a-d7aa65101e90)]
interface IQuietMoment : IUnknown
{
// Incomplete
}
[uuid(e813fe81-62b6-417d-b951-9d2e08486ac1)]
interface IQuietHoursProfile : IUnknown
{
[propget] HRESULT DisplayName([out, string, retval] LPWSTR* displayName);
[propget] HRESULT ProfileId([out, string, retval] LPWSTR* profileId);
HRESULT GetSetting(int setting, [out, retval] int* value);
HRESULT PutSetting(int setting, int value);
[propget] HRESULT IsCustomizable([out, retval] BOOL* result);
HRESULT GetAllowedContacts([out] UINT32* count, [out, retval] LPWSTR* allowedContacts);
HRESULT AddAllowedContact([in, string] LPWSTR allowedContact);
HRESULT RemoveAllowedContact([in, string] LPWSTR allowedContact);
HRESULT GetAllowedApps([out] UINT32* count, [out, retval] LPWSTR** allowedApps);
HRESULT AddAllowedApp([in, string] LPWSTR allowedApp);
HRESULT RemoveAllowedApp([in, string] LPWSTR allowedApp);
[propget] HRESULT Description([out, string, retval] LPWSTR* description);
[propget] HRESULT CustomizeLinkText([out, string, retval] LPWSTR* linkText);
[propget] HRESULT RestrictiveLevel([out, string, retval] LPWSTR* restrictiveLevel);
}
[uuid(cd86a976-8ea9-404b-a197-42e73dbaa901)]
interface IQuietHoursPinnedContactManager : IUnknown
{
HRESULT GetPinnedContactList([out] UINT32* count, [out, string, retval] LPWSTR* pinnedContacts);
}
[uuid(b0217783-87b7-422c-b902-5c148c14f150)]
interface IQuietMomentsManager : IUnknown
{
HRESULT GetAllQuietMomentModes([out] UINT32* count, [out, retval] UINT32** quietMomentModes);
HRESULT GetQuietMoment([in] UINT32 quietMomentId, [out, retval] IQuietMoment** quietMoment);
HRESULT TurnOffCurrentlyActiveQuietMoment();
HRESULT GetActiveQuietMoment([out, retval] UINT32* quietMomentId);
}
typedef struct _QH_PROFILE_DATA
{
char do_not_use__incomplete; // Incomplete
} QH_PROFILE_DATA;
[uuid(6bff4732-81ec-4ffb-ae67-b6c1bc29631f)]
interface IQuietHoursSettings : IUnknown
{
[propget] HRESULT UserSelectedProfile([out, string, retval] LPWSTR* profileId);
[propput] HRESULT UserSelectedProfile([in] LPWSTR profileId);
HRESULT GetProfile([in, string] LPWSTR profileId, [out, retval] IQuietHoursProfile**);
HRESULT GetAllProfileData(UINT32* count, QH_PROFILE_DATA*);
HRESULT GetDisplayNameForProfile([in, string] LPWSTR profileId, [out, string, retval] LPWSTR* displayName);
[propget] HRESULT QuietMomentsManager([out, retval] IQuietMomentsManager**);
[propget] HRESULT OffProfileId([out, string, retval] LPWSTR* profileId);
[propget] HRESULT ActiveQuietMomentProfile([out, string, retval] LPWSTR* profileId);
[propput] HRESULT ActiveQuietMomentProfile([in] LPWSTR profileId);
[propget] HRESULT ActiveProfile([out, string, retval] LPWSTR* profileId);
[propget] HRESULT QuietHoursPinnedContactManager([out, retval] IQuietHoursPinnedContactManager**);
[propput] HRESULT QuietMomentsShowSummaryEnabled([out, retval] BOOL* isEnabled);
HRESULT GetAlwaysAllowedApps([out] UINT32* count, [out, string, retval] LPWSTR** allowedApps);
HRESULT StartProcessing();
HRESULT StopProcessing();
}
}

View File

@@ -0,0 +1,92 @@
/*
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) {
DEBUG_LOG(("Toast Info: COM Activated \"%1\" with args \"%2\"."
).arg(QString::fromWCharArray(appUserModelId)
).arg(QString::fromWCharArray(invokedArgs)));
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,52 @@
/*
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 "base/platform/win/base_windows_rpcndr_h.h"
#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);
};