mirror of
https://github.com/reactos/reactos.git
synced 2024-11-02 21:09:15 +00:00
4127024b0d
With these, we generate the WM_CONTEXTMENU and NIN_(KEY)SELECT shell icon notifications that applications expect when they handle shell notification icons with uVersion >= 3. This fixes in particular the previously unresponsive icon of KVIrc 4.x, and more generally *all* the notifiation icons of Qt applications. CORE-10605 #resolve
1577 lines
44 KiB
C++
1577 lines
44 KiB
C++
/*
|
|
* ReactOS Explorer
|
|
*
|
|
* Copyright 2006 - 2007 Thomas Weidenmueller <w3seek@reactos.org>
|
|
* Copyright 2018 Ged Murphy <gedmurphy@reactos.org>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "precomp.h"
|
|
|
|
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(CONST NOTIFYICONDATA *iconData) :
|
|
hProcess(NULL), ProcessId(0)
|
|
{
|
|
IconData.cbSize = sizeof(NOTIFYICONDATA);
|
|
IconData.hWnd = iconData->hWnd;
|
|
IconData.uID = iconData->uID;
|
|
IconData.guidItem = iconData->guidItem;
|
|
}
|
|
|
|
~IconWatcherData()
|
|
{
|
|
if (hProcess)
|
|
{
|
|
CloseHandle(hProcess);
|
|
}
|
|
}
|
|
};
|
|
|
|
class CIconWatcher
|
|
{
|
|
CAtlList<IconWatcherData *> m_WatcherList;
|
|
CRITICAL_SECTION m_ListLock;
|
|
HANDLE m_hWatcherThread;
|
|
HANDLE m_WakeUpEvent;
|
|
HWND m_hwndSysTray;
|
|
bool m_Loop;
|
|
|
|
public:
|
|
CIconWatcher();
|
|
|
|
virtual ~CIconWatcher();
|
|
|
|
bool Initialize(_In_ HWND hWndParent);
|
|
void Uninitialize();
|
|
|
|
bool AddIconToWatcher(_In_ CONST NOTIFYICONDATA *iconData);
|
|
bool RemoveIconFromWatcher(_In_ CONST NOTIFYICONDATA *iconData);
|
|
|
|
IconWatcherData* GetListEntry(_In_opt_ CONST NOTIFYICONDATA *iconData, _In_opt_ HANDLE hProcess, _In_ bool Remove);
|
|
|
|
private:
|
|
|
|
static UINT WINAPI WatcherThread(_In_opt_ LPVOID lpParam);
|
|
};
|
|
|
|
class CNotifyToolbar;
|
|
|
|
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;
|
|
StringCchCopy(szInfo, _countof(szInfo), source->szInfo);
|
|
StringCchCopy(szInfoTitle, _countof(szInfoTitle), source->szInfoTitle);
|
|
uIcon = source->dwInfoFlags & NIIF_ICON_MASK;
|
|
if (source->dwInfoFlags == NIIF_USER)
|
|
uIcon = reinterpret_cast<WPARAM>(source->hIcon);
|
|
uTimeout = source->uTimeout;
|
|
}
|
|
};
|
|
|
|
HWND m_hwndParent;
|
|
|
|
CTooltips * m_tooltips;
|
|
|
|
CAtlList<Info> m_queue;
|
|
|
|
CNotifyToolbar * m_toolbar;
|
|
|
|
InternalIconData * m_current;
|
|
bool m_currentClosed;
|
|
|
|
int m_timer;
|
|
|
|
public:
|
|
CBalloonQueue();
|
|
|
|
void Init(HWND hwndParent, CNotifyToolbar * 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, IN UINT uReason);
|
|
};
|
|
|
|
class CNotifyToolbar :
|
|
public CWindowImplBaseT< CToolbar<InternalIconData>, CControlWinTraits >
|
|
{
|
|
HIMAGELIST m_ImageList;
|
|
int m_VisibleButtonCount;
|
|
|
|
CBalloonQueue * m_BalloonQueue;
|
|
|
|
public:
|
|
CNotifyToolbar();
|
|
virtual ~CNotifyToolbar();
|
|
|
|
int GetVisibleButtonCount();
|
|
int FindItem(IN HWND hWnd, IN UINT uID, InternalIconData ** pdata);
|
|
int FindExistingSharedIcon(HICON handle);
|
|
BOOL AddButton(IN CONST NOTIFYICONDATA *iconData);
|
|
BOOL SwitchVersion(IN CONST NOTIFYICONDATA *iconData);
|
|
BOOL UpdateButton(IN CONST NOTIFYICONDATA *iconData);
|
|
BOOL RemoveButton(IN CONST NOTIFYICONDATA *iconData);
|
|
VOID ResizeImagelist();
|
|
bool SendNotifyCallback(InternalIconData* notifyItem, UINT uMsg);
|
|
|
|
private:
|
|
LRESULT OnCtxMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
|
|
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_HANDLER(WM_CONTEXTMENU, OnCtxMenu)
|
|
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 CComCoClass<CSysPagerWnd>,
|
|
public CComObjectRootEx<CComMultiThreadModelNoCS>,
|
|
public CWindowImpl < CSysPagerWnd, CWindow, CControlWinTraits >,
|
|
public IOleWindow,
|
|
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 OnCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, 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);
|
|
LRESULT OnCopyData(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
|
|
LRESULT OnSettingChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
|
|
LRESULT OnGetMinimumSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
|
|
|
|
public:
|
|
|
|
HRESULT WINAPI GetWindow(HWND* phwnd)
|
|
{
|
|
if (!phwnd)
|
|
return E_INVALIDARG;
|
|
*phwnd = m_hWnd;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT WINAPI ContextSensitiveHelp(BOOL fEnterMode)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
DECLARE_NOT_AGGREGATABLE(CSysPagerWnd)
|
|
|
|
DECLARE_PROTECT_FINAL_CONSTRUCT()
|
|
BEGIN_COM_MAP(CSysPagerWnd)
|
|
COM_INTERFACE_ENTRY_IID(IID_IOleWindow, IOleWindow)
|
|
END_COM_MAP()
|
|
|
|
BOOL NotifyIcon(DWORD dwMessage, _In_ CONST NOTIFYICONDATA *iconData);
|
|
void GetSize(IN BOOL IsHorizontal, IN PSIZE size);
|
|
|
|
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_COMMAND, OnCommand)
|
|
MESSAGE_HANDLER(WM_SIZE, OnSize)
|
|
MESSAGE_HANDLER(WM_CONTEXTMENU, OnCtxMenu)
|
|
MESSAGE_HANDLER(WM_TIMER, OnTimer)
|
|
MESSAGE_HANDLER(WM_COPYDATA, OnCopyData)
|
|
MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChanged)
|
|
MESSAGE_HANDLER(TNWM_GETMINIMUMSIZE, OnGetMinimumSize)
|
|
NOTIFY_CODE_HANDLER(TTN_POP, OnBalloonPop)
|
|
NOTIFY_CODE_HANDLER(TBN_GETINFOTIPW, OnGetInfoTip)
|
|
NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW, OnCustomDraw)
|
|
END_MSG_MAP()
|
|
|
|
HRESULT Initialize(IN HWND hWndParent);
|
|
};
|
|
|
|
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_ CONST 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 = 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_ CONST 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_ CONST NOTIFYICONDATA *iconData, _In_opt_ HANDLE hProcess, _In_ bool Remove)
|
|
{
|
|
IconWatcherData *Entry = NULL;
|
|
POSITION NextPosition = m_WatcherList.GetHeadPosition();
|
|
POSITION Position;
|
|
do
|
|
{
|
|
Position = NextPosition;
|
|
|
|
Entry = m_WatcherList.GetNext(NextPosition);
|
|
if (Entry)
|
|
{
|
|
if ((iconData && ((Entry->IconData.hWnd == iconData->hWnd) && (Entry->IconData.uID == iconData->uID))) ||
|
|
(hProcess && (Entry->hProcess == hProcess)))
|
|
{
|
|
if (Remove)
|
|
m_WatcherList.RemoveAt(Position);
|
|
break;
|
|
}
|
|
}
|
|
Entry = NULL;
|
|
|
|
} while (NextPosition != NULL);
|
|
|
|
return Entry;
|
|
}
|
|
|
|
UINT WINAPI CIconWatcher::WatcherThread(_In_opt_ LPVOID lpParam)
|
|
{
|
|
CIconWatcher* This = reinterpret_cast<CIconWatcher *>(lpParam);
|
|
HANDLE *WatchList = NULL;
|
|
|
|
This->m_Loop = true;
|
|
while (This->m_Loop)
|
|
{
|
|
EnterCriticalSection(&This->m_ListLock);
|
|
|
|
DWORD Size;
|
|
Size = This->m_WatcherList.GetCount() + 1;
|
|
ASSERT(Size <= MAXIMUM_WAIT_OBJECTS);
|
|
|
|
if (WatchList)
|
|
delete[] WatchList;
|
|
WatchList = new HANDLE[Size];
|
|
WatchList[0] = This->m_WakeUpEvent;
|
|
|
|
POSITION Pos;
|
|
for (size_t i = 0; i < This->m_WatcherList.GetCount(); i++)
|
|
{
|
|
Pos = This->m_WatcherList.FindIndex(i);
|
|
if (Pos)
|
|
{
|
|
IconWatcherData *Icon;
|
|
Icon = This->m_WatcherList.GetAt(Pos);
|
|
WatchList[i + 1] = Icon->hProcess;
|
|
}
|
|
}
|
|
|
|
LeaveCriticalSection(&This->m_ListLock);
|
|
|
|
DWORD Status;
|
|
Status = WaitForMultipleObjects(Size,
|
|
WatchList,
|
|
FALSE,
|
|
INFINITE);
|
|
if (Status == WAIT_OBJECT_0)
|
|
{
|
|
// We've been kicked, we have updates to our list (or we're exiting the thread)
|
|
if (This->m_Loop)
|
|
TRACE("Updating watched icon list");
|
|
}
|
|
else if ((Status >= WAIT_OBJECT_0 + 1) && (Status < Size))
|
|
{
|
|
IconWatcherData *Icon;
|
|
Icon = This->GetListEntry(NULL, WatchList[Status], false);
|
|
|
|
TRACE("Pid %lu owns a notification icon and has stopped without deleting it. We'll cleanup on its behalf", Icon->ProcessId);
|
|
|
|
TRAYNOTIFYDATAW tnid = {0};
|
|
tnid.dwSignature = NI_NOTIFY_SIG;
|
|
tnid.dwMessage = NIM_DELETE;
|
|
CopyMemory(&tnid.nid, &Icon->IconData, Icon->IconData.cbSize);
|
|
|
|
COPYDATASTRUCT data;
|
|
data.dwData = 1;
|
|
data.cbData = sizeof(tnid);
|
|
data.lpData = &tnid;
|
|
|
|
BOOL Success = ::SendMessage(This->m_hwndSysTray, WM_COPYDATA,
|
|
(WPARAM)&Icon->IconData, (LPARAM)&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, CNotifyToolbar * 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, NIN_BALLOONTIMEOUT);
|
|
}
|
|
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, NIN_BALLOONHIDE);
|
|
}
|
|
}
|
|
|
|
void CBalloonQueue::RemoveInfo(InternalIconData * notifyItem)
|
|
{
|
|
Close(notifyItem, NIN_BALLOONHIDE);
|
|
|
|
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, NIN_BALLOONTIMEOUT);
|
|
}
|
|
}
|
|
|
|
int CBalloonQueue::IndexOf(InternalIconData * pdata)
|
|
{
|
|
int count = m_toolbar->GetButtonCount();
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
if (m_toolbar->GetItemData(i) == pdata)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void CBalloonQueue::SetTimer(int length)
|
|
{
|
|
m_timer = ::SetTimer(m_hwndParent, BalloonsTimerId, length, NULL);
|
|
}
|
|
|
|
void CBalloonQueue::Show(Info& info)
|
|
{
|
|
TRACE("ShowBalloonTip called for flags=%x text=%ws; title=%ws\n", info.uIcon, info.szInfo, info.szInfoTitle);
|
|
|
|
// TODO: NIF_REALTIME, NIIF_NOSOUND, other Vista+ flags
|
|
|
|
const int index = IndexOf(info.pSource);
|
|
RECT rc;
|
|
m_toolbar->GetItemRect(index, &rc);
|
|
m_toolbar->ClientToScreen(&rc);
|
|
const WORD x = (rc.left + rc.right) / 2;
|
|
const WORD y = (rc.top + rc.bottom) / 2;
|
|
|
|
m_tooltips->SetTitle(info.szInfoTitle, info.uIcon);
|
|
m_tooltips->TrackPosition(x, y);
|
|
m_tooltips->UpdateTipText(m_hwndParent, reinterpret_cast<LPARAM>(m_toolbar->m_hWnd), info.szInfo);
|
|
m_tooltips->TrackActivate(m_hwndParent, reinterpret_cast<LPARAM>(m_toolbar->m_hWnd));
|
|
|
|
m_current = info.pSource;
|
|
int timeout = info.uTimeout;
|
|
if (timeout < MinTimeout) timeout = MinTimeout;
|
|
if (timeout > MaxTimeout) timeout = MaxTimeout;
|
|
|
|
SetTimer(timeout);
|
|
|
|
m_toolbar->SendNotifyCallback(m_current, NIN_BALLOONSHOW);
|
|
}
|
|
|
|
void CBalloonQueue::Close(IN OUT InternalIconData * notifyItem, IN UINT uReason)
|
|
{
|
|
TRACE("HideBalloonTip called\n");
|
|
|
|
if (m_current == notifyItem && !m_currentClosed)
|
|
{
|
|
m_toolbar->SendNotifyCallback(m_current, uReason);
|
|
|
|
// 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 = { 0 };
|
|
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.
|
|
StringCchCopy(notifyItem->szInfo, _countof(notifyItem->szInfo), iconData->szInfo);
|
|
StringCchCopy(notifyItem->szInfoTitle, _countof(notifyItem->szInfoTitle), iconData->szInfoTitle);
|
|
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.
|
|
StringCchCopy(notifyItem->szInfo, _countof(notifyItem->szInfo), iconData->szInfo);
|
|
StringCchCopy(notifyItem->szInfoTitle, _countof(notifyItem->szInfoTitle), iconData->szInfoTitle);
|
|
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));
|
|
}
|
|
|
|
LRESULT CNotifyToolbar::OnCtxMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
|
|
{
|
|
bHandled = FALSE;
|
|
|
|
/*
|
|
* WM_CONTEXTMENU message can be generated either by the mouse,
|
|
* in which case lParam encodes the mouse coordinates where the
|
|
* user right-clicked the mouse, or can be generated by (Shift-)F10
|
|
* keyboard press, in which case lParam equals -1.
|
|
*/
|
|
INT iBtn = GetHotItem();
|
|
if (iBtn < 0)
|
|
return 0;
|
|
|
|
InternalIconData* notifyItem = GetItemData(iBtn);
|
|
|
|
if (!::IsWindow(notifyItem->hWnd))
|
|
return 0;
|
|
|
|
if (notifyItem->uVersionCopy >= NOTIFYICON_VERSION)
|
|
{
|
|
/* Transmit the WM_CONTEXTMENU message if the notification icon supports it */
|
|
::SendNotifyMessage(notifyItem->hWnd,
|
|
notifyItem->uCallbackMessage,
|
|
notifyItem->uID,
|
|
WM_CONTEXTMENU);
|
|
}
|
|
else if (lParam == -1)
|
|
{
|
|
/*
|
|
* Otherwise, and only if the WM_CONTEXTMENU message was generated
|
|
* from the keyboard, simulate right-click mouse messages. This is
|
|
* not needed if the message came from the mouse because in this
|
|
* case the right-click mouse messages were already sent together.
|
|
*/
|
|
::SendNotifyMessage(notifyItem->hWnd,
|
|
notifyItem->uCallbackMessage,
|
|
notifyItem->uID,
|
|
WM_RBUTTONDOWN);
|
|
::SendNotifyMessage(notifyItem->hWnd,
|
|
notifyItem->uCallbackMessage,
|
|
notifyItem->uID,
|
|
WM_RBUTTONUP);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool CNotifyToolbar::SendNotifyCallback(InternalIconData* notifyItem, UINT uMsg)
|
|
{
|
|
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);
|
|
|
|
/* Ask the parent to resize */
|
|
NMHDR nmh = {GetParent(), 0, NTNWM_REALIGN};
|
|
GetParent().SendMessage(WM_NOTIFY, 0, (LPARAM) &nmh);
|
|
|
|
return true;
|
|
}
|
|
|
|
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);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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 (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);
|
|
}
|
|
|
|
SendNotifyCallback(notifyItem, uMsg);
|
|
}
|
|
|
|
LRESULT CNotifyToolbar::OnMouseEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
|
|
{
|
|
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
|
|
INT iBtn = HitTest(&pt);
|
|
|
|
if (iBtn >= 0)
|
|
{
|
|
SendMouseEvent(iBtn, uMsg, wParam);
|
|
}
|
|
|
|
bHandled = FALSE;
|
|
return FALSE;
|
|
}
|
|
|
|
static VOID GetTooltipText(LPARAM data, LPTSTR szTip, DWORD cchTip)
|
|
{
|
|
InternalIconData * notifyItem = reinterpret_cast<InternalIconData *>(data);
|
|
if (notifyItem)
|
|
{
|
|
StringCchCopy(szTip, cchTip, notifyItem->szTip);
|
|
}
|
|
else
|
|
{
|
|
StringCchCopy(szTip, cchTip, L"");
|
|
}
|
|
}
|
|
|
|
LRESULT CNotifyToolbar::OnTooltipShow(INT uCode, LPNMHDR hdr, BOOL& bHandled)
|
|
{
|
|
RECT rcTip, rcItem;
|
|
::GetWindowRect(hdr->hwndFrom, &rcTip);
|
|
|
|
SIZE szTip = { rcTip.right - rcTip.left, rcTip.bottom - rcTip.top };
|
|
|
|
INT iBtn = GetHotItem();
|
|
|
|
if (iBtn >= 0)
|
|
{
|
|
MONITORINFO monInfo = { 0 };
|
|
HMONITOR hMon = MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTONEAREST);
|
|
|
|
monInfo.cbSize = sizeof(monInfo);
|
|
|
|
if (hMon)
|
|
GetMonitorInfo(hMon, &monInfo);
|
|
else
|
|
::GetWindowRect(GetDesktopWindow(), &monInfo.rcMonitor);
|
|
|
|
GetItemRect(iBtn, &rcItem);
|
|
|
|
POINT ptItem = { rcItem.left, rcItem.top };
|
|
SIZE szItem = { rcItem.right - rcItem.left, rcItem.bottom - rcItem.top };
|
|
ClientToScreen(&ptItem);
|
|
|
|
ptItem.x += szItem.cx / 2;
|
|
ptItem.y -= szTip.cy;
|
|
|
|
if (ptItem.x + szTip.cx > monInfo.rcMonitor.right)
|
|
ptItem.x = monInfo.rcMonitor.right - szTip.cx;
|
|
|
|
if (ptItem.y + szTip.cy > monInfo.rcMonitor.bottom)
|
|
ptItem.y = monInfo.rcMonitor.bottom - szTip.cy;
|
|
|
|
if (ptItem.x < monInfo.rcMonitor.left)
|
|
ptItem.x = monInfo.rcMonitor.left;
|
|
|
|
if (ptItem.y < monInfo.rcMonitor.top)
|
|
ptItem.y = monInfo.rcMonitor.top;
|
|
|
|
TRACE("ptItem { %d, %d }\n", ptItem.x, ptItem.y);
|
|
|
|
::SetWindowPos(hdr->hwndFrom, NULL, ptItem.x, ptItem.y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
bHandled = FALSE;
|
|
return 0;
|
|
}
|
|
|
|
void CNotifyToolbar::Initialize(HWND hWndParent, CBalloonQueue * queue)
|
|
{
|
|
m_BalloonQueue = queue;
|
|
|
|
DWORD styles =
|
|
WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN |
|
|
TBSTYLE_FLAT | TBSTYLE_TOOLTIPS | TBSTYLE_WRAPABLE | TBSTYLE_TRANSPARENT |
|
|
CCS_TOP | CCS_NORESIZE | CCS_NOPARENTALIGN | CCS_NODIVIDER;
|
|
|
|
SubclassWindow(CToolbar::Create(hWndParent, styles));
|
|
|
|
// Force the toolbar tooltips window to always show tooltips even if not foreground
|
|
HWND tooltipsWnd = (HWND)SendMessageW(TB_GETTOOLTIPS);
|
|
if (tooltipsWnd)
|
|
{
|
|
::SetWindowLong(tooltipsWnd, GWL_STYLE, ::GetWindowLong(tooltipsWnd, GWL_STYLE) | TTS_ALWAYSTIP);
|
|
}
|
|
|
|
SetWindowTheme(m_hWnd, L"TrayNotify", NULL);
|
|
|
|
m_ImageList = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32 | ILC_MASK, 0, 1000);
|
|
SetImageList(m_ImageList);
|
|
|
|
TBMETRICS tbm = {sizeof(tbm)};
|
|
tbm.dwMask = TBMF_BARPAD | TBMF_BUTTONSPACING | TBMF_PAD;
|
|
tbm.cxPad = 1;
|
|
tbm.cyPad = 1;
|
|
tbm.cxBarPad = 1;
|
|
tbm.cyBarPad = 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::OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
|
|
{
|
|
HDC hdc = (HDC) wParam;
|
|
|
|
if (!IsAppThemed())
|
|
{
|
|
bHandled = FALSE;
|
|
return 0;
|
|
}
|
|
|
|
RECT rect;
|
|
GetClientRect(&rect);
|
|
DrawThemeParentBackground(m_hWnd, hdc, &rect);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
LRESULT CSysPagerWnd::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
|
|
{
|
|
Toolbar.Initialize(m_hWnd, &m_BalloonQueue);
|
|
CIconWatcher::Initialize(m_hWnd);
|
|
|
|
HWND hWndTop = GetAncestor(m_hWnd, GA_ROOT);
|
|
|
|
m_Balloons.Create(hWndTop, TTS_NOPREFIX | TTS_BALLOON | TTS_CLOSE);
|
|
|
|
TOOLINFOW ti = { 0 };
|
|
ti.cbSize = TTTOOLINFOW_V1_SIZE;
|
|
ti.uFlags = TTF_TRACK | TTF_IDISHWND;
|
|
ti.uId = reinterpret_cast<UINT_PTR>(Toolbar.m_hWnd);
|
|
ti.hwnd = m_hWnd;
|
|
ti.lpszText = NULL;
|
|
ti.lParam = NULL;
|
|
|
|
BOOL ret = m_Balloons.AddTool(&ti);
|
|
if (!ret)
|
|
{
|
|
WARN("AddTool failed, LastError=%d (probably meaningless unless non-zero)\n", GetLastError());
|
|
}
|
|
|
|
m_BalloonQueue.Init(m_hWnd, &Toolbar, &m_Balloons);
|
|
|
|
// Explicitly request running applications to re-register their systray icons
|
|
::SendNotifyMessageW(HWND_BROADCAST,
|
|
RegisterWindowMessageW(L"TaskbarCreated"),
|
|
0, 0);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
LRESULT CSysPagerWnd::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
|
|
{
|
|
m_BalloonQueue.Deinit();
|
|
CIconWatcher::Uninitialize();
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL CSysPagerWnd::NotifyIcon(DWORD dwMessage, _In_ CONST NOTIFYICONDATA *iconData)
|
|
{
|
|
BOOL ret = FALSE;
|
|
|
|
int VisibleButtonCount = Toolbar.GetVisibleButtonCount();
|
|
|
|
TRACE("NotifyIcon received. Code=%d\n", dwMessage);
|
|
switch (dwMessage)
|
|
{
|
|
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;
|
|
break;
|
|
|
|
case NIM_SETVERSION:
|
|
ret = Toolbar.SwitchVersion(iconData);
|
|
break;
|
|
|
|
default:
|
|
TRACE("NotifyIcon received with unknown code %d.\n", dwMessage);
|
|
return FALSE;
|
|
}
|
|
|
|
if (VisibleButtonCount != Toolbar.GetVisibleButtonCount())
|
|
{
|
|
/* Ask the parent to resize */
|
|
NMHDR nmh = {GetParent(), 0, NTNWM_REALIGN};
|
|
GetParent().SendMessage(WM_NOTIFY, 0, (LPARAM) &nmh);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
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::OnCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
|
|
{
|
|
bHandled = FALSE;
|
|
|
|
/* Handles the BN_CLICKED notifications sent by the CNotifyToolbar member */
|
|
if (HIWORD(wParam) != BN_CLICKED)
|
|
return 0;
|
|
|
|
INT iBtn = LOWORD(wParam);
|
|
if (iBtn < 0)
|
|
return 0;
|
|
|
|
InternalIconData* notifyItem = Toolbar.GetItemData(iBtn);
|
|
|
|
if (!::IsWindow(notifyItem->hWnd))
|
|
return 0;
|
|
|
|
// TODO: Improve keyboard handling by looking whether one presses
|
|
// on ENTER, etc..., which roughly translates into "double-clicking".
|
|
|
|
if (notifyItem->uVersionCopy >= NOTIFYICON_VERSION)
|
|
{
|
|
/* Use new-style notifications if the notification icon supports them */
|
|
::SendNotifyMessage(notifyItem->hWnd,
|
|
notifyItem->uCallbackMessage,
|
|
notifyItem->uID,
|
|
NIN_SELECT); // TODO: Distinguish with NIN_KEYSELECT
|
|
}
|
|
else if (lParam == -1)
|
|
{
|
|
/*
|
|
* Otherwise, and only if the icon was selected via the keyboard,
|
|
* simulate right-click mouse messages. This is not needed if the
|
|
* selection was done by mouse because in this case the mouse
|
|
* messages were already sent.
|
|
*/
|
|
::SendNotifyMessage(notifyItem->hWnd,
|
|
notifyItem->uCallbackMessage,
|
|
notifyItem->uID,
|
|
WM_LBUTTONDOWN); // TODO: Distinguish with double-click WM_LBUTTONDBLCLK
|
|
::SendNotifyMessage(notifyItem->hWnd,
|
|
notifyItem->uCallbackMessage,
|
|
notifyItem->uID,
|
|
WM_LBUTTONUP);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
LRESULT CSysPagerWnd::OnCopyData(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
|
|
{
|
|
PCOPYDATASTRUCT cpData = (PCOPYDATASTRUCT)lParam;
|
|
if (cpData->dwData == 1)
|
|
{
|
|
/* A taskbar NotifyIcon notification */
|
|
PTRAYNOTIFYDATAW pData = (PTRAYNOTIFYDATAW)cpData->lpData;
|
|
if (pData->dwSignature == NI_NOTIFY_SIG)
|
|
return NotifyIcon(pData->dwMessage, &pData->nid);
|
|
}
|
|
// TODO: Handle other types of taskbar notifications
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
LRESULT CSysPagerWnd::OnSettingChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
|
|
{
|
|
if (wParam == SPI_SETNONCLIENTMETRICS)
|
|
{
|
|
Toolbar.ResizeImagelist();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
LRESULT CSysPagerWnd::OnGetMinimumSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
|
|
{
|
|
GetSize((BOOL)wParam, (PSIZE)lParam);
|
|
return 0;
|
|
}
|
|
|
|
HRESULT CSysPagerWnd::Initialize(IN HWND 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 | WS_VISIBLE;
|
|
Create(hWndParent, 0, NULL, dwStyle);
|
|
if (!m_hWnd)
|
|
return E_FAIL;
|
|
|
|
SetWindowTheme(m_hWnd, L"TrayNotify", NULL);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CSysPagerWnd_CreateInstance(HWND hwndParent, REFIID riid, void **ppv)
|
|
{
|
|
return ShellObjectCreatorInit<CSysPagerWnd>(hwndParent, riid, ppv);
|
|
}
|