diff --git a/Telegram/Resources/icons/win_quit.png b/Telegram/Resources/icons/win_quit.png new file mode 100644 index 000000000..a823f133e Binary files /dev/null and b/Telegram/Resources/icons/win_quit.png differ diff --git a/Telegram/Resources/icons/win_quit@2x.png b/Telegram/Resources/icons/win_quit@2x.png new file mode 100644 index 000000000..569097eda Binary files /dev/null and b/Telegram/Resources/icons/win_quit@2x.png differ diff --git a/Telegram/Resources/icons/win_quit@3x.png b/Telegram/Resources/icons/win_quit@3x.png new file mode 100644 index 000000000..c11e92bb2 Binary files /dev/null and b/Telegram/Resources/icons/win_quit@3x.png differ diff --git a/Telegram/SourceFiles/platform/win/integration_win.cpp b/Telegram/SourceFiles/platform/win/integration_win.cpp index b8d53a506..3d8b592e7 100644 --- a/Telegram/SourceFiles/platform/win/integration_win.cpp +++ b/Telegram/SourceFiles/platform/win/integration_win.cpp @@ -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 #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 dae978411..0b29007d3 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 77cc18de5..ef5c88e35 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 b1337c6d9..2a8e4615e 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 53631e556..cb79c3beb 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 d15a67a4e..38621c69b 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 99e36f9ac..7fef09421 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 99e36f9ac64048a6b7fcb0e6ee2e8eaca48935e1 +Subproject commit 7fef09421c2b71e5ab9cf481c0fcf2a0b6d2daf0