summaryrefslogtreecommitdiffstats
path: root/widget/windows/TaskbarTabPreview.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--widget/windows/TaskbarTabPreview.cpp345
1 files changed, 345 insertions, 0 deletions
diff --git a/widget/windows/TaskbarTabPreview.cpp b/widget/windows/TaskbarTabPreview.cpp
new file mode 100644
index 0000000000..a19a6bf12e
--- /dev/null
+++ b/widget/windows/TaskbarTabPreview.cpp
@@ -0,0 +1,345 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 "TaskbarTabPreview.h"
+#include "nsWindowGfx.h"
+#include "nsUXThemeData.h"
+#include "WinUtils.h"
+#include <nsITaskbarPreviewController.h>
+
+#define TASKBARPREVIEW_HWNDID L"TaskbarTabPreviewHwnd"
+
+namespace mozilla {
+namespace widget {
+
+NS_IMPL_ISUPPORTS(TaskbarTabPreview, nsITaskbarTabPreview)
+
+const wchar_t* const kWindowClass = L"MozillaTaskbarPreviewClass";
+
+TaskbarTabPreview::TaskbarTabPreview(ITaskbarList4* aTaskbar,
+ nsITaskbarPreviewController* aController,
+ HWND aHWND, nsIDocShell* aShell)
+ : TaskbarPreview(aTaskbar, aController, aHWND, aShell),
+ mProxyWindow(nullptr),
+ mIcon(nullptr),
+ mRegistered(false) {}
+
+TaskbarTabPreview::~TaskbarTabPreview() {
+ if (mIcon) {
+ ::DestroyIcon(mIcon);
+ mIcon = nullptr;
+ }
+
+ // We need to ensure that proxy window disappears or else Bad Things happen.
+ if (mProxyWindow) Disable();
+
+ NS_ASSERTION(!mProxyWindow, "Taskbar proxy window was not destroyed!");
+
+ if (IsWindowAvailable()) {
+ DetachFromNSWindow();
+ } else {
+ mWnd = nullptr;
+ }
+}
+
+nsresult TaskbarTabPreview::Init() {
+ nsresult rv = TaskbarPreview::Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ WindowHook* hook = GetWindowHook();
+ if (!hook) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return hook->AddMonitor(WM_WINDOWPOSCHANGED, MainWindowHook, this);
+}
+
+nsresult TaskbarTabPreview::ShowActive(bool active) {
+ NS_ASSERTION(mVisible && CanMakeTaskbarCalls(),
+ "ShowActive called on invisible window or before taskbar calls "
+ "can be made for this window");
+ return FAILED(
+ mTaskbar->SetTabActive(active ? mProxyWindow : nullptr, mWnd, 0))
+ ? NS_ERROR_FAILURE
+ : NS_OK;
+}
+
+HWND& TaskbarTabPreview::PreviewWindow() { return mProxyWindow; }
+
+nativeWindow TaskbarTabPreview::GetHWND() { return mProxyWindow; }
+
+void TaskbarTabPreview::EnsureRegistration() {
+ NS_ASSERTION(mVisible && CanMakeTaskbarCalls(),
+ "EnsureRegistration called when it is not safe to do so");
+
+ (void)UpdateTaskbarProperties();
+}
+
+NS_IMETHODIMP
+TaskbarTabPreview::GetTitle(nsAString& aTitle) {
+ aTitle = mTitle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarTabPreview::SetTitle(const nsAString& aTitle) {
+ mTitle = aTitle;
+ return mVisible ? UpdateTitle() : NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarTabPreview::SetIcon(imgIContainer* icon) {
+ HICON hIcon = nullptr;
+ if (icon) {
+ nsresult rv;
+ rv = nsWindowGfx::CreateIcon(
+ icon, false, LayoutDeviceIntPoint(),
+ nsWindowGfx::GetIconMetrics(nsWindowGfx::kSmallIcon), &hIcon);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mIcon) ::DestroyIcon(mIcon);
+ mIcon = hIcon;
+ mIconImage = icon;
+ return mVisible ? UpdateIcon() : NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarTabPreview::GetIcon(imgIContainer** icon) {
+ NS_IF_ADDREF(*icon = mIconImage);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarTabPreview::Move(nsITaskbarTabPreview* aNext) {
+ if (aNext == this) return NS_ERROR_INVALID_ARG;
+ mNext = aNext;
+ return CanMakeTaskbarCalls() ? UpdateNext() : NS_OK;
+}
+
+nsresult TaskbarTabPreview::UpdateTaskbarProperties() {
+ if (mRegistered) return NS_OK;
+
+ if (FAILED(mTaskbar->RegisterTab(mProxyWindow, mWnd)))
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = UpdateNext();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = TaskbarPreview::UpdateTaskbarProperties();
+ mRegistered = true;
+ return rv;
+}
+
+LRESULT
+TaskbarTabPreview::WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) {
+ RefPtr<TaskbarTabPreview> kungFuDeathGrip(this);
+ switch (nMsg) {
+ case WM_CREATE:
+ TaskbarPreview::EnableCustomDrawing(mProxyWindow, true);
+ return 0;
+ case WM_CLOSE:
+ mController->OnClose();
+ return 0;
+ case WM_ACTIVATE:
+ if (LOWORD(wParam) == WA_ACTIVE) {
+ // Activate the tab the user selected then restore the main window,
+ // keeping normal/max window state intact.
+ bool activateWindow;
+ nsresult rv = mController->OnActivate(&activateWindow);
+ if (NS_SUCCEEDED(rv) && activateWindow) {
+ nsWindow* win = WinUtils::GetNSWindowPtr(mWnd);
+ if (win) {
+ nsWindow* parent = win->GetTopLevelWindow(true);
+ if (parent) {
+ parent->Show(true);
+ }
+ }
+ }
+ }
+ return 0;
+ case WM_GETICON:
+ return (LRESULT)mIcon;
+ case WM_SYSCOMMAND:
+ // Send activation events to the top level window and select the proper
+ // tab through the controller.
+ if (wParam == SC_RESTORE || wParam == SC_MAXIMIZE) {
+ bool activateWindow;
+ nsresult rv = mController->OnActivate(&activateWindow);
+ if (NS_SUCCEEDED(rv) && activateWindow) {
+ // Note, restoring an iconic, maximized window here will only
+ // activate the maximized window. This is not a bug, it's default
+ // windows behavior.
+ ::SendMessageW(mWnd, WM_SYSCOMMAND, wParam, lParam);
+ }
+ return 0;
+ }
+ // Forward everything else to the top level window. Do not forward
+ // close since that's intended for the tab. When the preview proxy
+ // closes, we'll close the tab above.
+ return wParam == SC_CLOSE
+ ? ::DefWindowProcW(mProxyWindow, WM_SYSCOMMAND, wParam, lParam)
+ : ::SendMessageW(mWnd, WM_SYSCOMMAND, wParam, lParam);
+ return 0;
+ }
+ return TaskbarPreview::WndProc(nMsg, wParam, lParam);
+}
+
+/* static */
+LRESULT CALLBACK TaskbarTabPreview::GlobalWndProc(HWND hWnd, UINT nMsg,
+ WPARAM wParam,
+ LPARAM lParam) {
+ TaskbarTabPreview* preview(nullptr);
+ if (nMsg == WM_CREATE) {
+ CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lParam);
+ preview = reinterpret_cast<TaskbarTabPreview*>(cs->lpCreateParams);
+ if (!::SetPropW(hWnd, TASKBARPREVIEW_HWNDID, preview))
+ NS_ERROR("Could not associate native window with tab preview");
+ preview->mProxyWindow = hWnd;
+ } else {
+ preview = reinterpret_cast<TaskbarTabPreview*>(
+ ::GetPropW(hWnd, TASKBARPREVIEW_HWNDID));
+ if (nMsg == WM_DESTROY) ::RemovePropW(hWnd, TASKBARPREVIEW_HWNDID);
+ }
+
+ if (preview) return preview->WndProc(nMsg, wParam, lParam);
+ return ::DefWindowProcW(hWnd, nMsg, wParam, lParam);
+}
+
+nsresult TaskbarTabPreview::Enable() {
+ WNDCLASSW wc;
+ HINSTANCE module = GetModuleHandle(nullptr);
+
+ if (!GetClassInfoW(module, kWindowClass, &wc)) {
+ wc.style = 0;
+ wc.lpfnWndProc = GlobalWndProc;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ wc.hInstance = module;
+ wc.hIcon = nullptr;
+ wc.hCursor = nullptr;
+ wc.hbrBackground = (HBRUSH) nullptr;
+ wc.lpszMenuName = (LPCWSTR) nullptr;
+ wc.lpszClassName = kWindowClass;
+ RegisterClassW(&wc);
+ }
+ ::CreateWindowW(kWindowClass, L"TaskbarPreviewWindow",
+ WS_CAPTION | WS_SYSMENU, 0, 0, 200, 60, nullptr, nullptr,
+ module, this);
+ // GlobalWndProc will set mProxyWindow so that WM_CREATE can have a valid HWND
+ if (!mProxyWindow) return NS_ERROR_INVALID_ARG;
+
+ UpdateProxyWindowStyle();
+
+ nsresult rv = TaskbarPreview::Enable();
+ nsresult rvUpdate;
+ rvUpdate = UpdateTitle();
+ if (NS_FAILED(rvUpdate)) rv = rvUpdate;
+
+ rvUpdate = UpdateIcon();
+ if (NS_FAILED(rvUpdate)) rv = rvUpdate;
+
+ return rv;
+}
+
+nsresult TaskbarTabPreview::Disable() {
+ // TaskbarPreview::Disable assumes that mWnd is valid but this method can be
+ // called when it is null iff the nsWindow has already been destroyed and we
+ // are still visible for some reason during object destruction.
+ if (mWnd) TaskbarPreview::Disable();
+
+ if (FAILED(mTaskbar->UnregisterTab(mProxyWindow))) return NS_ERROR_FAILURE;
+ mRegistered = false;
+
+ // TaskbarPreview::WndProc will set mProxyWindow to null
+ if (!DestroyWindow(mProxyWindow)) return NS_ERROR_FAILURE;
+ mProxyWindow = nullptr;
+ return NS_OK;
+}
+
+void TaskbarTabPreview::DetachFromNSWindow() {
+ (void)SetVisible(false);
+ if (WindowHook* hook = GetWindowHook()) {
+ hook->RemoveMonitor(WM_WINDOWPOSCHANGED, MainWindowHook, this);
+ }
+ TaskbarPreview::DetachFromNSWindow();
+}
+
+/* static */
+bool TaskbarTabPreview::MainWindowHook(void* aContext, HWND hWnd, UINT nMsg,
+ WPARAM wParam, LPARAM lParam,
+ LRESULT* aResult) {
+ if (nMsg == WM_WINDOWPOSCHANGED) {
+ TaskbarTabPreview* preview = reinterpret_cast<TaskbarTabPreview*>(aContext);
+ WINDOWPOS* pos = reinterpret_cast<WINDOWPOS*>(lParam);
+ if (SWP_FRAMECHANGED == (pos->flags & SWP_FRAMECHANGED))
+ preview->UpdateProxyWindowStyle();
+ } else {
+ MOZ_ASSERT_UNREACHABLE(
+ "Style changed hook fired on non-style changed "
+ "message");
+ }
+ return false;
+}
+
+void TaskbarTabPreview::UpdateProxyWindowStyle() {
+ if (!mProxyWindow) return;
+
+ DWORD minMaxMask = WS_MINIMIZE | WS_MAXIMIZE;
+ DWORD windowStyle = GetWindowLongW(mWnd, GWL_STYLE);
+
+ DWORD proxyStyle = GetWindowLongW(mProxyWindow, GWL_STYLE);
+ proxyStyle &= ~minMaxMask;
+ proxyStyle |= windowStyle & minMaxMask;
+ SetWindowLongW(mProxyWindow, GWL_STYLE, proxyStyle);
+
+ DWORD exStyle =
+ (WS_MAXIMIZE == (windowStyle & WS_MAXIMIZE)) ? WS_EX_TOOLWINDOW : 0;
+ SetWindowLongW(mProxyWindow, GWL_EXSTYLE, exStyle);
+}
+
+nsresult TaskbarTabPreview::UpdateTitle() {
+ NS_ASSERTION(mVisible, "UpdateTitle called on invisible preview");
+
+ if (!::SetWindowTextW(mProxyWindow, mTitle.get())) return NS_ERROR_FAILURE;
+ return NS_OK;
+}
+
+nsresult TaskbarTabPreview::UpdateIcon() {
+ NS_ASSERTION(mVisible, "UpdateIcon called on invisible preview");
+
+ ::SendMessageW(mProxyWindow, WM_SETICON, ICON_SMALL, (LPARAM)mIcon);
+
+ return NS_OK;
+}
+
+nsresult TaskbarTabPreview::UpdateNext() {
+ NS_ASSERTION(CanMakeTaskbarCalls() && mVisible,
+ "UpdateNext called on invisible tab preview");
+ HWND hNext = nullptr;
+ if (mNext) {
+ bool visible;
+ nsresult rv = mNext->GetVisible(&visible);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Can only move next to enabled previews
+ if (!visible) return NS_ERROR_FAILURE;
+
+ hNext = (HWND)mNext->GetHWND();
+
+ // hNext must be registered with the taskbar if the call is to succeed
+ mNext->EnsureRegistration();
+ }
+ if (FAILED(mTaskbar->SetTabOrder(mProxyWindow, hNext)))
+ return NS_ERROR_FAILURE;
+ return NS_OK;
+}
+
+} // namespace widget
+} // namespace mozilla