systray.c 17.6 KB
Newer Older
1 2 3
/*
 * Copyright (C) 2004 Mike Hearn, for CodeWeavers
 * Copyright (C) 2005 Robert Shearman
4
 * Copyright (C) 2008 Alexandre Julliard
5 6 7 8 9 10 11 12 13 14 15 16 17
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 20 21 22 23 24 25
 */

#include <assert.h>

#define UNICODE
#define _WIN32_IE 0x500
#include <windows.h>
26
#include <commctrl.h>
27 28 29 30

#include <wine/debug.h>
#include <wine/list.h>

31
#include "explorer_private.h"
32 33 34

WINE_DEFAULT_DEBUG_CHANNEL(systray);

35 36 37
#define IS_OPTION_FALSE(ch) \
    ((ch) == 'n' || (ch) == 'N' || (ch) == 'f' || (ch) == 'F' || (ch) == '0')

38
static int (*wine_notify_icon)(DWORD,NOTIFYICONDATAW *);
39

40 41 42 43 44 45
/* an individual systray icon, unpacked from the NOTIFYICONDATA and always in unicode */
struct icon
{
    struct list    entry;
    HICON          image;    /* the image to render */
    HWND           owner;    /* the HWND passed in to the Shell_NotifyIcon call */
46
    HWND           tooltip;  /* Icon tooltip */
47 48
    UINT           id;       /* the unique id given by the app */
    UINT           callback_message;
49
    int            display;  /* index in display list, or -1 if hidden */
50
    WCHAR          tiptext[128]; /* Tooltip text. If empty => tooltip disabled */
51 52
};

53 54
static struct list icon_list = LIST_INIT( icon_list );
static HWND tray_window;
55

56 57 58
static unsigned int alloc_displayed;
static unsigned int nb_displayed;
static struct icon **displayed;  /* array of currently displayed icons */
59

60 61
static BOOL hide_systray;
static int icon_cx, icon_cy;
62

63 64
#define MIN_DISPLAYED 8
#define ICON_BORDER  2
65

66
/* Retrieves icon record by owner window and ID */
67 68 69 70 71
static struct icon *get_icon(HWND owner, UINT id)
{
    struct icon *this;

    /* search for the icon */
72
    LIST_FOR_EACH_ENTRY( this, &icon_list, struct icon, entry )
73
        if ((this->id == id) && (this->owner == owner)) return this;
74 75 76 77

    return NULL;
}

78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
/* compute the size of the tray window */
static SIZE get_window_size(void)
{
    SIZE size;
    RECT rect;

    rect.left = 0;
    rect.top = 0;
    rect.right = icon_cx * max( nb_displayed, MIN_DISPLAYED );
    rect.bottom = icon_cy;
    AdjustWindowRect( &rect, WS_CAPTION, FALSE );
    size.cx = rect.right - rect.left;
    size.cy = rect.bottom - rect.top;
    return size;
}

94 95
/* Creates tooltip window for icon. */
static void create_tooltip(struct icon *icon)
96 97
{
    TTTOOLINFOW ti;
98 99 100 101 102 103 104 105 106 107 108 109 110
    static BOOL tooltips_initialized = FALSE;

    /* Register tooltip classes if this is the first icon */
    if (!tooltips_initialized)
    {
        INITCOMMONCONTROLSEX init_tooltip;

        init_tooltip.dwSize = sizeof(INITCOMMONCONTROLSEX);
        init_tooltip.dwICC = ICC_TAB_CLASSES;

        InitCommonControlsEx(&init_tooltip);
        tooltips_initialized = TRUE;
    }
111

112
    icon->tooltip = CreateWindowExW(WS_EX_TOPMOST, TOOLTIPS_CLASSW, NULL,
113 114 115
                                   WS_POPUP | TTS_ALWAYSTIP,
                                   CW_USEDEFAULT, CW_USEDEFAULT,
                                   CW_USEDEFAULT, CW_USEDEFAULT,
116
                                   tray_window, NULL, NULL, NULL);
117 118

    ZeroMemory(&ti, sizeof(ti));
119
    ti.cbSize = sizeof(TTTOOLINFOW);
120
    ti.hwnd = tray_window;
121
    ti.lpszText = icon->tiptext;
122 123 124 125 126 127 128
    if (icon->display != -1)
    {
        ti.rect.left = icon_cx * icon->display;
        ti.rect.right = icon_cx * (icon->display + 1);
        ti.rect.top = 0;
        ti.rect.bottom = icon_cy;
    }
129 130
    SendMessageW(icon->tooltip, TTM_ADDTOOLW, 0, (LPARAM)&ti);
}
131

132 133 134 135 136 137 138
/* Synchronize tooltip text with tooltip window */
static void update_tooltip_text(struct icon *icon)
{
    TTTOOLINFOW ti;

    ZeroMemory(&ti, sizeof(ti));
    ti.cbSize = sizeof(TTTOOLINFOW);
139
    ti.hwnd = tray_window;
140 141 142
    ti.lpszText = icon->tiptext;

    SendMessageW(icon->tooltip, TTM_UPDATETIPTEXTW, 0, (LPARAM)&ti);
143 144
}

145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
/* synchronize tooltip position with tooltip window */
static void update_tooltip_position( struct icon *icon )
{
    TTTOOLINFOW ti;

    ZeroMemory(&ti, sizeof(ti));
    ti.cbSize = sizeof(TTTOOLINFOW);
    ti.hwnd = tray_window;
    if (icon->display != -1)
    {
        ti.rect.left = icon_cx * icon->display;
        ti.rect.right = icon_cx * (icon->display + 1);
        ti.rect.top = 0;
        ti.rect.bottom = icon_cy;
    }
    SendMessageW( icon->tooltip, TTM_NEWTOOLRECTW, 0, (LPARAM)&ti );
}

/* find the icon located at a certain point in the tray window */
static struct icon *icon_from_point( int x, int y )
{
    if (y < 0 || y >= icon_cy) return NULL;
    if (x < 0 || x >= icon_cx * nb_displayed) return NULL;
    return displayed[x / icon_cx];
}

/* invalidate the portion of the tray window that contains the specified icons */
static void invalidate_icons( unsigned int start, unsigned int end )
173 174 175
{
    RECT rect;

176 177 178 179 180 181 182 183 184 185 186
    rect.left = start * icon_cx;
    rect.top  = 0;
    rect.right = (end + 1) * icon_cx;
    rect.bottom = icon_cy;
    InvalidateRect( tray_window, &rect, TRUE );
}

/* make an icon visible */
static BOOL show_icon(struct icon *icon)
{
    WINE_TRACE("id=0x%x, hwnd=%p\n", icon->id, icon->owner);
187

188
    if (icon->display != -1) return TRUE;  /* already displayed */
189

190
    if (nb_displayed >= alloc_displayed)
191
    {
192 193 194 195 196 197 198
        unsigned int new_count = max( alloc_displayed * 2, 32 );
        struct icon **ptr;
        if (displayed) ptr = HeapReAlloc( GetProcessHeap(), 0, displayed, new_count * sizeof(*ptr) );
        else ptr = HeapAlloc( GetProcessHeap(), 0, new_count * sizeof(*ptr) );
        if (!ptr) return FALSE;
        displayed = ptr;
        alloc_displayed = new_count;
199 200
    }

201 202 203 204 205 206 207 208 209 210 211 212 213 214
    icon->display = nb_displayed;
    displayed[nb_displayed++] = icon;
    update_tooltip_position( icon );
    invalidate_icons( nb_displayed-1, nb_displayed-1 );

    if (nb_displayed > MIN_DISPLAYED)
    {
        SIZE size = get_window_size();
        SetWindowPos( tray_window, 0, 0, 0, size.cx, size.cy, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
    }
    else if (nb_displayed == 1)
    {
        if (!hide_systray) ShowWindow( tray_window, SW_SHOWNA );
    }
215

216
    create_tooltip(icon);
217 218 219 220 221 222 223
    return TRUE;
}

/* make an icon invisible */
static BOOL hide_icon(struct icon *icon)
{
    unsigned int i;
224

225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
    WINE_TRACE("id=0x%x, hwnd=%p\n", icon->id, icon->owner);

    if (icon->display == -1) return TRUE;  /* already hidden */

    assert( nb_displayed );
    for (i = icon->display; i < nb_displayed - 1; i++)
    {
        displayed[i] = displayed[i + 1];
        displayed[i]->display = i;
        update_tooltip_position( displayed[i] );
    }
    nb_displayed--;
    invalidate_icons( icon->display, nb_displayed );
    icon->display = -1;

    if (nb_displayed >= MIN_DISPLAYED)
    {
        SIZE size = get_window_size();
        SetWindowPos( tray_window, 0, 0, 0, size.cx, size.cy, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
    }
    else if (!nb_displayed)
    {
        ShowWindow( tray_window, SW_HIDE );
    }

    update_tooltip_position( icon );
251 252 253
    return TRUE;
}

254
/* Modifies an existing icon record */
255
static BOOL modify_icon( struct icon *icon, NOTIFYICONDATAW *nid )
256 257 258 259 260 261 262
{
    WINE_TRACE("id=0x%x, hwnd=%p\n", nid->uID, nid->hWnd);

    /* demarshal the request from the NID */
    if (!icon)
    {
        WINE_WARN("Invalid icon ID (0x%x) for HWND %p\n", nid->uID, nid->hWnd);
263
        return FALSE;
264 265
    }

266
    if ((nid->uFlags & NIF_STATE) && (nid->dwStateMask & NIS_HIDDEN))
267 268 269 270
    {
        if (nid->dwState & NIS_HIDDEN) hide_icon( icon );
        else show_icon( icon );
    }
271

272 273 274 275
    if (nid->uFlags & NIF_ICON)
    {
        if (icon->image) DestroyIcon(icon->image);
        icon->image = CopyIcon(nid->hIcon);
276
        if (icon->display != -1) invalidate_icons( icon->display, icon->display );
277 278 279 280 281 282
    }

    if (nid->uFlags & NIF_MESSAGE)
    {
        icon->callback_message = nid->uCallbackMessage;
    }
283 284
    if (nid->uFlags & NIF_TIP)
    {
285
        lstrcpynW(icon->tiptext, nid->szTip, sizeof(icon->tiptext)/sizeof(WCHAR));
286
        if (icon->display != -1) update_tooltip_text(icon);
287
    }
288 289 290 291
    if (nid->uFlags & NIF_INFO && nid->cbSize >= NOTIFYICONDATAA_V2_SIZE)
    {
        WINE_FIXME("balloon tip title %s, message %s\n", wine_dbgstr_w(nid->szInfoTitle), wine_dbgstr_w(nid->szInfo));
    }
292
    return TRUE;
293 294
}

295
/* Adds a new icon record to the list */
296
static BOOL add_icon(NOTIFYICONDATAW *nid)
297 298 299 300 301 302 303 304
{
    struct icon  *icon;

    WINE_TRACE("id=0x%x, hwnd=%p\n", nid->uID, nid->hWnd);

    if ((icon = get_icon(nid->hWnd, nid->uID)))
    {
        WINE_WARN("duplicate tray icon add, buggy app?\n");
305
        return FALSE;
306 307 308 309 310
    }

    if (!(icon = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*icon))))
    {
        WINE_ERR("out of memory\n");
311
        return FALSE;
312 313
    }

314
    ZeroMemory(icon, sizeof(struct icon));
315 316
    icon->id     = nid->uID;
    icon->owner  = nid->hWnd;
317
    icon->display = -1;
318

319
    list_add_tail(&icon_list, &icon->entry);
320

321 322 323 324
    modify_icon( icon, nid );
    /* show icon, unless hidden state was explicitly specified */
    if (!((nid->uFlags & NIF_STATE) && (nid->dwStateMask & NIS_HIDDEN))) show_icon( icon );
    return TRUE;
325 326
}

327
/* Deletes tray icon window and icon record */
328
static BOOL delete_icon(struct icon *icon)
329
{
330
    hide_icon(icon);
331 332 333 334 335 336
    list_remove(&icon->entry);
    DestroyIcon(icon->image);
    HeapFree(GetProcessHeap(), 0, icon);
    return TRUE;
}

337 338
/* cleanup icons belonging to windows that have been destroyed */
static void cleanup_destroyed_windows(void)
339
{
340
    struct icon *icon, *next;
341

342 343
    LIST_FOR_EACH_ENTRY_SAFE( icon, next, &icon_list, struct icon, entry )
        if (!IsWindow( icon->owner )) delete_icon( icon );
344 345
}

346
static BOOL handle_incoming(HWND hwndSource, COPYDATASTRUCT *cds)
347
{
348
    struct icon *icon = NULL;
349
    NOTIFYICONDATAW nid;
350
    DWORD cbSize;
351
    int ret = FALSE;
352

353
    if (cds->cbData < NOTIFYICONDATAW_V1_SIZE) return FALSE;
354
    cbSize = ((PNOTIFYICONDATAW)cds->lpData)->cbSize;
355
    if (cbSize < NOTIFYICONDATAW_V1_SIZE) return FALSE;
356 357 358

    ZeroMemory(&nid, sizeof(nid));
    memcpy(&nid, cds->lpData, min(sizeof(nid), cbSize));
359 360 361

    /* FIXME: if statement only needed because we don't support interprocess
     * icon handles */
362
    if ((nid.uFlags & NIF_ICON) && (cds->cbData >= nid.cbSize + 2 * sizeof(BITMAP)))
363 364 365 366 367 368 369
    {
        LONG cbMaskBits;
        LONG cbColourBits;
        BITMAP bmMask;
        BITMAP bmColour;
        const char *buffer = cds->lpData;

370
        buffer += nid.cbSize;
371 372 373 374 375 376 377 378 379

        memcpy(&bmMask, buffer, sizeof(bmMask));
        buffer += sizeof(bmMask);
        memcpy(&bmColour, buffer, sizeof(bmColour));
        buffer += sizeof(bmColour);

        cbMaskBits = (bmMask.bmPlanes * bmMask.bmWidth * bmMask.bmHeight * bmMask.bmBitsPixel) / 8;
        cbColourBits = (bmColour.bmPlanes * bmColour.bmWidth * bmColour.bmHeight * bmColour.bmBitsPixel) / 8;

380
        if (cds->cbData < nid.cbSize + 2 * sizeof(BITMAP) + cbMaskBits + cbColourBits)
381 382
        {
            WINE_ERR("buffer underflow\n");
383
            return FALSE;
384 385 386 387 388 389
        }

        /* sanity check */
        if ((bmColour.bmWidth != bmMask.bmWidth) || (bmColour.bmHeight != bmMask.bmHeight))
        {
            WINE_ERR("colour and mask bitmaps aren't consistent\n");
390
            return FALSE;
391 392 393 394 395 396 397
        }

        nid.hIcon = CreateIcon(NULL, bmColour.bmWidth, bmColour.bmHeight,
                               bmColour.bmPlanes, bmColour.bmBitsPixel,
                               buffer, buffer + cbMaskBits);
    }

398 399
    /* try forward to x11drv first */
    if (cds->dwData == NIM_ADD || !(icon = get_icon( nid.hWnd, nid.uID )))
400
    {
401
        if (wine_notify_icon && ((ret = wine_notify_icon( cds->dwData, &nid )) != -1))
402 403
        {
            if (nid.uFlags & NIF_ICON) DestroyIcon( nid.hIcon );
404
            return ret;
405
        }
406
        ret = FALSE;
407 408
    }

409 410 411
    switch (cds->dwData)
    {
    case NIM_ADD:
412
        ret = add_icon(&nid);
413 414
        break;
    case NIM_DELETE:
415
        if (icon) ret = delete_icon( icon );
416 417
        break;
    case NIM_MODIFY:
418
        if (icon) ret = modify_icon( icon, &nid );
419 420 421 422 423 424 425 426 427 428
        break;
    default:
        WINE_FIXME("unhandled tray message: %ld\n", cds->dwData);
        break;
    }

    /* FIXME: if statement only needed because we don't support interprocess
     * icon handles */
    if (nid.uFlags & NIF_ICON)
        DestroyIcon(nid.hIcon);
429 430

    return ret;
431 432
}

433 434 435 436 437 438 439 440
static void do_hide_systray(void)
{
    SetWindowPos( tray_window, 0,
                  GetSystemMetrics(SM_XVIRTUALSCREEN) + GetSystemMetrics(SM_CXVIRTUALSCREEN),
                  GetSystemMetrics(SM_YVIRTUALSCREEN) + GetSystemMetrics(SM_CYVIRTUALSCREEN),
                  0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE );
}

441
static LRESULT WINAPI tray_wndproc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
442 443 444
{
    switch (msg)
    {
445 446
    case WM_COPYDATA:
        return handle_incoming((HWND)wparam, (COPYDATASTRUCT *)lparam);
447

448 449 450 451
    case WM_DISPLAYCHANGE:
        if (hide_systray) do_hide_systray();
        break;

452 453 454
    case WM_TIMER:
        cleanup_destroyed_windows();
        break;
455

456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471
    case WM_PAINT:
        {
            unsigned int i;
            PAINTSTRUCT ps;
            HDC hdc;

            hdc = BeginPaint( hwnd, &ps );
            for (i = ps.rcPaint.left / icon_cx;
                 (i < (ps.rcPaint.right + icon_cx - 1) / icon_cx) && (i < nb_displayed);
                 i++)
            {
                DrawIconEx( hdc, i * icon_cx + ICON_BORDER, ICON_BORDER, displayed[i]->image,
                            icon_cx - 2*ICON_BORDER, icon_cy - 2*ICON_BORDER,
                            0, 0, DI_DEFAULTSIZE|DI_NORMAL);
            }
            EndPaint( hwnd, &ps );
472 473 474
            break;
        }

475 476 477 478 479 480 481 482 483 484 485 486 487 488 489
    case WM_MOUSEMOVE:
    case WM_LBUTTONDOWN:
    case WM_LBUTTONUP:
    case WM_RBUTTONDOWN:
    case WM_RBUTTONUP:
    case WM_MBUTTONDOWN:
    case WM_MBUTTONUP:
    case WM_LBUTTONDBLCLK:
    case WM_RBUTTONDBLCLK:
    case WM_MBUTTONDBLCLK:
        {
            MSG message;
            struct icon *icon = icon_from_point( (short)LOWORD(lparam), (short)HIWORD(lparam) );
            if (!icon) break;

490 491
            /* notify the owner hwnd of the message */
            WINE_TRACE("relaying 0x%x\n", msg);
492 493 494 495 496 497 498 499 500

            message.hwnd = hwnd;
            message.message = msg;
            message.wParam = wparam;
            message.lParam = lparam;
            SendMessageW( icon->tooltip, TTM_RELAYEVENT, 0, (LPARAM)&message );

            if (!PostMessageW( icon->owner, icon->callback_message, (WPARAM) icon->id, (LPARAM) msg ) &&
                GetLastError() == ERROR_INVALID_WINDOW_HANDLE)
501 502 503
            {
                WINE_WARN("application window was destroyed without removing "
                          "notification icon, removing automatically\n");
504
                delete_icon( icon );
505 506
            }
            break;
507
        }
508

509 510 511 512
    case WM_CLOSE:
        /* don't destroy the tray window, just hide it */
        ShowWindow( hwnd, SW_HIDE );
        return 0;
513

514 515
    default:
        return DefWindowProcW( hwnd, msg, wparam, lparam );
516 517 518 519
    }
    return 0;
}

520 521
static BOOL is_systray_hidden(void)
{
522 523 524
    const WCHAR show_systray_keyname[] = {'S','o','f','t','w','a','r','e','\\','W','i','n','e','\\',
                                          'X','1','1',' ','D','r','i','v','e','r',0};
    const WCHAR show_systray_valuename[] = {'S','h','o','w','S','y','s','t','r','a','y',0};
525 526 527
    HKEY hkey;
    BOOL ret = FALSE;

528 529
    /* @@ Wine registry key: HKCU\Software\Wine\X11 Driver */
    if (RegOpenKeyW(HKEY_CURRENT_USER, show_systray_keyname, &hkey) == ERROR_SUCCESS)
530
    {
531 532 533
        WCHAR value[10];
        DWORD type, size = sizeof(value);
        if (RegQueryValueExW(hkey, show_systray_valuename, 0, &type, (LPBYTE)&value, &size) == ERROR_SUCCESS)
534
        {
535
            ret = IS_OPTION_FALSE(value[0]);
536 537 538 539 540 541
        }
        RegCloseKey(hkey);
    }
    return ret;
}

542
/* this function creates the listener window */
543 544
void initialize_systray(void)
{
545
    HMODULE x11drv;
546
    SIZE size;
547
    WNDCLASSEXW class;
548 549
    static const WCHAR classname[] = {'S','h','e','l','l','_','T','r','a','y','W','n','d',0};
    static const WCHAR winname[] = {'W','i','n','e',' ','S','y','s','t','e','m',' ','T','r','a','y',0};
550

551 552 553
    if ((x11drv = GetModuleHandleA( "winex11.drv" )))
        wine_notify_icon = (void *)GetProcAddress( x11drv, "wine_notify_icon" );

554 555
    icon_cx = GetSystemMetrics( SM_CXSMICON ) + 2*ICON_BORDER;
    icon_cy = GetSystemMetrics( SM_CYSMICON ) + 2*ICON_BORDER;
556 557 558 559 560
    hide_systray = is_systray_hidden();

    /* register the systray listener window class */
    ZeroMemory(&class, sizeof(class));
    class.cbSize        = sizeof(class);
561 562
    class.style         = CS_DBLCLKS;
    class.lpfnWndProc   = tray_wndproc;
563
    class.hInstance     = NULL;
564 565
    class.hIcon         = LoadIconW(0, (LPCWSTR)IDI_WINLOGO);
    class.hCursor       = LoadCursorW(0, (LPCWSTR)IDC_ARROW);
566 567 568
    class.hbrBackground = (HBRUSH) COLOR_WINDOW;
    class.lpszClassName = (WCHAR *) &classname;

569
    if (!RegisterClassExW(&class))
570 571 572 573 574
    {
        WINE_ERR("Could not register SysTray window class\n");
        return;
    }

575 576 577 578
    size = get_window_size();
    tray_window = CreateWindowW( classname, winname, WS_OVERLAPPED | WS_CAPTION,
                                 CW_USEDEFAULT, CW_USEDEFAULT, size.cx, size.cy, 0, 0, 0, 0 );
    if (!tray_window)
579 580 581 582
    {
        WINE_ERR("Could not create tray window\n");
        return;
    }
583 584 585

    if (hide_systray) do_hide_systray();

586
    SetTimer( tray_window, 1, 2000, NULL );
587
}