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
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:
@@ -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
|
||||
10
Telegram/SourceFiles/platform/win/current_geo_location_win.h
Normal file
10
Telegram/SourceFiles/platform/win/current_geo_location_win.h
Normal 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"
|
||||
473
Telegram/SourceFiles/platform/win/file_utilities_win.cpp
Normal file
473
Telegram/SourceFiles/platform/win/file_utilities_win.cpp
Normal 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
|
||||
24
Telegram/SourceFiles/platform/win/file_utilities_win.h
Normal file
24
Telegram/SourceFiles/platform/win/file_utilities_win.h
Normal 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
|
||||
189
Telegram/SourceFiles/platform/win/integration_win.cpp
Normal file
189
Telegram/SourceFiles/platform/win/integration_win.cpp
Normal 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
|
||||
51
Telegram/SourceFiles/platform/win/integration_win.h
Normal file
51
Telegram/SourceFiles/platform/win/integration_win.h
Normal 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
|
||||
134
Telegram/SourceFiles/platform/win/launcher_win.cpp
Normal file
134
Telegram/SourceFiles/platform/win/launcher_win.cpp
Normal 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
|
||||
32
Telegram/SourceFiles/platform/win/launcher_win.h
Normal file
32
Telegram/SourceFiles/platform/win/launcher_win.h
Normal 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
|
||||
849
Telegram/SourceFiles/platform/win/main_window_win.cpp
Normal file
849
Telegram/SourceFiles/platform/win/main_window_win.cpp
Normal 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
|
||||
113
Telegram/SourceFiles/platform/win/main_window_win.h
Normal file
113
Telegram/SourceFiles/platform/win/main_window_win.h
Normal 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
|
||||
978
Telegram/SourceFiles/platform/win/notifications_manager_win.cpp
Normal file
978
Telegram/SourceFiles/platform/win/notifications_manager_win.cpp
Normal 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('&', "&").toStdWString();
|
||||
const auto fastReply = LR"(
|
||||
<input id="fastReply" type="text" placeHolderContent=""/>
|
||||
<action
|
||||
content="Send"
|
||||
arguments="action=reply&)" + wid + LR"("
|
||||
activationType="background"
|
||||
imageUri=""
|
||||
hint-inputId="fastReply"/>
|
||||
)";
|
||||
const auto markAsRead = LR"(
|
||||
<action
|
||||
content=""
|
||||
arguments="action=mark&)" + wid + LR"("
|
||||
activationType="background"/>
|
||||
)";
|
||||
const auto actions = (options.hideReplyButton ? L"" : fastReply)
|
||||
+ (options.hideMarkAsRead ? L"" : markAsRead);
|
||||
return LR"(
|
||||
<toast launch="action=open&)" + 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 ¬ification);
|
||||
[[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 ¬ification) {
|
||||
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
|
||||
@@ -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
|
||||
20
Telegram/SourceFiles/platform/win/overlay_widget_win.h
Normal file
20
Telegram/SourceFiles/platform/win/overlay_widget_win.h
Normal 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
|
||||
728
Telegram/SourceFiles/platform/win/specific_win.cpp
Normal file
728
Telegram/SourceFiles/platform/win/specific_win.cpp
Normal 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"
|
||||
71
Telegram/SourceFiles/platform/win/specific_win.h
Normal file
71
Telegram/SourceFiles/platform/win/specific_win.h
Normal 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() {
|
||||
}
|
||||
24
Telegram/SourceFiles/platform/win/text_recognition_win.h
Normal file
24
Telegram/SourceFiles/platform/win/text_recognition_win.h
Normal 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
|
||||
448
Telegram/SourceFiles/platform/win/tray_win.cpp
Normal file
448
Telegram/SourceFiles/platform/win/tray_win.cpp
Normal 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
|
||||
79
Telegram/SourceFiles/platform/win/tray_win.h
Normal file
79
Telegram/SourceFiles/platform/win/tray_win.h
Normal 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
|
||||
297
Telegram/SourceFiles/platform/win/webauthn_win.cpp
Normal file
297
Telegram/SourceFiles/platform/win/webauthn_win.cpp
Normal 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 ¶m : 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
|
||||
507
Telegram/SourceFiles/platform/win/windows_app_user_model_id.cpp
Normal file
507
Telegram/SourceFiles/platform/win/windows_app_user_model_id.cpp
Normal 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
|
||||
@@ -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
|
||||
111
Telegram/SourceFiles/platform/win/windows_autostart_task.cpp
Normal file
111
Telegram/SourceFiles/platform/win/windows_autostart_task.cpp
Normal 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
|
||||
16
Telegram/SourceFiles/platform/win/windows_autostart_task.h
Normal file
16
Telegram/SourceFiles/platform/win/windows_autostart_task.h
Normal 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
|
||||
102
Telegram/SourceFiles/platform/win/windows_dlls.cpp
Normal file
102
Telegram/SourceFiles/platform/win/windows_dlls.cpp
Normal 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
|
||||
132
Telegram/SourceFiles/platform/win/windows_dlls.h
Normal file
132
Telegram/SourceFiles/platform/win/windows_dlls.h
Normal 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
|
||||
79
Telegram/SourceFiles/platform/win/windows_quiethours.idl
Normal file
79
Telegram/SourceFiles/platform/win/windows_quiethours.idl
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
52
Telegram/SourceFiles/platform/win/windows_toast_activator.h
Normal file
52
Telegram/SourceFiles/platform/win/windows_toast_activator.h
Normal 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();
|
||||
29
Telegram/SourceFiles/platform/win/windows_toastactivator.idl
Normal file
29
Telegram/SourceFiles/platform/win/windows_toastactivator.idl
Normal file
@@ -0,0 +1,29 @@
|
||||
// ToastActivator.idl : IDL source for ToastActivator
|
||||
//
|
||||
|
||||
// This file will be processed by the MIDL tool to
|
||||
// produce the type library (ToastActivator.tlb) and marshalling code.
|
||||
|
||||
import "oaidl.idl";
|
||||
import "ocidl.idl";
|
||||
|
||||
typedef struct _NOTIFICATION_USER_INPUT_DATA
|
||||
{
|
||||
LPCWSTR Key;
|
||||
LPCWSTR Value;
|
||||
} NOTIFICATION_USER_INPUT_DATA;
|
||||
|
||||
[
|
||||
object,
|
||||
uuid("53E31837-6600-4A81-9395-75CFFE746F94"),
|
||||
pointer_default(ref)
|
||||
]
|
||||
|
||||
interface INotificationActivationCallback : IUnknown
|
||||
{
|
||||
HRESULT Activate(
|
||||
[in, string] LPCWSTR appUserModelId,
|
||||
[in, string] LPCWSTR arguments, // arugments from the invoked button
|
||||
[in, size_is(count), unique] const NOTIFICATION_USER_INPUT_DATA* data, // data from all the input elements in the XML
|
||||
[in] ULONG count);
|
||||
};
|
||||
Reference in New Issue
Block a user