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 
~TaskbarWindowPreview()63 TaskbarWindowPreview::~TaskbarWindowPreview() {
64   if (mOverlayIcon) {
65     ::DestroyIcon(mOverlayIcon);
66     mOverlayIcon = nullptr;
67   }
68 
69   // We need to clean up a hook associated with the "this" pointer.
70   SetVisible(false);
71 
72   if (IsWindowAvailable()) {
73     DetachFromNSWindow();
74   } else {
75     mWnd = nullptr;
76   }
77 }
78 
Init()79 nsresult TaskbarWindowPreview::Init() {
80   nsresult rv = TaskbarPreview::Init();
81   if (NS_FAILED(rv)) {
82     return rv;
83   }
84 
85   if (CanMakeTaskbarCalls()) {
86     return NS_OK;
87   }
88 
89   WindowHook* hook = GetWindowHook();
90   if (!hook) {
91     return NS_ERROR_NOT_AVAILABLE;
92   }
93 
94   return hook->AddMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(),
95                           TaskbarWindowHook, this);
96 }
97 
ShowActive(bool active)98 nsresult TaskbarWindowPreview::ShowActive(bool active) {
99   return FAILED(mTaskbar->ActivateTab(active ? mWnd : nullptr))
100              ? NS_ERROR_FAILURE
101              : NS_OK;
102 }
103 
PreviewWindow()104 HWND& TaskbarWindowPreview::PreviewWindow() { return mWnd; }
105 
GetButton(uint32_t index,nsITaskbarPreviewButton ** _retVal)106 nsresult TaskbarWindowPreview::GetButton(uint32_t index,
107                                          nsITaskbarPreviewButton** _retVal) {
108   if (index >= nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS)
109     return NS_ERROR_INVALID_ARG;
110 
111   nsCOMPtr<nsITaskbarPreviewButton> button(
112       do_QueryReferent(mWeakButtons[index]));
113 
114   if (!button) {
115     // Lost reference
116     button = new TaskbarPreviewButton(this, index);
117     if (!button) {
118       return NS_ERROR_OUT_OF_MEMORY;
119     }
120     mWeakButtons[index] = do_GetWeakReference(button);
121   }
122 
123   if (!mHaveButtons) {
124     mHaveButtons = true;
125 
126     WindowHook* hook = GetWindowHook();
127     if (!hook) {
128       return NS_ERROR_NOT_AVAILABLE;
129     }
130     (void)hook->AddHook(WM_COMMAND, WindowHookProc, this);
131 
132     if (mVisible && FAILED(mTaskbar->ThumbBarAddButtons(
133                         mWnd, nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS,
134                         mThumbButtons))) {
135       return NS_ERROR_FAILURE;
136     }
137   }
138   button.forget(_retVal);
139   return NS_OK;
140 }
141 
142 NS_IMETHODIMP
SetEnableCustomDrawing(bool aEnable)143 TaskbarWindowPreview::SetEnableCustomDrawing(bool aEnable) {
144   if (aEnable == mCustomDrawing) return NS_OK;
145 
146   WindowHook* hook = GetWindowHook();
147   if (!hook) {
148     return NS_ERROR_NOT_AVAILABLE;
149   }
150 
151   mCustomDrawing = aEnable;
152   TaskbarPreview::EnableCustomDrawing(mWnd, aEnable);
153 
154   if (aEnable) {
155     (void)hook->AddHook(WM_DWMSENDICONICTHUMBNAIL, WindowHookProc, this);
156     (void)hook->AddHook(WM_DWMSENDICONICLIVEPREVIEWBITMAP, WindowHookProc,
157                         this);
158   } else {
159     (void)hook->RemoveHook(WM_DWMSENDICONICLIVEPREVIEWBITMAP, WindowHookProc,
160                            this);
161     (void)hook->RemoveHook(WM_DWMSENDICONICTHUMBNAIL, WindowHookProc, this);
162   }
163   return NS_OK;
164 }
165 
166 NS_IMETHODIMP
GetEnableCustomDrawing(bool * aEnable)167 TaskbarWindowPreview::GetEnableCustomDrawing(bool* aEnable) {
168   *aEnable = mCustomDrawing;
169   return NS_OK;
170 }
171 
172 NS_IMETHODIMP
SetProgressState(nsTaskbarProgressState aState,uint64_t aCurrentValue,uint64_t aMaxValue)173 TaskbarWindowPreview::SetProgressState(nsTaskbarProgressState aState,
174                                        uint64_t aCurrentValue,
175                                        uint64_t aMaxValue) {
176   NS_ENSURE_ARG_RANGE(aState, nsTaskbarProgressState(0),
177                       nsTaskbarProgressState(ArrayLength(sNativeStates) - 1));
178 
179   TBPFLAG nativeState = sNativeStates[aState];
180   if (nativeState == TBPF_NOPROGRESS || nativeState == TBPF_INDETERMINATE) {
181     NS_ENSURE_TRUE(aCurrentValue == 0, NS_ERROR_INVALID_ARG);
182     NS_ENSURE_TRUE(aMaxValue == 0, NS_ERROR_INVALID_ARG);
183   }
184 
185   if (aCurrentValue > aMaxValue) return NS_ERROR_ILLEGAL_VALUE;
186 
187   mState = nativeState;
188   mCurrentValue = aCurrentValue;
189   mMaxValue = aMaxValue;
190 
191   // Only update if we can
192   return CanMakeTaskbarCalls() ? UpdateTaskbarProgress() : NS_OK;
193 }
194 
195 NS_IMETHODIMP
SetOverlayIcon(imgIContainer * aStatusIcon,const nsAString & aStatusDescription)196 TaskbarWindowPreview::SetOverlayIcon(imgIContainer* aStatusIcon,
197                                      const nsAString& aStatusDescription) {
198   nsresult rv;
199   if (aStatusIcon) {
200     // The image shouldn't be animated
201     bool isAnimated;
202     rv = aStatusIcon->GetAnimated(&isAnimated);
203     NS_ENSURE_SUCCESS(rv, rv);
204     NS_ENSURE_FALSE(isAnimated, NS_ERROR_INVALID_ARG);
205   }
206 
207   HICON hIcon = nullptr;
208   if (aStatusIcon) {
209     rv = nsWindowGfx::CreateIcon(
210         aStatusIcon, false, LayoutDeviceIntPoint(),
211         nsWindowGfx::GetIconMetrics(nsWindowGfx::kSmallIcon), &hIcon);
212     NS_ENSURE_SUCCESS(rv, rv);
213   }
214 
215   if (mOverlayIcon) ::DestroyIcon(mOverlayIcon);
216   mOverlayIcon = hIcon;
217   mIconDescription = aStatusDescription;
218 
219   // Only update if we can
220   return CanMakeTaskbarCalls() ? UpdateOverlayIcon() : NS_OK;
221 }
222 
UpdateTaskbarProperties()223 nsresult TaskbarWindowPreview::UpdateTaskbarProperties() {
224   if (mHaveButtons) {
225     if (FAILED(mTaskbar->ThumbBarAddButtons(
226             mWnd, nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS, mThumbButtons)))
227       return NS_ERROR_FAILURE;
228   }
229   nsresult rv = UpdateTaskbarProgress();
230   NS_ENSURE_SUCCESS(rv, rv);
231   rv = UpdateOverlayIcon();
232   NS_ENSURE_SUCCESS(rv, rv);
233   return TaskbarPreview::UpdateTaskbarProperties();
234 }
235 
UpdateTaskbarProgress()236 nsresult TaskbarWindowPreview::UpdateTaskbarProgress() {
237   HRESULT hr = mTaskbar->SetProgressState(mWnd, mState);
238   if (SUCCEEDED(hr) && mState != TBPF_NOPROGRESS &&
239       mState != TBPF_INDETERMINATE)
240     hr = mTaskbar->SetProgressValue(mWnd, mCurrentValue, mMaxValue);
241 
242   return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
243 }
244 
UpdateOverlayIcon()245 nsresult TaskbarWindowPreview::UpdateOverlayIcon() {
246   HRESULT hr =
247       mTaskbar->SetOverlayIcon(mWnd, mOverlayIcon, mIconDescription.get());
248   return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
249 }
250 
251 LRESULT
WndProc(UINT nMsg,WPARAM wParam,LPARAM lParam)252 TaskbarWindowPreview::WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) {
253   RefPtr<TaskbarWindowPreview> kungFuDeathGrip(this);
254   switch (nMsg) {
255     case WM_COMMAND: {
256       uint32_t id = LOWORD(wParam);
257       uint32_t index = id;
258       nsCOMPtr<nsITaskbarPreviewButton> button;
259       nsresult rv = GetButton(index, getter_AddRefs(button));
260       if (NS_SUCCEEDED(rv)) mController->OnClick(button);
261     }
262       return 0;
263   }
264   return TaskbarPreview::WndProc(nMsg, wParam, lParam);
265 }
266 
267 /* static */
TaskbarWindowHook(void * aContext,HWND hWnd,UINT nMsg,WPARAM wParam,LPARAM lParam,LRESULT * aResult)268 bool TaskbarWindowPreview::TaskbarWindowHook(void* aContext, HWND hWnd,
269                                              UINT nMsg, WPARAM wParam,
270                                              LPARAM lParam, LRESULT* aResult) {
271   NS_ASSERTION(nMsg == nsAppShell::GetTaskbarButtonCreatedMessage(),
272                "Window hook proc called with wrong message");
273   TaskbarWindowPreview* preview =
274       reinterpret_cast<TaskbarWindowPreview*>(aContext);
275   // Now we can make all the calls to mTaskbar
276   preview->UpdateTaskbarProperties();
277   return false;
278 }
279 
Enable()280 nsresult TaskbarWindowPreview::Enable() {
281   nsresult rv = TaskbarPreview::Enable();
282   NS_ENSURE_SUCCESS(rv, rv);
283 
284   return FAILED(mTaskbar->AddTab(mWnd)) ? NS_ERROR_FAILURE : NS_OK;
285 }
286 
Disable()287 nsresult TaskbarWindowPreview::Disable() {
288   nsresult rv = TaskbarPreview::Disable();
289   NS_ENSURE_SUCCESS(rv, rv);
290 
291   return FAILED(mTaskbar->DeleteTab(mWnd)) ? NS_ERROR_FAILURE : NS_OK;
292 }
293 
DetachFromNSWindow()294 void TaskbarWindowPreview::DetachFromNSWindow() {
295   // Remove the hooks we have for drawing
296   SetEnableCustomDrawing(false);
297 
298   if (WindowHook* hook = GetWindowHook()) {
299     (void)hook->RemoveHook(WM_COMMAND, WindowHookProc, this);
300     (void)hook->RemoveMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(),
301                               TaskbarWindowHook, this);
302   }
303   TaskbarPreview::DetachFromNSWindow();
304 }
305 
UpdateButtons()306 nsresult TaskbarWindowPreview::UpdateButtons() {
307   NS_ASSERTION(mVisible, "UpdateButtons called on invisible preview");
308 
309   if (FAILED(mTaskbar->ThumbBarUpdateButtons(
310           mWnd, nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS, mThumbButtons)))
311     return NS_ERROR_FAILURE;
312   return NS_OK;
313 }
314 
UpdateButton(uint32_t index)315 nsresult TaskbarWindowPreview::UpdateButton(uint32_t index) {
316   if (index >= nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS)
317     return NS_ERROR_INVALID_ARG;
318   if (mVisible) {
319     if (FAILED(mTaskbar->ThumbBarUpdateButtons(mWnd, 1, &mThumbButtons[index])))
320       return NS_ERROR_FAILURE;
321   }
322   return NS_OK;
323 }
324 
325 }  // namespace widget
326 }  // namespace mozilla
327