From 973f91b5e4a7dc87e56a5eef8cb79b44a62abcaf Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 3 Jan 2024 22:14:34 +0400 Subject: [PATCH] Add "Quit Telegram" taskbar menu item. Fixes #1161. --- Telegram/Resources/icons/win_quit.png | Bin 0 -> 406 bytes Telegram/Resources/icons/win_quit@2x.png | Bin 0 -> 686 bytes Telegram/Resources/icons/win_quit@3x.png | Bin 0 -> 952 bytes .../platform/win/integration_win.cpp | 92 ++++++++++++- .../platform/win/integration_win.h | 4 + .../SourceFiles/platform/win/specific_win.cpp | 3 +- .../SourceFiles/platform/win/tray_win.cpp | 123 ++++++++++++++++-- Telegram/SourceFiles/platform/win/tray_win.h | 3 + Telegram/SourceFiles/window/window.style | 4 + Telegram/lib_ui | 2 +- 10 files changed, 214 insertions(+), 17 deletions(-) create mode 100644 Telegram/Resources/icons/win_quit.png create mode 100644 Telegram/Resources/icons/win_quit@2x.png create mode 100644 Telegram/Resources/icons/win_quit@3x.png diff --git a/Telegram/Resources/icons/win_quit.png b/Telegram/Resources/icons/win_quit.png new file mode 100644 index 0000000000000000000000000000000000000000..a823f133e81b47e7d767c0bb3abbc9f0deedc772 GIT binary patch literal 406 zcmV;H0crk;P)+lEJEiKoo{g>4@1z$W>ruK0)C^B52Vg1U-S^L%8Sx ze1d!pF$iLyU9?KjmU9=0NVExExN`2o?!DeL=wJTxeKY64v;d&r?@uNZgb@5BP1ED? zm}ME%vs^CoJWrD3tCGj#5k--zstp1mWW8R$lswPx_j|{2>U>*K6xVfM+C0y9yPcva zr92o6{saJ^l-6%LpU;QG0RU#R+2wMX&*#qrO6hn!ZkV+a%d)a8dpe&8Rfny%wry*g z_RhK8ZrknlpMWurECvK@c#;zVAzt)P4tbyIon9OWO0iUaz-UEbjOF zTfs1l&1M4tx~_8^SJD8$7Dd6bY+dMdI%SVB#u)$h?Jx{Y(-cLKt7JGFu2!oc2pXQA zPNzwd&{|HXlO#!Xz6Agb!;D6wm+yVOUK@A&2H&KiL5SA>iU0rr07*qoM6N<$f+hR1 Ay#N3J literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/win_quit@2x.png b/Telegram/Resources/icons/win_quit@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..569097eda9395ba7da7e94a75bfc6030d794d739 GIT binary patch literal 686 zcmV;f0#W^mP)ne@qgZPP1qGM3LO};b1aVN8Qc%|} z>Q?s-9onh52rlic;#jKPD+ul)8VsT??WEwJ#i5Gie#854sjogIJxg-Q$@%5Jm%KcH zKbDFdj*gD5udiJ$m%(75S(nSDBuUk3wO+4p2l4y;pP!$|CRvuBpPzZ2XYIgZv52B5 z2!brj?RI-S9@DHG$6Z`p==FL4NT<{H_xDVMTrP)*v)Rn+^=>rp@9)39z9Qmsxip*2 zOn`2;i-?&_hRF?Ze0(g+G9pH!Q5L`2ZnpzKr_*5sj7B3ejO5160F_Dw0J1DI0#;_V zT1`ru&31cxOXg+`fIWAm>2wMJlgWgX*=+XZ<)v1u8I8s@Zu0Ua5(##I&*yu5eAH^S zq)aB0Bt#U&HH;kqKXrF^M`b7i_>G|yfXYx1u$IBmtf!}^H#awjhlkrTK0ZEz!Jxz8 zs8lL$x0~8QKA&e?{(jNb)fKgaQmN$ecn%H@w(Wr9IHS?Xaol`9mn2EyIXFK*9}EVF zxL7PAkqCgIle{KfLBRTSr2y3Q-2j)Dm#I`rqtTG^?Cgx1z8gRkMMR9p*VSS%KcMcP&Md_D(&)qt{SZ*LC(zQ4a& z%<5LF1pt9SfHB?abP9q10HP=|-SF`6K<4@B>B(xfYU$GJ^`TIx-|r*ha5!B3(Kd+Z z`DU}Z?JCB&XZbIkPFE-t$Zlt&zP`S~;V^r+YIPMX0!MA_j`MLYin!o@9*|_`ThQM zI;~sd_4PFv4BB7=A;uDo6^y8in5Iy*bNySvjgjp%-c z5k;}Bt*xr63bn|x?DP2^A0H`=BuT>m@A~>0LP!x(UtfQ4aDa1GS63-*004)FhbTo+ z6rayWol{+1{qXRBqTb%#IF6$XdU<(4DamA#+6J+>xQIJR6h*oaRuBYK-Q3(*6iTPl zxW=(X3*q^Oa1@(hOki1-QplnNAwxq$M@L7qv$F-~Tb_JIE{L&*R8_^b+wI=k+Tu8_ zudmPJ@k~xm>bm}83Lb__OG_4oJRXlM%et;-G8vc4h1(k&8&y>`P6}qdV51NK!0Yvn zjg7h8?!5L$Bx0P<&75+%oQ*;zOixb}IXNao>g1#lk(0whg`J!iD&SNSArmK_@cxE| zhL)BV8--G-)bjH3)YKHZlq5-2)o?gW)I(ieU7w$y`v0fDzn|z`77B$bD=Y2k8B+p* z0Fh84k+8lNAmQTTf@lmS5(&TG4xv$M0mzhAPaY;<%~(=0}^-cEf&favJxz%`C7nw6Cm{6Nmk%uw6(^z`7FCY4H2=P<3Ut>@?GxNp|i z*EcseZ*OmNxg24HD2l;gaBgm{qM`z|OiWB{Y-~_FfoL@P&H9#>HHk #include +#include +#include + namespace Platform { void WindowsIntegration::init() { @@ -48,6 +55,78 @@ bool WindowsIntegration::nativeEventFilter( }); } +void WindowsIntegration::createCustomJumpList() { + _jumpList = base::WinRT::TryCreateInstance( + 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( + 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()) { + 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( + 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( 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; diff --git a/Telegram/SourceFiles/platform/win/integration_win.h b/Telegram/SourceFiles/platform/win/integration_win.h index dae9784119..0b29007d32 100644 --- a/Telegram/SourceFiles/platform/win/integration_win.h +++ b/Telegram/SourceFiles/platform/win/integration_win.h @@ -37,8 +37,12 @@ private: LPARAM lParam, LRESULT *result); + void createCustomJumpList(); + void refreshCustomJumpList(); + uint32 _taskbarCreatedMsgId = 0; winrt::com_ptr _taskbarList; + winrt::com_ptr _jumpList; }; diff --git a/Telegram/SourceFiles/platform/win/specific_win.cpp b/Telegram/SourceFiles/platform/win/specific_win.cpp index 77cc18de50..ef5c88e351 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.cpp +++ b/Telegram/SourceFiles/platform/win/specific_win.cpp @@ -195,8 +195,7 @@ bool ManageAppLink( return true; } const auto shellLink = base::WinRT::TryCreateInstance( - 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; diff --git a/Telegram/SourceFiles/platform/win/tray_win.cpp b/Telegram/SourceFiles/platform/win/tray_win.cpp index b1337c6d9c..2a8e4615e0 100644 --- a/Telegram/SourceFiles/platform/win/tray_win.cpp +++ b/Telegram/SourceFiles/platform/win/tray_win.cpp @@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include +#include namespace Platform { @@ -33,18 +34,10 @@ namespace { constexpr auto kTooltipDelay = crl::time(10000); -[[nodiscard]] std::optional 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 DarkTaskbar; +bool DarkTasbarValueValid/* = false*/; +[[nodiscard]] std::optional 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 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 images) { + Expects(!images.empty()); + + auto buffer = QByteArray(); + const auto write = [&](auto value) { + buffer.append(reinterpret_cast(&value), sizeof(value)); + }; + + const auto count = int(images.size()); + + auto full = 0; + auto pngs = std::vector(); + 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 diff --git a/Telegram/SourceFiles/platform/win/tray_win.h b/Telegram/SourceFiles/platform/win/tray_win.h index 53631e5565..cb79c3beb5 100644 --- a/Telegram/SourceFiles/platform/win/tray_win.h +++ b/Telegram/SourceFiles/platform/win/tray_win.h @@ -59,6 +59,7 @@ public: bool smallIcon, bool monochrome, bool supportMode); + [[nodiscard]] static QString QuitJumpListIconPath(); private: base::unique_qptr _icon; @@ -73,4 +74,6 @@ private: }; +void RefreshTaskbarThemeValue(); + } // namespace Platform diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style index d15a67a4ed..38621c69bc 100644 --- a/Telegram/SourceFiles/window/window.style +++ b/Telegram/SourceFiles/window/window.style @@ -317,6 +317,10 @@ windowArchiveToast: Toast(defaultToast) { maxWidth: boxWideWidth; } +// Windows specific + +winQuitIcon: icon {{ "win_quit", windowFg }}; + // Mac specific macAccessoryWidth: 450.; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 99e36f9ac6..7fef09421c 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 99e36f9ac64048a6b7fcb0e6ee2e8eaca48935e1 +Subproject commit 7fef09421c2b71e5ab9cf481c0fcf2a0b6d2daf0