2 * System tray icon (aka docklet) plugin for Winpidgin
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>
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.
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.
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., 51 Franklin Street, Fifth Floor, Boston, MA
28 #include <gdk/gdkwin32.h>
36 #include "MinimizeToTray.h"
37 #include "gtkwin32dep.h"
38 #include "gtkdocklet.h"
39 #include "pidginicon.h"
42 * DEFINES, MACROS & DATA TYPES
44 #define WM_TRAYMESSAGE WM_USER /* User defined WM Message */
49 static HWND systray_hwnd
= NULL
;
50 /* additional two cached_icons entries for pending and connecting icons */
51 static HICON cached_icons
[PURPLE_STATUS_NUM_PRIMITIVES
+ 3];
52 static GtkWidget
*image
= NULL
;
53 /* This is used to trigger click events on so they appear to GTK+ as if they are triggered by input */
54 static GtkWidget
*dummy_button
= NULL
;
55 static GtkWidget
*dummy_window
= NULL
;
56 static NOTIFYICONDATAW _nicon_data
;
58 static gboolean
dummy_button_cb(GtkWidget
*widget
, GdkEventButton
*event
, gpointer user_data
) {
59 pidgin_docklet_clicked(event
->button
);
63 static LRESULT CALLBACK
systray_mainmsg_handler(HWND hwnd
, UINT msg
, WPARAM wparam
, LPARAM lparam
) {
64 static UINT taskbarRestartMsg
; /* static here means value is kept across multiple calls to this func */
68 purple_debug_info("docklet", "WM_CREATE\n");
69 taskbarRestartMsg
= RegisterWindowMessageW(L
"TaskbarCreated");
73 purple_debug_info("docklet", "WM_TIMER\n");
77 purple_debug_info("docklet", "WM_DESTROY\n");
84 GdkEventButton
*event_btn
;
86 /* We'll use Double Click - Single click over on linux */
87 if(lparam
== WM_LBUTTONDBLCLK
)
88 type
= GDK_BUTTOM_PRIMARY
;
89 else if(lparam
== WM_MBUTTONUP
)
90 type
= GDK_BUTTON_MIDDLE
;
91 else if(lparam
== WM_RBUTTONUP
)
92 type
= GDK_BUTTON_SECONDARY
;
96 gtk_widget_show_all(dummy_window
);
97 event
= gdk_event_new(GDK_BUTTON_PRESS
);
98 event_btn
= (GdkEventButton
*) event
;
99 event_btn
->window
= g_object_ref (gdk_get_default_root_window());
100 event_btn
->send_event
= TRUE
;
101 event_btn
->time
= GetTickCount();
102 event_btn
->axes
= NULL
;
103 event_btn
->state
= 0;
104 event_btn
->button
= type
;
105 event_btn
->device
= gdk_display_get_default ()->core_pointer
;
106 event
->any
.window
= g_object_ref(dummy_window
->window
);
107 gdk_window_set_user_data(event
->any
.window
, dummy_button
);
109 gtk_main_do_event((GdkEvent
*)event
);
110 gtk_widget_hide(dummy_window
);
111 gdk_event_free((GdkEvent
*)event
);
116 if (msg
== taskbarRestartMsg
) {
117 /* explorer crashed and left us hanging...
118 This will put the systray icon back in it's place, when it restarts */
119 Shell_NotifyIconW(NIM_ADD
, &_nicon_data
);
124 return DefWindowProc(hwnd
, msg
, wparam
, lparam
);
127 /* Create hidden window to process systray messages */
128 static HWND
systray_create_hiddenwin() {
132 wname
= L
"WinpidginSystrayWinCls";
134 wcex
.cbSize
= sizeof(wcex
);
136 wcex
.lpfnWndProc
= systray_mainmsg_handler
;
139 wcex
.hInstance
= winpidgin_exe_hinstance();
142 wcex
.hbrBackground
= NULL
;
143 wcex
.lpszMenuName
= NULL
;
144 wcex
.lpszClassName
= wname
;
147 RegisterClassExW(&wcex
);
149 /* Create the window */
150 return (CreateWindowW(wname
, L
"", 0, 0, 0, 0, 0, GetDesktopWindow(), NULL
, winpidgin_exe_hinstance(), 0));
153 static void systray_init_icon(HWND hWnd
) {
155 ZeroMemory(&_nicon_data
, sizeof(_nicon_data
));
156 _nicon_data
.cbSize
= sizeof(NOTIFYICONDATAW
);
157 _nicon_data
.hWnd
= hWnd
;
159 _nicon_data
.uFlags
= NIF_ICON
| NIF_MESSAGE
| NIF_TIP
;
160 _nicon_data
.uCallbackMessage
= WM_TRAYMESSAGE
;
161 _nicon_data
.hIcon
= NULL
;
162 w
= g_utf8_to_utf16(PIDGIN_NAME
, -1, NULL
, NULL
, NULL
);
163 wcsncpy(_nicon_data
.szTip
, w
, sizeof(_nicon_data
.szTip
) / sizeof(wchar_t));
165 Shell_NotifyIconW(NIM_ADD
, &_nicon_data
);
166 pidgin_docklet_embedded();
169 /* This is ganked from GTK+.
170 * When we can use GTK+ 2.10 and the GtkStatusIcon stuff, this will no longer be necesary */
171 #define WIN32_GDI_FAILED(api) printf("GDI FAILED %s\n", api)
174 _gdk_win32_pixbuf_to_hicon_supports_alpha (void)
176 static gboolean is_win_xp
=FALSE
, is_win_xp_checked
=FALSE
;
178 if (!is_win_xp_checked
)
180 is_win_xp_checked
= TRUE
;
182 if (!G_WIN32_IS_NT_BASED ())
186 OSVERSIONINFO version
;
188 memset (&version
, 0, sizeof (version
));
189 version
.dwOSVersionInfoSize
= sizeof (version
);
190 is_win_xp
= GetVersionEx (&version
)
191 && version
.dwPlatformId
== VER_PLATFORM_WIN32_NT
192 && (version
.dwMajorVersion
> 5
193 || (version
.dwMajorVersion
== 5 && version
.dwMinorVersion
>= 1));
200 create_alpha_bitmap (gint size
,
207 ZeroMemory (&bi
, sizeof (BITMAPV5HEADER
));
208 bi
.bV5Size
= sizeof (BITMAPV5HEADER
);
209 bi
.bV5Height
= bi
.bV5Width
= size
;
212 bi
.bV5Compression
= BI_BITFIELDS
;
213 /* The following mask specification specifies a supported 32 BPP
214 * alpha format for Windows XP (BGRA format).
216 bi
.bV5RedMask
= 0x00FF0000;
217 bi
.bV5GreenMask
= 0x0000FF00;
218 bi
.bV5BlueMask
= 0x000000FF;
219 bi
.bV5AlphaMask
= 0xFF000000;
221 /* Create the DIB section with an alpha channel. */
225 WIN32_GDI_FAILED ("GetDC");
228 hBitmap
= CreateDIBSection (hdc
, (BITMAPINFO
*)&bi
, DIB_RGB_COLORS
,
229 (PVOID
*) outdata
, NULL
, (DWORD
)0);
231 WIN32_GDI_FAILED ("CreateDIBSection");
232 ReleaseDC (NULL
, hdc
);
238 create_color_bitmap (gint size
,
243 BITMAPV4HEADER bmiHeader
;
244 RGBQUAD bmiColors
[2];
249 ZeroMemory (&bmi
, sizeof (bmi
));
250 bmi
.bmiHeader
.bV4Size
= sizeof (BITMAPV4HEADER
);
251 bmi
.bmiHeader
.bV4Height
= bmi
.bmiHeader
.bV4Width
= size
;
252 bmi
.bmiHeader
.bV4Planes
= 1;
253 bmi
.bmiHeader
.bV4BitCount
= bits
;
254 bmi
.bmiHeader
.bV4V4Compression
= BI_RGB
;
256 /* when bits is 1, these will be used.
257 * bmiColors[0] already zeroed from ZeroMemory()
259 bmi
.bmiColors
[1].rgbBlue
= 0xFF;
260 bmi
.bmiColors
[1].rgbGreen
= 0xFF;
261 bmi
.bmiColors
[1].rgbRed
= 0xFF;
266 WIN32_GDI_FAILED ("GetDC");
269 hBitmap
= CreateDIBSection (hdc
, (BITMAPINFO
*)&bmi
, DIB_RGB_COLORS
,
270 (PVOID
*) outdata
, NULL
, (DWORD
)0);
272 WIN32_GDI_FAILED ("CreateDIBSection");
273 ReleaseDC (NULL
, hdc
);
279 pixbuf_to_hbitmaps_alpha_winxp (GdkPixbuf
*pixbuf
,
283 /* Based on code from
284 * http://www.dotnet247.com/247reference/msgs/13/66301.aspx
286 HBITMAP hColorBitmap
, hMaskBitmap
;
287 guchar
*indata
, *inrow
;
288 guchar
*colordata
, *colorrow
, *maskdata
, *maskbyte
;
289 gint width
, height
, size
, i
, i_offset
, j
, j_offset
, rowstride
;
290 guint maskstride
, mask_bit
;
292 width
= gdk_pixbuf_get_width (pixbuf
); /* width of icon */
293 height
= gdk_pixbuf_get_height (pixbuf
); /* height of icon */
295 /* The bitmaps are created square */
296 size
= MAX (width
, height
);
298 hColorBitmap
= create_alpha_bitmap (size
, &colordata
);
301 hMaskBitmap
= create_color_bitmap (size
, &maskdata
, 1);
304 DeleteObject (hColorBitmap
);
308 /* MSDN says mask rows are aligned to "LONG" boundaries */
309 maskstride
= (((size
+ 31) & ~31) >> 3);
311 indata
= gdk_pixbuf_get_pixels (pixbuf
);
312 rowstride
= gdk_pixbuf_get_rowstride (pixbuf
);
317 j_offset
= (width
- height
) / 2;
321 i_offset
= (height
- width
) / 2;
325 for (j
= 0; j
< height
; j
++)
327 colorrow
= colordata
+ 4*(j
+j_offset
)*size
+ 4*i_offset
;
328 maskbyte
= maskdata
+ (j
+j_offset
)*maskstride
+ i_offset
/8;
329 mask_bit
= (0x80 >> (i_offset
% 8));
330 inrow
= indata
+ (height
-j
-1)*rowstride
;
331 for (i
= 0; i
< width
; i
++)
333 colorrow
[4*i
+0] = inrow
[4*i
+2];
334 colorrow
[4*i
+1] = inrow
[4*i
+1];
335 colorrow
[4*i
+2] = inrow
[4*i
+0];
336 colorrow
[4*i
+3] = inrow
[4*i
+3];
337 if (inrow
[4*i
+3] == 0)
338 maskbyte
[0] |= mask_bit
; /* turn ON bit */
340 maskbyte
[0] &= ~mask_bit
; /* turn OFF bit */
350 *color
= hColorBitmap
;
357 pixbuf_to_hbitmaps_normal (GdkPixbuf
*pixbuf
,
361 /* Based on code from
362 * http://www.dotnet247.com/247reference/msgs/13/66301.aspx
364 HBITMAP hColorBitmap
, hMaskBitmap
;
365 guchar
*indata
, *inrow
;
366 guchar
*colordata
, *colorrow
, *maskdata
, *maskbyte
;
367 gint width
, height
, size
, i
, i_offset
, j
, j_offset
, rowstride
, nc
, bmstride
;
369 guint maskstride
, mask_bit
;
371 width
= gdk_pixbuf_get_width (pixbuf
); /* width of icon */
372 height
= gdk_pixbuf_get_height (pixbuf
); /* height of icon */
374 /* The bitmaps are created square */
375 size
= MAX (width
, height
);
377 hColorBitmap
= create_color_bitmap (size
, &colordata
, 24);
380 hMaskBitmap
= create_color_bitmap (size
, &maskdata
, 1);
383 DeleteObject (hColorBitmap
);
387 /* rows are always aligned on 4-byte boundarys */
389 if (bmstride
% 4 != 0)
390 bmstride
+= 4 - (bmstride
% 4);
392 /* MSDN says mask rows are aligned to "LONG" boundaries */
393 maskstride
= (((size
+ 31) & ~31) >> 3);
395 indata
= gdk_pixbuf_get_pixels (pixbuf
);
396 rowstride
= gdk_pixbuf_get_rowstride (pixbuf
);
397 nc
= gdk_pixbuf_get_n_channels (pixbuf
);
398 has_alpha
= gdk_pixbuf_get_has_alpha (pixbuf
);
403 j_offset
= (width
- height
) / 2;
407 i_offset
= (height
- width
) / 2;
411 for (j
= 0; j
< height
; j
++)
413 colorrow
= colordata
+ (j
+j_offset
)*bmstride
+ 3*i_offset
;
414 maskbyte
= maskdata
+ (j
+j_offset
)*maskstride
+ i_offset
/8;
415 mask_bit
= (0x80 >> (i_offset
% 8));
416 inrow
= indata
+ (height
-j
-1)*rowstride
;
417 for (i
= 0; i
< width
; i
++)
419 if (has_alpha
&& inrow
[nc
*i
+3] < 128)
421 colorrow
[3*i
+0] = colorrow
[3*i
+1] = colorrow
[3*i
+2] = 0;
422 maskbyte
[0] |= mask_bit
; /* turn ON bit */
426 colorrow
[3*i
+0] = inrow
[nc
*i
+2];
427 colorrow
[3*i
+1] = inrow
[nc
*i
+1];
428 colorrow
[3*i
+2] = inrow
[nc
*i
+0];
429 maskbyte
[0] &= ~mask_bit
; /* turn OFF bit */
440 *color
= hColorBitmap
;
447 pixbuf_to_hicon (GdkPixbuf
*pixbuf
)
450 gboolean is_icon
= TRUE
;
458 if (_gdk_win32_pixbuf_to_hicon_supports_alpha() && gdk_pixbuf_get_has_alpha (pixbuf
))
459 success
= pixbuf_to_hbitmaps_alpha_winxp (pixbuf
, &ii
.hbmColor
, &ii
.hbmMask
);
461 success
= pixbuf_to_hbitmaps_normal (pixbuf
, &ii
.hbmColor
, &ii
.hbmMask
);
469 icon
= CreateIconIndirect (&ii
);
470 DeleteObject (ii
.hbmColor
);
471 DeleteObject (ii
.hbmMask
);
475 static HICON
load_hicon_from_stock(const char *stock
) {
477 GdkPixbuf
*pixbuf
= gtk_widget_render_icon(image
, stock
,
478 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
), NULL
);
481 hicon
= pixbuf_to_hicon(pixbuf
);
482 g_object_unref(pixbuf
);
484 purple_debug_error("docklet", "Unable to load pixbuf for %s.\n", stock
);
490 static void systray_change_icon(HICON hicon
) {
491 g_return_if_fail(hicon
!= NULL
);
493 _nicon_data
.hIcon
= hicon
;
494 Shell_NotifyIconW(NIM_MODIFY
, &_nicon_data
);
497 static void systray_remove_nid(void) {
498 Shell_NotifyIconW(NIM_DELETE
, &_nicon_data
);
501 static void winpidgin_tray_update_icon(PurpleStatusPrimitive status
,
502 PidginDockletFlag flags
) {
505 g_return_if_fail(image
!= NULL
);
507 if(flags
& PIDGIN_DOCKLET_CONNECTING
)
508 icon_index
= PURPLE_STATUS_NUM_PRIMITIVES
;
509 else if(flags
& PIDGIN_DOCKLET_EMAIL_PENDING
)
510 icon_index
= PURPLE_STATUS_NUM_PRIMITIVES
+2;
511 else if(flags
& PIDGIN_DOCKLET_CONV_PENDING
)
512 icon_index
= PURPLE_STATUS_NUM_PRIMITIVES
+1;
516 g_return_if_fail(icon_index
< (sizeof(cached_icons
) / sizeof(HICON
)));
518 /* Look up and cache the HICON if we don't already have it */
519 if (cached_icons
[icon_index
] == NULL
) {
520 const gchar
*icon_name
= NULL
;
522 icon_name
= pidgin_status_icon_from_primitive(status
);
524 if (flags
& PIDGIN_DOCKLET_EMAIL_PENDING
)
525 icon_name
= PIDGIN_ICON_MAIL_NEW
;
526 else if (flags
& PIDGIN_DOCKLET_CONV_PENDING
)
527 icon_name
= PIDGIN_ICON_MESSAGE_NEW
;
528 else if (flags
& PIDGIN_DOCKLET_CONNECTING
)
529 icon_name
= PIDGIN_ICON_CONNECT
;
531 g_return_if_fail(icon_name
!= NULL
);
533 cached_icons
[icon_index
] = load_hicon_from_stock(icon_name
);
536 systray_change_icon(cached_icons
[icon_index
]);
539 static void winpidgin_tray_blank_icon() {
540 _nicon_data
.hIcon
= NULL
;
541 Shell_NotifyIconW(NIM_MODIFY
, &_nicon_data
);
544 static void winpidgin_tray_set_tooltip(gchar
*tooltip
) {
545 const char *value
= tooltip
;
550 w
= g_utf8_to_utf16(value
, -1, NULL
, NULL
, NULL
);
551 wcsncpy(_nicon_data
.szTip
, w
, sizeof(_nicon_data
.szTip
) / sizeof(wchar_t));
553 Shell_NotifyIconW(NIM_MODIFY
, &_nicon_data
);
556 static void winpidgin_tray_minimize(PidginBuddyList
*gtkblist
) {
557 MinimizeWndToTray(GDK_WINDOW_HWND(gtkblist
->window
->window
));
560 static void winpidgin_tray_maximize(PidginBuddyList
*gtkblist
) {
561 RestoreWndFromTray(GDK_WINDOW_HWND(gtkblist
->window
->window
));
565 static void winpidgin_tray_create() {
566 OSVERSIONINFO osinfo
;
567 /* dummy window to process systray messages */
568 systray_hwnd
= systray_create_hiddenwin();
570 dummy_window
= gtk_window_new(GTK_WINDOW_POPUP
);
571 dummy_button
= gtk_button_new();
572 gtk_container_add(GTK_CONTAINER(dummy_window
), dummy_button
);
574 /* We trigger the click event indirectly so that gtk_get_current_event_state() is TRUE when the event is handled */
575 g_signal_connect(G_OBJECT(dummy_button
), "button-press-event",
576 G_CALLBACK(dummy_button_cb
), NULL
);
578 image
= gtk_image_new();
579 g_object_ref_sink(image
);
581 osinfo
.dwOSVersionInfoSize
= sizeof(OSVERSIONINFO
);
582 GetVersionEx(&osinfo
);
584 /* Load icons, and init systray notify icon
585 * NOTE: Windows < XP only supports displaying 4-bit images in the Systray,
586 * 2K and ME will use the highest color depth that the desktop will support,
587 * but will scale it back to 4-bits for display.
588 * That is why we use custom 4-bit icons for pre XP Windowses */
589 if (osinfo
.dwMajorVersion
< 5 || (osinfo
.dwMajorVersion
== 5 && osinfo
.dwMinorVersion
== 0))
591 cached_icons
[PURPLE_STATUS_OFFLINE
] = (HICON
) LoadImage(winpidgin_dll_hinstance(),
592 MAKEINTRESOURCE(PIDGIN_TRAY_OFFLINE_4BIT
), IMAGE_ICON
, 16, 16, LR_CREATEDIBSECTION
);
593 cached_icons
[PURPLE_STATUS_AVAILABLE
] = (HICON
) LoadImage(winpidgin_dll_hinstance(),
594 MAKEINTRESOURCE(PIDGIN_TRAY_AVAILABLE_4BIT
), IMAGE_ICON
, 16, 16, LR_CREATEDIBSECTION
);
595 cached_icons
[PURPLE_STATUS_AWAY
] = (HICON
) LoadImage(winpidgin_dll_hinstance(),
596 MAKEINTRESOURCE(PIDGIN_TRAY_AWAY_4BIT
), IMAGE_ICON
, 16, 16, LR_CREATEDIBSECTION
);
597 cached_icons
[PURPLE_STATUS_EXTENDED_AWAY
] = (HICON
) LoadImage(winpidgin_dll_hinstance(),
598 MAKEINTRESOURCE(PIDGIN_TRAY_XA_4BIT
), IMAGE_ICON
, 16, 16, LR_CREATEDIBSECTION
);
599 cached_icons
[PURPLE_STATUS_UNAVAILABLE
] = (HICON
) LoadImage(winpidgin_dll_hinstance(),
600 MAKEINTRESOURCE(PIDGIN_TRAY_BUSY_4BIT
), IMAGE_ICON
, 16, 16, LR_CREATEDIBSECTION
);
601 cached_icons
[PURPLE_STATUS_NUM_PRIMITIVES
] = (HICON
) LoadImage(winpidgin_dll_hinstance(),
602 MAKEINTRESOURCE(PIDGIN_TRAY_CONNECTING_4BIT
), IMAGE_ICON
, 16, 16, LR_CREATEDIBSECTION
);
603 cached_icons
[PURPLE_STATUS_NUM_PRIMITIVES
+1] = (HICON
) LoadImage(winpidgin_dll_hinstance(),
604 MAKEINTRESOURCE(PIDGIN_TRAY_PENDING_4BIT
), IMAGE_ICON
, 16, 16, LR_CREATEDIBSECTION
);
605 cached_icons
[PURPLE_STATUS_INVISIBLE
] = (HICON
) LoadImage(winpidgin_dll_hinstance(),
606 MAKEINTRESOURCE(PIDGIN_TRAY_INVISIBLE_4BIT
), IMAGE_ICON
, 16, 16, LR_CREATEDIBSECTION
);
609 /* Create icon in systray */
610 systray_init_icon(systray_hwnd
);
612 purple_signal_connect(pidgin_blist_get_handle(), "gtkblist-hiding",
613 pidgin_docklet_get_handle(), PURPLE_CALLBACK(winpidgin_tray_minimize
), NULL
);
614 purple_signal_connect(pidgin_blist_get_handle(), "gtkblist-unhiding",
615 pidgin_docklet_get_handle(), PURPLE_CALLBACK(winpidgin_tray_maximize
), NULL
);
617 purple_debug_info("docklet", "created\n");
620 static void winpidgin_tray_destroy() {
621 int cached_cnt
= sizeof(cached_icons
) / sizeof(HICON
);
622 systray_remove_nid();
624 purple_signals_disconnect_by_handle(pidgin_docklet_get_handle());
626 DestroyWindow(systray_hwnd
);
627 pidgin_docklet_remove();
629 while (--cached_cnt
>= 0) {
630 if (cached_icons
[cached_cnt
] != NULL
)
631 DestroyIcon(cached_icons
[cached_cnt
]);
632 cached_icons
[cached_cnt
] = NULL
;
635 g_object_unref(image
);
638 gtk_widget_destroy(dummy_window
);
643 static struct docklet_ui_ops winpidgin_tray_ops
=
645 winpidgin_tray_create
,
646 winpidgin_tray_destroy
,
647 winpidgin_tray_update_icon
,
648 winpidgin_tray_blank_icon
,
649 winpidgin_tray_set_tooltip
,
653 /* Used by docklet's plugin load func */
654 void docklet_ui_init() {
655 /* Initialize the cached icons to NULL */
656 ZeroMemory(cached_icons
, sizeof(cached_icons
));
658 pidgin_docklet_set_ui_ops(&winpidgin_tray_ops
);