diff --git a/Telegram/SourceFiles/platform/win/main_window_win.cpp b/Telegram/SourceFiles/platform/win/main_window_win.cpp index cae742eb1..4152e28a1 100644 --- a/Telegram/SourceFiles/platform/win/main_window_win.cpp +++ b/Telegram/SourceFiles/platform/win/main_window_win.cpp @@ -47,7 +47,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +// 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; @@ -61,6 +63,15 @@ namespace { // 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 window); @@ -82,9 +93,6 @@ private: }; -using namespace Microsoft::WRL; - -bool handleSessionNotification = false; [[nodiscard]] HICON NativeIcon(const QIcon &icon, QSize size) { if (!icon.isNull()) { @@ -96,6 +104,67 @@ bool handleSessionNotification = false; 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 window) : _window(window) { } @@ -150,6 +219,23 @@ bool EventFilter::mainWindowEvent( _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; } @@ -164,6 +250,46 @@ struct MainWindow::Private { ComPtr 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 controller) : Window::MainWindow(controller) , _private(std::make_unique(this)) @@ -180,6 +306,27 @@ MainWindow::MainWindow(not_null controller) ) | rpl::filter(_1) | rpl::start_with_next([=] { _lastDeactivateTime = crl::now(); }, lifetime()); + + setupPreviewPasscodeLock(); +} + +void MainWindow::setupPreviewPasscodeLock() { + Core::App().passcodeLockValue( + ) | rpl::start_with_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() { @@ -211,6 +358,60 @@ void MainWindow::destroyedFromSystem() { } } +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(); +} + int32 MainWindow::screenNameChecksum(const QString &name) const { constexpr int DeviceNameSize = base::array_size(MONITORINFOEX().szDevice); wchar_t buffer[DeviceNameSize] = { 0 }; diff --git a/Telegram/SourceFiles/platform/win/main_window_win.h b/Telegram/SourceFiles/platform/win/main_window_win.h index ec480fa4e..fcc467176 100644 --- a/Telegram/SourceFiles/platform/win/main_window_win.h +++ b/Telegram/SourceFiles/platform/win/main_window_win.h @@ -34,6 +34,9 @@ public: void destroyedFromSystem(); + bool setDwmThumbnail(QSize size); + bool setDwmPreview(QSize size, int radius); + ~MainWindow(); protected: @@ -50,12 +53,32 @@ protected: 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; const std::unique_ptr _taskbarHiderWindow; @@ -65,6 +88,13 @@ private: 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;