pidgin/pidgin

Make PurplePluginProtocolInfo definitions consistent
release-2.x.y
2020-05-20, David Woodhouse
159344ba2a49
Make PurplePluginProtocolInfo definitions consistent

Since we can't use C99 structure initialisers, we have to manually add
new NULL fields to all protocols whenever we extend the structure.

Make it slightly easier to script that, by making the current last
field (get_cb_alias) consistent in all cases. In particular, there's
no reason *not* to have the trailing comma, as most already do.

Now I can add a new field to the PRPL by doing something like this...

PROTOFILES=`grep -rl '[A-Za-z_][A-Za-z0-9_]*,[[:space:]]*/\* get_cb_alias \*/' libpurple/protocols/ `
sed '/\/\* get_cb_alias \*\//{p;s/[A-Za-z_][A-Za-
/*
* System tray icon (aka docklet) plugin for Winpidgin
*
* Copyright (C) 2002-3 Robert McQueen <robot101@debian.org>
* Copyright (C) 2003 Herman Bloggs <hermanator12002@yahoo.com>
* Inspired by a similar plugin by:
* John (J5) Palmieri <johnp@martianrock.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02111-1301, USA.
*/
#define _WIN32_IE 0x0500
#include <windows.h>
#include <gdk/gdkwin32.h>
#include <gdk/gdk.h>
#include "internal.h"
#include "gtkblist.h"
#include "debug.h"
#include "resource.h"
#include "MinimizeToTray.h"
#include "gtkwin32dep.h"
#include "gtkdocklet.h"
#include "pidginstock.h"
/*
* DEFINES, MACROS & DATA TYPES
*/
#define WM_TRAYMESSAGE WM_USER /* User defined WM Message */
/*
* LOCALS
*/
static HWND systray_hwnd = NULL;
/* additional two cached_icons entries for pending and connecting icons */
static HICON cached_icons[PURPLE_STATUS_NUM_PRIMITIVES + 2];
static GtkWidget *image = NULL;
/* This is used to trigger click events on so they appear to GTK+ as if they are triggered by input */
static GtkWidget *dummy_button = NULL;
static GtkWidget *dummy_window = NULL;
static NOTIFYICONDATAW _nicon_data;
static gboolean dummy_button_cb(GtkWidget *widget, GdkEventButton *event, gpointer user_data) {
pidgin_docklet_clicked(event->button);
return TRUE;
}
static LRESULT CALLBACK systray_mainmsg_handler(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
static UINT taskbarRestartMsg; /* static here means value is kept across multiple calls to this func */
switch(msg) {
case WM_CREATE:
purple_debug_info("docklet", "WM_CREATE\n");
taskbarRestartMsg = RegisterWindowMessageW(L"TaskbarCreated");
break;
case WM_TIMER:
purple_debug_info("docklet", "WM_TIMER\n");
break;
case WM_DESTROY:
purple_debug_info("docklet", "WM_DESTROY\n");
break;
case WM_TRAYMESSAGE:
{
int type = 0;
GdkEvent *event;
GdkEventButton *event_btn;
/* We'll use Double Click - Single click over on linux */
if(lparam == WM_LBUTTONDBLCLK)
type = 1;
else if(lparam == WM_MBUTTONUP)
type = 2;
else if(lparam == WM_RBUTTONUP)
type = 3;
else
break;
gtk_widget_show_all(dummy_window);
event = gdk_event_new(GDK_BUTTON_PRESS);
event_btn = (GdkEventButton *) event;
event_btn->window = g_object_ref (gdk_get_default_root_window());
event_btn->send_event = TRUE;
event_btn->time = GetTickCount();
event_btn->axes = NULL;
event_btn->state = 0;
event_btn->button = type;
event_btn->device = gdk_display_get_default ()->core_pointer;
event->any.window = g_object_ref(dummy_window->window);
gdk_window_set_user_data(event->any.window, dummy_button);
gtk_main_do_event((GdkEvent *)event);
gtk_widget_hide(dummy_window);
gdk_event_free((GdkEvent *)event);
break;
}
default:
if (msg == taskbarRestartMsg) {
/* explorer crashed and left us hanging...
This will put the systray icon back in it's place, when it restarts */
Shell_NotifyIconW(NIM_ADD, &_nicon_data);
}
break;
}/* end switch */
return DefWindowProc(hwnd, msg, wparam, lparam);
}
/* Create hidden window to process systray messages */
static HWND systray_create_hiddenwin() {
WNDCLASSEXW wcex;
wchar_t *wname;
wname = L"WinpidginSystrayWinCls";
wcex.cbSize = sizeof(wcex);
wcex.style = 0;
wcex.lpfnWndProc = systray_mainmsg_handler;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = winpidgin_exe_hinstance();
wcex.hIcon = NULL;
wcex.hCursor = NULL,
wcex.hbrBackground = NULL;
wcex.lpszMenuName = NULL;
wcex.lpszClassName = wname;
wcex.hIconSm = NULL;
RegisterClassExW(&wcex);
/* Create the window */
return (CreateWindowW(wname, L"", 0, 0, 0, 0, 0, GetDesktopWindow(), NULL, winpidgin_exe_hinstance(), 0));
}
static void systray_init_icon(HWND hWnd) {
wchar_t *w;
ZeroMemory(&_nicon_data, sizeof(_nicon_data));
_nicon_data.cbSize = sizeof(NOTIFYICONDATAW);
_nicon_data.hWnd = hWnd;
_nicon_data.uID = 0;
_nicon_data.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
_nicon_data.uCallbackMessage = WM_TRAYMESSAGE;
_nicon_data.hIcon = NULL;
w = g_utf8_to_utf16(PIDGIN_NAME, -1, NULL, NULL, NULL);
wcsncpy(_nicon_data.szTip, w, sizeof(_nicon_data.szTip) / sizeof(wchar_t));
g_free(w);
Shell_NotifyIconW(NIM_ADD, &_nicon_data);
pidgin_docklet_embedded();
}
/* This is ganked from GTK+.
* When we can use GTK+ 2.10 and the GtkStatusIcon stuff, this will no longer be necesary */
#define WIN32_GDI_FAILED(api) printf("GDI FAILED %s\n", api)
static gboolean
_gdk_win32_pixbuf_to_hicon_supports_alpha (void)
{
static gboolean is_win_xp=FALSE, is_win_xp_checked=FALSE;
if (!is_win_xp_checked)
{
is_win_xp_checked = TRUE;
if (!G_WIN32_IS_NT_BASED ())
is_win_xp = FALSE;
else
{
OSVERSIONINFO version;
memset (&version, 0, sizeof (version));
version.dwOSVersionInfoSize = sizeof (version);
is_win_xp = GetVersionEx (&version)
&& version.dwPlatformId == VER_PLATFORM_WIN32_NT
&& (version.dwMajorVersion > 5
|| (version.dwMajorVersion == 5 && version.dwMinorVersion >= 1));
}
}
return is_win_xp;
}
static HBITMAP
create_alpha_bitmap (gint size,
guchar **outdata)
{
BITMAPV5HEADER bi;
HDC hdc;
HBITMAP hBitmap;
ZeroMemory (&bi, sizeof (BITMAPV5HEADER));
bi.bV5Size = sizeof (BITMAPV5HEADER);
bi.bV5Height = bi.bV5Width = size;
bi.bV5Planes = 1;
bi.bV5BitCount = 32;
bi.bV5Compression = BI_BITFIELDS;
/* The following mask specification specifies a supported 32 BPP
* alpha format for Windows XP (BGRA format).
*/
bi.bV5RedMask = 0x00FF0000;
bi.bV5GreenMask = 0x0000FF00;
bi.bV5BlueMask = 0x000000FF;
bi.bV5AlphaMask = 0xFF000000;
/* Create the DIB section with an alpha channel. */
hdc = GetDC (NULL);
if (!hdc)
{
WIN32_GDI_FAILED ("GetDC");
return NULL;
}
hBitmap = CreateDIBSection (hdc, (BITMAPINFO *)&bi, DIB_RGB_COLORS,
(PVOID *) outdata, NULL, (DWORD)0);
if (hBitmap == NULL)
WIN32_GDI_FAILED ("CreateDIBSection");
ReleaseDC (NULL, hdc);
return hBitmap;
}
static HBITMAP
create_color_bitmap (gint size,
guchar **outdata,
gint bits)
{
struct {
BITMAPV4HEADER bmiHeader;
RGBQUAD bmiColors[2];
} bmi;
HDC hdc;
HBITMAP hBitmap;
ZeroMemory (&bmi, sizeof (bmi));
bmi.bmiHeader.bV4Size = sizeof (BITMAPV4HEADER);
bmi.bmiHeader.bV4Height = bmi.bmiHeader.bV4Width = size;
bmi.bmiHeader.bV4Planes = 1;
bmi.bmiHeader.bV4BitCount = bits;
bmi.bmiHeader.bV4V4Compression = BI_RGB;
/* when bits is 1, these will be used.
* bmiColors[0] already zeroed from ZeroMemory()
*/
bmi.bmiColors[1].rgbBlue = 0xFF;
bmi.bmiColors[1].rgbGreen = 0xFF;
bmi.bmiColors[1].rgbRed = 0xFF;
hdc = GetDC (NULL);
if (!hdc)
{
WIN32_GDI_FAILED ("GetDC");
return NULL;
}
hBitmap = CreateDIBSection (hdc, (BITMAPINFO *)&bmi, DIB_RGB_COLORS,
(PVOID *) outdata, NULL, (DWORD)0);
if (hBitmap == NULL)
WIN32_GDI_FAILED ("CreateDIBSection");
ReleaseDC (NULL, hdc);
return hBitmap;
}
static gboolean
pixbuf_to_hbitmaps_alpha_winxp (GdkPixbuf *pixbuf,
HBITMAP *color,
HBITMAP *mask)
{
/* Based on code from
* http://www.dotnet247.com/247reference/msgs/13/66301.aspx
*/
HBITMAP hColorBitmap, hMaskBitmap;
guchar *indata, *inrow;
guchar *colordata, *colorrow, *maskdata, *maskbyte;
gint width, height, size, i, i_offset, j, j_offset, rowstride;
guint maskstride, mask_bit;
width = gdk_pixbuf_get_width (pixbuf); /* width of icon */
height = gdk_pixbuf_get_height (pixbuf); /* height of icon */
/* The bitmaps are created square */
size = MAX (width, height);
hColorBitmap = create_alpha_bitmap (size, &colordata);
if (!hColorBitmap)
return FALSE;
hMaskBitmap = create_color_bitmap (size, &maskdata, 1);
if (!hMaskBitmap)
{
DeleteObject (hColorBitmap);
return FALSE;
}
/* MSDN says mask rows are aligned to "LONG" boundaries */
maskstride = (((size + 31) & ~31) >> 3);
indata = gdk_pixbuf_get_pixels (pixbuf);
rowstride = gdk_pixbuf_get_rowstride (pixbuf);
if (width > height)
{
i_offset = 0;
j_offset = (width - height) / 2;
}
else
{
i_offset = (height - width) / 2;
j_offset = 0;
}
for (j = 0; j < height; j++)
{
colorrow = colordata + 4*(j+j_offset)*size + 4*i_offset;
maskbyte = maskdata + (j+j_offset)*maskstride + i_offset/8;
mask_bit = (0x80 >> (i_offset % 8));
inrow = indata + (height-j-1)*rowstride;
for (i = 0; i < width; i++)
{
colorrow[4*i+0] = inrow[4*i+2];
colorrow[4*i+1] = inrow[4*i+1];
colorrow[4*i+2] = inrow[4*i+0];
colorrow[4*i+3] = inrow[4*i+3];
if (inrow[4*i+3] == 0)
maskbyte[0] |= mask_bit; /* turn ON bit */
else
maskbyte[0] &= ~mask_bit; /* turn OFF bit */
mask_bit >>= 1;
if (mask_bit == 0)
{
mask_bit = 0x80;
maskbyte++;
}
}
}
*color = hColorBitmap;
*mask = hMaskBitmap;
return TRUE;
}
static gboolean
pixbuf_to_hbitmaps_normal (GdkPixbuf *pixbuf,
HBITMAP *color,
HBITMAP *mask)
{
/* Based on code from
* http://www.dotnet247.com/247reference/msgs/13/66301.aspx
*/
HBITMAP hColorBitmap, hMaskBitmap;
guchar *indata, *inrow;
guchar *colordata, *colorrow, *maskdata, *maskbyte;
gint width, height, size, i, i_offset, j, j_offset, rowstride, nc, bmstride;
gboolean has_alpha;
guint maskstride, mask_bit;
width = gdk_pixbuf_get_width (pixbuf); /* width of icon */
height = gdk_pixbuf_get_height (pixbuf); /* height of icon */
/* The bitmaps are created square */
size = MAX (width, height);
hColorBitmap = create_color_bitmap (size, &colordata, 24);
if (!hColorBitmap)
return FALSE;
hMaskBitmap = create_color_bitmap (size, &maskdata, 1);
if (!hMaskBitmap)
{
DeleteObject (hColorBitmap);
return FALSE;
}
/* rows are always aligned on 4-byte boundarys */
bmstride = size * 3;
if (bmstride % 4 != 0)
bmstride += 4 - (bmstride % 4);
/* MSDN says mask rows are aligned to "LONG" boundaries */
maskstride = (((size + 31) & ~31) >> 3);
indata = gdk_pixbuf_get_pixels (pixbuf);
rowstride = gdk_pixbuf_get_rowstride (pixbuf);
nc = gdk_pixbuf_get_n_channels (pixbuf);
has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
if (width > height)
{
i_offset = 0;
j_offset = (width - height) / 2;
}
else
{
i_offset = (height - width) / 2;
j_offset = 0;
}
for (j = 0; j < height; j++)
{
colorrow = colordata + (j+j_offset)*bmstride + 3*i_offset;
maskbyte = maskdata + (j+j_offset)*maskstride + i_offset/8;
mask_bit = (0x80 >> (i_offset % 8));
inrow = indata + (height-j-1)*rowstride;
for (i = 0; i < width; i++)
{
if (has_alpha && inrow[nc*i+3] < 128)
{
colorrow[3*i+0] = colorrow[3*i+1] = colorrow[3*i+2] = 0;
maskbyte[0] |= mask_bit; /* turn ON bit */
}
else
{
colorrow[3*i+0] = inrow[nc*i+2];
colorrow[3*i+1] = inrow[nc*i+1];
colorrow[3*i+2] = inrow[nc*i+0];
maskbyte[0] &= ~mask_bit; /* turn OFF bit */
}
mask_bit >>= 1;
if (mask_bit == 0)
{
mask_bit = 0x80;
maskbyte++;
}
}
}
*color = hColorBitmap;
*mask = hMaskBitmap;
return TRUE;
}
static HICON
pixbuf_to_hicon (GdkPixbuf *pixbuf)
{
gint x = 0, y = 0;
gboolean is_icon = TRUE;
ICONINFO ii;
HICON icon;
gboolean success;
if (pixbuf == NULL)
return NULL;
if (_gdk_win32_pixbuf_to_hicon_supports_alpha() && gdk_pixbuf_get_has_alpha (pixbuf))
success = pixbuf_to_hbitmaps_alpha_winxp (pixbuf, &ii.hbmColor, &ii.hbmMask);
else
success = pixbuf_to_hbitmaps_normal (pixbuf, &ii.hbmColor, &ii.hbmMask);
if (!success)
return NULL;
ii.fIcon = is_icon;
ii.xHotspot = x;
ii.yHotspot = y;
icon = CreateIconIndirect (&ii);
DeleteObject (ii.hbmColor);
DeleteObject (ii.hbmMask);
return icon;
}
static HICON load_hicon_from_stock(const char *stock) {
HICON hicon = NULL;
GdkPixbuf *pixbuf = gtk_widget_render_icon(image, stock,
gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL), NULL);
if (pixbuf) {
hicon = pixbuf_to_hicon(pixbuf);
g_object_unref(pixbuf);
} else
purple_debug_error("docklet", "Unable to load pixbuf for %s.\n", stock);
return hicon;
}
static void systray_change_icon(HICON hicon) {
g_return_if_fail(hicon != NULL);
_nicon_data.hIcon = hicon;
Shell_NotifyIconW(NIM_MODIFY, &_nicon_data);
}
static void systray_remove_nid(void) {
Shell_NotifyIconW(NIM_DELETE, &_nicon_data);
}
static void winpidgin_tray_update_icon(PurpleStatusPrimitive status,
gboolean connecting, gboolean pending) {
int icon_index;
g_return_if_fail(image != NULL);
if(connecting)
icon_index = PURPLE_STATUS_NUM_PRIMITIVES;
else if(pending)
icon_index = PURPLE_STATUS_NUM_PRIMITIVES+1;
else
icon_index = status;
g_return_if_fail(icon_index < (sizeof(cached_icons) / sizeof(HICON)));
/* Look up and cache the HICON if we don't already have it */
if (cached_icons[icon_index] == NULL) {
const gchar *icon_name = NULL;
switch (status) {
case PURPLE_STATUS_OFFLINE:
icon_name = PIDGIN_STOCK_TRAY_OFFLINE;
break;
case PURPLE_STATUS_AWAY:
icon_name = PIDGIN_STOCK_TRAY_AWAY;
break;
case PURPLE_STATUS_UNAVAILABLE:
icon_name = PIDGIN_STOCK_TRAY_BUSY;
break;
case PURPLE_STATUS_EXTENDED_AWAY:
icon_name = PIDGIN_STOCK_TRAY_XA;
break;
case PURPLE_STATUS_INVISIBLE:
icon_name = PIDGIN_STOCK_TRAY_INVISIBLE;
break;
default:
icon_name = PIDGIN_STOCK_TRAY_AVAILABLE;
break;
}
if (pending)
icon_name = PIDGIN_STOCK_TRAY_PENDING;
if (connecting)
icon_name = PIDGIN_STOCK_TRAY_CONNECT;
g_return_if_fail(icon_name != NULL);
cached_icons[icon_index] = load_hicon_from_stock(icon_name);
}
systray_change_icon(cached_icons[icon_index]);
}
static void winpidgin_tray_blank_icon() {
_nicon_data.hIcon = NULL;
Shell_NotifyIconW(NIM_MODIFY, &_nicon_data);
}
static void winpidgin_tray_set_tooltip(gchar *tooltip) {
const char *value = tooltip;
wchar_t *w;
if (value == NULL) {
value = PIDGIN_NAME;
}
w = g_utf8_to_utf16(value, -1, NULL, NULL, NULL);
wcsncpy(_nicon_data.szTip, w, sizeof(_nicon_data.szTip) / sizeof(wchar_t));
g_free(w);
Shell_NotifyIconW(NIM_MODIFY, &_nicon_data);
}
static void winpidgin_tray_minimize(PidginBuddyList *gtkblist) {
MinimizeWndToTray(GDK_WINDOW_HWND(gtkblist->window->window));
}
static void winpidgin_tray_maximize(PidginBuddyList *gtkblist) {
RestoreWndFromTray(GDK_WINDOW_HWND(gtkblist->window->window));
}
static void winpidgin_tray_create() {
OSVERSIONINFO osinfo;
/* dummy window to process systray messages */
systray_hwnd = systray_create_hiddenwin();
dummy_window = gtk_window_new(GTK_WINDOW_POPUP);
dummy_button = gtk_button_new();
gtk_container_add(GTK_CONTAINER(dummy_window), dummy_button);
/* We trigger the click event indirectly so that gtk_get_current_event_state() is TRUE when the event is handled */
g_signal_connect(G_OBJECT(dummy_button), "button-press-event",
G_CALLBACK(dummy_button_cb), NULL);
image = gtk_image_new();
g_object_ref_sink(image);
osinfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&osinfo);
/* Load icons, and init systray notify icon
* NOTE: Windows < XP only supports displaying 4-bit images in the Systray,
* 2K and ME will use the highest color depth that the desktop will support,
* but will scale it back to 4-bits for display.
* That is why we use custom 4-bit icons for pre XP Windowses */
if (osinfo.dwMajorVersion < 5 || (osinfo.dwMajorVersion == 5 && osinfo.dwMinorVersion == 0))
{
cached_icons[PURPLE_STATUS_OFFLINE] = (HICON) LoadImage(winpidgin_dll_hinstance(),
MAKEINTRESOURCE(PIDGIN_TRAY_OFFLINE_4BIT), IMAGE_ICON, 16, 16, LR_CREATEDIBSECTION);
cached_icons[PURPLE_STATUS_AVAILABLE] = (HICON) LoadImage(winpidgin_dll_hinstance(),
MAKEINTRESOURCE(PIDGIN_TRAY_AVAILABLE_4BIT), IMAGE_ICON, 16, 16, LR_CREATEDIBSECTION);
cached_icons[PURPLE_STATUS_AWAY] = (HICON) LoadImage(winpidgin_dll_hinstance(),
MAKEINTRESOURCE(PIDGIN_TRAY_AWAY_4BIT), IMAGE_ICON, 16, 16, LR_CREATEDIBSECTION);
cached_icons[PURPLE_STATUS_EXTENDED_AWAY] = (HICON) LoadImage(winpidgin_dll_hinstance(),
MAKEINTRESOURCE(PIDGIN_TRAY_XA_4BIT), IMAGE_ICON, 16, 16, LR_CREATEDIBSECTION);
cached_icons[PURPLE_STATUS_UNAVAILABLE] = (HICON) LoadImage(winpidgin_dll_hinstance(),
MAKEINTRESOURCE(PIDGIN_TRAY_BUSY_4BIT), IMAGE_ICON, 16, 16, LR_CREATEDIBSECTION);
cached_icons[PURPLE_STATUS_NUM_PRIMITIVES] = (HICON) LoadImage(winpidgin_dll_hinstance(),
MAKEINTRESOURCE(PIDGIN_TRAY_CONNECTING_4BIT), IMAGE_ICON, 16, 16, LR_CREATEDIBSECTION);
cached_icons[PURPLE_STATUS_NUM_PRIMITIVES+1] = (HICON) LoadImage(winpidgin_dll_hinstance(),
MAKEINTRESOURCE(PIDGIN_TRAY_PENDING_4BIT), IMAGE_ICON, 16, 16, LR_CREATEDIBSECTION);
cached_icons[PURPLE_STATUS_INVISIBLE] = (HICON) LoadImage(winpidgin_dll_hinstance(),
MAKEINTRESOURCE(PIDGIN_TRAY_INVISIBLE_4BIT), IMAGE_ICON, 16, 16, LR_CREATEDIBSECTION);
}
/* Create icon in systray */
systray_init_icon(systray_hwnd);
purple_signal_connect(pidgin_blist_get_handle(), "gtkblist-hiding",
pidgin_docklet_get_handle(), PURPLE_CALLBACK(winpidgin_tray_minimize), NULL);
purple_signal_connect(pidgin_blist_get_handle(), "gtkblist-unhiding",
pidgin_docklet_get_handle(), PURPLE_CALLBACK(winpidgin_tray_maximize), NULL);
purple_debug_info("docklet", "created\n");
}
static void winpidgin_tray_destroy() {
int cached_cnt = sizeof(cached_icons) / sizeof(HICON);
systray_remove_nid();
purple_signals_disconnect_by_handle(pidgin_docklet_get_handle());
DestroyWindow(systray_hwnd);
pidgin_docklet_remove();
while (--cached_cnt >= 0) {
if (cached_icons[cached_cnt] != NULL)
DestroyIcon(cached_icons[cached_cnt]);
cached_icons[cached_cnt] = NULL;
}
g_object_unref(image);
image = NULL;
gtk_widget_destroy(dummy_window);
dummy_button = NULL;
dummy_window = NULL;
}
static struct docklet_ui_ops winpidgin_tray_ops =
{
winpidgin_tray_create,
winpidgin_tray_destroy,
winpidgin_tray_update_icon,
winpidgin_tray_blank_icon,
winpidgin_tray_set_tooltip,
NULL
};
/* Used by docklet's plugin load func */
void docklet_ui_init() {
/* Initialize the cached icons to NULL */
ZeroMemory(cached_icons, sizeof(cached_icons));
pidgin_docklet_set_ui_ops(&winpidgin_tray_ops);
}