/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "WindowsUIOverlayImage.h" #include #include #include #include #include #include #include "mozilla/Maybe.h" #include "mozilla/RefPtr.h" #include "nsDirectoryServiceDefs.h" #include "nsDirectoryServiceUtils.h" #include "nsIFile.h" namespace mozilla { static void PaintOverlayFrame(HWND aOverlayWindow, HDC aMemDC, SIZE aSize, void* aDibBits, const std::vector& aFrame) { memcpy(aDibBits, aFrame.data(), aFrame.size()); POINT point{}; BLENDFUNCTION blend{AC_SRC_OVER, 0, 255, AC_SRC_ALPHA}; HDC screenDC{GetDC(nullptr)}; UpdateLayeredWindow(aOverlayWindow, screenDC, nullptr, &aSize, aMemDC, &point, 0, &blend, ULW_ALPHA); ReleaseDC(nullptr, screenDC); } static HWND CreateOverlayWindow(const RECT& aRect) { return CreateWindowExW(WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_NOACTIVATE | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW, L"WindowsUIOverlayImage", nullptr, WS_POPUP, aRect.left, aRect.top, aRect.right - aRect.left, aRect.bottom - aRect.top, nullptr, nullptr, GetModuleHandleW(nullptr), nullptr); } static mozilla::Maybe ComputeOverlayRect(IUIAutomationElement* aElement, SIZE aSize) { RECT rect{}; HRESULT hr{aElement->get_CurrentBoundingRectangle(&rect)}; if (FAILED(hr)) { return mozilla::Nothing(); } const LONG overlayX{(rect.left + rect.right) / 2 - aSize.cx / 2}; const LONG overlayY{rect.top - aSize.cy}; return mozilla::Some(RECT{overlayX, overlayY, overlayX + aSize.cx, rect.top}); } static HBITMAP CreateTopDown32bppDIB(int aWidth, int aHeight, void** aOutBits) { BITMAPINFO bmi{}; bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = aWidth; bmi.bmiHeader.biHeight = -aHeight; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; HDC screenDC{GetDC(nullptr)}; HBITMAP dib{ CreateDIBSection(screenDC, &bmi, DIB_RGB_COLORS, aOutBits, nullptr, 0)}; ReleaseDC(nullptr, screenDC); return dib; } static HDC CreateOverlayMemoryDC() { HDC screenDC{GetDC(nullptr)}; HDC memDC{CreateCompatibleDC(screenDC)}; ReleaseDC(nullptr, screenDC); return memDC; } static void RegisterWindowClass() { WNDCLASSEXW wc{sizeof(wc)}; wc.lpfnWndProc = DefWindowProcW; wc.hInstance = GetModuleHandleW(nullptr); wc.lpszClassName = L"WindowsUIOverlayImage"; RegisterClassExW(&wc); } static bool LoadFrame(IWICImagingFactory* aFactory, IWICBitmapDecoder* aDecoder, UINT aIndex, int aWidth, int aHeight, std::vector>& aFrames) { RefPtr frame; HRESULT hr{aDecoder->GetFrame(aIndex, getter_AddRefs(frame))}; if (FAILED(hr)) { return false; } RefPtr converted; hr = WICConvertBitmapSource(GUID_WICPixelFormat32bppPBGRA, frame, getter_AddRefs(converted)); if (FAILED(hr)) { return false; } RefPtr scaler; hr = aFactory->CreateBitmapScaler(getter_AddRefs(scaler)); if (FAILED(hr)) { return false; } hr = scaler->Initialize(converted, static_cast(aWidth), static_cast(aHeight), WICBitmapInterpolationModeFant); if (FAILED(hr)) { return false; } constexpr size_t kBytesPerPixel{4}; std::vector pixels(static_cast(aWidth) * aHeight * kBytesPerPixel); const UINT stride{static_cast(aWidth * kBytesPerPixel)}; hr = scaler->CopyPixels(nullptr, stride, static_cast(pixels.size()), pixels.data()); if (FAILED(hr)) { return false; } aFrames.push_back(std::move(pixels)); return true; } static bool LoadFrames(WindowsUIOverlayImage::DisplayMode aDisplayMode, IWICImagingFactory* aFactory, IWICBitmapDecoder* aDecoder, UINT aFrameCount, SIZE aSize, std::vector>& aFrames) { if (aDisplayMode == WindowsUIOverlayImage::DisplayMode::Static) { // Load just the last frame aFrames.reserve(1); return LoadFrame(aFactory, aDecoder, aFrameCount - 1, aSize.cx, aSize.cy, aFrames); } aFrames.reserve(aFrameCount); for (UINT i{0}; i < aFrameCount; ++i) { if (!LoadFrame(aFactory, aDecoder, i, aSize.cx, aSize.cy, aFrames)) { return false; } } return true; } static mozilla::Maybe GetImageSize(RefPtr aElement, RefPtr aDecoder) { RefPtr firstFrame; HRESULT hr{aDecoder->GetFrame(0, getter_AddRefs(firstFrame))}; if (FAILED(hr)) { return mozilla::Nothing(); } SIZE frameSize{}; hr = firstFrame->GetSize(reinterpret_cast(&frameSize.cx), reinterpret_cast(&frameSize.cy)); if (FAILED(hr) || frameSize.cx == 0 || frameSize.cy == 0) { return mozilla::Nothing(); } RECT rect{}; hr = aElement->get_CurrentBoundingRectangle(&rect); if (FAILED(hr)) { return mozilla::Nothing(); } // Match the element's width and scale the height to preserve the aspect ratio LONG width{rect.right - rect.left}; float imageScale{static_cast(width) / frameSize.cx}; LONG height{static_cast(imageScale * frameSize.cy)}; return mozilla::Some(SIZE{width, height}); } static mozilla::Maybe LoadFrameCount(IWICBitmapDecoder* aDecoder) { UINT frameCount{0}; HRESULT hr{aDecoder->GetFrameCount(&frameCount)}; if (FAILED(hr) || frameCount == 0) { return mozilla::Nothing(); } return mozilla::Some(frameCount); } static RefPtr CreateWICBitmapDecoder( RefPtr aFactory, nsIFile* aImageFile) { nsAutoString imagePath; aImageFile->GetPath(imagePath); RefPtr decoder; HRESULT hr{aFactory->CreateDecoderFromFilename( imagePath.get(), nullptr, GENERIC_READ, WICDecodeMetadataCacheOnDemand, getter_AddRefs(decoder))}; return SUCCEEDED(hr) ? decoder : nullptr; } static already_AddRefed GetImageFile() { nsCOMPtr file; nsresult rv{NS_GetSpecialDirectory(NS_GRE_BIN_DIR, getter_AddRefs(file))}; if (NS_FAILED(rv)) { return nullptr; } constexpr auto kImageRelativePath{ R"(browser\components\shell\assets\kit.gif)"_ns}; rv = file->AppendRelativeNativePath(kImageRelativePath); if (NS_FAILED(rv)) { return nullptr; } return file.forget(); } static RefPtr CreateWICImagingFactory() { RefPtr factory; HRESULT hr{CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_IWICImagingFactory, getter_AddRefs(factory))}; return SUCCEEDED(hr) ? factory : nullptr; } already_AddRefed WindowsUIOverlayImage::Create( HWND aWindow, RefPtr aElement, DisplayMode aDisplayMode) { RefPtr overlayImage{ new WindowsUIOverlayImage(aWindow, aElement, aDisplayMode)}; if (!overlayImage->Initialize()) { return nullptr; } return overlayImage.forget(); } WindowsUIOverlayImage::WindowsUIOverlayImage( HWND aWindow, RefPtr aElement, DisplayMode aDisplayMode) : mWindow{aWindow}, mElement{aElement}, mDisplayMode{aDisplayMode}, mSize{}, mDibBits{nullptr}, mOldBmp{nullptr}, mRect{}, mOverlayWindow{nullptr}, mCurrentFrame{} {} WindowsUIOverlayImage::~WindowsUIOverlayImage() { if (mOverlayWindow) { DestroyWindow(mOverlayWindow); mOverlayWindow = nullptr; } if (mMemDC && mOldBmp) { SelectObject(mMemDC, mOldBmp); } } static bool IsWindowPointVisible(HWND aWindow, POINT aPoint) { HWND topWindow{WindowFromPoint(aPoint)}; if (aWindow != topWindow && !IsChild(aWindow, topWindow)) { return false; } return true; } bool WindowsUIOverlayImage::IsVisible() { POINT pointTopLeft{mRect.left, mRect.top}; if (!IsWindowPointVisible(mWindow, pointTopLeft)) { return false; } POINT pointBottomLeft{mRect.left, mRect.bottom}; if (!IsWindowPointVisible(mWindow, pointBottomLeft)) { return false; } POINT pointTopRight{mRect.right, mRect.top}; if (!IsWindowPointVisible(mWindow, pointTopRight)) { return false; } POINT pointBottomRight{mRect.right, mRect.bottom}; if (!IsWindowPointVisible(mWindow, pointBottomRight)) { return false; } return true; } void WindowsUIOverlayImage::AdvanceFrame() { if (!mOverlayWindow || mDisplayMode != DisplayMode::Animated || mFrames.empty()) { return; } if (mCurrentFrame + 1 >= mFrames.size()) { return; } ++mCurrentFrame; PaintOverlayFrame(mOverlayWindow, mMemDC, mSize, mDibBits, mFrames[mCurrentFrame]); } bool WindowsUIOverlayImage::Initialize() { RefPtr factory{CreateWICImagingFactory()}; if (!factory) { return false; } nsCOMPtr imageFile{GetImageFile()}; if (!imageFile) { return false; } RefPtr decoder{CreateWICBitmapDecoder(factory, imageFile)}; if (!decoder) { return false; } mozilla::Maybe frameCount{LoadFrameCount(decoder)}; if (!frameCount) { return false; } mozilla::Maybe size{GetImageSize(mElement, decoder)}; if (!size) { return false; } mSize = *size; if (!LoadFrames(mDisplayMode, factory, decoder, *frameCount, mSize, mFrames)) { return false; } RegisterWindowClass(); mMemDC.own(CreateOverlayMemoryDC()); mDib.own(CreateTopDown32bppDIB(mSize.cx, mSize.cy, &mDibBits)); if (!mMemDC || !mDib) { return false; } mOldBmp = SelectObject(mMemDC, mDib); auto rect{ComputeOverlayRect(mElement, mSize)}; if (!rect) { return false; } mRect = *rect; mOverlayWindow = CreateOverlayWindow(mRect); if (!mOverlayWindow) { return false; } mCurrentFrame = mDisplayMode == DisplayMode::Animated ? 0 : mFrames.size() - 1; PaintOverlayFrame(mOverlayWindow, mMemDC, mSize, mDibBits, mFrames[mCurrentFrame]); ShowWindow(mOverlayWindow, SW_SHOWNOACTIVATE); return true; } } // namespace mozilla