1 /* vim: se cin sw=2 ts=2 et : */
2 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3  *
4  * This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 
8 #include "mozilla/ArrayUtils.h"
9 
10 #include <nsITaskbarPreviewController.h>
11 #include "TaskbarWindowPreview.h"
12 #include "WindowHook.h"
13 #include "nsUXThemeData.h"
14 #include "TaskbarPreviewButton.h"
15 #include "nsWindow.h"
16 #include "nsWindowGfx.h"
17 
18 namespace mozilla {
19 namespace widget {
20 
21 namespace {
WindowHookProc(void * aContext,HWND hWnd,UINT nMsg,WPARAM wParam,LPARAM lParam,LRESULT * aResult)22 bool WindowHookProc(void *aContext, HWND hWnd, UINT nMsg, WPARAM wParam,
23                     LPARAM lParam, LRESULT *aResult) {
24   TaskbarWindowPreview *preview =
25       reinterpret_cast<TaskbarWindowPreview *>(aContext);
26   *aResult = preview->WndProc(nMsg, wParam, lParam);
27   return true;
28 }
29 }  // namespace
30 
31 NS_IMPL_ISUPPORTS(TaskbarWindowPreview, nsITaskbarWindowPreview,
32                   nsITaskbarProgress, nsITaskbarOverlayIconController,
33                   nsISupportsWeakReference)
34 
35 /**
36  * These correspond directly to the states defined in nsITaskbarProgress.idl, so
37  * they should be kept in sync.
38  */
39 static TBPFLAG sNativeStates[] = {TBPF_NOPROGRESS, TBPF_INDETERMINATE,
40                                   TBPF_NORMAL, TBPF_ERROR, TBPF_PAUSED};
41 
TaskbarWindowPreview(ITaskbarList4 * aTaskbar,nsITaskbarPreviewController * aController,HWND aHWND,nsIDocShell * aShell)42 TaskbarWindowPreview::TaskbarWindowPreview(
43     ITaskbarList4 *aTaskbar, nsITaskbarPreviewController *aController,
44     HWND aHWND, nsIDocShell *aShell)
45     : TaskbarPreview(aTaskbar, aController, aHWND, aShell),
46       mCustomDrawing(false),
47       mHaveButtons(false),
48       mState(TBPF_NOPROGRESS),
49       mCurrentValue(0),
50       mMaxValue(0),
51       mOverlayIcon(nullptr) {
52   // Window previews are visible by default
53   (void)SetVisible(true);
54 
55   memset(mThumbButtons, 0, sizeof mThumbButtons);
56   for (int32_t i = 0; i < nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS; i++) {
57     mThumbButtons[i].dwMask = THB_FLAGS | THB_ICON | THB_TOOLTIP;
58     mThumbButtons[i].iId = i;
59     mThumbButtons[i].dwFlags = THBF_HIDDEN;
60   }
61 
62   WindowHook &hook = GetWindowHook();
63   if (!CanMakeTaskbarCalls())
64     hook.AddMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(),
65                     TaskbarWindowHook, this);
66 }
67 
~TaskbarWindowPreview()68 TaskbarWindowPreview::~TaskbarWindowPreview() {
69   if (mOverlayIcon) {
70     ::DestroyIcon(mOverlayIcon);
71     mOverlayIcon = nullptr;
72   }
73 
74   if (IsWindowAvailable()) {
75     DetachFromNSWindow();
76   } else {
77     mWnd = nullptr;
78   }
79 }
80 
ShowActive(bool active)81 nsresult TaskbarWindowPreview::ShowActive(bool active) {
82   return FAILED(mTaskbar->ActivateTab(active ? mWnd : nullptr))
83              ? NS_ERROR_FAILURE
84              : NS_OK;
85 }
86 
PreviewWindow()87 HWND &TaskbarWindowPreview::PreviewWindow() { return mWnd; }
88 
GetButton(uint32_t index,nsITaskbarPreviewButton ** _retVal)89 nsresult TaskbarWindowPreview::GetButton(uint32_t index,
90                                          nsITaskbarPreviewButton **_retVal) {
91   if (index >= nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS)
92     return NS_ERROR_INVALID_ARG;
93 
94   nsCOMPtr<nsITaskbarPreviewButton> button(
95       do_QueryReferent(mWeakButtons[index]));
96 
97   if (!button) {
98     // Lost reference
99     button = new TaskbarPreviewButton(this, index);
100     if (!button) {
101       return NS_ERROR_OUT_OF_MEMORY;
102     }
103     mWeakButtons[index] = do_GetWeakReference(button);
104   }
105 
106   if (!mHaveButtons) {
107     mHaveButtons = true;
108 
109     WindowHook &hook = GetWindowHook();
110     (void)hook.AddHook(WM_COMMAND, WindowHookProc, this);
111 
112     if (mVisible && FAILED(mTaskbar->ThumbBarAddButtons(
113                         mWnd, nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS,
114                         mThumbButtons))) {
115       return NS_ERROR_FAILURE;
116     }
117   }
118   button.forget(_retVal);
119   return NS_OK;
120 }
121 
122 NS_IMETHODIMP
SetEnableCustomDrawing(bool aEnable)123 TaskbarWindowPreview::SetEnableCustomDrawing(bool aEnable) {
124   if (aEnable == mCustomDrawing) return NS_OK;
125   mCustomDrawing = aEnable;
126   TaskbarPreview::EnableCustomDrawing(mWnd, aEnable);
127 
128   WindowHook &hook = GetWindowHook();
129   if (aEnable) {
130     (void)hook.AddHook(WM_DWMSENDICONICTHUMBNAIL, WindowHookProc, this);
131     (void)hook.AddHook(WM_DWMSENDICONICLIVEPREVIEWBITMAP, WindowHookProc, this);
132   } else {
133     (void)hook.RemoveHook(WM_DWMSENDICONICLIVEPREVIEWBITMAP, WindowHookProc,
134                           this);
135     (void)hook.RemoveHook(WM_DWMSENDICONICTHUMBNAIL, WindowHookProc, this);
136   }
137   return NS_OK;
138 }
139 
140 NS_IMETHODIMP
GetEnableCustomDrawing(bool * aEnable)141 TaskbarWindowPreview::GetEnableCustomDrawing(bool *aEnable) {
142   *aEnable = mCustomDrawing;
143   return NS_OK;
144 }
145 
146 NS_IMETHODIMP
SetProgressState(nsTaskbarProgressState aState,uint64_t aCurrentValue,uint64_t aMaxValue)147 TaskbarWindowPreview::SetProgressState(nsTaskbarProgressState aState,
148                                        uint64_t aCurrentValue,
149                                        uint64_t aMaxValue) {
150   NS_ENSURE_ARG_RANGE(aState, nsTaskbarProgressState(0),
151                       nsTaskbarProgressState(ArrayLength(sNativeStates) - 1));
152 
153   TBPFLAG nativeState = sNativeStates[aState];
154   if (nativeState == TBPF_NOPROGRESS || nativeState == TBPF_INDETERMINATE) {
155     NS_ENSURE_TRUE(aCurrentValue == 0, NS_ERROR_INVALID_ARG);
156     NS_ENSURE_TRUE(aMaxValue == 0, NS_ERROR_INVALID_ARG);
157   }
158 
159   if (aCurrentValue > aMaxValue) return NS_ERROR_ILLEGAL_VALUE;
160 
161   mState = nativeState;
162   mCurrentValue = aCurrentValue;
163   mMaxValue = aMaxValue;
164 
165   // Only update if we can
166   return CanMakeTaskbarCalls() ? UpdateTaskbarProgress() : NS_OK;
167 }
168 
169 NS_IMETHODIMP
SetOverlayIcon(imgIContainer * aStatusIcon,const nsAString & aStatusDescription)170 TaskbarWindowPreview::SetOverlayIcon(imgIContainer *aStatusIcon,
171                                      const nsAString &aStatusDescription) {
172   nsresult rv;
173   if (aStatusIcon) {
174     // The image shouldn't be animated
175     bool isAnimated;
176     rv = aStatusIcon->GetAnimated(&isAnimated);
177     NS_ENSURE_SUCCESS(rv, rv);
178     NS_ENSURE_FALSE(isAnimated, NS_ERROR_INVALID_ARG);
179   }
180 
181   HICON hIcon = nullptr;
182   if (aStatusIcon) {
183     rv = nsWindowGfx::CreateIcon(
184         aStatusIcon, false, 0, 0,
185         nsWindowGfx::GetIconMetrics(nsWindowGfx::kSmallIcon), &hIcon);
186     NS_ENSURE_SUCCESS(rv, rv);
187   }
188 
189   if (mOverlayIcon) ::DestroyIcon(mOverlayIcon);
190   mOverlayIcon = hIcon;
191   mIconDescription = aStatusDescription;
192 
193   // Only update if we can
194   return CanMakeTaskbarCalls() ? UpdateOverlayIcon() : NS_OK;
195 }
196 
UpdateTaskbarProperties()197 nsresult TaskbarWindowPreview::UpdateTaskbarProperties() {
198   if (mHaveButtons) {
199     if (FAILED(mTaskbar->ThumbBarAddButtons(
200             mWnd, nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS, mThumbButtons)))
201       return NS_ERROR_FAILURE;
202   }
203   nsresult rv = UpdateTaskbarProgress();
204   NS_ENSURE_SUCCESS(rv, rv);
205   rv = UpdateOverlayIcon();
206   NS_ENSURE_SUCCESS(rv, rv);
207   return TaskbarPreview::UpdateTaskbarProperties();
208 }
209 
UpdateTaskbarProgress()210 nsresult TaskbarWindowPreview::UpdateTaskbarProgress() {
211   HRESULT hr = mTaskbar->SetProgressState(mWnd, mState);
212   if (SUCCEEDED(hr) && mState != TBPF_NOPROGRESS &&
213       mState != TBPF_INDETERMINATE)
214     hr = mTaskbar->SetProgressValue(mWnd, mCurrentValue, mMaxValue);
215 
216   return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
217 }
218 
UpdateOverlayIcon()219 nsresult TaskbarWindowPreview::UpdateOverlayIcon() {
220   HRESULT hr =
221       mTaskbar->SetOverlayIcon(mWnd, mOverlayIcon, mIconDescription.get());
222   return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
223 }
224 
225 LRESULT
WndProc(UINT nMsg,WPARAM wParam,LPARAM lParam)226 TaskbarWindowPreview::WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) {
227   RefPtr<TaskbarWindowPreview> kungFuDeathGrip(this);
228   switch (nMsg) {
229     case WM_COMMAND: {
230       uint32_t id = LOWORD(wParam);
231       uint32_t index = id;
232       nsCOMPtr<nsITaskbarPreviewButton> button;
233       nsresult rv = GetButton(index, getter_AddRefs(button));
234       if (NS_SUCCEEDED(rv)) mController->OnClick(button);
235     }
236       return 0;
237   }
238   return TaskbarPreview::WndProc(nMsg, wParam, lParam);
239 }
240 
241 /* static */
TaskbarWindowHook(void * aContext,HWND hWnd,UINT nMsg,WPARAM wParam,LPARAM lParam,LRESULT * aResult)242 bool TaskbarWindowPreview::TaskbarWindowHook(void *aContext, HWND hWnd,
243                                              UINT nMsg, WPARAM wParam,
244                                              LPARAM lParam, LRESULT *aResult) {
245   NS_ASSERTION(nMsg == nsAppShell::GetTaskbarButtonCreatedMessage(),
246                "Window hook proc called with wrong message");
247   TaskbarWindowPreview *preview =
248       reinterpret_cast<TaskbarWindowPreview *>(aContext);
249   // Now we can make all the calls to mTaskbar
250   preview->UpdateTaskbarProperties();
251   return false;
252 }
253 
Enable()254 nsresult TaskbarWindowPreview::Enable() {
255   nsresult rv = TaskbarPreview::Enable();
256   NS_ENSURE_SUCCESS(rv, rv);
257 
258   return FAILED(mTaskbar->AddTab(mWnd)) ? NS_ERROR_FAILURE : NS_OK;
259 }
260 
Disable()261 nsresult TaskbarWindowPreview::Disable() {
262   nsresult rv = TaskbarPreview::Disable();
263   NS_ENSURE_SUCCESS(rv, rv);
264 
265   return FAILED(mTaskbar->DeleteTab(mWnd)) ? NS_ERROR_FAILURE : NS_OK;
266 }
267 
DetachFromNSWindow()268 void TaskbarWindowPreview::DetachFromNSWindow() {
269   // Remove the hooks we have for drawing
270   SetEnableCustomDrawing(false);
271 
272   WindowHook &hook = GetWindowHook();
273   (void)hook.RemoveHook(WM_COMMAND, WindowHookProc, this);
274   (void)hook.RemoveMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(),
275                            TaskbarWindowHook, this);
276 
277   TaskbarPreview::DetachFromNSWindow();
278 }
279 
UpdateButtons()280 nsresult TaskbarWindowPreview::UpdateButtons() {
281   NS_ASSERTION(mVisible, "UpdateButtons called on invisible preview");
282 
283   if (FAILED(mTaskbar->ThumbBarUpdateButtons(
284           mWnd, nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS, mThumbButtons)))
285     return NS_ERROR_FAILURE;
286   return NS_OK;
287 }
288 
UpdateButton(uint32_t index)289 nsresult TaskbarWindowPreview::UpdateButton(uint32_t index) {
290   if (index >= nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS)
291     return NS_ERROR_INVALID_ARG;
292   if (mVisible) {
293     if (FAILED(mTaskbar->ThumbBarUpdateButtons(mWnd, 1, &mThumbButtons[index])))
294       return NS_ERROR_FAILURE;
295   }
296   return NS_OK;
297 }
298 
299 }  // namespace widget
300 }  // namespace mozilla
301