Add "Quit Telegram" taskbar menu item.

Fixes #1161.
This commit is contained in:
John Preston 2024-01-03 22:14:34 +04:00
parent d2246337a2
commit 973f91b5e4
10 changed files with 214 additions and 17 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 686 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 952 B

View file

@ -7,17 +7,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "platform/win/integration_win.h"
#include "platform/platform_integration.h"
#include "platform/platform_specific.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 "base/platform/win/base_windows_winrt.h"
#include "styles/style_window.h"
#include <QtCore/QCoreApplication>
#include <QtCore/QAbstractNativeEventFilter>
#include <propvarutil.h>
#include <propkey.h>
namespace Platform {
void WindowsIntegration::init() {
@ -48,6 +55,78 @@ bool WindowsIntegration::nativeEventFilter(
});
}
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,
@ -58,6 +137,9 @@ bool WindowsIntegration::processEvent(
_taskbarList = base::WinRT::TryCreateInstance<ITaskbarList3>(
CLSID_TaskbarList,
CLSCTX_ALL);
if (_taskbarList) {
createCustomJumpList();
}
}
switch (msg) {
@ -80,10 +162,14 @@ bool WindowsIntegration::processEvent(
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;

View file

@ -37,8 +37,12 @@ private:
LPARAM lParam,
LRESULT *result);
void createCustomJumpList();
void refreshCustomJumpList();
uint32 _taskbarCreatedMsgId = 0;
winrt::com_ptr<ITaskbarList3> _taskbarList;
winrt::com_ptr<ICustomDestinationList> _jumpList;
};

View file

@ -195,8 +195,7 @@ bool ManageAppLink(
return true;
}
const auto shellLink = base::WinRT::TryCreateInstance<IShellLink>(
CLSID_ShellLink,
CLSCTX_INPROC_SERVER);
CLSID_ShellLink);
if (!shellLink) {
if (!silent) LOG(("App Error: could not create instance of IID_IShellLink %1").arg(hr));
return false;

View file

@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <private/qguiapplication_p.h>
#include <private/qhighdpiscaling_p.h>
#include <QSvgRenderer>
#include <QBuffer>
namespace Platform {
@ -33,18 +34,10 @@ namespace {
constexpr auto kTooltipDelay = crl::time(10000);
[[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;
}
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";
@ -64,6 +57,23 @@ constexpr auto kTooltipDelay = crl::time(10000);
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);
@ -339,8 +349,99 @@ QPixmap Tray::IconWithCounter(
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]);
}
auto f = QFile(path);
if (f.open(QIODevice::WriteOnly)) {
f.write(buffer);
}
}
QString Tray::QuitJumpListIconPath() {
const auto dark = IsDarkTaskbar();
const auto key = !dark ? 0 : *dark ? 1 : 2;
const auto path = cWorkingDir() + u"tdata/temp/quit_%1.ico"_q.arg(key);
if (QFile::exists(path)) {
return path;
}
const auto color = !dark
? st::trayCounterBg->c
: *dark
? QColor(255, 255, 255)
: QColor(0, 0, 0, 228);
WriteIco(path, {
st::winQuitIcon.instance(color, 100, true),
st::winQuitIcon.instance(color, 200, true),
st::winQuitIcon.instance(color, 300, true),
});
return path;
}
bool HasMonochromeSetting() {
return IsDarkTaskbar().has_value();
}
void RefreshTaskbarThemeValue() {
DarkTasbarValueValid = false;
}
} // namespace Platform

View file

@ -59,6 +59,7 @@ public:
bool smallIcon,
bool monochrome,
bool supportMode);
[[nodiscard]] static QString QuitJumpListIconPath();
private:
base::unique_qptr<QPlatformSystemTrayIcon> _icon;
@ -73,4 +74,6 @@ private:
};
void RefreshTaskbarThemeValue();
} // namespace Platform

View file

@ -317,6 +317,10 @@ windowArchiveToast: Toast(defaultToast) {
maxWidth: boxWideWidth;
}
// Windows specific
winQuitIcon: icon {{ "win_quit", windowFg }};
// Mac specific
macAccessoryWidth: 450.;

@ -1 +1 @@
Subproject commit 99e36f9ac64048a6b7fcb0e6ee2e8eaca48935e1
Subproject commit 7fef09421c2b71e5ab9cf481c0fcf2a0b6d2daf0