1{%MainUnit win32wsextctrls.pp}
2{ $Id: win32trayicon.inc 11994 2007-09-10 22:30:15Z marc $ }
3{******************************************************************************
4                 Implementation of TWin32WSCustomTrayIcon
5
6 *****************************************************************************
7  This file is part of the Lazarus Component Library (LCL)
8
9  See the file COPYING.modifiedLGPL.txt, included in this distribution,
10  for details about the license.
11 *****************************************************************************
12}
13
14{ TWin32WSCustomTrayIcon }
15
16type
17  // IE 5+ version of TNotifyIconDataW
18  TNotifyIconDataW2 = record
19    cbSize: DWORD;
20    hWnd: HWND;
21    uID: UINT;
22    uFlags: UINT;
23    uCallbackMessage: UINT;
24    hIcon: HICON;
25    szTip: array [0..127] of WideChar;
26    dwState: DWORD;
27    dwStateMask: DWORD;
28    szInfo: array [0..255] of WideChar;
29    u: record
30         case longint of
31           0 : ( uTimeout : UINT );
32           1 : ( uVersion : UINT );
33          end;
34    szInfoTitle: array[0..63] of WideChar;
35    dwInfoFlags: DWORD;
36  end;
37
38const
39  szClassName = 'TTrayIconClass';
40  szAppTitle = 'apptitle';
41  uIDTrayIcon = 25;
42
43var
44  msgTaskbarRestart: DWord = DWord(-1);
45
46{*******************************************************************
47*  TrayWndProc ()
48*
49*  DESCRIPTION:    Window procedure that processes messages for the
50*                 systray icon
51*
52*  PARAMETERS:     Standard Mouse Messages have this parameters:
53*
54*                  fwKeys = wParam;        // key flags
55*                  xPos = LOWORD(lParam);  // horizontal position of cursor
56*                  yPos = HIWORD(lParam);  // vertical position of cursor
57*                                          //* Those positions seam to be wrong
58*                                          // Use Mouse.CursorPos instead
59*
60*  RETURNS:        A pointer to the newly created object
61*
62*******************************************************************}
63function TrayWndProc(Handle: HWND; iMsg: UINT; WParam_: WPARAM; LParam_:LPARAM):LRESULT; stdcall;
64var
65  pt: TPoint;
66  vwsTrayIcon: TCustomTrayIcon;
67begin
68  if iMsg = WM_USER + uIDTrayIcon then
69  begin
70    vwsTrayIcon := TCustomTrayIcon(PtrUInt(GetWindowLong(Handle, GWL_USERDATA)));
71    case LParam_ of
72      WM_RBUTTONUP:
73      begin
74        pt := Mouse.CursorPos;
75        if Assigned(vwsTrayIcon.OnMouseUp) then
76          vwsTrayIcon.OnMouseUp(Application, mbRight, KeysToShiftState(WParam_), pt.x, pt.y);
77        if Assigned(vwsTrayIcon.PopUpMenu) then
78        begin
79          // Apparently SetForegroundWindow and PostMessage are necessary
80          // because we're invoking the shortcut menu from a notification icon
81          // This is an attempt to prevent from messing with the Z-order
82          SetForegroundWindow(Handle);
83          PostMessage(Handle, WM_NULL, 0, 0);
84          vwsTrayIcon.PopUpMenu.Popup(pt.x, pt.y);
85        end;
86      end;
87      WM_RBUTTONDOWN:
88        if Assigned(vwsTrayIcon.OnMouseDown) then
89        begin
90          pt := Mouse.CursorPos;
91          vwsTrayIcon.OnMouseDown(Application, mbRight, KeysToShiftState(WParam_), pt.x, pt.y);
92        end;
93      WM_RBUTTONDBLCLK:
94        if Assigned(vwsTrayIcon.OnDblClick) then
95          vwsTrayIcon.OnDblClick(Application);
96      WM_MBUTTONDOWN:
97        if Assigned(vwsTrayIcon.OnMouseDown) then
98        begin
99          pt := Mouse.CursorPos;
100          vwsTrayIcon.OnMouseDown(Application, mbMiddle, KeysToShiftState(WParam_), pt.x, pt.y);
101        end;
102      WM_MBUTTONUP:
103        if Assigned(vwsTrayIcon.OnMouseUp) then
104        begin
105          pt := Mouse.CursorPos;
106          vwsTrayIcon.OnMouseUp(Application, mbMiddle, KeysToShiftState(WParam_), pt.x, pt.y);
107        end;
108      WM_LBUTTONUP:
109      begin
110        pt := Mouse.CursorPos;
111        if Assigned(vwsTrayIcon.OnMouseUp) then
112          vwsTrayIcon.OnMouseUp(Application, mbLeft, KeysToShiftState(WParam_), pt.x, pt.y);
113        if Assigned(vwsTrayIcon.OnClick) then
114          vwsTrayIcon.OnClick(Application);
115      end;
116      WM_LBUTTONDOWN:
117        if Assigned(vwsTrayIcon.OnMouseDown) then
118        begin
119          pt := Mouse.CursorPos;
120          vwsTrayIcon.OnMouseDown(Application, mbLeft, KeysToShiftState(WParam_), pt.x, pt.y);
121        end;
122      WM_LBUTTONDBLCLK:
123        if Assigned(vwsTrayIcon.OnDblClick) then
124          vwsTrayIcon.OnDblClick(Application);
125      WM_MOUSEMOVE:
126        if Assigned(vwsTrayIcon.OnMouseMove) then
127        begin
128          pt := Mouse.CursorPos;
129          vwsTrayIcon.OnMouseMove(Application, KeysToShiftState(WParam_), pt.x, pt.y);
130        end;
131    end;
132
133    Result := 1;
134    Exit;
135  end
136  else
137  if iMsg = WM_CREATE then
138  begin
139    msgTaskbarRestart := RegisterWindowMessage('TaskbarCreated');
140    SetWindowLong(Handle, GWL_USERDATA, PtrInt(PCREATESTRUCT(LParam_)^.lpCreateParams));
141  end
142  else
143  if (iMsg = msgTaskbarRestart) then
144  begin
145    // add taskbar icon
146    vwsTrayIcon := TCustomTrayIcon(PtrUInt(GetWindowLong(Handle, GWL_USERDATA)));
147    if Assigned(vwsTrayIcon) then
148      TWin32WSCustomTrayIcon.AddIcon(vwsTrayIcon);
149  end;
150
151  Result := DefWindowProc(Handle, iMsg, WParam_, LParam_);
152end;
153
154{ TWin32WSCustomTrayIcon }
155
156class function TWin32WSCustomTrayIcon.AddIcon(ATrayIcon: TCustomTrayIcon): Boolean;
157var
158  tnidw: TNotifyIconDataW2;
159  WideBuffer: widestring;
160begin
161  // Fill TNotifyIconDataW
162  FillChar(tnidw, SizeOf(tnidw), 0);
163  tnidw.cbSize := SizeOf(tnidw);
164  tnidw.hWnd := ATrayIcon.Handle;
165  tnidw.uID := uIDTrayIcon;
166  tnidw.uFlags := NIF_MESSAGE or NIF_ICON;
167  if (ATrayIcon.Hint <> '') then tnidw.uFlags := tnidw.uFlags or NIF_TIP;
168  tnidw.uCallbackMessage := WM_USER + uIDTrayIcon;
169  tnidw.hIcon := ATrayIcon.Icon.Handle;
170
171  WideBuffer := UTF8ToUTF16(ATrayIcon.Hint);
172  WideStrLCopy(@tnidw.szTip, PWideChar(WideBuffer), 127);
173
174  Result := Shell_NotifyIconW(NIM_ADD, @tnidw);
175  if not Result then
176  begin
177    // Try old version of TNotifyIconDataW
178    tnidw.cbSize := SizeOf(TNotifyIconDataW);
179    WideStrLCopy(@tnidw.szTip, PWideChar(WideBuffer), 63);
180    Result := Shell_NotifyIconW(NIM_MODIFY, @tnidw);
181  end;
182end;
183
184{*******************************************************************
185*  TWin32WSCustomTrayIcon.Hide ()
186*
187*  DESCRIPTION:    Hides the main tray icon of the program
188*
189*  PARAMETERS:     None
190*
191*  RETURNS:        True if sucessfull, otherwise False
192*
193*******************************************************************}
194
195class function TWin32WSCustomTrayIcon.Hide(const ATrayIcon: TCustomTrayIcon): Boolean;
196var
197  tnid: TNotifyIconData;
198begin
199  // Fill TNotifyIconData
200  FillChar(tnid, SizeOf(tnid), 0);
201  tnid.cbSize := SizeOf(TNotifyIconData);
202  tnid.hWnd := ATrayIcon.Handle;
203  tnid.uID := uIDTrayIcon;
204
205  // Remove the icon
206  Result := Shell_NotifyIconA(NIM_DELETE, @tnid);
207
208  // Destroys the helper Windows
209  SendMessage(ATrayIcon.Handle, WM_CLOSE, 0, 0);
210  SendMessage(ATrayIcon.Handle, WM_DESTROY, 0, 0);
211end;
212
213{*******************************************************************
214*  TWin32WSCustomTrayIcon.Show ()
215*
216*  DESCRIPTION:    Shows the main tray icon of the program
217*
218*  PARAMETERS:     None
219*
220*  RETURNS:        True if sucessfull, otherwise False
221*
222*******************************************************************}
223class function TWin32WSCustomTrayIcon.Show(const ATrayIcon: TCustomTrayIcon): Boolean;
224var
225  Window: Windows.TWndClassEx;
226begin
227  if not GetClassInfo(hInstance, szClassName, @Window) then
228  begin
229    ZeroMemory(@Window, SizeOf(TWndClassEx));
230    Window.cbSize := SizeOf(TWndClassEx);
231    Window.style := CS_OWNDC;
232    Window.lpfnWndProc := @TrayWndProc;
233    Window.cbClsExtra := 0;
234    Window.cbWndExtra := 0;
235    Window.hInstance := hInstance;
236    Window.hCursor := Windows.LoadCursor(0, IDC_ARROW);
237    Window.hbrBackground := HBRUSH(GetStockObject(NULL_BRUSH));
238    Window.lpszMenuName := nil;
239    Window.lpszClassName := szClassName;
240    Windows.RegisterClassEx(Window);
241  end;
242
243  ATrayIcon.Handle := CreateWindowEx(
244        0,            //* Ensure that there will be no button in the bar */
245        szClassName,        //* Name of the registered class */
246        szAppTitle,         //* Title of the window */
247        0,                  //* Style of the window */
248        0,                  //* x-position (at beginning) */
249        0,                  //* y-position (at beginning) */
250        CW_USEDEFAULT,      //* window width */
251        CW_USEDEFAULT,      //* window height */
252        0,                  //* handle to parent or owner window */
253        0,                  //* handle to menu */
254        hInstance,          //* handle to application instance */
255        ATrayIcon);               //* pointer to window-creation data */
256
257  Result := AddIcon(ATrayIcon);
258end;
259
260{*******************************************************************
261*  TWin32WSCustomTrayIcon.InternalUpdate ()
262*
263*  DESCRIPTION:    Makes modifications to the Icon while running
264*                  i.e. without hiding it and showing again
265*
266*******************************************************************}
267class procedure TWin32WSCustomTrayIcon.InternalUpdate(const ATrayIcon: TCustomTrayIcon);
268var
269  tnidw: TNotifyIconDataW2;
270  WideBuffer: widestring;
271begin
272  // Fill TNotifyIconDataW
273  FillChar(tnidw, SizeOf(tnidw), 0);
274  tnidw.cbSize := SizeOf(tnidw);
275  tnidw.hWnd := ATrayIcon.Handle;
276  tnidw.uID := uIDTrayIcon;
277  tnidw.hIcon := ATrayIcon.Icon.Handle;
278  tnidw.uFlags := NIF_TIP or NIF_ICON;
279
280  WideBuffer := UTF8ToUTF16(ATrayIcon.Hint);
281  WideStrLCopy(@tnidw.szTip, PWideChar(WideBuffer), 127);
282
283  if not Shell_NotifyIconW(NIM_MODIFY, @tnidw) then
284  begin
285    // Try old version of TNotifyIconDataW
286    tnidw.cbSize := SizeOf(TNotifyIconDataW);
287    WideStrLCopy(@tnidw.szTip, PWideChar(WideBuffer), 63);
288    Shell_NotifyIconW(NIM_MODIFY, @tnidw);
289  end;
290end;
291
292{*******************************************************************
293*  TWin32WSCustomTrayIcon.ShowBalloonHint ()
294*
295*  DESCRIPTION:    Shows a small message balloon near the tray icon
296*
297*  RETURNS:        False if the default cross-platform hint should be used
298*                  True if a platform-specific hint will be used
299*
300*******************************************************************}
301class function TWin32WSCustomTrayIcon.ShowBalloonHint(const ATrayIcon: TCustomTrayIcon): Boolean;
302const
303  FlagsMap: array[TBalloonFlags] of dword = (NIIF_NONE, NIIF_INFO, NIIF_WARNING, NIIF_ERROR);
304var
305  NotifyData: TNotifyIconDataW2;
306  w: WideString;
307begin
308  NotifyData.cbSize:=SizeOf(NotifyData);
309  NotifyData.hWnd := ATrayIcon.Handle;
310  NotifyData.uID := uIDTrayIcon;
311  NotifyData.uFlags:=NIF_INFO;
312  NotifyData.u.uTimeout:=ATrayIcon.BalloonTimeout;
313  w:=UTF8ToUTF16(ATrayIcon.BalloonHint);
314  WideStrLCopy(@NotifyData.szInfo, PWideChar(w), High(NotifyData.szInfo));
315  w:=UTF8ToUTF16(ATrayIcon.BalloonTitle);
316  WideStrLCopy(@NotifyData.szInfoTitle, PWideChar(w), High(NotifyData.szInfoTitle));
317  NotifyData.dwInfoFlags:=FlagsMap[ATrayIcon.BalloonFlags];
318
319  Result:= Shell_NotifyIconW(NIM_MODIFY, @NotifyData);
320end;
321
322{*******************************************************************
323*  TWin32WSCustomTrayIcon.GetPosition ()
324*
325*  DESCRIPTION:    Returns the position of the tray icon on the display.
326*                  This function is utilized to show message boxes near
327*                  the icon
328*
329*******************************************************************}
330function EnumChildProc(handle: HWND; lp: LParam): LongBool; stdcall;
331begin
332  if Pos('ToolbarWindow32', WndClassName(handle)) > 0 then
333  begin
334    LParam(Pointer(lp)^) := handle;
335    Result := False;
336  end
337  else
338    Result := True;
339end;
340
341class function TWin32WSCustomTrayIcon.GetPosition(const ATrayIcon: TCustomTrayIcon): TPoint;
342var
343  hWndTaskbar, hWndTray: HWND;
344  TaskbarRect, TrayRect: TRect;
345  TaskbarMonitor: TMonitor;
346begin
347  Result := Point(0, 0);
348
349  { First we get the Taskbar window and it's screen position }
350  hWndTaskbar := FindWindow('Shell_TrayWnd', nil);
351
352  if hWndTaskbar = 0 then Exit;
353
354  Windows.GetWindowRect(hWndTaskbar, @TaskbarRect);
355
356  hWndTray := ATrayIcon.Handle;
357
358  { Then we locate inside the Tray area, which is just a Toolbar control }
359  EnumChildWindows(hWndTaskbar, @EnumChildProc, LParam(@hWndTray));
360
361  if hWndTray = 0 then Exit;
362
363  { And we get the size of that control }
364  Windows.GetWindowRect(hWndTray, @TrayRect);
365  // OBS: Here TrayRect seams to have a wrong value, so we don't use it
366
367  { We need this in order to "normalize" in later if statements. This is required to get right coordinates on multiple monitors }
368  TaskbarMonitor := Screen.MonitorFromWindow(hWndTaskbar);
369
370  { Returns an aproximate position of the tray area }
371  if (TaskbarRect.Top - TaskbarMonitor.Top = 0) and (TaskbarRect.Left - TaskbarMonitor.Left = 0) then
372  begin
373    { Taskbar is at the top of the monitor area OR on the left side of the monitor area }
374    Result.X := TaskbarRect.Right;
375    Result.Y := TaskbarRect.Bottom;
376  end
377  else if (TaskbarRect.Left - TaskbarMonitor.Left = 0) then
378  begin
379    { Taskbar is at the bottom of the monitor area }
380    Result.X := TaskbarRect.Right;
381    Result.Y := TaskbarRect.Top;
382  end
383  else
384  begin
385    Result.X := TaskbarRect.Left;
386    Result.Y := TaskbarRect.Bottom;
387    { Taskbar is on the right side of the monitor area }
388  end;
389end;
390
391
392