diff --git a/base/shell/explorer/CMakeLists.txt b/base/shell/explorer/CMakeLists.txt index f935f4dafd7..fa66d2e428c 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 9aaa351f76d..1aacb42ce3d 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 00000000000..7a553608533 --- /dev/null +++ b/base/shell/explorer/syspager.cpp @@ -0,0 +1,1228 @@ +/* + * ReactOS Explorer + * + * Copyright 2006 - 2007 Thomas Weidenmueller + * Copyright 2018 Ged Murphy + * + * 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(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 * 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(m_toolbar->m_hWnd), info.szInfo); + m_tooltips->TrackActivate(m_hwndParent, reinterpret_cast(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(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(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 00000000000..381c143a322 --- /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 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(source->hIcon); + uTimeout = source->uTimeout; + } + }; + + HWND m_hwndParent; + + CTooltips * m_tooltips; + + CAtlList m_queue; + + CToolbar * m_toolbar; + + InternalIconData * m_current; + bool m_currentClosed; + + int m_timer; + +public: + CBalloonQueue(); + + void Init(HWND hwndParent, CToolbar * 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, 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, + 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 00000000000..125fc73b7d2 --- /dev/null +++ b/base/shell/explorer/trayclock.cpp @@ -0,0 +1,576 @@ +/* + * ReactOS Explorer + * + * Copyright 2006 - 2007 Thomas Weidenmueller + * Copyright 2018 Ged Murphy + * + * 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 00000000000..23911dfae9b --- /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, + 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 1b05b501a70..9a0294e500a 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 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(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(source->hIcon); - uTimeout = source->uTimeout; - } - }; - - HWND m_hwndParent; - - CTooltips * m_tooltips; - - CAtlList m_queue; - - CToolbar * 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 * 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(m_toolbar->m_hWnd), info.szInfo); - m_tooltips->TrackActivate(m_hwndParent, reinterpret_cast(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, 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(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, - 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(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, - 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 */