owntone-server/win32/FireflyShell/NotifyIcon.cpp
Mike Crowe 94b4883cd2 Make the configuration dialog appear near the mouse pointer if
launched from the shell notification icon as requested by Roku's
Anthony Wood.
2006-05-30 16:47:12 +00:00

271 lines
6.2 KiB
C++

/*
*(C) 2006 Roku LLC
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License Version 2 as published
* by the Free Software Foundation.
*
* This program 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 General
* Public License for more details.
*
* Please read README.txt in the same directory as this source file for
* further license information.
*/
#include "stdafx.h"
#include "resource.h"
#include "NotifyIcon.h"
#include "FireflyShell.h"
CNotifyIcon::CNotifyIcon()
{
ZeroMemory(&m_nid, sizeof(NOTIFYICONDATA));
m_nid.cbSize = sizeof(NOTIFYICONDATA);
m_nid.uID = ID_SHELLNOTIFY;
m_running_icon = AtlLoadIcon(IDI_SHELL_RUNNING);
m_stopped_icon = AtlLoadIcon(IDI_SHELL_STOPPED);
}
BOOL CNotifyIcon::Create()
{
m_registered_activation_message = GetApplication()->GetRegisteredActivationMessage();
RECT rect = {0, 0, 0, 0};
// Hidden window.
if (base::Create(NULL, rect, _T("FireflyShellNotifyIconHidden"), WS_POPUP))
{
m_nid.uFlags = NIF_ICON|NIF_MESSAGE|NIF_TIP;
InflictIconState();
//wcsncpy(niData.szTip, TEXT("Foo?"), sizeof(niData.szTip));
m_nid.hWnd = m_hWnd;
m_nid.uCallbackMessage = PRIVATE_WM_NOTIFYICON;
Shell_NotifyIcon(NIM_ADD, &m_nid);
SetTimer(TIMER_ID, 5000, NULL);
GetApplication()->ServiceStatusSubscribe(this);
EnableUserSwitchNotifications();
return TRUE;
}
return FALSE;
}
void CNotifyIcon::Destroy()
{
GetApplication()->ServiceStatusUnsubscribe(this);
KillTimer(TIMER_ID);
Shell_NotifyIcon(NIM_DELETE, &m_nid);
DestroyIcon(m_nid.hIcon);
base::DestroyWindow();
}
void CNotifyIcon::PopupBalloon(UINT title_id, UINT text_id, DWORD flags)
{
CString title, text;
title.LoadString(title_id);
text.LoadString(text_id);
m_nid.uFlags |= NIF_INFO;
SafeStringCopy(m_nid.szInfoTitle, title, 64);
SafeStringCopy(m_nid.szInfo, text, 256);
m_nid.dwInfoFlags = flags;
m_nid.uTimeout = 10000;
Shell_NotifyIcon(NIM_MODIFY, &m_nid);
}
void CNotifyIcon::Update()
{
InflictIconState();
// I suspect we'll need this line too.
// m_nid.uFlags &= ~NIF_INFO;
Shell_NotifyIcon(NIM_MODIFY, &m_nid);
}
void CNotifyIcon::InflictIconState()
{
// Will the icons leak?
Service::Status status = GetApplication()->GetServiceStatus();
UINT state_id;
if (status.IsPending())
{
state_id = IDS_SERVER_PENDING;
m_nid.hIcon = m_stopped_icon; // As good as any?
}
else if (status.IsRunning())
{
state_id = IDS_SERVER_RUNNING;
m_nid.hIcon = m_running_icon;
}
else
{
state_id = IDS_SERVER_STOPPED;
m_nid.hIcon = m_stopped_icon;
}
CString tip;
tip.LoadString(state_id);
SafeStringCopy(m_nid.szTip, tip, 64);
}
void CNotifyIcon::OnServiceStatus(Service::Status old_status, Service::Status new_status)
{
Update();
}
void CNotifyIcon::OnTimer(UINT id, TIMERPROC proc)
{
if (id == TIMER_ID)
{
GetApplication()->CheckServiceStatus();
}
}
LRESULT CNotifyIcon::OnNotifyIconMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
switch (lParam)
{
case WM_LBUTTONDBLCLK:
GetApplication()->Configure(true);
bHandled = true;
return 0L;
case WM_RBUTTONDOWN:
case WM_CONTEXTMENU:
OnContextMenu();
bHandled = true;
return 0L;
}
return 0L;
}
void CNotifyIcon::OnContextMenu()
{
HMENU hMenu;
HINSTANCE hInstance = _Module.GetResourceInstance();
hMenu = ::LoadMenu(hInstance, MAKEINTRESOURCE(IDM_CONTEXT));
POINT pt;
GetCursorPos(&pt);
// See TrackPopupMenu in MSDN.
SetForegroundWindow(m_hWnd);
//::SetForegroundWindow(m_hWnd);
HMENU hPopup = GetSubMenu(hMenu, 0);
::SetMenuDefaultItem(hPopup, ID_CONFIGURE, FALSE);
TrackPopupMenu(hPopup, TPM_LEFTALIGN | TPM_BOTTOMALIGN, pt.x, pt.y, 0, m_hWnd, NULL);
::PostMessage(m_hWnd, WM_NULL, 0, 0);
::DestroyMenu(hMenu);
}
LRESULT CNotifyIcon::OnConfigure(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
GetApplication()->Configure(true);
return 0;
}
LRESULT CNotifyIcon::OnExit(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
/// @todo
Application::GetInstance()->Exit();
return 0;
}
LRESULT CNotifyIcon::OnRegisteredActivation(UINT, WPARAM, LPARAM, BOOL &bHandled)
{
ATLTRACE(_T("Activate\n"));
bHandled = true;
GetApplication()->Configure(false);
// We return a magic number so that the caller knows we've been found
// and can give up.
return m_registered_activation_message;
}
void CNotifyIcon::OnServerEvent(UINT32 id, UINT32 intval, const CString &strval)
{
// Note that we're running on a different thread here. We need to punt
// the event over to the main thread using SendMessage.
switch (id)
{
case 0:
break;
case 1:
case 2:
SendMessage(WM_SERVEREVENT, id);
break;
default:
ATLASSERT(false);
break;
}
}
LRESULT CNotifyIcon::OnServerEvent(UINT, WPARAM wparam, LPARAM, BOOL &bHandled)
{
bHandled = true;
switch (wparam)
{
case 1:
PopupBalloon(IDR_MAINFRAME, IDS_SCAN_START);
break;
case 2:
PopupBalloon(IDR_MAINFRAME, IDS_SCAN_STOP);
break;
}
return 0;
}
void CNotifyIcon::EnableUserSwitchNotifications()
{
HMODULE h = ::LoadLibrary(_T("WtsApi32.dll"));
if (h)
{
typedef BOOL (WINAPI *Proc)(HWND, DWORD);
Proc fn = reinterpret_cast<Proc>(GetProcAddress(h, "WTSRegisterSessionNotification"));
if (fn)
{
(*fn)(m_hWnd, NOTIFY_FOR_THIS_SESSION);
}
::FreeLibrary(h);
}
}
LRESULT CNotifyIcon::OnSessionChange(UINT, WPARAM wparam, LPARAM, BOOL &bHandled)
{
// Because only one process can get events through the mailslot we
// disconnect from it when the user uses XP fast user switching to
// switch to a different user.
switch (wparam)
{
case WTS_CONSOLE_CONNECT:
case WTS_REMOTE_CONNECT:
ATLTRACE("SESSION CONNECT\n");
GetApplication()->EnableServerEvents(true);
break;
case WTS_CONSOLE_DISCONNECT:
case WTS_REMOTE_DISCONNECT:
ATLTRACE("SESSION DISCONNECT\n");
GetApplication()->EnableServerEvents(false);
break;
}
bHandled = true;
return 0;
}