https://git.reactos.org/?p=reactos.git;a=commitdiff;h=3d75cc0814cc7e2167b9a…
commit 3d75cc0814cc7e2167b9aac05823ca18b8a3cdc6
Author: David Quintana <gigaherz(a)gmail.com>
AuthorDate: Wed Jan 24 14:41:06 2018 +0100
Commit: David Quintana <gigaherz(a)gmail.com>
CommitDate: Wed Jan 24 14:41:31 2018 +0100
[EXPLORER] Split up the notification area into a few more manageable pieces.
---
base/shell/explorer/CMakeLists.txt | 2 +
base/shell/explorer/precomp.h | 12 +
base/shell/explorer/syspager.cpp | 1228 +++++++++++++++++++++++
base/shell/explorer/syspager.h | 203 ++++
base/shell/explorer/trayclock.cpp | 576 +++++++++++
base/shell/explorer/trayclock.h | 94 ++
base/shell/explorer/trayntfy.cpp | 1947 ------------------------------------
7 files changed, 2115 insertions(+), 1947 deletions(-)
diff --git a/base/shell/explorer/CMakeLists.txt b/base/shell/explorer/CMakeLists.txt
index f935f4dafd..fa66d2e428 100644
--- a/base/shell/explorer/CMakeLists.txt
+++ b/base/shell/explorer/CMakeLists.txt
@@ -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
diff --git a/base/shell/explorer/precomp.h b/base/shell/explorer/precomp.h
index 9aaa351f76..1aacb42ce3 100644
--- a/base/shell/explorer/precomp.h
+++ b/base/shell/explorer/precomp.h
@@ -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
index 0000000000..7a55360853
--- /dev/null
+++ b/base/shell/explorer/syspager.cpp
@@ -0,0 +1,1228 @@
+/*
+ * ReactOS Explorer
+ *
+ * Copyright 2006 - 2007 Thomas Weidenmueller <w3seek(a)reactos.org>
+ * Copyright 2018 Ged Murphy <gedmurphy(a)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, ¬ifyItem);
+ 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, ¬ifyItem);
+ 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, ¬ifyItem);
+ 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, ¬ifyItem);
+ 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
index 0000000000..381c143a32
--- /dev/null
+++ b/base/shell/explorer/syspager.h
@@ -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
index 0000000000..125fc73b7d
--- /dev/null
+++ b/base/shell/explorer/trayclock.cpp
@@ -0,0 +1,576 @@
+/*
+ * ReactOS Explorer
+ *
+ * Copyright 2006 - 2007 Thomas Weidenmueller <w3seek(a)reactos.org>
+ * Copyright 2018 Ged Murphy <gedmurphy(a)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
index 0000000000..23911dfae9
--- /dev/null
+++ b/base/shell/explorer/trayclock.h
@@ -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
diff --git a/base/shell/explorer/trayntfy.cpp b/base/shell/explorer/trayntfy.cpp
index 1b05b501a7..9a0294e500 100644
--- a/base/shell/explorer/trayntfy.cpp
+++ b/base/shell/explorer/trayntfy.cpp
@@ -21,1953 +21,6 @@
#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, ¬ifyItem);
- 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, ¬ifyItem);
- 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, ¬ifyItem);
- 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, ¬ifyItem);
- 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
*/