pidgin/win32/gtkdocklet-win32.c

branch
cpw.khc.msnp14
changeset 20478
46933dc62880
parent 20472
6a6d2ef151e6
parent 16058
dc1c7b09a058
child 20481
65485e2ed8a3
equal deleted inserted replaced
20476:198222e01a7d 20478:46933dc62880
1 /*
2 * System tray icon (aka docklet) plugin for Winpidgin
3 *
4 * Copyright (C) 2002-3 Robert McQueen <robot101@debian.org>
5 * Copyright (C) 2003 Herman Bloggs <hermanator12002@yahoo.com>
6 * Inspired by a similar plugin by:
7 * John (J5) Palmieri <johnp@martianrock.com>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as
11 * published by the Free Software Foundation; either version 2 of the
12 * License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
22 * 02111-1307, USA.
23 */
24
25 #include <windows.h>
26 #include <gdk/gdkwin32.h>
27 #include <gdk/gdk.h>
28
29 #include "internal.h"
30 #include "gtkblist.h"
31 #include "debug.h"
32
33 #include "resource.h"
34 #include "MinimizeToTray.h"
35 #include "gtkwin32dep.h"
36 #include "gtkdocklet.h"
37 #include "pidginstock.h"
38
39 /*
40 * DEFINES, MACROS & DATA TYPES
41 */
42 #define WM_TRAYMESSAGE WM_USER /* User defined WM Message */
43
44 /*
45 * LOCALS
46 */
47 static HWND systray_hwnd = NULL;
48 static HICON cached_icons[DOCKLET_STATUS_CONNECTING + 1];
49 static GtkWidget *image = NULL;
50 static NOTIFYICONDATA _nicon_data;
51
52 static LRESULT CALLBACK systray_mainmsg_handler(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
53 static UINT taskbarRestartMsg; /* static here means value is kept across multiple calls to this func */
54
55 switch(msg) {
56 case WM_CREATE:
57 purple_debug_info("docklet", "WM_CREATE\n");
58 taskbarRestartMsg = RegisterWindowMessage("TaskbarCreated");
59 break;
60
61 case WM_TIMER:
62 purple_debug_info("docklet", "WM_TIMER\n");
63 break;
64
65 case WM_DESTROY:
66 purple_debug_info("docklet", "WM_DESTROY\n");
67 break;
68
69 case WM_TRAYMESSAGE:
70 {
71 int type = 0;
72
73 /* We'll use Double Click - Single click over on linux */
74 if(lparam == WM_LBUTTONDBLCLK)
75 type = 1;
76 else if(lparam == WM_MBUTTONUP)
77 type = 2;
78 else if(lparam == WM_RBUTTONUP)
79 type = 3;
80 else
81 break;
82
83 pidgin_docklet_clicked(type);
84 break;
85 }
86 default:
87 if (msg == taskbarRestartMsg) {
88 /* explorer crashed and left us hanging...
89 This will put the systray icon back in it's place, when it restarts */
90 Shell_NotifyIcon(NIM_ADD, &_nicon_data);
91 }
92 break;
93 }/* end switch */
94
95 return DefWindowProc(hwnd, msg, wparam, lparam);
96 }
97
98 /* Create hidden window to process systray messages */
99 static HWND systray_create_hiddenwin() {
100 WNDCLASSEX wcex;
101 LPCTSTR wname;
102
103 wname = TEXT("WinpidginSystrayWinCls");
104
105 wcex.cbSize = sizeof(wcex);
106 wcex.style = 0;
107 wcex.lpfnWndProc = systray_mainmsg_handler;
108 wcex.cbClsExtra = 0;
109 wcex.cbWndExtra = 0;
110 wcex.hInstance = winpidgin_exe_hinstance();
111 wcex.hIcon = NULL;
112 wcex.hCursor = NULL,
113 wcex.hbrBackground = NULL;
114 wcex.lpszMenuName = NULL;
115 wcex.lpszClassName = wname;
116 wcex.hIconSm = NULL;
117
118 RegisterClassEx(&wcex);
119
120 /* Create the window */
121 return (CreateWindow(wname, "", 0, 0, 0, 0, 0, GetDesktopWindow(), NULL, winpidgin_exe_hinstance(), 0));
122 }
123
124 static void systray_init_icon(HWND hWnd) {
125 ZeroMemory(&_nicon_data, sizeof(_nicon_data));
126 _nicon_data.cbSize = sizeof(NOTIFYICONDATA);
127 _nicon_data.hWnd = hWnd;
128 _nicon_data.uID = 0;
129 _nicon_data.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
130 _nicon_data.uCallbackMessage = WM_TRAYMESSAGE;
131 _nicon_data.hIcon = NULL;
132 lstrcpy(_nicon_data.szTip, PIDGIN_NAME);
133 Shell_NotifyIcon(NIM_ADD, &_nicon_data);
134 pidgin_docklet_embedded();
135 }
136
137 /* This is ganked from GTK+.
138 * When we can use GTK+ 2.10 and the GtkStatusIcon stuff, this will no longer be necesary */
139 #define WIN32_GDI_FAILED(api) printf("GDI FAILED %s\n", api)
140
141 static gboolean
142 _gdk_win32_pixbuf_to_hicon_supports_alpha (void)
143 {
144 static gboolean is_win_xp=FALSE, is_win_xp_checked=FALSE;
145
146 if (!is_win_xp_checked)
147 {
148 is_win_xp_checked = TRUE;
149
150 if (!G_WIN32_IS_NT_BASED ())
151 is_win_xp = FALSE;
152 else
153 {
154 OSVERSIONINFO version;
155
156 memset (&version, 0, sizeof (version));
157 version.dwOSVersionInfoSize = sizeof (version);
158 is_win_xp = GetVersionEx (&version)
159 && version.dwPlatformId == VER_PLATFORM_WIN32_NT
160 && (version.dwMajorVersion > 5
161 || (version.dwMajorVersion == 5 && version.dwMinorVersion >= 1));
162 }
163 }
164 return is_win_xp;
165 }
166
167 static HBITMAP
168 create_alpha_bitmap (gint size,
169 guchar **outdata)
170 {
171 BITMAPV5HEADER bi;
172 HDC hdc;
173 HBITMAP hBitmap;
174
175 ZeroMemory (&bi, sizeof (BITMAPV5HEADER));
176 bi.bV5Size = sizeof (BITMAPV5HEADER);
177 bi.bV5Height = bi.bV5Width = size;
178 bi.bV5Planes = 1;
179 bi.bV5BitCount = 32;
180 bi.bV5Compression = BI_BITFIELDS;
181 /* The following mask specification specifies a supported 32 BPP
182 * alpha format for Windows XP (BGRA format).
183 */
184 bi.bV5RedMask = 0x00FF0000;
185 bi.bV5GreenMask = 0x0000FF00;
186 bi.bV5BlueMask = 0x000000FF;
187 bi.bV5AlphaMask = 0xFF000000;
188
189 /* Create the DIB section with an alpha channel. */
190 hdc = GetDC (NULL);
191 if (!hdc)
192 {
193 WIN32_GDI_FAILED ("GetDC");
194 return NULL;
195 }
196 hBitmap = CreateDIBSection (hdc, (BITMAPINFO *)&bi, DIB_RGB_COLORS,
197 (PVOID *) outdata, NULL, (DWORD)0);
198 if (hBitmap == NULL)
199 WIN32_GDI_FAILED ("CreateDIBSection");
200 ReleaseDC (NULL, hdc);
201
202 return hBitmap;
203 }
204
205 static HBITMAP
206 create_color_bitmap (gint size,
207 guchar **outdata,
208 gint bits)
209 {
210 struct {
211 BITMAPV4HEADER bmiHeader;
212 RGBQUAD bmiColors[2];
213 } bmi;
214 HDC hdc;
215 HBITMAP hBitmap;
216
217 ZeroMemory (&bmi, sizeof (bmi));
218 bmi.bmiHeader.bV4Size = sizeof (BITMAPV4HEADER);
219 bmi.bmiHeader.bV4Height = bmi.bmiHeader.bV4Width = size;
220 bmi.bmiHeader.bV4Planes = 1;
221 bmi.bmiHeader.bV4BitCount = bits;
222 bmi.bmiHeader.bV4V4Compression = BI_RGB;
223
224 /* when bits is 1, these will be used.
225 * bmiColors[0] already zeroed from ZeroMemory()
226 */
227 bmi.bmiColors[1].rgbBlue = 0xFF;
228 bmi.bmiColors[1].rgbGreen = 0xFF;
229 bmi.bmiColors[1].rgbRed = 0xFF;
230
231 hdc = GetDC (NULL);
232 if (!hdc)
233 {
234 WIN32_GDI_FAILED ("GetDC");
235 return NULL;
236 }
237 hBitmap = CreateDIBSection (hdc, (BITMAPINFO *)&bmi, DIB_RGB_COLORS,
238 (PVOID *) outdata, NULL, (DWORD)0);
239 if (hBitmap == NULL)
240 WIN32_GDI_FAILED ("CreateDIBSection");
241 ReleaseDC (NULL, hdc);
242
243 return hBitmap;
244 }
245
246 static gboolean
247 pixbuf_to_hbitmaps_alpha_winxp (GdkPixbuf *pixbuf,
248 HBITMAP *color,
249 HBITMAP *mask)
250 {
251 /* Based on code from
252 * http://www.dotnet247.com/247reference/msgs/13/66301.aspx
253 */
254 HBITMAP hColorBitmap, hMaskBitmap;
255 guchar *indata, *inrow;
256 guchar *colordata, *colorrow, *maskdata, *maskbyte;
257 gint width, height, size, i, i_offset, j, j_offset, rowstride;
258 guint maskstride, mask_bit;
259
260 width = gdk_pixbuf_get_width (pixbuf); /* width of icon */
261 height = gdk_pixbuf_get_height (pixbuf); /* height of icon */
262
263 /* The bitmaps are created square */
264 size = MAX (width, height);
265
266 hColorBitmap = create_alpha_bitmap (size, &colordata);
267 if (!hColorBitmap)
268 return FALSE;
269 hMaskBitmap = create_color_bitmap (size, &maskdata, 1);
270 if (!hMaskBitmap)
271 {
272 DeleteObject (hColorBitmap);
273 return FALSE;
274 }
275
276 /* MSDN says mask rows are aligned to "LONG" boundaries */
277 maskstride = (((size + 31) & ~31) >> 3);
278
279 indata = gdk_pixbuf_get_pixels (pixbuf);
280 rowstride = gdk_pixbuf_get_rowstride (pixbuf);
281
282 if (width > height)
283 {
284 i_offset = 0;
285 j_offset = (width - height) / 2;
286 }
287 else
288 {
289 i_offset = (height - width) / 2;
290 j_offset = 0;
291 }
292
293 for (j = 0; j < height; j++)
294 {
295 colorrow = colordata + 4*(j+j_offset)*size + 4*i_offset;
296 maskbyte = maskdata + (j+j_offset)*maskstride + i_offset/8;
297 mask_bit = (0x80 >> (i_offset % 8));
298 inrow = indata + (height-j-1)*rowstride;
299 for (i = 0; i < width; i++)
300 {
301 colorrow[4*i+0] = inrow[4*i+2];
302 colorrow[4*i+1] = inrow[4*i+1];
303 colorrow[4*i+2] = inrow[4*i+0];
304 colorrow[4*i+3] = inrow[4*i+3];
305 if (inrow[4*i+3] == 0)
306 maskbyte[0] |= mask_bit; /* turn ON bit */
307 else
308 maskbyte[0] &= ~mask_bit; /* turn OFF bit */
309 mask_bit >>= 1;
310 if (mask_bit == 0)
311 {
312 mask_bit = 0x80;
313 maskbyte++;
314 }
315 }
316 }
317
318 *color = hColorBitmap;
319 *mask = hMaskBitmap;
320
321 return TRUE;
322 }
323
324 static gboolean
325 pixbuf_to_hbitmaps_normal (GdkPixbuf *pixbuf,
326 HBITMAP *color,
327 HBITMAP *mask)
328 {
329 /* Based on code from
330 * http://www.dotnet247.com/247reference/msgs/13/66301.aspx
331 */
332 HBITMAP hColorBitmap, hMaskBitmap;
333 guchar *indata, *inrow;
334 guchar *colordata, *colorrow, *maskdata, *maskbyte;
335 gint width, height, size, i, i_offset, j, j_offset, rowstride, nc, bmstride;
336 gboolean has_alpha;
337 guint maskstride, mask_bit;
338
339 width = gdk_pixbuf_get_width (pixbuf); /* width of icon */
340 height = gdk_pixbuf_get_height (pixbuf); /* height of icon */
341
342 /* The bitmaps are created square */
343 size = MAX (width, height);
344
345 hColorBitmap = create_color_bitmap (size, &colordata, 24);
346 if (!hColorBitmap)
347 return FALSE;
348 hMaskBitmap = create_color_bitmap (size, &maskdata, 1);
349 if (!hMaskBitmap)
350 {
351 DeleteObject (hColorBitmap);
352 return FALSE;
353 }
354
355 /* rows are always aligned on 4-byte boundarys */
356 bmstride = size * 3;
357 if (bmstride % 4 != 0)
358 bmstride += 4 - (bmstride % 4);
359
360 /* MSDN says mask rows are aligned to "LONG" boundaries */
361 maskstride = (((size + 31) & ~31) >> 3);
362
363 indata = gdk_pixbuf_get_pixels (pixbuf);
364 rowstride = gdk_pixbuf_get_rowstride (pixbuf);
365 nc = gdk_pixbuf_get_n_channels (pixbuf);
366 has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
367
368 if (width > height)
369 {
370 i_offset = 0;
371 j_offset = (width - height) / 2;
372 }
373 else
374 {
375 i_offset = (height - width) / 2;
376 j_offset = 0;
377 }
378
379 for (j = 0; j < height; j++)
380 {
381 colorrow = colordata + (j+j_offset)*bmstride + 3*i_offset;
382 maskbyte = maskdata + (j+j_offset)*maskstride + i_offset/8;
383 mask_bit = (0x80 >> (i_offset % 8));
384 inrow = indata + (height-j-1)*rowstride;
385 for (i = 0; i < width; i++)
386 {
387 if (has_alpha && inrow[nc*i+3] < 128)
388 {
389 colorrow[3*i+0] = colorrow[3*i+1] = colorrow[3*i+2] = 0;
390 maskbyte[0] |= mask_bit; /* turn ON bit */
391 }
392 else
393 {
394 colorrow[3*i+0] = inrow[nc*i+2];
395 colorrow[3*i+1] = inrow[nc*i+1];
396 colorrow[3*i+2] = inrow[nc*i+0];
397 maskbyte[0] &= ~mask_bit; /* turn OFF bit */
398 }
399 mask_bit >>= 1;
400 if (mask_bit == 0)
401 {
402 mask_bit = 0x80;
403 maskbyte++;
404 }
405 }
406 }
407
408 *color = hColorBitmap;
409 *mask = hMaskBitmap;
410
411 return TRUE;
412 }
413
414 static HICON
415 pixbuf_to_hicon (GdkPixbuf *pixbuf)
416 {
417 gint x = 0, y = 0;
418 gboolean is_icon = TRUE;
419 ICONINFO ii;
420 HICON icon;
421 gboolean success;
422
423 if (pixbuf == NULL)
424 return NULL;
425
426 if (_gdk_win32_pixbuf_to_hicon_supports_alpha() && gdk_pixbuf_get_has_alpha (pixbuf))
427 success = pixbuf_to_hbitmaps_alpha_winxp (pixbuf, &ii.hbmColor, &ii.hbmMask);
428 else
429 success = pixbuf_to_hbitmaps_normal (pixbuf, &ii.hbmColor, &ii.hbmMask);
430
431 if (!success)
432 return NULL;
433
434 ii.fIcon = is_icon;
435 ii.xHotspot = x;
436 ii.yHotspot = y;
437 icon = CreateIconIndirect (&ii);
438 DeleteObject (ii.hbmColor);
439 DeleteObject (ii.hbmMask);
440 return icon;
441 }
442
443 static HICON load_hicon_from_stock(const char *stock) {
444 HICON hicon = NULL;
445 GdkPixbuf *pixbuf = gtk_widget_render_icon(image, stock,
446 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL), NULL);
447
448 if (pixbuf) {
449 hicon = pixbuf_to_hicon(pixbuf);
450 g_object_unref(pixbuf);
451 } else
452 purple_debug_error("Unable to load pixbuf for %s.\n", stock);
453
454 return hicon;
455 }
456
457
458 static void systray_change_icon(HICON hicon) {
459 g_return_if_fail(hicon != NULL);
460
461 _nicon_data.hIcon = hicon;
462 Shell_NotifyIcon(NIM_MODIFY, &_nicon_data);
463 }
464
465 static void systray_remove_nid(void) {
466 Shell_NotifyIcon(NIM_DELETE, &_nicon_data);
467 }
468
469 static void winpidgin_tray_update_icon(DockletStatus icon) {
470
471 g_return_if_fail(image != NULL);
472 g_return_if_fail(icon < (sizeof(cached_icons) / sizeof(HICON)));
473
474 /* Look up and cache the HICON if we don't already have it */
475 if (cached_icons[icon] == NULL) {
476 const gchar *icon_name = NULL;
477 switch (icon) {
478 case DOCKLET_STATUS_OFFLINE:
479 icon_name = PIDGIN_STOCK_TRAY_OFFLINE;
480 break;
481 case DOCKLET_STATUS_CONNECTING:
482 icon_name = PIDGIN_STOCK_TRAY_CONNECT;
483 break;
484 case DOCKLET_STATUS_AVAILABLE:
485 icon_name = PIDGIN_STOCK_TRAY_AVAILABLE;
486 break;
487 case DOCKLET_STATUS_PENDING:
488 icon_name = PIDGIN_STOCK_TRAY_PENDING;
489 break;
490 case DOCKLET_STATUS_AWAY:
491 icon_name = PIDGIN_STOCK_TRAY_AWAY;
492 break;
493 case DOCKLET_STATUS_BUSY:
494 icon_name = PIDGIN_STOCK_TRAY_BUSY;
495 break;
496 case DOCKLET_STATUS_XA:
497 icon_name = PIDGIN_STOCK_TRAY_XA;
498 break;
499 }
500
501 g_return_if_fail(icon_name != NULL);
502
503 cached_icons[icon] = load_hicon_from_stock(icon_name);
504 }
505
506 systray_change_icon(cached_icons[icon]);
507 }
508
509 static void winpidgin_tray_blank_icon() {
510 _nicon_data.hIcon = NULL;
511 Shell_NotifyIcon(NIM_MODIFY, &_nicon_data);
512 }
513
514 static void winpidgin_tray_set_tooltip(gchar *tooltip) {
515 if (tooltip) {
516 char *locenc = NULL;
517 locenc = g_locale_from_utf8(tooltip, -1, NULL, NULL, NULL);
518 lstrcpyn(_nicon_data.szTip, locenc, sizeof(_nicon_data.szTip) / sizeof(TCHAR));
519 g_free(locenc);
520 } else {
521 lstrcpy(_nicon_data.szTip, PIDGIN_NAME);
522 }
523 Shell_NotifyIcon(NIM_MODIFY, &_nicon_data);
524 }
525
526 static void winpidgin_tray_minimize(PidginBuddyList *gtkblist) {
527 MinimizeWndToTray(GDK_WINDOW_HWND(gtkblist->window->window));
528 }
529
530 static void winpidgin_tray_maximize(PidginBuddyList *gtkblist) {
531 RestoreWndFromTray(GDK_WINDOW_HWND(gtkblist->window->window));
532 }
533
534
535 static void winpidgin_tray_create() {
536 OSVERSIONINFO osinfo;
537 /* dummy window to process systray messages */
538 systray_hwnd = systray_create_hiddenwin();
539
540 image = gtk_image_new();
541 #if GLIB_CHECK_VERSION(2,10,0)
542 g_object_ref_sink(image);
543 #else
544 g_object_ref(image);
545 gtk_object_sink(GTK_OBJECT(image));
546 #endif
547
548 osinfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
549 GetVersionEx(&osinfo);
550
551 /* Load icons, and init systray notify icon
552 * NOTE: Windows < XP only supports displaying 4-bit images in the Systray,
553 * 2K and ME will use the highest color depth that the desktop will support,
554 * but will scale it back to 4-bits for display.
555 * That is why we use custom 4-bit icons for pre XP Windowses */
556 if (osinfo.dwMajorVersion < 5 || (osinfo.dwMajorVersion == 5 && osinfo.dwMinorVersion == 0))
557 {
558 cached_icons[DOCKLET_STATUS_OFFLINE] = (HICON) LoadImage(winpidgin_dll_hinstance(),
559 MAKEINTRESOURCE(PIDGIN_TRAY_OFFLINE_4BIT), IMAGE_ICON, 16, 16, LR_CREATEDIBSECTION);
560 cached_icons[DOCKLET_STATUS_AVAILABLE] = (HICON) LoadImage(winpidgin_dll_hinstance(),
561 MAKEINTRESOURCE(PIDGIN_TRAY_AVAILABLE_4BIT), IMAGE_ICON, 16, 16, LR_CREATEDIBSECTION);
562 cached_icons[DOCKLET_STATUS_AWAY] = (HICON) LoadImage(winpidgin_dll_hinstance(),
563 MAKEINTRESOURCE(PIDGIN_TRAY_AWAY_4BIT), IMAGE_ICON, 16, 16, LR_CREATEDIBSECTION);
564 cached_icons[DOCKLET_STATUS_XA] = (HICON) LoadImage(winpidgin_dll_hinstance(),
565 MAKEINTRESOURCE(PIDGIN_TRAY_XA_4BIT), IMAGE_ICON, 16, 16, LR_CREATEDIBSECTION);
566 cached_icons[DOCKLET_STATUS_BUSY] = (HICON) LoadImage(winpidgin_dll_hinstance(),
567 MAKEINTRESOURCE(PIDGIN_TRAY_BUSY_4BIT), IMAGE_ICON, 16, 16, LR_CREATEDIBSECTION);
568 cached_icons[DOCKLET_STATUS_CONNECTING] = (HICON) LoadImage(winpidgin_dll_hinstance(),
569 MAKEINTRESOURCE(PIDGIN_TRAY_CONNECTING_4BIT), IMAGE_ICON, 16, 16, LR_CREATEDIBSECTION);
570 cached_icons[DOCKLET_STATUS_PENDING] = (HICON) LoadImage(winpidgin_dll_hinstance(),
571 MAKEINTRESOURCE(PIDGIN_TRAY_PENDING_4BIT), IMAGE_ICON, 16, 16, LR_CREATEDIBSECTION);
572 }
573
574 /* Create icon in systray */
575 systray_init_icon(systray_hwnd);
576
577 purple_signal_connect(pidgin_blist_get_handle(), "gtkblist-hiding",
578 pidgin_docklet_get_handle(), PURPLE_CALLBACK(winpidgin_tray_minimize), NULL);
579 purple_signal_connect(pidgin_blist_get_handle(), "gtkblist-unhiding",
580 pidgin_docklet_get_handle(), PURPLE_CALLBACK(winpidgin_tray_maximize), NULL);
581
582 purple_debug_info("docklet", "created\n");
583 }
584
585 static void winpidgin_tray_destroy() {
586 int cached_cnt = sizeof(cached_icons) / sizeof(HICON);
587 systray_remove_nid();
588 DestroyWindow(systray_hwnd);
589 pidgin_docklet_remove();
590
591 while (--cached_cnt >= 0) {
592 if (cached_icons[cached_cnt] != NULL)
593 DestroyIcon(cached_icons[cached_cnt]);
594 cached_icons[cached_cnt] = NULL;
595 }
596
597 g_object_unref(image);
598 image = NULL;
599 }
600
601 static struct docklet_ui_ops winpidgin_tray_ops =
602 {
603 winpidgin_tray_create,
604 winpidgin_tray_destroy,
605 winpidgin_tray_update_icon,
606 winpidgin_tray_blank_icon,
607 winpidgin_tray_set_tooltip,
608 NULL
609 };
610
611 /* Used by docklet's plugin load func */
612 void docklet_ui_init() {
613 /* Initialize the cached icons to NULL */
614 ZeroMemory(cached_icons, sizeof(cached_icons));
615
616 pidgin_docklet_set_ui_ops(&winpidgin_tray_ops);
617 }

mercurial