michael@0: /* vim: se cin sw=2 ts=2 et : */ michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "TaskbarPreview.h" michael@0: #include michael@0: #include michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "nsUXThemeData.h" michael@0: #include "nsWindow.h" michael@0: #include "nsAppShell.h" michael@0: #include "TaskbarPreviewButton.h" michael@0: #include "WinUtils.h" michael@0: #include "gfxWindowsPlatform.h" michael@0: michael@0: #include michael@0: #include michael@0: #include "mozilla/dom/CanvasRenderingContext2D.h" michael@0: #include michael@0: #include michael@0: michael@0: #include "mozilla/Telemetry.h" michael@0: michael@0: // Defined in dwmapi in a header that needs a higher numbered _WINNT #define michael@0: #define DWM_SIT_DISPLAYFRAME 0x1 michael@0: michael@0: namespace mozilla { michael@0: namespace widget { michael@0: michael@0: namespace { michael@0: michael@0: // Shared by all TaskbarPreviews to avoid the expensive creation process. michael@0: // Manually refcounted (see gInstCount) by the ctor and dtor of TaskbarPreview. michael@0: // This is done because static constructors aren't allowed for perf reasons. michael@0: dom::CanvasRenderingContext2D* gCtx = nullptr; michael@0: // Used in tracking the number of previews. Used in freeing michael@0: // the static 2d rendering context on shutdown. michael@0: uint32_t gInstCount = 0; michael@0: michael@0: /* Helper method to lazily create a canvas rendering context and associate a given michael@0: * surface with it. michael@0: * michael@0: * @param shell The docShell used by the canvas context for text settings and other michael@0: * misc things. michael@0: * @param surface The gfxSurface backing the context michael@0: * @param width The width of the given surface michael@0: * @param height The height of the given surface michael@0: */ michael@0: nsresult michael@0: GetRenderingContext(nsIDocShell *shell, gfxASurface *surface, michael@0: uint32_t width, uint32_t height) { michael@0: if (!gCtx) { michael@0: // create the canvas rendering context michael@0: Telemetry::Accumulate(Telemetry::CANVAS_2D_USED, 1); michael@0: gCtx = new mozilla::dom::CanvasRenderingContext2D(); michael@0: NS_ADDREF(gCtx); michael@0: } michael@0: michael@0: // Set the surface we'll use to render. michael@0: return gCtx->InitializeWithSurface(shell, surface, width, height); michael@0: } michael@0: michael@0: /* Helper method for freeing surface resources associated with the rendering context. michael@0: */ michael@0: void michael@0: ResetRenderingContext() { michael@0: if (!gCtx) michael@0: return; michael@0: michael@0: if (NS_FAILED(gCtx->Reset())) { michael@0: NS_RELEASE(gCtx); michael@0: gCtx = nullptr; michael@0: } michael@0: } michael@0: michael@0: } michael@0: michael@0: TaskbarPreview::TaskbarPreview(ITaskbarList4 *aTaskbar, nsITaskbarPreviewController *aController, HWND aHWND, nsIDocShell *aShell) michael@0: : mTaskbar(aTaskbar), michael@0: mController(aController), michael@0: mWnd(aHWND), michael@0: mVisible(false), michael@0: mDocShell(do_GetWeakReference(aShell)) michael@0: { michael@0: // TaskbarPreview may outlive the WinTaskbar that created it michael@0: ::CoInitialize(nullptr); michael@0: michael@0: gInstCount++; michael@0: michael@0: WindowHook &hook = GetWindowHook(); michael@0: hook.AddMonitor(WM_DESTROY, MainWindowHook, this); michael@0: } michael@0: michael@0: TaskbarPreview::~TaskbarPreview() { michael@0: // Avoid dangling pointer michael@0: if (sActivePreview == this) michael@0: sActivePreview = nullptr; michael@0: michael@0: // Our subclass should have invoked DetachFromNSWindow already. michael@0: NS_ASSERTION(!mWnd, "TaskbarPreview::DetachFromNSWindow was not called before destruction"); michael@0: michael@0: // Make sure to release before potentially uninitializing COM michael@0: mTaskbar = nullptr; michael@0: michael@0: if (--gInstCount == 0) michael@0: NS_IF_RELEASE(gCtx); michael@0: michael@0: ::CoUninitialize(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TaskbarPreview::SetController(nsITaskbarPreviewController *aController) { michael@0: NS_ENSURE_ARG(aController); michael@0: michael@0: mController = aController; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TaskbarPreview::GetController(nsITaskbarPreviewController **aController) { michael@0: NS_ADDREF(*aController = mController); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TaskbarPreview::GetTooltip(nsAString &aTooltip) { michael@0: aTooltip = mTooltip; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TaskbarPreview::SetTooltip(const nsAString &aTooltip) { michael@0: mTooltip = aTooltip; michael@0: return CanMakeTaskbarCalls() ? UpdateTooltip() : NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TaskbarPreview::SetVisible(bool visible) { michael@0: if (mVisible == visible) return NS_OK; michael@0: mVisible = visible; michael@0: michael@0: // If the nsWindow has already been destroyed but the caller is still trying michael@0: // to use it then just pretend that everything succeeded. The caller doesn't michael@0: // actually have a way to detect this since it's the same case as when we michael@0: // CanMakeTaskbarCalls returns false. michael@0: if (!mWnd) michael@0: return NS_OK; michael@0: michael@0: return visible ? Enable() : Disable(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TaskbarPreview::GetVisible(bool *visible) { michael@0: *visible = mVisible; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TaskbarPreview::SetActive(bool active) { michael@0: if (active) michael@0: sActivePreview = this; michael@0: else if (sActivePreview == this) michael@0: sActivePreview = nullptr; michael@0: michael@0: return CanMakeTaskbarCalls() ? ShowActive(active) : NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TaskbarPreview::GetActive(bool *active) { michael@0: *active = sActivePreview == this; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TaskbarPreview::Invalidate() { michael@0: if (!mVisible) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // DWM Composition is required for previews michael@0: if (!nsUXThemeData::CheckForCompositor()) michael@0: return NS_OK; michael@0: michael@0: HWND previewWindow = PreviewWindow(); michael@0: return FAILED(WinUtils::dwmInvalidateIconicBitmapsPtr(previewWindow)) michael@0: ? NS_ERROR_FAILURE michael@0: : NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: TaskbarPreview::UpdateTaskbarProperties() { michael@0: nsresult rv = UpdateTooltip(); michael@0: michael@0: // If we are the active preview and our window is the active window, restore michael@0: // our active state - otherwise some other non-preview window is now active michael@0: // and should be displayed as so. michael@0: if (sActivePreview == this) { michael@0: if (mWnd == ::GetActiveWindow()) { michael@0: nsresult rvActive = ShowActive(true); michael@0: if (NS_FAILED(rvActive)) michael@0: rv = rvActive; michael@0: } else { michael@0: sActivePreview = nullptr; michael@0: } michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: TaskbarPreview::Enable() { michael@0: nsresult rv = NS_OK; michael@0: if (CanMakeTaskbarCalls()) { michael@0: rv = UpdateTaskbarProperties(); michael@0: } else { michael@0: WindowHook &hook = GetWindowHook(); michael@0: hook.AddMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(), MainWindowHook, this); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: TaskbarPreview::Disable() { michael@0: WindowHook &hook = GetWindowHook(); michael@0: (void) hook.RemoveMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(), MainWindowHook, this); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: TaskbarPreview::IsWindowAvailable() const { michael@0: if (mWnd) { michael@0: nsWindow* win = WinUtils::GetNSWindowPtr(mWnd); michael@0: if(win && !win->Destroyed()) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: TaskbarPreview::DetachFromNSWindow() { michael@0: WindowHook &hook = GetWindowHook(); michael@0: hook.RemoveMonitor(WM_DESTROY, MainWindowHook, this); michael@0: mWnd = nullptr; michael@0: } michael@0: michael@0: LRESULT michael@0: TaskbarPreview::WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) { michael@0: switch (nMsg) { michael@0: case WM_DWMSENDICONICTHUMBNAIL: michael@0: { michael@0: uint32_t width = HIWORD(lParam); michael@0: uint32_t height = LOWORD(lParam); michael@0: float aspectRatio = width/float(height); michael@0: michael@0: nsresult rv; michael@0: float preferredAspectRatio; michael@0: rv = mController->GetThumbnailAspectRatio(&preferredAspectRatio); michael@0: if (NS_FAILED(rv)) michael@0: break; michael@0: michael@0: uint32_t thumbnailWidth = width; michael@0: uint32_t thumbnailHeight = height; michael@0: michael@0: if (aspectRatio > preferredAspectRatio) { michael@0: thumbnailWidth = uint32_t(thumbnailHeight * preferredAspectRatio); michael@0: } else { michael@0: thumbnailHeight = uint32_t(thumbnailWidth / preferredAspectRatio); michael@0: } michael@0: michael@0: DrawBitmap(thumbnailWidth, thumbnailHeight, false); michael@0: } michael@0: break; michael@0: case WM_DWMSENDICONICLIVEPREVIEWBITMAP: michael@0: { michael@0: uint32_t width, height; michael@0: nsresult rv; michael@0: rv = mController->GetWidth(&width); michael@0: if (NS_FAILED(rv)) michael@0: break; michael@0: rv = mController->GetHeight(&height); michael@0: if (NS_FAILED(rv)) michael@0: break; michael@0: michael@0: double scale = nsIWidget::DefaultScaleOverride(); michael@0: if (scale <= 0.0) michael@0: scale = gfxWindowsPlatform::GetPlatform()->GetDPIScale(); michael@0: michael@0: DrawBitmap(NSToIntRound(scale * width), NSToIntRound(scale * height), true); michael@0: } michael@0: break; michael@0: } michael@0: return ::DefWindowProcW(PreviewWindow(), nMsg, wParam, lParam); michael@0: } michael@0: michael@0: bool michael@0: TaskbarPreview::CanMakeTaskbarCalls() { michael@0: // If the nsWindow has already been destroyed and we know it but our caller michael@0: // clearly doesn't so we can't make any calls. michael@0: if (!mWnd) michael@0: return false; michael@0: // Certain functions like SetTabOrder seem to require a visible window. During michael@0: // window close, the window seems to be hidden before being destroyed. michael@0: if (!::IsWindowVisible(mWnd)) michael@0: return false; michael@0: if (mVisible) { michael@0: nsWindow *window = WinUtils::GetNSWindowPtr(mWnd); michael@0: NS_ASSERTION(window, "Could not get nsWindow from HWND"); michael@0: return window->HasTaskbarIconBeenCreated(); michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: WindowHook& michael@0: TaskbarPreview::GetWindowHook() { michael@0: nsWindow *window = WinUtils::GetNSWindowPtr(mWnd); michael@0: NS_ASSERTION(window, "Cannot use taskbar previews in an embedded context!"); michael@0: michael@0: return window->GetWindowHook(); michael@0: } michael@0: michael@0: void michael@0: TaskbarPreview::EnableCustomDrawing(HWND aHWND, bool aEnable) { michael@0: BOOL enabled = aEnable; michael@0: WinUtils::dwmSetWindowAttributePtr( michael@0: aHWND, michael@0: DWMWA_FORCE_ICONIC_REPRESENTATION, michael@0: &enabled, michael@0: sizeof(enabled)); michael@0: michael@0: WinUtils::dwmSetWindowAttributePtr( michael@0: aHWND, michael@0: DWMWA_HAS_ICONIC_BITMAP, michael@0: &enabled, michael@0: sizeof(enabled)); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: TaskbarPreview::UpdateTooltip() { michael@0: NS_ASSERTION(CanMakeTaskbarCalls() && mVisible, "UpdateTooltip called on invisible tab preview"); michael@0: michael@0: if (FAILED(mTaskbar->SetThumbnailTooltip(PreviewWindow(), mTooltip.get()))) michael@0: return NS_ERROR_FAILURE; michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: TaskbarPreview::DrawBitmap(uint32_t width, uint32_t height, bool isPreview) { michael@0: nsresult rv; michael@0: nsRefPtr surface = new gfxWindowsSurface(gfxIntSize(width, height), gfxImageFormat::ARGB32); michael@0: michael@0: nsCOMPtr shell = do_QueryReferent(mDocShell); michael@0: michael@0: if (!shell) michael@0: return; michael@0: michael@0: rv = GetRenderingContext(shell, surface, width, height); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: bool drawFrame = false; michael@0: if (isPreview) michael@0: rv = mController->DrawPreview(gCtx, &drawFrame); michael@0: else michael@0: rv = mController->DrawThumbnail(gCtx, width, height, &drawFrame); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: HDC hDC = surface->GetDC(); michael@0: HBITMAP hBitmap = (HBITMAP)GetCurrentObject(hDC, OBJ_BITMAP); michael@0: michael@0: DWORD flags = drawFrame ? DWM_SIT_DISPLAYFRAME : 0; michael@0: POINT pptClient = { 0, 0 }; michael@0: if (isPreview) michael@0: WinUtils::dwmSetIconicLivePreviewBitmapPtr(PreviewWindow(), hBitmap, &pptClient, flags); michael@0: else michael@0: WinUtils::dwmSetIconicThumbnailPtr(PreviewWindow(), hBitmap, flags); michael@0: michael@0: ResetRenderingContext(); michael@0: } michael@0: michael@0: /* static */ michael@0: bool michael@0: TaskbarPreview::MainWindowHook(void *aContext, michael@0: HWND hWnd, UINT nMsg, michael@0: WPARAM wParam, LPARAM lParam, michael@0: LRESULT *aResult) michael@0: { michael@0: NS_ASSERTION(nMsg == nsAppShell::GetTaskbarButtonCreatedMessage() || michael@0: nMsg == WM_DESTROY, michael@0: "Window hook proc called with wrong message"); michael@0: NS_ASSERTION(aContext, "Null context in MainWindowHook"); michael@0: if (!aContext) michael@0: return false; michael@0: TaskbarPreview *preview = reinterpret_cast(aContext); michael@0: if (nMsg == WM_DESTROY) { michael@0: // nsWindow is being destroyed michael@0: // We can't really do anything at this point including removing hooks michael@0: preview->mWnd = nullptr; michael@0: } else { michael@0: nsWindow *window = WinUtils::GetNSWindowPtr(preview->mWnd); michael@0: if (window) { michael@0: window->SetHasTaskbarIconBeenCreated(); michael@0: michael@0: if (preview->mVisible) michael@0: preview->UpdateTaskbarProperties(); michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: TaskbarPreview * michael@0: TaskbarPreview::sActivePreview = nullptr; michael@0: michael@0: } // namespace widget michael@0: } // namespace mozilla michael@0: