[EXPLORER] Split up the notification area into a few more manageable pieces.
authorDavid Quintana <gigaherz@gmail.com>
Wed, 24 Jan 2018 13:41:06 +0000 (14:41 +0100)
committerDavid Quintana <gigaherz@gmail.com>
Wed, 24 Jan 2018 13:41:31 +0000 (14:41 +0100)
base/shell/explorer/CMakeLists.txt
base/shell/explorer/precomp.h
base/shell/explorer/syspager.cpp [new file with mode: 0644]
base/shell/explorer/syspager.h [new file with mode: 0644]
base/shell/explorer/trayclock.cpp [new file with mode: 0644]
base/shell/explorer/trayclock.h [new file with mode: 0644]
base/shell/explorer/trayntfy.cpp

index f935f4d..fa66d2e 100644 (file)
@@ -16,9 +16,11 @@ list(APPEND SOURCE
     startmnucust.cpp
     startmnusite.cpp
     startup.cpp
+    syspager.cpp
     taskband.cpp
     taskswnd.cpp
     tbsite.cpp
+    trayclock.cpp
     trayntfy.cpp
     trayprop.cpp
     traywnd.cpp
index 9aaa351..1aacb42 100644 (file)
@@ -384,4 +384,16 @@ Tray_OnStartMenuDismissed(ITrayWindow* Tray);
 HRESULT
 IsSameObject(IN IUnknown *punk1, IN IUnknown *punk2);
 
+/*
+* syspager.c
+*/
+
+#include "syspager.h"
+
+/*
+* trayclock.c
+*/
+
+#include "trayclock.h"
+
 #endif /* _EXPLORER_PRECOMP__H_ */
diff --git a/base/shell/explorer/syspager.cpp b/base/shell/explorer/syspager.cpp
new file mode 100644 (file)
index 0000000..7a55360
--- /dev/null
@@ -0,0 +1,1228 @@
+/*
+ * ReactOS Explorer
+ *
+ * Copyright 2006 - 2007 Thomas Weidenmueller <w3seek@reactos.org>
+ * Copyright 2018 Ged Murphy <gedmurphy@reactos.org>
+ *
+ * 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
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "precomp.h"
+
+// Data comes from shell32/systray.cpp -> TrayNotifyCDS_Dummy
+typedef struct _SYS_PAGER_COPY_DATA
+{
+    DWORD           cookie;
+    DWORD           notify_code;
+    NOTIFYICONDATA  nicon_data;
+} SYS_PAGER_COPY_DATA, *PSYS_PAGER_COPY_DATA;
+
+CIconWatcher::CIconWatcher() :
+    m_hWatcherThread(NULL),
+    m_WakeUpEvent(NULL),
+    m_hwndSysTray(NULL),
+    m_Loop(false)
+{
+}
+
+CIconWatcher::~CIconWatcher()
+{
+    Uninitialize();
+    DeleteCriticalSection(&m_ListLock);
+
+    if (m_WakeUpEvent)
+        CloseHandle(m_WakeUpEvent);
+    if (m_hWatcherThread)
+        CloseHandle(m_hWatcherThread);
+}
+
+bool CIconWatcher::Initialize(_In_ HWND hWndParent)
+{
+    m_hwndSysTray = hWndParent;
+
+    InitializeCriticalSection(&m_ListLock);
+    m_WakeUpEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
+    if (m_WakeUpEvent == NULL)
+        return false;
+
+    m_hWatcherThread = (HANDLE)_beginthreadex(NULL,
+                                                0,
+                                                WatcherThread,
+                                                (LPVOID)this,
+                                                0,
+                                                NULL);
+    if (m_hWatcherThread == NULL)
+        return false;
+
+    return true;
+}
+
+void CIconWatcher::Uninitialize()
+{
+    m_Loop = false;
+    if (m_WakeUpEvent)
+        SetEvent(m_WakeUpEvent);
+
+    EnterCriticalSection(&m_ListLock);
+
+    POSITION Pos;
+    for (size_t i = 0; i < m_WatcherList.GetCount(); i++)
+    {
+        Pos = m_WatcherList.FindIndex(i);
+        if (Pos)
+        {
+            IconWatcherData *Icon;
+            Icon = m_WatcherList.GetAt(Pos);
+            delete Icon;
+        }
+    }
+    m_WatcherList.RemoveAll();
+
+    LeaveCriticalSection(&m_ListLock);
+}
+
+bool CIconWatcher::AddIconToWatcher(_In_ NOTIFYICONDATA *iconData)
+{
+    DWORD ProcessId;
+    (void)GetWindowThreadProcessId(iconData->hWnd, &ProcessId);
+
+    HANDLE hProcess;
+    hProcess = OpenProcess(SYNCHRONIZE, FALSE, ProcessId);
+    if (hProcess == NULL)
+    {
+        return false;
+    }
+
+    IconWatcherData *Icon = new IconWatcherData(iconData);
+    Icon->hProcess = hProcess;
+    Icon->ProcessId;
+
+    bool Added = false;
+    EnterCriticalSection(&m_ListLock);
+
+    // The likelyhood of someone having more than 64 icons in their tray is
+    // pretty slim. We could spin up a new thread for each multiple of 64, but
+    // it's not worth the effort, so we just won't bother watching those icons
+    if (m_WatcherList.GetCount() < MAXIMUM_WAIT_OBJECTS)
+    {
+        m_WatcherList.AddTail(Icon);
+        SetEvent(m_WakeUpEvent);
+        Added = true;
+    }
+
+    LeaveCriticalSection(&m_ListLock);
+
+    if (!Added)
+    {
+        delete Icon;
+    }
+
+    return Added;
+}
+
+bool CIconWatcher::RemoveIconFromWatcher(_In_ NOTIFYICONDATA *iconData)
+{
+    EnterCriticalSection(&m_ListLock);
+        
+    IconWatcherData *Icon;
+    Icon = GetListEntry(iconData, NULL, true);
+
+    SetEvent(m_WakeUpEvent);
+    LeaveCriticalSection(&m_ListLock);
+
+    delete Icon;
+    return true;
+}
+
+IconWatcherData* CIconWatcher::GetListEntry(_In_opt_ NOTIFYICONDATA *iconData, _In_opt_ HANDLE hProcess, _In_ bool Remove)
+{
+    IconWatcherData *Entry = NULL;
+    POSITION NextPosition = m_WatcherList.GetHeadPosition();
+    POSITION Position;
+    do
+    {
+        Position = NextPosition;
+
+        Entry = m_WatcherList.GetNext(NextPosition);
+        if (Entry)
+        {
+            if ((iconData && ((Entry->IconData.hWnd == iconData->hWnd) && (Entry->IconData.uID == iconData->uID))) ||
+                    (hProcess && (Entry->hProcess == hProcess)))
+            {
+                if (Remove)
+                    m_WatcherList.RemoveAt(Position);
+                break;
+            }
+        }
+        Entry = NULL;
+
+    } while (NextPosition != NULL);
+
+    return Entry;
+}
+
+UINT WINAPI CIconWatcher::WatcherThread(_In_opt_ LPVOID lpParam)
+{
+    CIconWatcher* This = reinterpret_cast<CIconWatcher *>(lpParam);
+    HANDLE *WatchList = NULL;
+
+    This->m_Loop = true;
+    while (This->m_Loop)
+    {
+        EnterCriticalSection(&This->m_ListLock);
+
+        DWORD Size;
+        Size = This->m_WatcherList.GetCount() + 1;
+        ASSERT(Size <= MAXIMUM_WAIT_OBJECTS);
+
+        if (WatchList)
+            delete WatchList;
+        WatchList = new HANDLE[Size];
+        WatchList[0] = This->m_WakeUpEvent;
+
+        POSITION Pos;
+        for (size_t i = 0; i < This->m_WatcherList.GetCount(); i++)
+        {
+            Pos = This->m_WatcherList.FindIndex(i);
+            if (Pos)
+            {
+                IconWatcherData *Icon;
+                Icon = This->m_WatcherList.GetAt(Pos);
+                WatchList[i + 1] = Icon->hProcess;
+            }
+        }
+
+        LeaveCriticalSection(&This->m_ListLock);
+
+        DWORD Status;
+        Status = WaitForMultipleObjects(Size,
+                                        WatchList,
+                                        FALSE,
+                                        INFINITE);
+        if (Status == WAIT_OBJECT_0)
+        {
+            // We've been kicked, we have updates to our list (or we're exiting the thread)
+            if (This->m_Loop)
+                TRACE("Updating watched icon list");
+        }
+        else if ((Status >= WAIT_OBJECT_0 + 1) && (Status < Size))
+        {
+            IconWatcherData *Icon;
+            Icon = This->GetListEntry(NULL, WatchList[Status], false);
+
+            TRACE("Pid %lu owns a notification icon and has stopped without deleting it. We'll cleanup on its behalf", Icon->ProcessId);
+
+            int len = FIELD_OFFSET(SYS_PAGER_COPY_DATA, nicon_data) + Icon->IconData.cbSize;
+            PSYS_PAGER_COPY_DATA pnotify_data = (PSYS_PAGER_COPY_DATA)new BYTE[len];
+            pnotify_data->cookie = 1;
+            pnotify_data->notify_code = NIM_DELETE;
+            memcpy(&pnotify_data->nicon_data, &Icon->IconData, Icon->IconData.cbSize);
+
+            COPYDATASTRUCT data;
+            data.dwData = 1;
+            data.cbData = len;
+            data.lpData = pnotify_data;
+
+            BOOL Success = FALSE;
+            HWND parentHWND = ::GetParent(GetParent(This->m_hwndSysTray));
+            if (parentHWND)
+                Success = ::SendMessage(parentHWND, WM_COPYDATA, (WPARAM)&Icon->IconData, (LPARAM)&data);
+
+            delete pnotify_data;
+
+            if (!Success)
+            {
+                // If we failed to handle the delete message, forcibly remove it
+                This->RemoveIconFromWatcher(&Icon->IconData);
+            }
+        }
+        else
+        {
+            if (Status == WAIT_FAILED)
+            {
+                Status = GetLastError();
+            }
+            ERR("Failed to wait on process handles : %lu\n", Status);
+            This->Uninitialize();
+        }
+    }
+
+    if (WatchList)
+        delete WatchList;
+
+    return 0;
+}
+
+/*
+* NotifyToolbar
+*/
+
+CBalloonQueue::CBalloonQueue() :
+    m_hwndParent(NULL),
+    m_tooltips(NULL),
+    m_toolbar(NULL),
+    m_current(NULL),
+    m_currentClosed(false),
+    m_timer(-1)
+{
+}
+
+void CBalloonQueue::Init(HWND hwndParent, CToolbar<InternalIconData> * toolbar, CTooltips * balloons)
+{
+    m_hwndParent = hwndParent;
+    m_toolbar = toolbar;
+    m_tooltips = balloons;
+}
+
+void CBalloonQueue::Deinit()
+{
+    if (m_timer >= 0)
+    {
+        ::KillTimer(m_hwndParent, m_timer);
+    }
+}
+
+bool CBalloonQueue::OnTimer(int timerId)
+{
+    if (timerId != m_timer)
+        return false;
+
+    ::KillTimer(m_hwndParent, m_timer);
+    m_timer = -1;
+
+    if (m_current && !m_currentClosed)
+    {
+        Close(m_current);
+    }
+    else
+    {
+        m_current = NULL;
+        m_currentClosed = false;
+        if (!m_queue.IsEmpty())
+        {
+            Info info = m_queue.RemoveHead();
+            Show(info);
+        }
+    }
+
+    return true;
+}
+
+void CBalloonQueue::UpdateInfo(InternalIconData * notifyItem)
+{
+    size_t len = 0;
+    HRESULT hr = StringCchLength(notifyItem->szInfo, _countof(notifyItem->szInfo), &len);
+    if (SUCCEEDED(hr) && len > 0)
+    {
+        Info info(notifyItem);
+
+        // If m_current == notifyItem, we want to replace the previous balloon even if there is a queue.
+        if (m_current != notifyItem && (m_current != NULL || !m_queue.IsEmpty()))
+        {
+            m_queue.AddTail(info);
+        }
+        else
+        {
+            Show(info);
+        }
+    }
+    else
+    {
+        Close(notifyItem);
+    }
+}
+
+void CBalloonQueue::RemoveInfo(InternalIconData * notifyItem)
+{
+    Close(notifyItem);
+
+    POSITION position = m_queue.GetHeadPosition();
+    while(position != NULL)
+    {
+        Info& info = m_queue.GetNext(position);
+        if (info.pSource == notifyItem)
+        {
+            m_queue.RemoveAt(position);
+        }
+    }
+}
+
+void CBalloonQueue::CloseCurrent()
+{
+    if (m_current != NULL)
+        Close(m_current);
+}
+
+int CBalloonQueue::IndexOf(InternalIconData * pdata)
+{
+    int count = m_toolbar->GetButtonCount();
+    for (int i = 0; i < count; i++)
+    {
+        if (m_toolbar->GetItemData(i) == pdata)
+            return i;
+    }
+    return -1;
+}
+
+void CBalloonQueue::SetTimer(int length)
+{
+    m_timer = ::SetTimer(m_hwndParent, BalloonsTimerId, length, NULL);
+}
+
+void CBalloonQueue::Show(Info& info)
+{
+    TRACE("ShowBalloonTip called for flags=%x text=%ws; title=%ws\n", info.uIcon, info.szInfo, info.szInfoTitle);
+
+    // TODO: NIF_REALTIME, NIIF_NOSOUND, other Vista+ flags
+
+    const int index = IndexOf(info.pSource);
+    RECT rc;
+    m_toolbar->GetItemRect(index, &rc);
+    m_toolbar->ClientToScreen(&rc);
+    const WORD x = (rc.left + rc.right) / 2;
+    const WORD y = (rc.top + rc.bottom) / 2;
+
+    m_tooltips->SetTitle(info.szInfoTitle, info.uIcon);
+    m_tooltips->TrackPosition(x, y);
+    m_tooltips->UpdateTipText(m_hwndParent, reinterpret_cast<LPARAM>(m_toolbar->m_hWnd), info.szInfo);
+    m_tooltips->TrackActivate(m_hwndParent, reinterpret_cast<LPARAM>(m_toolbar->m_hWnd));
+
+    m_current = info.pSource;
+    int timeout = info.uTimeout;
+    if (timeout < MinTimeout) timeout = MinTimeout;
+    if (timeout > MaxTimeout) timeout = MaxTimeout;
+
+    SetTimer(timeout);
+}
+
+void CBalloonQueue::Close(IN OUT InternalIconData * notifyItem)
+{
+    TRACE("HideBalloonTip called\n");
+
+    if (m_current == notifyItem && !m_currentClosed)
+    {
+        // Prevent Re-entry
+        m_currentClosed = true;
+        m_tooltips->TrackDeactivate();
+        SetTimer(CooldownBetweenBalloons);
+    }
+}
+
+/*
+ * NotifyToolbar
+ */
+
+CNotifyToolbar::CNotifyToolbar() :
+    m_ImageList(NULL),
+    m_VisibleButtonCount(0),
+    m_BalloonQueue(NULL)
+{
+}
+
+CNotifyToolbar::~CNotifyToolbar()
+{
+}
+
+int CNotifyToolbar::GetVisibleButtonCount()
+{
+    return m_VisibleButtonCount;
+}
+
+int CNotifyToolbar::FindItem(IN HWND hWnd, IN UINT uID, InternalIconData ** pdata)
+{
+    int count = GetButtonCount();
+
+    for (int i = 0; i < count; i++)
+    {
+        InternalIconData * data = GetItemData(i);
+
+        if (data->hWnd == hWnd &&
+            data->uID == uID)
+        {
+            if (pdata)
+                *pdata = data;
+            return i;
+        }
+    }
+
+    return -1;
+}
+
+int CNotifyToolbar::FindExistingSharedIcon(HICON handle)
+{
+    int count = GetButtonCount();
+    for (int i = 0; i < count; i++)
+    {
+        InternalIconData * data = GetItemData(i);
+        if (data->hIcon == handle)
+        {
+            TBBUTTON btn;
+            GetButton(i, &btn);
+            return btn.iBitmap;
+        }
+    }
+
+    return -1;
+}
+
+BOOL CNotifyToolbar::AddButton(IN CONST NOTIFYICONDATA *iconData)
+{
+    TBBUTTON tbBtn;
+    InternalIconData * notifyItem;
+    WCHAR text[] = L"";
+
+    TRACE("Adding icon %d from hWnd %08x flags%s%s state%s%s", 
+        iconData->uID, iconData->hWnd,
+        (iconData->uFlags & NIF_ICON) ? " ICON" : "",
+        (iconData->uFlags & NIF_STATE) ? " STATE" : "",
+        (iconData->dwState & NIS_HIDDEN) ? " HIDDEN" : "",
+        (iconData->dwState & NIS_SHAREDICON) ? " SHARED" : "");
+
+    int index = FindItem(iconData->hWnd, iconData->uID, &notifyItem);
+    if (index >= 0)
+    {
+        TRACE("Icon %d from hWnd %08x ALREADY EXISTS!", iconData->uID, iconData->hWnd);
+        return FALSE;
+    }
+
+    notifyItem = new InternalIconData();
+    ZeroMemory(notifyItem, sizeof(*notifyItem));
+
+    notifyItem->hWnd = iconData->hWnd;
+    notifyItem->uID = iconData->uID;
+
+    tbBtn.fsState = TBSTATE_ENABLED;
+    tbBtn.fsStyle = BTNS_NOPREFIX;
+    tbBtn.dwData = (DWORD_PTR)notifyItem;
+    tbBtn.iString = (INT_PTR) text;
+    tbBtn.idCommand = GetButtonCount();
+
+    if (iconData->uFlags & NIF_STATE)
+    {
+        notifyItem->dwState = iconData->dwState & iconData->dwStateMask;
+    }
+
+    if (iconData->uFlags & NIF_MESSAGE)
+    {
+        notifyItem->uCallbackMessage = iconData->uCallbackMessage;
+    }
+
+    if (iconData->uFlags & NIF_ICON)
+    {
+        notifyItem->hIcon = iconData->hIcon;
+        BOOL hasSharedIcon = notifyItem->dwState & NIS_SHAREDICON;
+        if (hasSharedIcon)
+        {
+            INT iIcon = FindExistingSharedIcon(notifyItem->hIcon);
+            if (iIcon < 0)
+            {
+                notifyItem->hIcon = NULL;
+                TRACE("Shared icon requested, but HICON not found!!!");
+            }
+            tbBtn.iBitmap = iIcon;
+        }
+        else
+        {
+            tbBtn.iBitmap = ImageList_AddIcon(m_ImageList, notifyItem->hIcon);
+        }
+    }
+
+    if (iconData->uFlags & NIF_TIP)
+    {
+        StringCchCopy(notifyItem->szTip, _countof(notifyItem->szTip), iconData->szTip);
+    }
+
+    if (iconData->uFlags & NIF_INFO)
+    {
+        // NOTE: In Vista+, the uTimeout value is disregarded, and the accessibility settings are used always.
+        StrNCpy(notifyItem->szInfo, iconData->szInfo, _countof(notifyItem->szInfo));
+        StrNCpy(notifyItem->szInfoTitle, iconData->szInfoTitle, _countof(notifyItem->szInfo));
+        notifyItem->dwInfoFlags = iconData->dwInfoFlags;
+        notifyItem->uTimeout = iconData->uTimeout;
+    }
+
+    if (notifyItem->dwState & NIS_HIDDEN)
+    {
+        tbBtn.fsState |= TBSTATE_HIDDEN;
+    }
+    else
+    {
+        m_VisibleButtonCount++;
+    }
+
+    /* TODO: support VERSION_4 (NIF_GUID, NIF_REALTIME, NIF_SHOWTIP) */
+
+    CToolbar::AddButton(&tbBtn);
+    SetButtonSize(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
+
+    if (iconData->uFlags & NIF_INFO)
+    {
+        m_BalloonQueue->UpdateInfo(notifyItem);
+    }
+
+    return TRUE;
+}
+
+BOOL CNotifyToolbar::SwitchVersion(IN CONST NOTIFYICONDATA *iconData)
+{
+    InternalIconData * notifyItem;
+    int index = FindItem(iconData->hWnd, iconData->uID, &notifyItem);
+    if (index < 0)
+    {
+        WARN("Icon %d from hWnd %08x DOES NOT EXIST!", iconData->uID, iconData->hWnd);
+        return FALSE;
+    }
+
+    if (iconData->uVersion != 0 && iconData->uVersion != NOTIFYICON_VERSION)
+    {
+        WARN("Tried to set the version of icon %d from hWnd %08x, to an unknown value %d. Vista+ program?", iconData->uID, iconData->hWnd, iconData->uVersion);
+        return FALSE;
+    }
+
+    // We can not store the version in the uVersion field, because it's union'd with uTimeout,
+    // which we also need to keep track of.
+    notifyItem->uVersionCopy = iconData->uVersion;
+
+    return TRUE;
+}
+
+BOOL CNotifyToolbar::UpdateButton(IN CONST NOTIFYICONDATA *iconData)
+{
+    InternalIconData * notifyItem;
+    TBBUTTONINFO tbbi = { 0 };
+
+    TRACE("Updating icon %d from hWnd %08x flags%s%s state%s%s",
+        iconData->uID, iconData->hWnd,
+        (iconData->uFlags & NIF_ICON) ? " ICON" : "",
+        (iconData->uFlags & NIF_STATE) ? " STATE" : "",
+        (iconData->dwState & NIS_HIDDEN) ? " HIDDEN" : "",
+        (iconData->dwState & NIS_SHAREDICON) ? " SHARED" : "");
+
+    int index = FindItem(iconData->hWnd, iconData->uID, &notifyItem);
+    if (index < 0)
+    {
+        WARN("Icon %d from hWnd %08x DOES NOT EXIST!", iconData->uID, iconData->hWnd);
+        return AddButton(iconData);
+    }
+
+    TBBUTTON btn;
+    GetButton(index, &btn);
+    int oldIconIndex = btn.iBitmap;
+
+    tbbi.cbSize = sizeof(tbbi);
+    tbbi.dwMask = TBIF_BYINDEX | TBIF_COMMAND;
+    tbbi.idCommand = index;
+
+    if (iconData->uFlags & NIF_STATE)
+    {
+        if (iconData->dwStateMask & NIS_HIDDEN &&
+            (notifyItem->dwState & NIS_HIDDEN) != (iconData->dwState & NIS_HIDDEN))
+        {
+            tbbi.dwMask |= TBIF_STATE;
+            if (iconData->dwState & NIS_HIDDEN)
+            {
+                tbbi.fsState |= TBSTATE_HIDDEN;
+                m_VisibleButtonCount--;
+            }
+            else
+            {
+                tbbi.fsState &= ~TBSTATE_HIDDEN;
+                m_VisibleButtonCount++;
+            }
+        }
+
+        notifyItem->dwState &= ~iconData->dwStateMask;
+        notifyItem->dwState |= (iconData->dwState & iconData->dwStateMask);
+    }
+
+    if (iconData->uFlags & NIF_MESSAGE)
+    {
+        notifyItem->uCallbackMessage = iconData->uCallbackMessage;
+    }
+
+    if (iconData->uFlags & NIF_ICON)
+    {
+        BOOL hasSharedIcon = notifyItem->dwState & NIS_SHAREDICON;
+        if (hasSharedIcon)
+        {
+            INT iIcon = FindExistingSharedIcon(iconData->hIcon);
+            if (iIcon >= 0)
+            {
+                notifyItem->hIcon = iconData->hIcon;
+                tbbi.dwMask |= TBIF_IMAGE;
+                tbbi.iImage = iIcon;
+            }
+            else
+            {
+                TRACE("Shared icon requested, but HICON not found!!! IGNORING!");
+            }
+        }
+        else
+        {
+            notifyItem->hIcon = iconData->hIcon;
+            tbbi.dwMask |= TBIF_IMAGE;
+            tbbi.iImage = ImageList_ReplaceIcon(m_ImageList, oldIconIndex, notifyItem->hIcon);
+        }
+    }
+
+    if (iconData->uFlags & NIF_TIP)
+    {
+        StringCchCopy(notifyItem->szTip, _countof(notifyItem->szTip), iconData->szTip);
+    }
+
+    if (iconData->uFlags & NIF_INFO)
+    {
+        // NOTE: In Vista+, the uTimeout value is disregarded, and the accessibility settings are used always.
+        StrNCpy(notifyItem->szInfo, iconData->szInfo, _countof(notifyItem->szInfo));
+        StrNCpy(notifyItem->szInfoTitle, iconData->szInfoTitle, _countof(notifyItem->szInfo));
+        notifyItem->dwInfoFlags = iconData->dwInfoFlags;
+        notifyItem->uTimeout = iconData->uTimeout;
+    }
+
+    /* TODO: support VERSION_4 (NIF_GUID, NIF_REALTIME, NIF_SHOWTIP) */
+
+    SetButtonInfo(index, &tbbi);
+
+    if (iconData->uFlags & NIF_INFO)
+    {
+        m_BalloonQueue->UpdateInfo(notifyItem);
+    }
+
+    return TRUE;
+}
+
+BOOL CNotifyToolbar::RemoveButton(IN CONST NOTIFYICONDATA *iconData)
+{
+    InternalIconData * notifyItem;
+
+    TRACE("Removing icon %d from hWnd %08x", iconData->uID, iconData->hWnd);
+
+    int index = FindItem(iconData->hWnd, iconData->uID, &notifyItem);
+    if (index < 0)
+    {
+        TRACE("Icon %d from hWnd %08x ALREADY MISSING!", iconData->uID, iconData->hWnd);
+
+        return FALSE;
+    }
+
+    if (!(notifyItem->dwState & NIS_HIDDEN))
+    {
+        m_VisibleButtonCount--;
+    }
+
+    if (!(notifyItem->dwState & NIS_SHAREDICON))
+    {
+        TBBUTTON btn;
+        GetButton(index, &btn);
+        int oldIconIndex = btn.iBitmap;
+        ImageList_Remove(m_ImageList, oldIconIndex);
+
+        // Update other icons!
+        int count = GetButtonCount();
+        for (int i = 0; i < count; i++)
+        {
+            TBBUTTON btn;
+            GetButton(i, &btn);
+
+            if (btn.iBitmap > oldIconIndex)
+            {
+                TBBUTTONINFO tbbi2 = { 0 };
+                tbbi2.cbSize = sizeof(tbbi2);
+                tbbi2.dwMask = TBIF_BYINDEX | TBIF_IMAGE;
+                tbbi2.iImage = btn.iBitmap-1;
+                SetButtonInfo(i, &tbbi2);
+            }
+        }
+    }
+
+    m_BalloonQueue->RemoveInfo(notifyItem);
+
+    DeleteButton(index);
+
+    delete notifyItem;
+
+    return TRUE;
+}
+
+VOID CNotifyToolbar::ResizeImagelist()
+{
+    int cx, cy;
+    HIMAGELIST iml;
+
+    if (!ImageList_GetIconSize(m_ImageList, &cx, &cy))
+        return;
+
+    if (cx == GetSystemMetrics(SM_CXSMICON) && cy == GetSystemMetrics(SM_CYSMICON))
+        return;
+
+    iml = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32 | ILC_MASK, 0, 1000);
+    if (!iml)
+        return;
+
+    ImageList_Destroy(m_ImageList);
+    m_ImageList = iml;
+    SetImageList(m_ImageList);
+
+    int count = GetButtonCount();
+    for (int i = 0; i < count; i++)
+    {
+        InternalIconData * data = GetItemData(i);
+        BOOL hasSharedIcon = data->dwState & NIS_SHAREDICON;
+        INT iIcon = hasSharedIcon ? FindExistingSharedIcon(data->hIcon) : -1;
+        if (iIcon < 0)
+            iIcon = ImageList_AddIcon(iml, data->hIcon);
+        TBBUTTONINFO tbbi = { sizeof(tbbi), TBIF_BYINDEX | TBIF_IMAGE, 0, iIcon};
+        SetButtonInfo(i, &tbbi);
+    }
+
+    SetButtonSize(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
+}
+
+VOID CNotifyToolbar::SendMouseEvent(IN WORD wIndex, IN UINT uMsg, IN WPARAM wParam)
+{
+    static LPCWSTR eventNames [] = {
+        L"WM_MOUSEMOVE",
+        L"WM_LBUTTONDOWN",
+        L"WM_LBUTTONUP",
+        L"WM_LBUTTONDBLCLK",
+        L"WM_RBUTTONDOWN",
+        L"WM_RBUTTONUP",
+        L"WM_RBUTTONDBLCLK",
+        L"WM_MBUTTONDOWN",
+        L"WM_MBUTTONUP",
+        L"WM_MBUTTONDBLCLK",
+        L"WM_MOUSEWHEEL",
+        L"WM_XBUTTONDOWN",
+        L"WM_XBUTTONUP",
+        L"WM_XBUTTONDBLCLK"
+    };
+
+    InternalIconData * notifyItem = GetItemData(wIndex);
+
+    if (!::IsWindow(notifyItem->hWnd))
+    {
+        // We detect and destroy icons with invalid handles only on mouse move over systray, same as MS does.
+        // Alternatively we could search for them periodically (would waste more resources).
+        TRACE("Destroying icon %d with invalid handle hWnd=%08x\n", notifyItem->uID, notifyItem->hWnd);
+
+        RemoveButton(notifyItem);
+
+        HWND parentHWND = ::GetParent(::GetParent(GetParent()));
+        ::SendMessage(parentHWND, WM_SIZE, 0, 0);
+
+        return;
+    }
+
+    if (uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST)
+    {
+        TRACE("Sending message %S from button %d to %p (msg=%x, w=%x, l=%x)...\n",
+                    eventNames[uMsg - WM_MOUSEFIRST], wIndex,
+                    notifyItem->hWnd, notifyItem->uCallbackMessage, notifyItem->uID, uMsg);
+    }
+
+    DWORD pid;
+    GetWindowThreadProcessId(notifyItem->hWnd, &pid);
+
+    if (pid == GetCurrentProcessId() ||
+        (uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST))
+    {
+        ::PostMessage(notifyItem->hWnd,
+                        notifyItem->uCallbackMessage,
+                        notifyItem->uID,
+                        uMsg);
+    }
+    else
+    {
+        SendMessage(notifyItem->hWnd,
+                    notifyItem->uCallbackMessage,
+                    notifyItem->uID,
+                    uMsg);
+    }
+}
+
+LRESULT CNotifyToolbar::OnMouseEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+    POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
+
+    INT iBtn = HitTest(&pt);
+
+    if (iBtn >= 0)
+    {
+        SendMouseEvent(iBtn, uMsg, wParam);
+    }
+
+    bHandled = FALSE;
+    return FALSE;
+}
+
+static VOID GetTooltipText(LPARAM data, LPTSTR szTip, DWORD cchTip)
+{
+    InternalIconData * notifyItem = reinterpret_cast<InternalIconData *>(data);
+    if (notifyItem)
+    {
+        StringCchCopy(szTip, cchTip, notifyItem->szTip);
+    }
+    else
+    {
+        StringCchCopy(szTip, cchTip, L"");
+    }
+}
+
+LRESULT CNotifyToolbar::OnTooltipShow(INT uCode, LPNMHDR hdr, BOOL& bHandled)
+{
+    RECT rcTip, rcItem;
+    ::GetWindowRect(hdr->hwndFrom, &rcTip);
+
+    SIZE szTip = { rcTip.right - rcTip.left, rcTip.bottom - rcTip.top };
+
+    INT iBtn = GetHotItem();
+
+    if (iBtn >= 0)
+    {
+        MONITORINFO monInfo = { 0 };
+        HMONITOR hMon = MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTONEAREST);
+
+        monInfo.cbSize = sizeof(monInfo);
+
+        if (hMon)
+            GetMonitorInfo(hMon, &monInfo);
+        else
+            ::GetWindowRect(GetDesktopWindow(), &monInfo.rcMonitor);
+
+        GetItemRect(iBtn, &rcItem);
+
+        POINT ptItem = { rcItem.left, rcItem.top };
+        SIZE szItem = { rcItem.right - rcItem.left, rcItem.bottom - rcItem.top };
+        ClientToScreen(&ptItem);
+
+        ptItem.x += szItem.cx / 2;
+        ptItem.y -= szTip.cy;
+
+        if (ptItem.x + szTip.cx > monInfo.rcMonitor.right)
+            ptItem.x = monInfo.rcMonitor.right - szTip.cx;
+
+        if (ptItem.y + szTip.cy > monInfo.rcMonitor.bottom)
+            ptItem.y = monInfo.rcMonitor.bottom - szTip.cy;
+
+        if (ptItem.x < monInfo.rcMonitor.left)
+            ptItem.x = monInfo.rcMonitor.left;
+
+        if (ptItem.y < monInfo.rcMonitor.top)
+            ptItem.y = monInfo.rcMonitor.top;
+
+        TRACE("ptItem { %d, %d }\n", ptItem.x, ptItem.y);
+
+        ::SetWindowPos(hdr->hwndFrom, NULL, ptItem.x, ptItem.y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
+
+        return TRUE;
+    }
+
+    bHandled = FALSE;
+    return 0;
+}
+
+void CNotifyToolbar::Initialize(HWND hWndParent, CBalloonQueue * queue)
+{
+    m_BalloonQueue = queue;
+
+    DWORD styles =
+        WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN |
+        TBSTYLE_FLAT | TBSTYLE_TOOLTIPS | TBSTYLE_WRAPABLE | TBSTYLE_TRANSPARENT |
+        CCS_TOP | CCS_NORESIZE | CCS_NOPARENTALIGN | CCS_NODIVIDER;
+
+    SubclassWindow(CToolbar::Create(hWndParent, styles));
+
+    // Force the toolbar tooltips window to always show tooltips even if not foreground
+    HWND tooltipsWnd = (HWND)SendMessageW(TB_GETTOOLTIPS);
+    if (tooltipsWnd)
+    {
+        ::SetWindowLong(tooltipsWnd, GWL_STYLE, ::GetWindowLong(tooltipsWnd, GWL_STYLE) | TTS_ALWAYSTIP);
+    }
+
+    SetWindowTheme(m_hWnd, L"TrayNotify", NULL);
+
+    m_ImageList = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32 | ILC_MASK, 0, 1000);        
+    SetImageList(m_ImageList);
+
+    TBMETRICS tbm = {sizeof(tbm)};
+    tbm.dwMask = TBMF_BARPAD | TBMF_BUTTONSPACING | TBMF_PAD;
+    tbm.cxPad = 1;
+    tbm.cyPad = 1;
+    tbm.cxButtonSpacing = 1;
+    tbm.cyButtonSpacing = 1;
+    SetMetrics(&tbm);
+
+    SetButtonSize(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
+}
+
+/*
+ * SysPagerWnd
+ */
+const WCHAR szSysPagerWndClass[] = L"SysPager";
+
+CSysPagerWnd::CSysPagerWnd() {}
+CSysPagerWnd::~CSysPagerWnd() {}
+
+LRESULT CSysPagerWnd::DrawBackground(HDC hdc)
+{
+    RECT rect;
+
+    GetClientRect(&rect);
+    DrawThemeParentBackground(m_hWnd, hdc, &rect);
+
+    return TRUE;
+}
+
+LRESULT CSysPagerWnd::OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+    HDC hdc = (HDC) wParam;
+
+    if (!IsAppThemed())
+    {
+        bHandled = FALSE;
+        return 0;
+    }
+
+    return DrawBackground(hdc);
+}
+
+LRESULT CSysPagerWnd::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+    Toolbar.Initialize(m_hWnd, &m_BalloonQueue);
+    CIconWatcher::Initialize(m_hWnd);
+
+    HWND hWndTop = GetAncestor(m_hWnd, GA_ROOT);
+
+    m_Balloons.Create(hWndTop, TTS_NOPREFIX | TTS_BALLOON | TTS_CLOSE);
+        
+    TOOLINFOW ti = { 0 };
+    ti.cbSize = TTTOOLINFOW_V1_SIZE;
+    ti.uFlags = TTF_TRACK | TTF_IDISHWND;
+    ti.uId = reinterpret_cast<UINT_PTR>(Toolbar.m_hWnd);
+    ti.hwnd = m_hWnd;
+    ti.lpszText = NULL;
+    ti.lParam = NULL;
+
+    BOOL ret = m_Balloons.AddTool(&ti);
+    if (!ret)
+    {
+        WARN("AddTool failed, LastError=%d (probably meaningless unless non-zero)\n", GetLastError());
+    }
+
+    m_BalloonQueue.Init(m_hWnd, &Toolbar, &m_Balloons);
+
+    // Explicitly request running applications to re-register their systray icons
+    ::SendNotifyMessageW(HWND_BROADCAST,
+                            RegisterWindowMessageW(L"TaskbarCreated"),
+                            0, 0);
+
+    return TRUE;
+}
+
+LRESULT CSysPagerWnd::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+    m_BalloonQueue.Deinit();
+    CIconWatcher::Uninitialize();
+    return TRUE;
+}
+
+BOOL CSysPagerWnd::NotifyIconCmd(WPARAM wParam, LPARAM lParam)
+{
+    PCOPYDATASTRUCT cpData = (PCOPYDATASTRUCT) lParam;
+    if (cpData->dwData == 1)
+    {
+        SYS_PAGER_COPY_DATA * data;
+        NOTIFYICONDATA *iconData;
+        BOOL ret = FALSE;
+
+        int VisibleButtonCount = Toolbar.GetVisibleButtonCount();
+
+        data = (PSYS_PAGER_COPY_DATA) cpData->lpData;
+        iconData = &data->nicon_data;
+
+        TRACE("NotifyIconCmd received. Code=%d\n", data->notify_code);
+        switch (data->notify_code)
+        {
+        case NIM_ADD:
+            ret = Toolbar.AddButton(iconData);
+            if (ret == TRUE)
+            {
+                (void)AddIconToWatcher(iconData);
+            }
+            break;
+        case NIM_MODIFY:
+            ret = Toolbar.UpdateButton(iconData);
+            break;
+        case NIM_DELETE:
+            ret = Toolbar.RemoveButton(iconData);
+            if (ret == TRUE)
+            {
+                (void)RemoveIconFromWatcher(iconData);
+            }
+            break;
+        case NIM_SETFOCUS:
+            Toolbar.SetFocus();
+            ret = TRUE;
+        case NIM_SETVERSION:
+            ret = Toolbar.SwitchVersion(iconData);
+        default:
+            TRACE("NotifyIconCmd received with unknown code %d.\n", data->notify_code);
+            return FALSE;
+        }
+
+        if (VisibleButtonCount != Toolbar.GetVisibleButtonCount())
+        {
+            HWND parentHWND = ::GetParent(GetParent());
+            ::SendMessage(parentHWND, WM_SIZE, 0, 0);
+        }
+
+        return ret;
+    }
+
+    return TRUE;
+}
+
+void CSysPagerWnd::GetSize(IN BOOL IsHorizontal, IN PSIZE size)
+{
+    /* Get the ideal height or width */
+#if 0 
+    /* Unfortunately this doens't work correctly in ros */
+    Toolbar.GetIdealSize(!IsHorizontal, size);
+
+    /* Make the reference dimension an exact multiple of the icon size */
+    if (IsHorizontal)
+        size->cy -= size->cy % GetSystemMetrics(SM_CYSMICON);
+    else
+        size->cx -= size->cx % GetSystemMetrics(SM_CXSMICON);
+
+#else
+    INT rows = 0;
+    INT columns = 0;
+    INT cyButton = GetSystemMetrics(SM_CYSMICON) + 2;
+    INT cxButton = GetSystemMetrics(SM_CXSMICON) + 2;
+    int VisibleButtonCount = Toolbar.GetVisibleButtonCount();
+
+    if (IsHorizontal)
+    {
+        rows = max(size->cy / cyButton, 1);
+        columns = (VisibleButtonCount + rows - 1) / rows;
+    }
+    else
+    {
+        columns = max(size->cx / cxButton, 1);
+        rows = (VisibleButtonCount + columns - 1) / columns;
+    }
+    size->cx = columns * cxButton;
+    size->cy = rows * cyButton;
+#endif
+}
+
+LRESULT CSysPagerWnd::OnGetInfoTip(INT uCode, LPNMHDR hdr, BOOL& bHandled)
+{
+    NMTBGETINFOTIPW * nmtip = (NMTBGETINFOTIPW *) hdr;
+    GetTooltipText(nmtip->lParam, nmtip->pszText, nmtip->cchTextMax);
+    return TRUE;
+}
+
+LRESULT CSysPagerWnd::OnCustomDraw(INT uCode, LPNMHDR hdr, BOOL& bHandled)
+{
+    NMCUSTOMDRAW * cdraw = (NMCUSTOMDRAW *) hdr;
+    switch (cdraw->dwDrawStage)
+    {
+    case CDDS_PREPAINT:
+        return CDRF_NOTIFYITEMDRAW;
+
+    case CDDS_ITEMPREPAINT:
+        return TBCDRF_NOBACKGROUND | TBCDRF_NOEDGES | TBCDRF_NOOFFSET | TBCDRF_NOMARK | TBCDRF_NOETCHEDEFFECT;
+    }
+    return TRUE;
+}
+
+LRESULT CSysPagerWnd::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+    LRESULT Ret = TRUE;
+    SIZE szClient;
+    szClient.cx = LOWORD(lParam);
+    szClient.cy = HIWORD(lParam);
+
+    Ret = DefWindowProc(uMsg, wParam, lParam);
+
+    if (Toolbar)
+    {
+        Toolbar.SetWindowPos(NULL, 0, 0, szClient.cx, szClient.cy, SWP_NOZORDER);
+        Toolbar.AutoSize();
+
+        RECT rc;
+        Toolbar.GetClientRect(&rc);
+
+        SIZE szBar = { rc.right - rc.left, rc.bottom - rc.top };
+
+        INT xOff = (szClient.cx - szBar.cx) / 2;
+        INT yOff = (szClient.cy - szBar.cy) / 2;
+
+        Toolbar.SetWindowPos(NULL, xOff, yOff, szBar.cx, szBar.cy, SWP_NOZORDER);
+    }
+    return Ret;
+}
+
+LRESULT CSysPagerWnd::OnCtxMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+    bHandled = TRUE;
+    return 0;
+}
+
+LRESULT CSysPagerWnd::OnBalloonPop(UINT uCode, LPNMHDR hdr , BOOL& bHandled)
+{
+    m_BalloonQueue.CloseCurrent();
+    bHandled = TRUE;
+    return 0;
+}
+
+LRESULT CSysPagerWnd::OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+    if (m_BalloonQueue.OnTimer(wParam))
+    {
+        bHandled = TRUE;
+    }
+
+    return 0;
+}
+
+void CSysPagerWnd::ResizeImagelist()
+{
+    Toolbar.ResizeImagelist();
+}
+
+HWND CSysPagerWnd::_Init(IN HWND hWndParent, IN BOOL bVisible)
+{
+    DWORD dwStyle;
+
+    /* Create the window. The tray window is going to move it to the correct
+        position and resize it as needed. */
+    dwStyle = WS_CHILD | WS_CLIPSIBLINGS;
+    if (bVisible)
+        dwStyle |= WS_VISIBLE;
+
+    Create(hWndParent, 0, NULL, dwStyle);
+
+    if (!m_hWnd)
+    {
+        return NULL;
+    }
+
+    SetWindowTheme(m_hWnd, L"TrayNotify", NULL);
+
+    return m_hWnd;
+}
diff --git a/base/shell/explorer/syspager.h b/base/shell/explorer/syspager.h
new file mode 100644 (file)
index 0000000..381c143
--- /dev/null
@@ -0,0 +1,203 @@
+#pragma once
+
+struct InternalIconData : NOTIFYICONDATA
+{
+    // Must keep a separate copy since the original is unioned with uTimeout.
+    UINT uVersionCopy;
+};
+
+struct IconWatcherData
+{
+    HANDLE hProcess;
+    DWORD ProcessId;
+    NOTIFYICONDATA IconData;
+
+    IconWatcherData(NOTIFYICONDATA *iconData) :
+        hProcess(NULL), ProcessId(0)
+    {
+        IconData.cbSize = sizeof(NOTIFYICONDATA);
+        IconData.hWnd = iconData->hWnd;
+        IconData.uID = iconData->uID;
+        IconData.guidItem = iconData->guidItem;
+    }
+
+    ~IconWatcherData()
+    {
+        if (hProcess)
+        {
+            CloseHandle(hProcess);
+        }
+    }
+};
+
+class CIconWatcher
+{
+    CAtlList<IconWatcherData *> m_WatcherList;
+    CRITICAL_SECTION m_ListLock;
+    HANDLE m_hWatcherThread;
+    HANDLE m_WakeUpEvent;
+    HWND m_hwndSysTray;
+    bool m_Loop;
+
+public:
+    CIconWatcher();
+
+    virtual ~CIconWatcher();
+
+    bool Initialize(_In_ HWND hWndParent);
+    void Uninitialize();
+
+    bool AddIconToWatcher(_In_ NOTIFYICONDATA *iconData);
+    bool RemoveIconFromWatcher(_In_ NOTIFYICONDATA *iconData);
+
+    IconWatcherData* GetListEntry(_In_opt_ NOTIFYICONDATA *iconData, _In_opt_ HANDLE hProcess, _In_ bool Remove);
+
+private:
+
+    static UINT WINAPI WatcherThread(_In_opt_ LPVOID lpParam);
+};
+
+class CBalloonQueue
+{
+public:
+    static const int TimerInterval = 2000;
+    static const int BalloonsTimerId = 1;
+    static const int MinTimeout = 10000;
+    static const int MaxTimeout = 30000;
+    static const int CooldownBetweenBalloons = 2000;
+
+private:
+    struct Info
+    {
+        InternalIconData * pSource;
+        WCHAR szInfo[256];
+        WCHAR szInfoTitle[64];
+        WPARAM uIcon;
+        UINT uTimeout;
+
+        Info(InternalIconData * source)
+        {
+            pSource = source;
+            StrNCpy(szInfo, source->szInfo, _countof(szInfo));
+            StrNCpy(szInfoTitle, source->szInfoTitle, _countof(szInfoTitle));
+            uIcon = source->dwInfoFlags & NIIF_ICON_MASK;
+            if (source->dwInfoFlags == NIIF_USER)
+                uIcon = reinterpret_cast<WPARAM>(source->hIcon);
+            uTimeout = source->uTimeout;
+        }
+    };
+
+    HWND m_hwndParent;
+
+    CTooltips * m_tooltips;
+
+    CAtlList<Info> m_queue;
+
+    CToolbar<InternalIconData> * m_toolbar;
+
+    InternalIconData * m_current;
+    bool m_currentClosed;
+
+    int m_timer;
+
+public:
+    CBalloonQueue();
+
+    void Init(HWND hwndParent, CToolbar<InternalIconData> * toolbar, CTooltips * balloons);
+    void Deinit();
+
+    bool OnTimer(int timerId);
+    void UpdateInfo(InternalIconData * notifyItem);
+    void RemoveInfo(InternalIconData * notifyItem);
+    void CloseCurrent();
+
+private:
+
+    int IndexOf(InternalIconData * pdata);
+    void SetTimer(int length);
+    void Show(Info& info);
+    void Close(IN OUT InternalIconData * notifyItem);
+};
+
+class CNotifyToolbar :
+    public CWindowImplBaseT< CToolbar<InternalIconData>, CControlWinTraits >
+{
+    HIMAGELIST m_ImageList;
+    int m_VisibleButtonCount;
+
+    CBalloonQueue * m_BalloonQueue;
+
+public:
+    CNotifyToolbar();
+    virtual ~CNotifyToolbar();
+
+    int GetVisibleButtonCount();
+    int FindItem(IN HWND hWnd, IN UINT uID, InternalIconData ** pdata);
+    int FindExistingSharedIcon(HICON handle);
+    BOOL AddButton(IN CONST NOTIFYICONDATA *iconData);
+    BOOL SwitchVersion(IN CONST NOTIFYICONDATA *iconData);
+    BOOL UpdateButton(IN CONST NOTIFYICONDATA *iconData);
+    BOOL RemoveButton(IN CONST NOTIFYICONDATA *iconData);
+    VOID ResizeImagelist();
+
+private:
+    VOID SendMouseEvent(IN WORD wIndex, IN UINT uMsg, IN WPARAM wParam);
+    LRESULT OnMouseEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+    LRESULT OnTooltipShow(INT uCode, LPNMHDR hdr, BOOL& bHandled);
+
+public:
+    BEGIN_MSG_MAP(CNotifyToolbar)
+        MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseEvent)
+        NOTIFY_CODE_HANDLER(TTN_SHOW, OnTooltipShow)
+    END_MSG_MAP()
+
+    void Initialize(HWND hWndParent, CBalloonQueue * queue);
+};
+
+extern const WCHAR szSysPagerWndClass[];
+
+class CSysPagerWnd :
+    public CComObjectRootEx<CComMultiThreadModelNoCS>,
+    public CWindowImpl < CSysPagerWnd, CWindow, CControlWinTraits >,
+    public CIconWatcher
+{
+    CNotifyToolbar Toolbar;
+    CTooltips m_Balloons;
+    CBalloonQueue m_BalloonQueue;
+
+public:
+    CSysPagerWnd();
+    virtual ~CSysPagerWnd();
+
+    LRESULT DrawBackground(HDC hdc);
+    LRESULT OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+    LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+    LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+    LRESULT OnGetInfoTip(INT uCode, LPNMHDR hdr, BOOL& bHandled);
+    LRESULT OnCustomDraw(INT uCode, LPNMHDR hdr, BOOL& bHandled);
+    LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+    LRESULT OnCtxMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+    LRESULT OnBalloonPop(UINT uCode, LPNMHDR hdr, BOOL& bHandled);
+    LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+
+public:
+    BOOL NotifyIconCmd(WPARAM wParam, LPARAM lParam);
+    void GetSize(IN BOOL IsHorizontal, IN PSIZE size);
+    void ResizeImagelist();
+
+    DECLARE_WND_CLASS_EX(szSysPagerWndClass, CS_DBLCLKS, COLOR_3DFACE)
+
+    BEGIN_MSG_MAP(CSysPagerWnd)
+        MESSAGE_HANDLER(WM_CREATE, OnCreate)
+        MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
+        MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
+        MESSAGE_HANDLER(WM_SIZE, OnSize)
+        MESSAGE_HANDLER(WM_CONTEXTMENU, OnCtxMenu)
+        MESSAGE_HANDLER(WM_TIMER, OnTimer)
+        NOTIFY_CODE_HANDLER(TTN_POP, OnBalloonPop)
+        NOTIFY_CODE_HANDLER(TBN_GETINFOTIPW, OnGetInfoTip)
+        NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW, OnCustomDraw)
+    END_MSG_MAP()
+
+    HWND _Init(IN HWND hWndParent, IN BOOL bVisible);
+};
diff --git a/base/shell/explorer/trayclock.cpp b/base/shell/explorer/trayclock.cpp
new file mode 100644 (file)
index 0000000..125fc73
--- /dev/null
@@ -0,0 +1,576 @@
+/*
+ * ReactOS Explorer
+ *
+ * Copyright 2006 - 2007 Thomas Weidenmueller <w3seek@reactos.org>
+ * Copyright 2018 Ged Murphy <gedmurphy@reactos.org>
+ *
+ * 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
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "precomp.h"
+
+/*
+ * TrayClockWnd
+ */
+
+const WCHAR szTrayClockWndClass[] = L"TrayClockWClass";
+
+#define ID_TRAYCLOCK_TIMER  0
+#define ID_TRAYCLOCK_TIMER_INIT 1
+
+#define TRAY_CLOCK_WND_SPACING_X    0
+#define TRAY_CLOCK_WND_SPACING_Y    0
+
+CTrayClockWnd::CTrayClockWnd() :
+        hWndNotify(NULL),
+        hFont(NULL),
+        dwFlags(0),
+        LineSpacing(0),
+        VisibleLines(0)
+{
+    ZeroMemory(&textColor, sizeof(textColor));
+    ZeroMemory(&rcText, sizeof(rcText));
+    ZeroMemory(&LocalTime, sizeof(LocalTime));
+    ZeroMemory(&CurrentSize, sizeof(CurrentSize));
+    ZeroMemory(LineSizes, sizeof(LineSizes));
+    ZeroMemory(szLines, sizeof(szLines));
+}
+CTrayClockWnd::~CTrayClockWnd() { }
+
+LRESULT CTrayClockWnd::OnThemeChanged()
+{
+    LOGFONTW clockFont;
+    HTHEME clockTheme;
+    HFONT hFont;
+
+    clockTheme = OpenThemeData(m_hWnd, L"Clock");
+
+    if (clockTheme)
+    {
+        GetThemeFont(clockTheme,
+            NULL,
+            CLP_TIME,
+            0,
+            TMT_FONT,
+            &clockFont);
+
+        hFont = CreateFontIndirectW(&clockFont);
+
+        GetThemeColor(clockTheme,
+            CLP_TIME,
+            0,
+            TMT_TEXTCOLOR,
+            &textColor);
+
+        if (this->hFont != NULL)
+            DeleteObject(this->hFont);
+
+        SetFont(hFont, FALSE);
+    }
+    else
+    {
+        /* We don't need to set a font here, our parent will use 
+            * WM_SETFONT to set the right one when themes are not enabled. */
+        textColor = RGB(0, 0, 0);
+    }
+
+    CloseThemeData(clockTheme);
+
+    return TRUE;
+}
+
+LRESULT CTrayClockWnd::OnThemeChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+    return OnThemeChanged();
+}
+
+BOOL CTrayClockWnd::MeasureLines()
+{
+    HDC hDC;
+    HFONT hPrevFont;
+    UINT c, i;
+    BOOL bRet = TRUE;
+
+    hDC = GetDC();
+    if (hDC != NULL)
+    {
+        if (hFont)
+            hPrevFont = (HFONT) SelectObject(hDC, hFont);
+
+        for (i = 0; i < CLOCKWND_FORMAT_COUNT && bRet; i++)
+        {
+            if (szLines[i][0] != L'\0' &&
+                !GetTextExtentPointW(hDC, szLines[i], wcslen(szLines[i]),
+                                        &LineSizes[i]))
+            {
+                bRet = FALSE;
+                break;
+            }
+        }
+
+        if (hFont)
+            SelectObject(hDC, hPrevFont);
+
+        ReleaseDC(hDC);
+
+        if (bRet)
+        {
+            LineSpacing = 0;
+
+            /* calculate the line spacing */
+            for (i = 0, c = 0; i < CLOCKWND_FORMAT_COUNT; i++)
+            {
+                if (LineSizes[i].cx > 0)
+                {
+                    LineSpacing += LineSizes[i].cy;
+                    c++;
+                }
+            }
+
+            if (c > 0)
+            {
+                /* We want a spacing of 1/2 line */
+                LineSpacing = (LineSpacing / c) / 2;
+            }
+
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+WORD CTrayClockWnd::GetMinimumSize(IN BOOL Horizontal, IN OUT PSIZE pSize)
+{
+    WORD iLinesVisible = 0;
+    UINT i;
+    SIZE szMax = { 0, 0 };
+
+    if (!LinesMeasured)
+        LinesMeasured = MeasureLines();
+
+    if (!LinesMeasured)
+        return 0;
+
+    for (i = 0; i < CLOCKWND_FORMAT_COUNT; i++)
+    {
+        if (LineSizes[i].cx != 0)
+        {
+            if (iLinesVisible > 0)
+            {
+                if (Horizontal)
+                {
+                    if (szMax.cy + LineSizes[i].cy + (LONG) LineSpacing >
+                        pSize->cy - (2 * TRAY_CLOCK_WND_SPACING_Y))
+                    {
+                        break;
+                    }
+                }
+                else
+                {
+                    if (LineSizes[i].cx > pSize->cx - (2 * TRAY_CLOCK_WND_SPACING_X))
+                        break;
+                }
+
+                /* Add line spacing */
+                szMax.cy += LineSpacing;
+            }
+
+            iLinesVisible++;
+
+            /* Increase maximum rectangle */
+            szMax.cy += LineSizes[i].cy;
+            if (LineSizes[i].cx > szMax.cx - (2 * TRAY_CLOCK_WND_SPACING_X))
+                szMax.cx = LineSizes[i].cx + (2 * TRAY_CLOCK_WND_SPACING_X);
+        }
+    }
+
+    szMax.cx += 2 * TRAY_CLOCK_WND_SPACING_X;
+    szMax.cy += 2 * TRAY_CLOCK_WND_SPACING_Y;
+
+    *pSize = szMax;
+
+    return iLinesVisible;
+}
+
+VOID CTrayClockWnd::UpdateWnd()
+{
+    SIZE szPrevCurrent;
+    UINT BufSize, i;
+    INT iRet;
+    RECT rcClient;
+
+    ZeroMemory(LineSizes, sizeof(LineSizes));
+
+    szPrevCurrent = CurrentSize;
+
+    for (i = 0; i < CLOCKWND_FORMAT_COUNT; i++)
+    {
+        szLines[i][0] = L'\0';
+        BufSize = _countof(szLines[0]);
+
+        if (ClockWndFormats[i].IsTime)
+        {
+            iRet = GetTimeFormat(LOCALE_USER_DEFAULT,
+                g_TaskbarSettings.bShowSeconds ? ClockWndFormats[i].dwFormatFlags : TIME_NOSECONDS,
+                &LocalTime,
+                ClockWndFormats[i].lpFormat,
+                szLines[i],
+                BufSize);
+        }
+        else
+        {
+            iRet = GetDateFormat(LOCALE_USER_DEFAULT,
+                ClockWndFormats[i].dwFormatFlags,
+                &LocalTime,
+                ClockWndFormats[i].lpFormat,
+                szLines[i],
+                BufSize);
+        }
+
+        if (iRet != 0 && i == 0)
+        {
+            /* Set the window text to the time only */
+            SetWindowText(szLines[i]);
+        }
+    }
+
+    LinesMeasured = MeasureLines();
+
+    if (LinesMeasured &&
+        GetClientRect(&rcClient))
+    {
+        SIZE szWnd;
+
+        szWnd.cx = rcClient.right;
+        szWnd.cy = rcClient.bottom;
+
+        VisibleLines = GetMinimumSize(IsHorizontal, &szWnd);
+        CurrentSize = szWnd;
+    }
+
+    if (IsWindowVisible())
+    {
+        InvalidateRect(NULL, TRUE);
+
+        if (hWndNotify != NULL &&
+            (szPrevCurrent.cx != CurrentSize.cx ||
+            szPrevCurrent.cy != CurrentSize.cy))
+        {
+            NMHDR nmh;
+
+            nmh.hwndFrom = m_hWnd;
+            nmh.idFrom = GetWindowLongPtr(GWLP_ID);
+            nmh.code = NTNWM_REALIGN;
+
+            ::SendMessage(hWndNotify,
+                WM_NOTIFY,
+                (WPARAM) nmh.idFrom,
+                (LPARAM) &nmh);
+        }
+    }
+}
+
+VOID CTrayClockWnd::Update()
+{
+    GetLocalTime(&LocalTime);
+    UpdateWnd();
+}
+
+UINT CTrayClockWnd::CalculateDueTime()
+{
+    UINT uiDueTime;
+
+    /* Calculate the due time */
+    GetLocalTime(&LocalTime);
+    uiDueTime = 1000 - (UINT) LocalTime.wMilliseconds;
+    if (g_TaskbarSettings.bShowSeconds)
+        uiDueTime += (UINT) LocalTime.wSecond * 100;
+    else
+        uiDueTime += (59 - (UINT) LocalTime.wSecond) * 1000;
+
+    if (uiDueTime < USER_TIMER_MINIMUM || uiDueTime > USER_TIMER_MAXIMUM)
+        uiDueTime = 1000;
+    else
+    {
+        /* Add an artificial delay of 0.05 seconds to make sure the timer
+            doesn't fire too early*/
+        uiDueTime += 50;
+    }
+
+    return uiDueTime;
+}
+
+BOOL CTrayClockWnd::ResetTime()
+{
+    UINT uiDueTime;
+    BOOL Ret;
+
+    /* Disable all timers */
+    if (IsTimerEnabled)
+    {
+        KillTimer(ID_TRAYCLOCK_TIMER);
+        IsTimerEnabled = FALSE;
+    }
+
+    if (IsInitTimerEnabled)
+    {
+        KillTimer(ID_TRAYCLOCK_TIMER_INIT);
+    }
+
+    uiDueTime = CalculateDueTime();
+
+    /* Set the new timer */
+    Ret = SetTimer(ID_TRAYCLOCK_TIMER_INIT, uiDueTime, NULL) != 0;
+    IsInitTimerEnabled = Ret;
+
+    /* Update the time */
+    Update();
+
+    return Ret;
+}
+
+VOID CTrayClockWnd::CalibrateTimer()
+{
+    UINT uiDueTime;
+    BOOL Ret;
+    UINT uiWait1, uiWait2;
+
+    /* Kill the initialization timer */
+    KillTimer(ID_TRAYCLOCK_TIMER_INIT);
+    IsInitTimerEnabled = FALSE;
+
+    uiDueTime = CalculateDueTime();
+
+    if (g_TaskbarSettings.bShowSeconds)
+    {
+        uiWait1 = 1000 - 200;
+        uiWait2 = 1000;
+    }
+    else
+    {
+        uiWait1 = 60 * 1000 - 200;
+        uiWait2 = 60 * 1000;
+    }
+
+    if (uiDueTime > uiWait1)
+    {
+        /* The update of the clock will be up to 200 ms late, but that's
+            acceptable. We're going to setup a timer that fires depending
+            uiWait2. */
+        Ret = SetTimer(ID_TRAYCLOCK_TIMER, uiWait2, NULL) != 0;
+        IsTimerEnabled = Ret;
+
+        /* Update the time */
+        Update();
+    }
+    else
+    {
+        /* Recalibrate the timer and recalculate again when the current
+            minute/second ends. */
+        ResetTime();
+    }
+}
+
+LRESULT CTrayClockWnd::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+    /* Disable all timers */
+    if (IsTimerEnabled)
+    {
+        KillTimer(ID_TRAYCLOCK_TIMER);
+    }
+
+    if (IsInitTimerEnabled)
+    {
+        KillTimer(ID_TRAYCLOCK_TIMER_INIT);
+    }
+
+    return TRUE;
+}
+
+LRESULT CTrayClockWnd::OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+    RECT rcClient;
+    HFONT hPrevFont;
+    INT iPrevBkMode;
+    UINT i, line;
+
+    PAINTSTRUCT ps;
+    HDC hDC = (HDC) wParam;
+
+    if (wParam == 0)
+    {
+        hDC = BeginPaint(&ps);
+    }
+
+    if (hDC == NULL)
+        return FALSE;
+
+    if (LinesMeasured &&
+        GetClientRect(&rcClient))
+    {
+        iPrevBkMode = SetBkMode(hDC, TRANSPARENT);
+
+        SetTextColor(hDC, textColor);
+
+        hPrevFont = (HFONT) SelectObject(hDC, hFont);
+
+        rcClient.left = (rcClient.right / 2) - (CurrentSize.cx / 2);
+        rcClient.top = (rcClient.bottom / 2) - (CurrentSize.cy / 2);
+        rcClient.right = rcClient.left + CurrentSize.cx;
+        rcClient.bottom = rcClient.top + CurrentSize.cy;
+
+        for (i = 0, line = 0;
+                i < CLOCKWND_FORMAT_COUNT && line < VisibleLines;
+                i++)
+        {
+            if (LineSizes[i].cx != 0)
+            {
+                TextOut(hDC,
+                    rcClient.left + (CurrentSize.cx / 2) - (LineSizes[i].cx / 2) +
+                    TRAY_CLOCK_WND_SPACING_X,
+                    rcClient.top + TRAY_CLOCK_WND_SPACING_Y,
+                    szLines[i],
+                    wcslen(szLines[i]));
+
+                rcClient.top += LineSizes[i].cy + LineSpacing;
+                line++;
+            }
+        }
+
+        SelectObject(hDC, hPrevFont);
+
+        SetBkMode(hDC, iPrevBkMode);
+    }
+
+    if (wParam == 0)
+    {
+        EndPaint(&ps);
+    }
+
+    return TRUE;
+}
+
+VOID CTrayClockWnd::SetFont(IN HFONT hNewFont, IN BOOL bRedraw)
+{
+    hFont = hNewFont;
+    LinesMeasured = MeasureLines();
+    if (bRedraw)
+    {
+        InvalidateRect(NULL, TRUE);
+    }
+}
+
+LRESULT CTrayClockWnd::DrawBackground(HDC hdc)
+{
+    RECT rect;
+
+    GetClientRect(&rect);
+    DrawThemeParentBackground(m_hWnd, hdc, &rect);
+
+    return TRUE;
+}
+
+LRESULT CTrayClockWnd::OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+    HDC hdc = (HDC) wParam;
+
+    if (!IsAppThemed())
+    {
+        bHandled = FALSE;
+        return 0;
+    }
+
+    return DrawBackground(hdc);
+}
+
+LRESULT CTrayClockWnd::OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+    switch (wParam)
+    {
+    case ID_TRAYCLOCK_TIMER:
+        Update();
+        break;
+
+    case ID_TRAYCLOCK_TIMER_INIT:
+        CalibrateTimer();
+        break;
+    }
+    return TRUE;
+}
+
+LRESULT CTrayClockWnd::OnGetMinimumSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+    IsHorizontal = (BOOL) wParam;
+
+    return (LRESULT) GetMinimumSize((BOOL) wParam, (PSIZE) lParam) != 0;
+}
+
+LRESULT CTrayClockWnd::OnUpdateTime(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+    return (LRESULT) ResetTime();
+}
+
+LRESULT CTrayClockWnd::OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+    return HTTRANSPARENT;
+}
+
+LRESULT CTrayClockWnd::OnSetFont(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+    SetFont((HFONT) wParam, (BOOL) LOWORD(lParam));
+    return TRUE;
+}
+
+LRESULT CTrayClockWnd::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+    ResetTime();
+    return TRUE;
+}
+
+LRESULT CTrayClockWnd::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+    SIZE szClient;
+
+    szClient.cx = LOWORD(lParam);
+    szClient.cy = HIWORD(lParam);
+
+    VisibleLines = GetMinimumSize(IsHorizontal, &szClient);
+    CurrentSize = szClient;
+
+    InvalidateRect(NULL, TRUE);
+    return TRUE;
+}
+
+HWND CTrayClockWnd::_Init(IN HWND hWndParent, IN BOOL bVisible)
+{
+    IsHorizontal = TRUE;
+
+    hWndNotify = hWndParent;
+
+    /* Create the window. The tray window is going to move it to the correct
+        position and resize it as needed. */
+    DWORD dwStyle = WS_CHILD | WS_CLIPSIBLINGS;
+    if (bVisible)
+        dwStyle |= WS_VISIBLE;
+
+    Create(hWndParent, 0, NULL, dwStyle);
+
+    if (m_hWnd != NULL)
+        SetWindowTheme(m_hWnd, L"TrayNotify", NULL);
+
+    return m_hWnd;
+
+};
diff --git a/base/shell/explorer/trayclock.h b/base/shell/explorer/trayclock.h
new file mode 100644 (file)
index 0000000..23911df
--- /dev/null
@@ -0,0 +1,94 @@
+#pragma once
+
+const struct
+{
+    BOOL IsTime;
+    DWORD dwFormatFlags;
+    LPCWSTR lpFormat;
+} ClockWndFormats[] = {
+    { TRUE, 0, NULL },
+    { FALSE, 0, L"dddd" },
+    { FALSE, DATE_SHORTDATE, NULL }
+};
+const UINT ClockWndFormatsCount = _ARRAYSIZE(ClockWndFormats);
+
+#define CLOCKWND_FORMAT_COUNT ClockWndFormatsCount
+
+extern const WCHAR szTrayClockWndClass[];
+class CTrayClockWnd :
+    public CComObjectRootEx<CComMultiThreadModelNoCS>,
+    public CWindowImpl < CTrayClockWnd, CWindow, CControlWinTraits >
+{
+    HWND hWndNotify;
+    HFONT hFont;
+    COLORREF textColor;
+    RECT rcText;
+    SYSTEMTIME LocalTime;
+
+    union
+    {
+        DWORD dwFlags;
+        struct
+        {
+            DWORD IsTimerEnabled : 1;
+            DWORD IsInitTimerEnabled : 1;
+            DWORD LinesMeasured : 1;
+            DWORD IsHorizontal : 1;
+        };
+    };
+    DWORD LineSpacing;
+    SIZE CurrentSize;
+    WORD VisibleLines;
+    SIZE LineSizes[CLOCKWND_FORMAT_COUNT];
+    WCHAR szLines[CLOCKWND_FORMAT_COUNT][48];
+
+public:
+    CTrayClockWnd();
+    virtual ~CTrayClockWnd();
+
+private:
+    LRESULT OnThemeChanged();
+    LRESULT OnThemeChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+
+    BOOL MeasureLines();
+    WORD GetMinimumSize(IN BOOL Horizontal, IN OUT PSIZE pSize);
+    VOID UpdateWnd();
+    VOID Update();
+    UINT CalculateDueTime();
+    BOOL ResetTime();
+    VOID CalibrateTimer();
+    LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+    LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+    VOID SetFont(IN HFONT hNewFont, IN BOOL bRedraw);
+    LRESULT DrawBackground(HDC hdc);
+    LRESULT OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+    LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+    LRESULT OnGetMinimumSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+    LRESULT OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+    LRESULT OnSetFont(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+    LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+    LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+public:
+    LRESULT OnUpdateTime(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+
+public:
+    DECLARE_WND_CLASS_EX(szTrayClockWndClass, CS_DBLCLKS, COLOR_3DFACE)
+
+    BEGIN_MSG_MAP(CTrayClockWnd)
+        MESSAGE_HANDLER(WM_CREATE, OnCreate)
+        MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
+        MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
+        MESSAGE_HANDLER(WM_SIZE, OnSize)
+        MESSAGE_HANDLER(WM_PAINT, OnPaint)
+        MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint)
+        MESSAGE_HANDLER(WM_THEMECHANGED, OnThemeChanged)
+        MESSAGE_HANDLER(WM_TIMER, OnTimer)
+        MESSAGE_HANDLER(WM_NCHITTEST, OnNcHitTest)
+        MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
+        MESSAGE_HANDLER(TCWM_GETMINIMUMSIZE, OnGetMinimumSize)
+        MESSAGE_HANDLER(TCWM_UPDATETIME, OnUpdateTime)
+
+    END_MSG_MAP()
+
+    HWND _Init(IN HWND hWndParent, IN BOOL bVisible);
+};
\ No newline at end of file
index 1b05b50..9a0294e 100644 (file)
 
 #include "precomp.h"
 
-/*
- * SysPagerWnd
- */
-static const WCHAR szSysPagerWndClass [] = L"SysPager";
-
-// Data comes from shell32/systray.cpp -> TrayNotifyCDS_Dummy
-typedef struct _SYS_PAGER_COPY_DATA
-{
-    DWORD           cookie;
-    DWORD           notify_code;
-    NOTIFYICONDATA  nicon_data;
-} SYS_PAGER_COPY_DATA, *PSYS_PAGER_COPY_DATA;
-
-struct InternalIconData : NOTIFYICONDATA
-{
-    // Must keep a separate copy since the original is unioned with uTimeout.
-    UINT uVersionCopy;
-};
-
-struct IconWatcherData
-{
-    HANDLE hProcess;
-    DWORD ProcessId;
-    NOTIFYICONDATA IconData;
-
-    IconWatcherData(NOTIFYICONDATA *iconData) :
-        hProcess(NULL), ProcessId(0)
-    {
-        IconData.cbSize = sizeof(NOTIFYICONDATA);
-        IconData.hWnd = iconData->hWnd;
-        IconData.uID = iconData->uID;
-        IconData.guidItem = iconData->guidItem;
-    }
-
-    ~IconWatcherData()
-    {
-        if (hProcess)
-        {
-            CloseHandle(hProcess);
-        }
-    }
-};
-
-class CIconWatcher
-{
-    CAtlList<IconWatcherData *> m_WatcherList;
-    CRITICAL_SECTION m_ListLock;
-    HANDLE m_hWatcherThread;
-    HANDLE m_WakeUpEvent;
-    HWND m_hwndSysTray;
-    bool m_Loop;
-
-public:
-    CIconWatcher() :
-        m_hWatcherThread(NULL),
-        m_WakeUpEvent(NULL),
-        m_hwndSysTray(NULL),
-        m_Loop(false)
-    {
-    }
-
-    virtual ~CIconWatcher()
-    {
-        Uninitialize();
-        DeleteCriticalSection(&m_ListLock);
-
-        if (m_WakeUpEvent)
-            CloseHandle(m_WakeUpEvent);
-        if (m_hWatcherThread)
-            CloseHandle(m_hWatcherThread);
-    }
-
-    bool Initialize(_In_ HWND hWndParent)
-    {
-        m_hwndSysTray = hWndParent;
-
-        InitializeCriticalSection(&m_ListLock);
-        m_WakeUpEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
-        if (m_WakeUpEvent == NULL)
-            return false;
-
-        m_hWatcherThread = (HANDLE)_beginthreadex(NULL,
-                                                  0,
-                                                  WatcherThread,
-                                                  (LPVOID)this,
-                                                  0,
-                                                  NULL);
-        if (m_hWatcherThread == NULL)
-            return false;
-
-        return true;
-    }
-
-    void Uninitialize()
-    {
-        m_Loop = false;
-        if (m_WakeUpEvent)
-            SetEvent(m_WakeUpEvent);
-
-        EnterCriticalSection(&m_ListLock);
-
-        POSITION Pos;
-        for (size_t i = 0; i < m_WatcherList.GetCount(); i++)
-        {
-            Pos = m_WatcherList.FindIndex(i);
-            if (Pos)
-            {
-                IconWatcherData *Icon;
-                Icon = m_WatcherList.GetAt(Pos);
-                delete Icon;
-            }
-        }
-        m_WatcherList.RemoveAll();
-
-        LeaveCriticalSection(&m_ListLock);
-    }
-
-    bool AddIconToWatcher(_In_ NOTIFYICONDATA *iconData)
-    {
-        DWORD ProcessId;
-        (void)GetWindowThreadProcessId(iconData->hWnd, &ProcessId);
-
-        HANDLE hProcess;
-        hProcess = OpenProcess(SYNCHRONIZE, FALSE, ProcessId);
-        if (hProcess == NULL)
-        {
-            return false;
-        }
-
-        IconWatcherData *Icon = new IconWatcherData(iconData);
-        Icon->hProcess = hProcess;
-        Icon->ProcessId;
-
-        bool Added = false;
-        EnterCriticalSection(&m_ListLock);
-
-        // The likelyhood of someone having more than 64 icons in their tray is
-        // pretty slim. We could spin up a new thread for each multiple of 64, but
-        // it's not worth the effort, so we just won't bother watching those icons
-        if (m_WatcherList.GetCount() < MAXIMUM_WAIT_OBJECTS)
-        {
-            m_WatcherList.AddTail(Icon);
-            SetEvent(m_WakeUpEvent);
-            Added = true;
-        }
-
-        LeaveCriticalSection(&m_ListLock);
-
-        if (!Added)
-        {
-            delete Icon;
-        }
-
-        return Added;
-    }
-
-    bool RemoveIconFromWatcher(_In_ NOTIFYICONDATA *iconData)
-    {
-        EnterCriticalSection(&m_ListLock);
-        
-        IconWatcherData *Icon;
-        Icon = GetListEntry(iconData, NULL, true);
-
-        SetEvent(m_WakeUpEvent);
-        LeaveCriticalSection(&m_ListLock);
-
-        delete Icon;
-        return true;
-    }
-
-    IconWatcherData* GetListEntry(_In_opt_ NOTIFYICONDATA *iconData, _In_opt_ HANDLE hProcess, _In_ bool Remove)
-    {
-        IconWatcherData *Entry = NULL;
-        POSITION NextPosition = m_WatcherList.GetHeadPosition();
-        POSITION Position;
-        do
-        {
-            Position = NextPosition;
-
-            Entry = m_WatcherList.GetNext(NextPosition);
-            if (Entry)
-            {
-                if ((iconData && ((Entry->IconData.hWnd == iconData->hWnd) && (Entry->IconData.uID == iconData->uID))) ||
-                     (hProcess && (Entry->hProcess == hProcess)))
-                {
-                    if (Remove)
-                        m_WatcherList.RemoveAt(Position);
-                    break;
-                }
-            }
-            Entry = NULL;
-
-        } while (NextPosition != NULL);
-
-        return Entry;
-    }
-
-private:
-
-    static UINT WINAPI WatcherThread(_In_opt_ LPVOID lpParam)
-    {
-        CIconWatcher* This = reinterpret_cast<CIconWatcher *>(lpParam);
-        HANDLE *WatchList = NULL;
-
-        This->m_Loop = true;
-        while (This->m_Loop)
-        {
-            EnterCriticalSection(&This->m_ListLock);
-
-            DWORD Size;
-            Size = This->m_WatcherList.GetCount() + 1;
-            ASSERT(Size <= MAXIMUM_WAIT_OBJECTS);
-
-            if (WatchList)
-                delete WatchList;
-            WatchList = new HANDLE[Size];
-            WatchList[0] = This->m_WakeUpEvent;
-
-            POSITION Pos;
-            for (size_t i = 0; i < This->m_WatcherList.GetCount(); i++)
-            {
-                Pos = This->m_WatcherList.FindIndex(i);
-                if (Pos)
-                {
-                    IconWatcherData *Icon;
-                    Icon = This->m_WatcherList.GetAt(Pos);
-                    WatchList[i + 1] = Icon->hProcess;
-                }
-            }
-
-            LeaveCriticalSection(&This->m_ListLock);
-
-            DWORD Status;
-            Status = WaitForMultipleObjects(Size,
-                                            WatchList,
-                                            FALSE,
-                                            INFINITE);
-            if (Status == WAIT_OBJECT_0)
-            {
-                // We've been kicked, we have updates to our list (or we're exiting the thread)
-                if (This->m_Loop)
-                    TRACE("Updating watched icon list");
-            }
-            else if ((Status >= WAIT_OBJECT_0 + 1) && (Status < Size))
-            {
-                IconWatcherData *Icon;
-                Icon = This->GetListEntry(NULL, WatchList[Status], false);
-
-                TRACE("Pid %lu owns a notification icon and has stopped without deleting it. We'll cleanup on its behalf", Icon->ProcessId);
-
-                int len = FIELD_OFFSET(SYS_PAGER_COPY_DATA, nicon_data) + Icon->IconData.cbSize;
-                PSYS_PAGER_COPY_DATA pnotify_data = (PSYS_PAGER_COPY_DATA)new BYTE[len];
-                pnotify_data->cookie = 1;
-                pnotify_data->notify_code = NIM_DELETE;
-                memcpy(&pnotify_data->nicon_data, &Icon->IconData, Icon->IconData.cbSize);
-
-                COPYDATASTRUCT data;
-                data.dwData = 1;
-                data.cbData = len;
-                data.lpData = pnotify_data;
-
-                BOOL Success = FALSE;
-                HWND parentHWND = ::GetParent(GetParent(This->m_hwndSysTray));
-                if (parentHWND)
-                    Success = ::SendMessage(parentHWND, WM_COPYDATA, (WPARAM)&Icon->IconData, (LPARAM)&data);
-
-                delete pnotify_data;
-
-                if (!Success)
-                {
-                    // If we failed to handle the delete message, forcibly remove it
-                    This->RemoveIconFromWatcher(&Icon->IconData);
-                }
-            }
-            else
-            {
-                if (Status == WAIT_FAILED)
-                {
-                    Status = GetLastError();
-                }
-                ERR("Failed to wait on process handles : %lu\n", Status);
-                This->Uninitialize();
-            }
-        }
-
-        if (WatchList)
-            delete WatchList;
-
-        return 0;
-    }
-};
-
-class CBalloonQueue
-{
-public:
-    static const int TimerInterval = 2000;
-    static const int BalloonsTimerId = 1;
-    static const int MinTimeout = 10000;
-    static const int MaxTimeout = 30000;
-    static const int CooldownBetweenBalloons = 2000;
-
-private:
-    struct Info
-    {
-        InternalIconData * pSource;
-        WCHAR szInfo[256];
-        WCHAR szInfoTitle[64];
-        WPARAM uIcon;
-        UINT uTimeout;
-
-        Info(InternalIconData * source)
-        {
-            pSource = source;
-            StrNCpy(szInfo, source->szInfo, _countof(szInfo));
-            StrNCpy(szInfoTitle, source->szInfoTitle, _countof(szInfoTitle));
-            uIcon = source->dwInfoFlags & NIIF_ICON_MASK;
-            if (source->dwInfoFlags == NIIF_USER)
-                uIcon = reinterpret_cast<WPARAM>(source->hIcon);
-            uTimeout = source->uTimeout;
-        }
-    };
-
-    HWND m_hwndParent;
-
-    CTooltips * m_tooltips;
-
-    CAtlList<Info> m_queue;
-
-    CToolbar<InternalIconData> * m_toolbar;
-
-    InternalIconData * m_current;
-    bool m_currentClosed;
-
-    int m_timer;
-
-public:
-    CBalloonQueue() :
-        m_hwndParent(NULL),
-        m_tooltips(NULL),
-        m_toolbar(NULL),
-        m_current(NULL),
-        m_currentClosed(false),
-        m_timer(-1)
-    {
-    }
-
-    void Init(HWND hwndParent, CToolbar<InternalIconData> * toolbar, CTooltips * balloons)
-    {
-        m_hwndParent = hwndParent;
-        m_toolbar = toolbar;
-        m_tooltips = balloons;
-    }
-
-    void Deinit()
-    {
-        if (m_timer >= 0)
-        {
-            ::KillTimer(m_hwndParent, m_timer);
-        }
-    }
-
-    bool OnTimer(int timerId)
-    {
-        if (timerId != m_timer)
-            return false;
-
-        ::KillTimer(m_hwndParent, m_timer);
-        m_timer = -1;
-
-        if (m_current && !m_currentClosed)
-        {
-            Close(m_current);
-        }
-        else
-        {
-            m_current = NULL;
-            m_currentClosed = false;
-            if (!m_queue.IsEmpty())
-            {
-                Info info = m_queue.RemoveHead();
-                Show(info);
-            }
-        }
-
-        return true;
-    }
-
-    void UpdateInfo(InternalIconData * notifyItem)
-    {
-        size_t len = 0;
-        HRESULT hr = StringCchLength(notifyItem->szInfo, _countof(notifyItem->szInfo), &len);
-        if (SUCCEEDED(hr) && len > 0)
-        {
-            Info info(notifyItem);
-
-            // If m_current == notifyItem, we want to replace the previous balloon even if there is a queue.
-            if (m_current != notifyItem && (m_current != NULL || !m_queue.IsEmpty()))
-            {
-                m_queue.AddTail(info);
-            }
-            else
-            {
-                Show(info);
-            }
-        }
-        else
-        {
-            Close(notifyItem);
-        }
-    }
-
-    void RemoveInfo(InternalIconData * notifyItem)
-    {
-        Close(notifyItem);
-
-        POSITION position = m_queue.GetHeadPosition();
-        while(position != NULL)
-        {
-            Info& info = m_queue.GetNext(position);
-            if (info.pSource == notifyItem)
-            {
-                m_queue.RemoveAt(position);
-            }
-        }
-    }
-
-    void CloseCurrent()
-    {
-        if (m_current != NULL)
-            Close(m_current);
-    }
-
-private:
-
-    int IndexOf(InternalIconData * pdata)
-    {
-        int count = m_toolbar->GetButtonCount();
-        for (int i = 0; i < count; i++)
-        {
-            if (m_toolbar->GetItemData(i) == pdata)
-                return i;
-        }
-        return -1;
-    }
-
-    void SetTimer(int length)
-    {
-        m_timer = ::SetTimer(m_hwndParent, BalloonsTimerId, length, NULL);
-    }
-
-    void Show(Info& info)
-    {
-        TRACE("ShowBalloonTip called for flags=%x text=%ws; title=%ws\n", info.uIcon, info.szInfo, info.szInfoTitle);
-
-        // TODO: NIF_REALTIME, NIIF_NOSOUND, other Vista+ flags
-
-        const int index = IndexOf(info.pSource);
-        RECT rc;
-        m_toolbar->GetItemRect(index, &rc);
-        m_toolbar->ClientToScreen(&rc);
-        const WORD x = (rc.left + rc.right) / 2;
-        const WORD y = (rc.top + rc.bottom) / 2;
-
-        m_tooltips->SetTitle(info.szInfoTitle, info.uIcon);
-        m_tooltips->TrackPosition(x, y);
-        m_tooltips->UpdateTipText(m_hwndParent, reinterpret_cast<LPARAM>(m_toolbar->m_hWnd), info.szInfo);
-        m_tooltips->TrackActivate(m_hwndParent, reinterpret_cast<LPARAM>(m_toolbar->m_hWnd));
-
-        m_current = info.pSource;
-        int timeout = info.uTimeout;
-        if (timeout < MinTimeout) timeout = MinTimeout;
-        if (timeout > MaxTimeout) timeout = MaxTimeout;
-
-        SetTimer(timeout);
-    }
-
-    void Close(IN OUT InternalIconData * notifyItem)
-    {
-        TRACE("HideBalloonTip called\n");
-
-        if (m_current == notifyItem && !m_currentClosed)
-        {
-            // Prevent Re-entry
-            m_currentClosed = true;
-            m_tooltips->TrackDeactivate();
-            SetTimer(CooldownBetweenBalloons);
-        }
-    }
-};
-
-class CNotifyToolbar :
-    public CWindowImplBaseT< CToolbar<InternalIconData>, CControlWinTraits >
-{
-    HIMAGELIST m_ImageList;
-    int m_VisibleButtonCount;
-
-    CBalloonQueue * m_BalloonQueue;
-
-public:
-    CNotifyToolbar() :
-        m_ImageList(NULL),
-        m_VisibleButtonCount(0),
-        m_BalloonQueue(NULL)
-    {
-    }
-
-    ~CNotifyToolbar()
-    {
-    }
-
-    int GetVisibleButtonCount()
-    {
-        return m_VisibleButtonCount;
-    }
-
-    int FindItem(IN HWND hWnd, IN UINT uID, InternalIconData ** pdata)
-    {
-        int count = GetButtonCount();
-
-        for (int i = 0; i < count; i++)
-        {
-            InternalIconData * data = GetItemData(i);
-
-            if (data->hWnd == hWnd &&
-                data->uID == uID)
-            {
-                if (pdata)
-                    *pdata = data;
-                return i;
-            }
-        }
-
-        return -1;
-    }
-
-    int FindExistingSharedIcon(HICON handle)
-    {
-        int count = GetButtonCount();
-        for (int i = 0; i < count; i++)
-        {
-            InternalIconData * data = GetItemData(i);
-            if (data->hIcon == handle)
-            {
-                TBBUTTON btn;
-                GetButton(i, &btn);
-                return btn.iBitmap;
-            }
-        }
-
-        return -1;
-    }
-
-    BOOL AddButton(IN CONST NOTIFYICONDATA *iconData)
-    {
-        TBBUTTON tbBtn;
-        InternalIconData * notifyItem;
-        WCHAR text[] = L"";
-
-        TRACE("Adding icon %d from hWnd %08x flags%s%s state%s%s", 
-            iconData->uID, iconData->hWnd,
-            (iconData->uFlags & NIF_ICON) ? " ICON" : "",
-            (iconData->uFlags & NIF_STATE) ? " STATE" : "",
-            (iconData->dwState & NIS_HIDDEN) ? " HIDDEN" : "",
-            (iconData->dwState & NIS_SHAREDICON) ? " SHARED" : "");
-
-        int index = FindItem(iconData->hWnd, iconData->uID, &notifyItem);
-        if (index >= 0)
-        {
-            TRACE("Icon %d from hWnd %08x ALREADY EXISTS!", iconData->uID, iconData->hWnd);
-            return FALSE;
-        }
-
-        notifyItem = new InternalIconData();
-        ZeroMemory(notifyItem, sizeof(*notifyItem));
-
-        notifyItem->hWnd = iconData->hWnd;
-        notifyItem->uID = iconData->uID;
-
-        tbBtn.fsState = TBSTATE_ENABLED;
-        tbBtn.fsStyle = BTNS_NOPREFIX;
-        tbBtn.dwData = (DWORD_PTR)notifyItem;
-        tbBtn.iString = (INT_PTR) text;
-        tbBtn.idCommand = GetButtonCount();
-
-        if (iconData->uFlags & NIF_STATE)
-        {
-            notifyItem->dwState = iconData->dwState & iconData->dwStateMask;
-        }
-
-        if (iconData->uFlags & NIF_MESSAGE)
-        {
-            notifyItem->uCallbackMessage = iconData->uCallbackMessage;
-        }
-
-        if (iconData->uFlags & NIF_ICON)
-        {
-            notifyItem->hIcon = iconData->hIcon;
-            BOOL hasSharedIcon = notifyItem->dwState & NIS_SHAREDICON;
-            if (hasSharedIcon)
-            {
-                INT iIcon = FindExistingSharedIcon(notifyItem->hIcon);
-                if (iIcon < 0)
-                {
-                    notifyItem->hIcon = NULL;
-                    TRACE("Shared icon requested, but HICON not found!!!");
-                }
-                tbBtn.iBitmap = iIcon;
-            }
-            else
-            {
-                tbBtn.iBitmap = ImageList_AddIcon(m_ImageList, notifyItem->hIcon);
-            }
-        }
-
-        if (iconData->uFlags & NIF_TIP)
-        {
-            StringCchCopy(notifyItem->szTip, _countof(notifyItem->szTip), iconData->szTip);
-        }
-
-        if (iconData->uFlags & NIF_INFO)
-        {
-            // NOTE: In Vista+, the uTimeout value is disregarded, and the accessibility settings are used always.
-            StrNCpy(notifyItem->szInfo, iconData->szInfo, _countof(notifyItem->szInfo));
-            StrNCpy(notifyItem->szInfoTitle, iconData->szInfoTitle, _countof(notifyItem->szInfo));
-            notifyItem->dwInfoFlags = iconData->dwInfoFlags;
-            notifyItem->uTimeout = iconData->uTimeout;
-        }
-
-        if (notifyItem->dwState & NIS_HIDDEN)
-        {
-            tbBtn.fsState |= TBSTATE_HIDDEN;
-        }
-        else
-        {
-            m_VisibleButtonCount++;
-        }
-
-        /* TODO: support VERSION_4 (NIF_GUID, NIF_REALTIME, NIF_SHOWTIP) */
-
-        CToolbar::AddButton(&tbBtn);
-        SetButtonSize(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
-
-        if (iconData->uFlags & NIF_INFO)
-        {
-            m_BalloonQueue->UpdateInfo(notifyItem);
-        }
-
-        return TRUE;
-    }
-
-    BOOL SwitchVersion(IN CONST NOTIFYICONDATA *iconData)
-    {
-        InternalIconData * notifyItem;
-        int index = FindItem(iconData->hWnd, iconData->uID, &notifyItem);
-        if (index < 0)
-        {
-            WARN("Icon %d from hWnd %08x DOES NOT EXIST!", iconData->uID, iconData->hWnd);
-            return FALSE;
-        }
-
-        if (iconData->uVersion != 0 && iconData->uVersion != NOTIFYICON_VERSION)
-        {
-            WARN("Tried to set the version of icon %d from hWnd %08x, to an unknown value %d. Vista+ program?", iconData->uID, iconData->hWnd, iconData->uVersion);
-            return FALSE;
-        }
-
-        // We can not store the version in the uVersion field, because it's union'd with uTimeout,
-        // which we also need to keep track of.
-        notifyItem->uVersionCopy = iconData->uVersion;
-
-        return TRUE;
-    }
-
-    BOOL UpdateButton(IN CONST NOTIFYICONDATA *iconData)
-    {
-        InternalIconData * notifyItem;
-        TBBUTTONINFO tbbi = { 0 };
-
-        TRACE("Updating icon %d from hWnd %08x flags%s%s state%s%s",
-            iconData->uID, iconData->hWnd,
-            (iconData->uFlags & NIF_ICON) ? " ICON" : "",
-            (iconData->uFlags & NIF_STATE) ? " STATE" : "",
-            (iconData->dwState & NIS_HIDDEN) ? " HIDDEN" : "",
-            (iconData->dwState & NIS_SHAREDICON) ? " SHARED" : "");
-
-        int index = FindItem(iconData->hWnd, iconData->uID, &notifyItem);
-        if (index < 0)
-        {
-            WARN("Icon %d from hWnd %08x DOES NOT EXIST!", iconData->uID, iconData->hWnd);
-            return AddButton(iconData);
-        }
-
-        TBBUTTON btn;
-        GetButton(index, &btn);
-        int oldIconIndex = btn.iBitmap;
-
-        tbbi.cbSize = sizeof(tbbi);
-        tbbi.dwMask = TBIF_BYINDEX | TBIF_COMMAND;
-        tbbi.idCommand = index;
-
-        if (iconData->uFlags & NIF_STATE)
-        {
-            if (iconData->dwStateMask & NIS_HIDDEN &&
-                (notifyItem->dwState & NIS_HIDDEN) != (iconData->dwState & NIS_HIDDEN))
-            {
-                tbbi.dwMask |= TBIF_STATE;
-                if (iconData->dwState & NIS_HIDDEN)
-                {
-                    tbbi.fsState |= TBSTATE_HIDDEN;
-                    m_VisibleButtonCount--;
-                }
-                else
-                {
-                    tbbi.fsState &= ~TBSTATE_HIDDEN;
-                    m_VisibleButtonCount++;
-                }
-            }
-
-            notifyItem->dwState &= ~iconData->dwStateMask;
-            notifyItem->dwState |= (iconData->dwState & iconData->dwStateMask);
-        }
-
-        if (iconData->uFlags & NIF_MESSAGE)
-        {
-            notifyItem->uCallbackMessage = iconData->uCallbackMessage;
-        }
-
-        if (iconData->uFlags & NIF_ICON)
-        {
-            BOOL hasSharedIcon = notifyItem->dwState & NIS_SHAREDICON;
-            if (hasSharedIcon)
-            {
-                INT iIcon = FindExistingSharedIcon(iconData->hIcon);
-                if (iIcon >= 0)
-                {
-                    notifyItem->hIcon = iconData->hIcon;
-                    tbbi.dwMask |= TBIF_IMAGE;
-                    tbbi.iImage = iIcon;
-                }
-                else
-                {
-                    TRACE("Shared icon requested, but HICON not found!!! IGNORING!");
-                }
-            }
-            else
-            {
-                notifyItem->hIcon = iconData->hIcon;
-                tbbi.dwMask |= TBIF_IMAGE;
-                tbbi.iImage = ImageList_ReplaceIcon(m_ImageList, oldIconIndex, notifyItem->hIcon);
-            }
-        }
-
-        if (iconData->uFlags & NIF_TIP)
-        {
-            StringCchCopy(notifyItem->szTip, _countof(notifyItem->szTip), iconData->szTip);
-        }
-
-        if (iconData->uFlags & NIF_INFO)
-        {
-            // NOTE: In Vista+, the uTimeout value is disregarded, and the accessibility settings are used always.
-            StrNCpy(notifyItem->szInfo, iconData->szInfo, _countof(notifyItem->szInfo));
-            StrNCpy(notifyItem->szInfoTitle, iconData->szInfoTitle, _countof(notifyItem->szInfo));
-            notifyItem->dwInfoFlags = iconData->dwInfoFlags;
-            notifyItem->uTimeout = iconData->uTimeout;
-        }
-
-        /* TODO: support VERSION_4 (NIF_GUID, NIF_REALTIME, NIF_SHOWTIP) */
-
-        SetButtonInfo(index, &tbbi);
-
-        if (iconData->uFlags & NIF_INFO)
-        {
-            m_BalloonQueue->UpdateInfo(notifyItem);
-        }
-
-        return TRUE;
-    }
-
-    BOOL RemoveButton(IN CONST NOTIFYICONDATA *iconData)
-    {
-        InternalIconData * notifyItem;
-
-        TRACE("Removing icon %d from hWnd %08x", iconData->uID, iconData->hWnd);
-
-        int index = FindItem(iconData->hWnd, iconData->uID, &notifyItem);
-        if (index < 0)
-        {
-            TRACE("Icon %d from hWnd %08x ALREADY MISSING!", iconData->uID, iconData->hWnd);
-
-            return FALSE;
-        }
-
-        if (!(notifyItem->dwState & NIS_HIDDEN))
-        {
-            m_VisibleButtonCount--;
-        }
-
-        if (!(notifyItem->dwState & NIS_SHAREDICON))
-        {
-            TBBUTTON btn;
-            GetButton(index, &btn);
-            int oldIconIndex = btn.iBitmap;
-            ImageList_Remove(m_ImageList, oldIconIndex);
-
-            // Update other icons!
-            int count = GetButtonCount();
-            for (int i = 0; i < count; i++)
-            {
-                TBBUTTON btn;
-                GetButton(i, &btn);
-
-                if (btn.iBitmap > oldIconIndex)
-                {
-                    TBBUTTONINFO tbbi2 = { 0 };
-                    tbbi2.cbSize = sizeof(tbbi2);
-                    tbbi2.dwMask = TBIF_BYINDEX | TBIF_IMAGE;
-                    tbbi2.iImage = btn.iBitmap-1;
-                    SetButtonInfo(i, &tbbi2);
-                }
-            }
-        }
-
-        m_BalloonQueue->RemoveInfo(notifyItem);
-
-        DeleteButton(index);
-
-        delete notifyItem;
-
-        return TRUE;
-    }
-
-
-    VOID GetTooltipText(LPARAM data, LPTSTR szTip, DWORD cchTip)
-    {
-        InternalIconData * notifyItem = reinterpret_cast<InternalIconData *>(data);
-        if (notifyItem)
-        {
-            StringCchCopy(szTip, cchTip, notifyItem->szTip);
-        }
-        else
-        {
-            StringCchCopy(szTip, cchTip, L"");
-        }
-    }
-
-    VOID ResizeImagelist()
-    {
-        int cx, cy;
-        HIMAGELIST iml;
-
-        if (!ImageList_GetIconSize(m_ImageList, &cx, &cy))
-            return;
-
-        if (cx == GetSystemMetrics(SM_CXSMICON) && cy == GetSystemMetrics(SM_CYSMICON))
-            return;
-
-        iml = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32 | ILC_MASK, 0, 1000);
-        if (!iml)
-            return;
-
-        ImageList_Destroy(m_ImageList);
-        m_ImageList = iml;
-        SetImageList(m_ImageList);
-
-        int count = GetButtonCount();
-        for (int i = 0; i < count; i++)
-        {
-            InternalIconData * data = GetItemData(i);
-            BOOL hasSharedIcon = data->dwState & NIS_SHAREDICON;
-            INT iIcon = hasSharedIcon ? FindExistingSharedIcon(data->hIcon) : -1;
-            if (iIcon < 0)
-                iIcon = ImageList_AddIcon(iml, data->hIcon);
-            TBBUTTONINFO tbbi = { sizeof(tbbi), TBIF_BYINDEX | TBIF_IMAGE, 0, iIcon};
-            SetButtonInfo(i, &tbbi);
-        }
-
-        SetButtonSize(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
-    }
-
-private:
-
-    VOID SendMouseEvent(IN WORD wIndex, IN UINT uMsg, IN WPARAM wParam)
-    {
-        static LPCWSTR eventNames [] = {
-            L"WM_MOUSEMOVE",
-            L"WM_LBUTTONDOWN",
-            L"WM_LBUTTONUP",
-            L"WM_LBUTTONDBLCLK",
-            L"WM_RBUTTONDOWN",
-            L"WM_RBUTTONUP",
-            L"WM_RBUTTONDBLCLK",
-            L"WM_MBUTTONDOWN",
-            L"WM_MBUTTONUP",
-            L"WM_MBUTTONDBLCLK",
-            L"WM_MOUSEWHEEL",
-            L"WM_XBUTTONDOWN",
-            L"WM_XBUTTONUP",
-            L"WM_XBUTTONDBLCLK"
-        };
-
-        InternalIconData * notifyItem = GetItemData(wIndex);
-
-        if (!::IsWindow(notifyItem->hWnd))
-        {
-            // We detect and destroy icons with invalid handles only on mouse move over systray, same as MS does.
-            // Alternatively we could search for them periodically (would waste more resources).
-            TRACE("Destroying icon %d with invalid handle hWnd=%08x\n", notifyItem->uID, notifyItem->hWnd);
-
-            RemoveButton(notifyItem);
-
-            HWND parentHWND = ::GetParent(::GetParent(GetParent()));
-            ::SendMessage(parentHWND, WM_SIZE, 0, 0);
-
-            return;
-        }
-
-        if (uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST)
-        {
-            TRACE("Sending message %S from button %d to %p (msg=%x, w=%x, l=%x)...\n",
-                     eventNames[uMsg - WM_MOUSEFIRST], wIndex,
-                     notifyItem->hWnd, notifyItem->uCallbackMessage, notifyItem->uID, uMsg);
-        }
-
-        DWORD pid;
-        GetWindowThreadProcessId(notifyItem->hWnd, &pid);
-
-        if (pid == GetCurrentProcessId() ||
-            (uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST))
-        {
-            ::PostMessage(notifyItem->hWnd,
-                          notifyItem->uCallbackMessage,
-                          notifyItem->uID,
-                          uMsg);
-        }
-        else
-        {
-            SendMessage(notifyItem->hWnd,
-                        notifyItem->uCallbackMessage,
-                        notifyItem->uID,
-                        uMsg);
-        }
-    }
-
-    LRESULT OnMouseEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
-    {
-        POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
-
-        INT iBtn = HitTest(&pt);
-
-        if (iBtn >= 0)
-        {
-            SendMouseEvent(iBtn, uMsg, wParam);
-        }
-
-        bHandled = FALSE;
-        return FALSE;
-    }
-
-    LRESULT OnTooltipShow(INT uCode, LPNMHDR hdr, BOOL& bHandled)
-    {
-        RECT rcTip, rcItem;
-        ::GetWindowRect(hdr->hwndFrom, &rcTip);
-
-        SIZE szTip = { rcTip.right - rcTip.left, rcTip.bottom - rcTip.top };
-
-        INT iBtn = GetHotItem();
-
-        if (iBtn >= 0)
-        {
-            MONITORINFO monInfo = { 0 };
-            HMONITOR hMon = MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTONEAREST);
-
-            monInfo.cbSize = sizeof(monInfo);
-
-            if (hMon)
-                GetMonitorInfo(hMon, &monInfo);
-            else
-                ::GetWindowRect(GetDesktopWindow(), &monInfo.rcMonitor);
-
-            GetItemRect(iBtn, &rcItem);
-
-            POINT ptItem = { rcItem.left, rcItem.top };
-            SIZE szItem = { rcItem.right - rcItem.left, rcItem.bottom - rcItem.top };
-            ClientToScreen(&ptItem);
-
-            ptItem.x += szItem.cx / 2;
-            ptItem.y -= szTip.cy;
-
-            if (ptItem.x + szTip.cx > monInfo.rcMonitor.right)
-                ptItem.x = monInfo.rcMonitor.right - szTip.cx;
-
-            if (ptItem.y + szTip.cy > monInfo.rcMonitor.bottom)
-                ptItem.y = monInfo.rcMonitor.bottom - szTip.cy;
-
-            if (ptItem.x < monInfo.rcMonitor.left)
-                ptItem.x = monInfo.rcMonitor.left;
-
-            if (ptItem.y < monInfo.rcMonitor.top)
-                ptItem.y = monInfo.rcMonitor.top;
-
-            TRACE("ptItem { %d, %d }\n", ptItem.x, ptItem.y);
-
-            ::SetWindowPos(hdr->hwndFrom, NULL, ptItem.x, ptItem.y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
-
-            return TRUE;
-        }
-
-        bHandled = FALSE;
-        return 0;
-    }
-
-public:
-    BEGIN_MSG_MAP(CNotifyToolbar)
-        MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseEvent)
-        NOTIFY_CODE_HANDLER(TTN_SHOW, OnTooltipShow)
-    END_MSG_MAP()
-
-    void Initialize(HWND hWndParent, CBalloonQueue * queue)
-    {
-        m_BalloonQueue = queue;
-
-        DWORD styles =
-            WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN |
-            TBSTYLE_FLAT | TBSTYLE_TOOLTIPS | TBSTYLE_WRAPABLE | TBSTYLE_TRANSPARENT |
-            CCS_TOP | CCS_NORESIZE | CCS_NOPARENTALIGN | CCS_NODIVIDER;
-
-        SubclassWindow(CToolbar::Create(hWndParent, styles));
-
-        // Force the toolbar tooltips window to always show tooltips even if not foreground
-        HWND tooltipsWnd = (HWND)SendMessageW(TB_GETTOOLTIPS);
-        if (tooltipsWnd)
-        {
-            ::SetWindowLong(tooltipsWnd, GWL_STYLE, ::GetWindowLong(tooltipsWnd, GWL_STYLE) | TTS_ALWAYSTIP);
-        }
-
-        SetWindowTheme(m_hWnd, L"TrayNotify", NULL);
-
-        m_ImageList = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32 | ILC_MASK, 0, 1000);        
-        SetImageList(m_ImageList);
-
-        TBMETRICS tbm = {sizeof(tbm)};
-        tbm.dwMask = TBMF_BARPAD | TBMF_BUTTONSPACING | TBMF_PAD;
-        tbm.cxPad = 1;
-        tbm.cyPad = 1;
-        tbm.cxButtonSpacing = 1;
-        tbm.cyButtonSpacing = 1;
-        SetMetrics(&tbm);
-
-        SetButtonSize(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
-    }
-};
-
-class CSysPagerWnd :
-    public CComObjectRootEx<CComMultiThreadModelNoCS>,
-    public CWindowImpl < CSysPagerWnd, CWindow, CControlWinTraits >,
-    public CIconWatcher
-{
-    CNotifyToolbar Toolbar;
-    CTooltips m_Balloons;
-    CBalloonQueue m_BalloonQueue;
-
-public:
-    CSysPagerWnd() {}
-    virtual ~CSysPagerWnd() {}
-
-    LRESULT DrawBackground(HDC hdc)
-    {
-        RECT rect;
-
-        GetClientRect(&rect);
-        DrawThemeParentBackground(m_hWnd, hdc, &rect);
-
-        return TRUE;
-    }
-
-    LRESULT OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
-    {
-        HDC hdc = (HDC) wParam;
-
-        if (!IsAppThemed())
-        {
-            bHandled = FALSE;
-            return 0;
-        }
-
-        return DrawBackground(hdc);
-    }
-
-    LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
-    {
-        Toolbar.Initialize(m_hWnd, &m_BalloonQueue);
-        CIconWatcher::Initialize(m_hWnd);
-
-        HWND hWndTop = GetAncestor(m_hWnd, GA_ROOT);
-
-        m_Balloons.Create(hWndTop, TTS_NOPREFIX | TTS_BALLOON | TTS_CLOSE);
-        
-        TOOLINFOW ti = { 0 };
-        ti.cbSize = TTTOOLINFOW_V1_SIZE;
-        ti.uFlags = TTF_TRACK | TTF_IDISHWND;
-        ti.uId = reinterpret_cast<UINT_PTR>(Toolbar.m_hWnd);
-        ti.hwnd = m_hWnd;
-        ti.lpszText = NULL;
-        ti.lParam = NULL;
-
-        BOOL ret = m_Balloons.AddTool(&ti);
-        if (!ret)
-        {
-            WARN("AddTool failed, LastError=%d (probably meaningless unless non-zero)\n", GetLastError());
-        }
-
-        m_BalloonQueue.Init(m_hWnd, &Toolbar, &m_Balloons);
-
-        // Explicitly request running applications to re-register their systray icons
-        ::SendNotifyMessageW(HWND_BROADCAST,
-                             RegisterWindowMessageW(L"TaskbarCreated"),
-                             0, 0);
-
-        return TRUE;
-    }
-
-    LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
-    {
-        m_BalloonQueue.Deinit();
-        CIconWatcher::Uninitialize();
-        return TRUE;
-    }
-
-    BOOL NotifyIconCmd(WPARAM wParam, LPARAM lParam)
-    {
-        PCOPYDATASTRUCT cpData = (PCOPYDATASTRUCT) lParam;
-        if (cpData->dwData == 1)
-        {
-            SYS_PAGER_COPY_DATA * data;
-            NOTIFYICONDATA *iconData;
-            BOOL ret = FALSE;
-
-            int VisibleButtonCount = Toolbar.GetVisibleButtonCount();
-
-            data = (PSYS_PAGER_COPY_DATA) cpData->lpData;
-            iconData = &data->nicon_data;
-
-            TRACE("NotifyIconCmd received. Code=%d\n", data->notify_code);
-            switch (data->notify_code)
-            {
-            case NIM_ADD:
-                ret = Toolbar.AddButton(iconData);
-                if (ret == TRUE)
-                {
-                    (void)AddIconToWatcher(iconData);
-                }
-                break;
-            case NIM_MODIFY:
-                ret = Toolbar.UpdateButton(iconData);
-                break;
-            case NIM_DELETE:
-                ret = Toolbar.RemoveButton(iconData);
-                if (ret == TRUE)
-                {
-                    (void)RemoveIconFromWatcher(iconData);
-                }
-                break;
-            case NIM_SETFOCUS:
-                Toolbar.SetFocus();
-                ret = TRUE;
-            case NIM_SETVERSION:
-                ret = Toolbar.SwitchVersion(iconData);
-            default:
-                TRACE("NotifyIconCmd received with unknown code %d.\n", data->notify_code);
-                return FALSE;
-            }
-
-            if (VisibleButtonCount != Toolbar.GetVisibleButtonCount())
-            {
-                HWND parentHWND = ::GetParent(GetParent());
-                ::SendMessage(parentHWND, WM_SIZE, 0, 0);
-            }
-
-            return ret;
-        }
-
-        return TRUE;
-    }
-
-    void GetSize(IN BOOL IsHorizontal, IN PSIZE size)
-    {
-        /* Get the ideal height or width */
-#if 0 
-        /* Unfortunately this doens't work correctly in ros */
-        Toolbar.GetIdealSize(!IsHorizontal, size);
-
-        /* Make the reference dimension an exact multiple of the icon size */
-        if (IsHorizontal)
-            size->cy -= size->cy % GetSystemMetrics(SM_CYSMICON);
-        else
-            size->cx -= size->cx % GetSystemMetrics(SM_CXSMICON);
-
-#else
-        INT rows = 0;
-        INT columns = 0;
-        INT cyButton = GetSystemMetrics(SM_CYSMICON) + 2;
-        INT cxButton = GetSystemMetrics(SM_CXSMICON) + 2;
-        int VisibleButtonCount = Toolbar.GetVisibleButtonCount();
-
-        if (IsHorizontal)
-        {
-            rows = max(size->cy / cyButton, 1);
-            columns = (VisibleButtonCount + rows - 1) / rows;
-        }
-        else
-        {
-            columns = max(size->cx / cxButton, 1);
-            rows = (VisibleButtonCount + columns - 1) / columns;
-        }
-        size->cx = columns * cxButton;
-        size->cy = rows * cyButton;
-#endif
-    }
-
-    LRESULT OnGetInfoTip(INT uCode, LPNMHDR hdr, BOOL& bHandled)
-    {
-        NMTBGETINFOTIPW * nmtip = (NMTBGETINFOTIPW *) hdr;
-        Toolbar.GetTooltipText(nmtip->lParam, nmtip->pszText, nmtip->cchTextMax);
-        return TRUE;
-    }
-
-    LRESULT OnCustomDraw(INT uCode, LPNMHDR hdr, BOOL& bHandled)
-    {
-        NMCUSTOMDRAW * cdraw = (NMCUSTOMDRAW *) hdr;
-        switch (cdraw->dwDrawStage)
-        {
-        case CDDS_PREPAINT:
-            return CDRF_NOTIFYITEMDRAW;
-
-        case CDDS_ITEMPREPAINT:
-            return TBCDRF_NOBACKGROUND | TBCDRF_NOEDGES | TBCDRF_NOOFFSET | TBCDRF_NOMARK | TBCDRF_NOETCHEDEFFECT;
-        }
-        return TRUE;
-    }
-
-    LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
-    {
-        LRESULT Ret = TRUE;
-        SIZE szClient;
-        szClient.cx = LOWORD(lParam);
-        szClient.cy = HIWORD(lParam);
-
-        Ret = DefWindowProc(uMsg, wParam, lParam);
-
-        if (Toolbar)
-        {
-            Toolbar.SetWindowPos(NULL, 0, 0, szClient.cx, szClient.cy, SWP_NOZORDER);
-            Toolbar.AutoSize();
-
-            RECT rc;
-            Toolbar.GetClientRect(&rc);
-
-            SIZE szBar = { rc.right - rc.left, rc.bottom - rc.top };
-
-            INT xOff = (szClient.cx - szBar.cx) / 2;
-            INT yOff = (szClient.cy - szBar.cy) / 2;
-
-            Toolbar.SetWindowPos(NULL, xOff, yOff, szBar.cx, szBar.cy, SWP_NOZORDER);
-        }
-        return Ret;
-    }
-
-    LRESULT OnCtxMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
-    {
-        bHandled = TRUE;
-        return 0;
-    }
-
-    LRESULT OnBalloonPop(UINT uCode, LPNMHDR hdr , BOOL& bHandled)
-    {
-        m_BalloonQueue.CloseCurrent();
-        bHandled = TRUE;
-        return 0;
-    }
-
-    LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
-    {
-        if (m_BalloonQueue.OnTimer(wParam))
-        {
-            bHandled = TRUE;
-        }
-
-        return 0;
-    }
-
-    void ResizeImagelist()
-    {
-        Toolbar.ResizeImagelist();
-    }
-
-    DECLARE_WND_CLASS_EX(szSysPagerWndClass, CS_DBLCLKS, COLOR_3DFACE)
-
-    BEGIN_MSG_MAP(CSysPagerWnd)
-        MESSAGE_HANDLER(WM_CREATE, OnCreate)
-        MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
-        MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
-        MESSAGE_HANDLER(WM_SIZE, OnSize)
-        MESSAGE_HANDLER(WM_CONTEXTMENU, OnCtxMenu)
-        MESSAGE_HANDLER(WM_TIMER, OnTimer)
-        NOTIFY_CODE_HANDLER(TTN_POP, OnBalloonPop)
-        NOTIFY_CODE_HANDLER(TBN_GETINFOTIPW, OnGetInfoTip)
-        NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW, OnCustomDraw)
-    END_MSG_MAP()
-
-    HWND _Init(IN HWND hWndParent, IN BOOL bVisible)
-    {
-        DWORD dwStyle;
-
-        /* Create the window. The tray window is going to move it to the correct
-            position and resize it as needed. */
-        dwStyle = WS_CHILD | WS_CLIPSIBLINGS;
-        if (bVisible)
-            dwStyle |= WS_VISIBLE;
-
-        Create(hWndParent, 0, NULL, dwStyle);
-
-        if (!m_hWnd)
-        {
-            return NULL;
-        }
-
-        SetWindowTheme(m_hWnd, L"TrayNotify", NULL);
-
-        return m_hWnd;
-    }
-};
-
-/*
- * TrayClockWnd
- */
-
-static const WCHAR szTrayClockWndClass[] = L"TrayClockWClass";
-
-#define ID_TRAYCLOCK_TIMER  0
-#define ID_TRAYCLOCK_TIMER_INIT 1
-
-static const struct
-{
-    BOOL IsTime;
-    DWORD dwFormatFlags;
-    LPCWSTR lpFormat;
-} ClockWndFormats [] = {
-    { TRUE, 0, NULL },
-    { FALSE, 0, L"dddd" },
-    { FALSE, DATE_SHORTDATE, NULL }
-};
-
-#define CLOCKWND_FORMAT_COUNT (_ARRAYSIZE(ClockWndFormats))
-
-#define TRAY_CLOCK_WND_SPACING_X    0
-#define TRAY_CLOCK_WND_SPACING_Y    0
-
-class CTrayClockWnd :
-    public CComObjectRootEx<CComMultiThreadModelNoCS>,
-    public CWindowImpl < CTrayClockWnd, CWindow, CControlWinTraits >
-{
-    HWND hWndNotify;
-    HFONT hFont;
-    COLORREF textColor;
-    RECT rcText;
-    SYSTEMTIME LocalTime;
-
-    union
-    {
-        DWORD dwFlags;
-        struct
-        {
-            DWORD IsTimerEnabled : 1;
-            DWORD IsInitTimerEnabled : 1;
-            DWORD LinesMeasured : 1;
-            DWORD IsHorizontal : 1;
-        };
-    };
-    DWORD LineSpacing;
-    SIZE CurrentSize;
-    WORD VisibleLines;
-    SIZE LineSizes[CLOCKWND_FORMAT_COUNT];
-    WCHAR szLines[CLOCKWND_FORMAT_COUNT][48];
-
-public:
-    CTrayClockWnd() :
-        hWndNotify(NULL),
-        hFont(NULL),
-        dwFlags(0),
-        LineSpacing(0),
-        VisibleLines(0)
-    {
-        ZeroMemory(&textColor, sizeof(textColor));
-        ZeroMemory(&rcText, sizeof(rcText));
-        ZeroMemory(&LocalTime, sizeof(LocalTime));
-        ZeroMemory(&CurrentSize, sizeof(CurrentSize));
-        ZeroMemory(LineSizes, sizeof(LineSizes));
-        ZeroMemory(szLines, sizeof(szLines));
-    }
-    virtual ~CTrayClockWnd() { }
-
-    LRESULT OnThemeChanged()
-    {
-        LOGFONTW clockFont;
-        HTHEME clockTheme;
-        HFONT hFont;
-
-        clockTheme = OpenThemeData(m_hWnd, L"Clock");
-
-        if (clockTheme)
-        {
-            GetThemeFont(clockTheme,
-                NULL,
-                CLP_TIME,
-                0,
-                TMT_FONT,
-                &clockFont);
-
-            hFont = CreateFontIndirectW(&clockFont);
-
-            GetThemeColor(clockTheme,
-                CLP_TIME,
-                0,
-                TMT_TEXTCOLOR,
-                &textColor);
-
-            if (this->hFont != NULL)
-                DeleteObject(this->hFont);
-
-            SetFont(hFont, FALSE);
-        }
-        else
-        {
-            /* We don't need to set a font here, our parent will use 
-              * WM_SETFONT to set the right one when themes are not enabled. */
-            textColor = RGB(0, 0, 0);
-        }
-
-        CloseThemeData(clockTheme);
-
-        return TRUE;
-    }
-
-    LRESULT OnThemeChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
-    {
-        return OnThemeChanged();
-    }
-
-    BOOL MeasureLines()
-    {
-        HDC hDC;
-        HFONT hPrevFont;
-        UINT c, i;
-        BOOL bRet = TRUE;
-
-        hDC = GetDC();
-        if (hDC != NULL)
-        {
-            if (hFont)
-                hPrevFont = (HFONT) SelectObject(hDC, hFont);
-
-            for (i = 0; i < CLOCKWND_FORMAT_COUNT && bRet; i++)
-            {
-                if (szLines[i][0] != L'\0' &&
-                    !GetTextExtentPointW(hDC, szLines[i], wcslen(szLines[i]),
-                                         &LineSizes[i]))
-                {
-                    bRet = FALSE;
-                    break;
-                }
-            }
-
-            if (hFont)
-                SelectObject(hDC, hPrevFont);
-
-            ReleaseDC(hDC);
-
-            if (bRet)
-            {
-                LineSpacing = 0;
-
-                /* calculate the line spacing */
-                for (i = 0, c = 0; i < CLOCKWND_FORMAT_COUNT; i++)
-                {
-                    if (LineSizes[i].cx > 0)
-                    {
-                        LineSpacing += LineSizes[i].cy;
-                        c++;
-                    }
-                }
-
-                if (c > 0)
-                {
-                    /* We want a spacing of 1/2 line */
-                    LineSpacing = (LineSpacing / c) / 2;
-                }
-
-                return TRUE;
-            }
-        }
-
-        return FALSE;
-    }
-
-    WORD GetMinimumSize(IN BOOL Horizontal, IN OUT PSIZE pSize)
-    {
-        WORD iLinesVisible = 0;
-        UINT i;
-        SIZE szMax = { 0, 0 };
-
-        if (!LinesMeasured)
-            LinesMeasured = MeasureLines();
-
-        if (!LinesMeasured)
-            return 0;
-
-        for (i = 0; i < CLOCKWND_FORMAT_COUNT; i++)
-        {
-            if (LineSizes[i].cx != 0)
-            {
-                if (iLinesVisible > 0)
-                {
-                    if (Horizontal)
-                    {
-                        if (szMax.cy + LineSizes[i].cy + (LONG) LineSpacing >
-                            pSize->cy - (2 * TRAY_CLOCK_WND_SPACING_Y))
-                        {
-                            break;
-                        }
-                    }
-                    else
-                    {
-                        if (LineSizes[i].cx > pSize->cx - (2 * TRAY_CLOCK_WND_SPACING_X))
-                            break;
-                    }
-
-                    /* Add line spacing */
-                    szMax.cy += LineSpacing;
-                }
-
-                iLinesVisible++;
-
-                /* Increase maximum rectangle */
-                szMax.cy += LineSizes[i].cy;
-                if (LineSizes[i].cx > szMax.cx - (2 * TRAY_CLOCK_WND_SPACING_X))
-                    szMax.cx = LineSizes[i].cx + (2 * TRAY_CLOCK_WND_SPACING_X);
-            }
-        }
-
-        szMax.cx += 2 * TRAY_CLOCK_WND_SPACING_X;
-        szMax.cy += 2 * TRAY_CLOCK_WND_SPACING_Y;
-
-        *pSize = szMax;
-
-        return iLinesVisible;
-    }
-
-
-    VOID UpdateWnd()
-    {
-        SIZE szPrevCurrent;
-        UINT BufSize, i;
-        INT iRet;
-        RECT rcClient;
-
-        ZeroMemory(LineSizes, sizeof(LineSizes));
-
-        szPrevCurrent = CurrentSize;
-
-        for (i = 0; i < CLOCKWND_FORMAT_COUNT; i++)
-        {
-            szLines[i][0] = L'\0';
-            BufSize = _countof(szLines[0]);
-
-            if (ClockWndFormats[i].IsTime)
-            {
-                iRet = GetTimeFormat(LOCALE_USER_DEFAULT,
-                    g_TaskbarSettings.bShowSeconds ? ClockWndFormats[i].dwFormatFlags : TIME_NOSECONDS,
-                    &LocalTime,
-                    ClockWndFormats[i].lpFormat,
-                    szLines[i],
-                    BufSize);
-            }
-            else
-            {
-                iRet = GetDateFormat(LOCALE_USER_DEFAULT,
-                    ClockWndFormats[i].dwFormatFlags,
-                    &LocalTime,
-                    ClockWndFormats[i].lpFormat,
-                    szLines[i],
-                    BufSize);
-            }
-
-            if (iRet != 0 && i == 0)
-            {
-                /* Set the window text to the time only */
-                SetWindowText(szLines[i]);
-            }
-        }
-
-        LinesMeasured = MeasureLines();
-
-        if (LinesMeasured &&
-            GetClientRect(&rcClient))
-        {
-            SIZE szWnd;
-
-            szWnd.cx = rcClient.right;
-            szWnd.cy = rcClient.bottom;
-
-            VisibleLines = GetMinimumSize(IsHorizontal, &szWnd);
-            CurrentSize = szWnd;
-        }
-
-        if (IsWindowVisible())
-        {
-            InvalidateRect(NULL, TRUE);
-
-            if (hWndNotify != NULL &&
-                (szPrevCurrent.cx != CurrentSize.cx ||
-                szPrevCurrent.cy != CurrentSize.cy))
-            {
-                NMHDR nmh;
-
-                nmh.hwndFrom = m_hWnd;
-                nmh.idFrom = GetWindowLongPtr(GWLP_ID);
-                nmh.code = NTNWM_REALIGN;
-
-                SendMessage(hWndNotify,
-                    WM_NOTIFY,
-                    (WPARAM) nmh.idFrom,
-                    (LPARAM) &nmh);
-            }
-        }
-    }
-
-    VOID Update()
-    {
-        GetLocalTime(&LocalTime);
-        UpdateWnd();
-    }
-
-    UINT CalculateDueTime()
-    {
-        UINT uiDueTime;
-
-        /* Calculate the due time */
-        GetLocalTime(&LocalTime);
-        uiDueTime = 1000 - (UINT) LocalTime.wMilliseconds;
-        if (g_TaskbarSettings.bShowSeconds)
-            uiDueTime += (UINT) LocalTime.wSecond * 100;
-        else
-            uiDueTime += (59 - (UINT) LocalTime.wSecond) * 1000;
-
-        if (uiDueTime < USER_TIMER_MINIMUM || uiDueTime > USER_TIMER_MAXIMUM)
-            uiDueTime = 1000;
-        else
-        {
-            /* Add an artificial delay of 0.05 seconds to make sure the timer
-               doesn't fire too early*/
-            uiDueTime += 50;
-        }
-
-        return uiDueTime;
-    }
-
-    BOOL ResetTime()
-    {
-        UINT uiDueTime;
-        BOOL Ret;
-
-        /* Disable all timers */
-        if (IsTimerEnabled)
-        {
-            KillTimer(ID_TRAYCLOCK_TIMER);
-            IsTimerEnabled = FALSE;
-        }
-
-        if (IsInitTimerEnabled)
-        {
-            KillTimer(ID_TRAYCLOCK_TIMER_INIT);
-        }
-
-        uiDueTime = CalculateDueTime();
-
-        /* Set the new timer */
-        Ret = SetTimer(ID_TRAYCLOCK_TIMER_INIT, uiDueTime, NULL) != 0;
-        IsInitTimerEnabled = Ret;
-
-        /* Update the time */
-        Update();
-
-        return Ret;
-    }
-
-    VOID CalibrateTimer()
-    {
-        UINT uiDueTime;
-        BOOL Ret;
-        UINT uiWait1, uiWait2;
-
-        /* Kill the initialization timer */
-        KillTimer(ID_TRAYCLOCK_TIMER_INIT);
-        IsInitTimerEnabled = FALSE;
-
-        uiDueTime = CalculateDueTime();
-
-        if (g_TaskbarSettings.bShowSeconds)
-        {
-            uiWait1 = 1000 - 200;
-            uiWait2 = 1000;
-        }
-        else
-        {
-            uiWait1 = 60 * 1000 - 200;
-            uiWait2 = 60 * 1000;
-        }
-
-        if (uiDueTime > uiWait1)
-        {
-            /* The update of the clock will be up to 200 ms late, but that's
-               acceptable. We're going to setup a timer that fires depending
-               uiWait2. */
-            Ret = SetTimer(ID_TRAYCLOCK_TIMER, uiWait2, NULL) != 0;
-            IsTimerEnabled = Ret;
-
-            /* Update the time */
-            Update();
-        }
-        else
-        {
-            /* Recalibrate the timer and recalculate again when the current
-               minute/second ends. */
-            ResetTime();
-        }
-    }
-
-    LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
-    {
-        /* Disable all timers */
-        if (IsTimerEnabled)
-        {
-            KillTimer(ID_TRAYCLOCK_TIMER);
-        }
-
-        if (IsInitTimerEnabled)
-        {
-            KillTimer(ID_TRAYCLOCK_TIMER_INIT);
-        }
-
-        return TRUE;
-    }
-
-    LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
-    {
-        RECT rcClient;
-        HFONT hPrevFont;
-        INT iPrevBkMode;
-        UINT i, line;
-
-        PAINTSTRUCT ps;
-        HDC hDC = (HDC) wParam;
-
-        if (wParam == 0)
-        {
-            hDC = BeginPaint(&ps);
-        }
-
-        if (hDC == NULL)
-            return FALSE;
-
-        if (LinesMeasured &&
-            GetClientRect(&rcClient))
-        {
-            iPrevBkMode = SetBkMode(hDC, TRANSPARENT);
-
-            SetTextColor(hDC, textColor);
-
-            hPrevFont = (HFONT) SelectObject(hDC, hFont);
-
-            rcClient.left = (rcClient.right / 2) - (CurrentSize.cx / 2);
-            rcClient.top = (rcClient.bottom / 2) - (CurrentSize.cy / 2);
-            rcClient.right = rcClient.left + CurrentSize.cx;
-            rcClient.bottom = rcClient.top + CurrentSize.cy;
-
-            for (i = 0, line = 0;
-                 i < CLOCKWND_FORMAT_COUNT && line < VisibleLines;
-                 i++)
-            {
-                if (LineSizes[i].cx != 0)
-                {
-                    TextOut(hDC,
-                        rcClient.left + (CurrentSize.cx / 2) - (LineSizes[i].cx / 2) +
-                        TRAY_CLOCK_WND_SPACING_X,
-                        rcClient.top + TRAY_CLOCK_WND_SPACING_Y,
-                        szLines[i],
-                        wcslen(szLines[i]));
-
-                    rcClient.top += LineSizes[i].cy + LineSpacing;
-                    line++;
-                }
-            }
-
-            SelectObject(hDC, hPrevFont);
-
-            SetBkMode(hDC, iPrevBkMode);
-        }
-
-        if (wParam == 0)
-        {
-            EndPaint(&ps);
-        }
-
-        return TRUE;
-    }
-
-    VOID SetFont(IN HFONT hNewFont, IN BOOL bRedraw)
-    {
-        hFont = hNewFont;
-        LinesMeasured = MeasureLines();
-        if (bRedraw)
-        {
-            InvalidateRect(NULL, TRUE);
-        }
-    }
-
-    LRESULT DrawBackground(HDC hdc)
-    {
-        RECT rect;
-
-        GetClientRect(&rect);
-        DrawThemeParentBackground(m_hWnd, hdc, &rect);
-
-        return TRUE;
-    }
-
-    LRESULT OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
-    {
-        HDC hdc = (HDC) wParam;
-
-        if (!IsAppThemed())
-        {
-            bHandled = FALSE;
-            return 0;
-        }
-
-        return DrawBackground(hdc);
-    }
-
-    LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
-    {
-        switch (wParam)
-        {
-        case ID_TRAYCLOCK_TIMER:
-            Update();
-            break;
-
-        case ID_TRAYCLOCK_TIMER_INIT:
-            CalibrateTimer();
-            break;
-        }
-        return TRUE;
-    }
-
-    LRESULT OnGetMinimumSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
-    {
-        IsHorizontal = (BOOL) wParam;
-
-        return (LRESULT) GetMinimumSize((BOOL) wParam, (PSIZE) lParam) != 0;
-    }
-
-    LRESULT OnUpdateTime(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
-    {
-        return (LRESULT) ResetTime();
-    }
-
-    LRESULT OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
-    {
-        return HTTRANSPARENT;
-    }
-
-    LRESULT OnSetFont(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
-    {
-        SetFont((HFONT) wParam, (BOOL) LOWORD(lParam));
-        return TRUE;
-    }
-
-    LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
-    {
-        ResetTime();
-        return TRUE;
-    }
-
-    LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
-    {
-        SIZE szClient;
-
-        szClient.cx = LOWORD(lParam);
-        szClient.cy = HIWORD(lParam);
-
-        VisibleLines = GetMinimumSize(IsHorizontal, &szClient);
-        CurrentSize = szClient;
-
-        InvalidateRect(NULL, TRUE);
-        return TRUE;
-    }
-
-    DECLARE_WND_CLASS_EX(szTrayClockWndClass, CS_DBLCLKS, COLOR_3DFACE)
-
-    BEGIN_MSG_MAP(CTrayClockWnd)
-        MESSAGE_HANDLER(WM_CREATE, OnCreate)
-        MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
-        MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
-        MESSAGE_HANDLER(WM_SIZE, OnSize)
-        MESSAGE_HANDLER(WM_PAINT, OnPaint)
-        MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint)
-        MESSAGE_HANDLER(WM_THEMECHANGED, OnThemeChanged)
-        MESSAGE_HANDLER(WM_TIMER, OnTimer)
-        MESSAGE_HANDLER(WM_NCHITTEST, OnNcHitTest)
-        MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
-        MESSAGE_HANDLER(TCWM_GETMINIMUMSIZE, OnGetMinimumSize)
-        MESSAGE_HANDLER(TCWM_UPDATETIME, OnUpdateTime)
-
-    END_MSG_MAP()
-
-    HWND _Init(IN HWND hWndParent, IN BOOL bVisible)
-    {
-        IsHorizontal = TRUE;
-
-        hWndNotify = hWndParent;
-
-        /* Create the window. The tray window is going to move it to the correct
-            position and resize it as needed. */
-        DWORD dwStyle = WS_CHILD | WS_CLIPSIBLINGS;
-        if (bVisible)
-            dwStyle |= WS_VISIBLE;
-
-        Create(hWndParent, 0, NULL, dwStyle);
-
-        if (m_hWnd != NULL)
-            SetWindowTheme(m_hWnd, L"TrayNotify", NULL);
-
-        return m_hWnd;
-
-    }
-};
-
 /*
  * TrayNotifyWnd
  */