#include "stdafx.h"
#include "trayicon.h"
#include <strsafe.h>
#include <afxpriv.h>		// for AfxLoadString

IMPLEMENT_DYNAMIC(ITrayIcon, CCmdTarget)
typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); 

ITrayIcon::ITrayIcon(UINT uID)
{
	// Initialize NOTIFYICONDATA
	memset(&m_nid, 0 , sizeof(m_nid));
	m_nid.cbSize = sizeof(m_nid);
	m_nid.uID = uID;	// never changes after construction
	OpendThread();
	
	bShowMinibox = FALSE;
	//m_pWnd = AfxGetMainWnd();
	// Use resource string as tip if there is one
	AfxLoadString(uID, m_nid.szTip, sizeof(m_nid.szTip));
}

ITrayIcon::~ITrayIcon()
{
	SetIcon(0); // remove icon from system tray
	TerminalThread();
}

//////////////////
// Set notification window. It must created already.
//
void ITrayIcon::SetNotificationWnd(CWnd* pNotifyWnd, UINT uCbMsg)
{
	// If the following assert fails, you're probably
	// calling me before you created your window. Oops.
	ASSERT(pNotifyWnd==NULL || ::IsWindow(pNotifyWnd->GetSafeHwnd()));
	m_nid.hWnd = pNotifyWnd->GetSafeHwnd();

	ASSERT(uCbMsg==0 || uCbMsg>=WM_USER);
	m_nid.uCallbackMessage = uCbMsg;
}

//////////////////
// This is the main variant for setting the icon.
// Sets both the icon and tooltip from resource ID
// To remove the icon, call SetIcon(0)
//
BOOL ITrayIcon::SetIcon(UINT uID)
{
	HICON hicon=NULL;
	if (uID) 
		hicon = AfxGetApp()->LoadIcon(uID);

	UINT msg;
	m_nid.uFlags = 0;

	// Set the icon
	if (hicon) 
	{
		// Add or replace icon in system tray
		msg = m_nid.hIcon ? NIM_MODIFY : NIM_ADD;
		m_nid.hIcon = hicon;
		m_nid.uFlags |= NIF_ICON;
	}
	else 
	{ // remove icon from tray
		if (m_nid.hIcon==NULL)
			return TRUE;		// already deleted
		msg = NIM_DELETE;
	}

	// Use callback if any
	if (m_nid.uCallbackMessage && m_nid.hWnd)
		m_nid.uFlags |= NIF_MESSAGE;

	// Do it
	BOOL bRet = Shell_NotifyIcon(msg, &m_nid);
	if (msg==NIM_DELETE || !bRet)
		m_nid.hIcon = NULL;	// failed
	return bRet;
}

BOOL ITrayIcon::SetIcon(UINT uID, LPCTSTR lpTip)
{ 
	HICON hicon=NULL;
	if (uID) 
	{
		AfxLoadString(uID, m_nid.szTip, sizeof(m_nid.szTip));
		hicon = AfxGetApp()->LoadIcon(uID);
	}

	return SetIcon(hicon, lpTip ? lpTip:NULL); 
}

//////////////////
// Common SetIcon for all overloads. 
//
BOOL ITrayIcon::SetIcon(HICON hicon, LPCTSTR lpTip)
{
	UINT msg;
	m_nid.uFlags = 0;

	// Set the icon
	if (hicon) 
	{
		// Add or replace icon in system tray
		msg = m_nid.hIcon ? NIM_MODIFY : NIM_ADD;
		m_nid.hIcon = hicon;
		m_nid.uFlags |= NIF_ICON;
	} 
	else 
	{ // remove icon from tray
		if (m_nid.hIcon==NULL)
			return TRUE;		// already deleted
		msg = NIM_DELETE;
	}

	// Use the tip, if any
	if (lpTip)
		//strncpy(m_nid.szTip, lpTip, sizeof(m_nid.szTip));
		StringCchCopy(m_nid.szTip, sizeof(m_nid.szTip),lpTip);
	if (m_nid.szTip[0])
		m_nid.uFlags |= NIF_TIP;

	// Use callback if any
	if (m_nid.uCallbackMessage && m_nid.hWnd)
		m_nid.uFlags |= NIF_MESSAGE;

	// Do it
	BOOL bRet = Shell_NotifyIcon(msg, &m_nid);
	if (msg==NIM_DELETE || !bRet)
		m_nid.hIcon = NULL;	// failed
	return bRet;
}

BOOL ITrayIcon::SetTip(LPCTSTR lpTip) 
{
	UINT msg;
	m_nid.uFlags = 0;

	if (m_nid.hIcon == NULL)
		return TRUE;		// already deleted

	msg = NIM_MODIFY;

	// Use the tip, if any
	//if (lpTip)
	//strncpy(m_nid.szTip, lpTip, sizeof(m_nid.szTip));
	StringCchCopy(m_nid.szTip, sizeof(m_nid.szTip), lpTip);
	if (m_nid.szTip[0])
		m_nid.uFlags |= NIF_TIP;

	// Use callback if any
	if (m_nid.uCallbackMessage && m_nid.hWnd)
		m_nid.uFlags |= NIF_MESSAGE;

	return Shell_NotifyIcon(msg, &m_nid);
}

BOOL ITrayIcon::SetTipTitle(LPCTSTR lpTipTitle)
{
	UINT msg;
	m_nid.uFlags = 0;

	if (m_nid.hIcon == NULL)
		return TRUE;		// already deleted

	msg = NIM_MODIFY;
	StringCchCopy(m_nid.szInfo, sizeof(m_nid.szInfo), _lpTip);
	if ( lpTipTitle != NULL)
	{
		StringCchCopy(m_nid.szInfoTitle, sizeof(m_nid.szInfoTitle), lpTipTitle);
		m_nid.uFlags |= NIF_INFO|NIIF_INFO;
		m_nid.uTimeout = 1000;
	}
	else
	{
		m_nid.uFlags |= NIF_INFO;
	}

	// Use callback if any
	if (m_nid.uCallbackMessage && m_nid.hWnd)
		m_nid.uFlags |= NIF_MESSAGE;

	return Shell_NotifyIcon(msg, &m_nid);
}

//BOOL ITrayIcon::SetInfo(LPCSTR lpInof, LPCSTR lpInfoTitle) 
BOOL ITrayIcon::SetInfo(LPCTSTR lpInof, LPCTSTR lpInfoTitle)
{
	UINT msg;
	m_nid.uFlags = 0;

	if (m_nid.hIcon == NULL)
		return TRUE;		// already deleted

	msg = NIM_MODIFY;
	//strncpy(m_nid.szInfo, lpInof, sizeof(m_nid.szInfo));
	StringCchCopy(m_nid.szInfo, sizeof(m_nid.szInfo),lpInof);
	if ( lpInfoTitle != NULL)
	{
		StringCchCopy(m_nid.szInfoTitle, sizeof(m_nid.szInfoTitle), lpInfoTitle);
		m_nid.uFlags |= NIF_INFO|NIIF_INFO;
		m_nid.uTimeout = 1000;
	}
	else
	{
		m_nid.uFlags |= NIF_INFO;
	}

	// Use callback if any
	if (m_nid.uCallbackMessage && m_nid.hWnd)
		m_nid.uFlags |= NIF_MESSAGE;

	return Shell_NotifyIcon(msg, &m_nid);
}

/////////////////
// Default event handler handles right-menu and doubleclick.
// Call this function from your own notification handler.
//
LRESULT ITrayIcon::OnTrayNotification(WPARAM wID, LPARAM lEvent)
{
	if (wID!=m_nid.uID || (lEvent!=WM_RBUTTONUP && lEvent!=WM_LBUTTONDBLCLK))
		return 0;

	// If there's a resource menu with the same ID as the icon, use it as 
	// the right-button popup menu. CTrayIcon will interprets the first
	// item in the menu as the default command for WM_LBUTTONDBLCLK
	// 
#if 0
	CMenu menu;
	if (!menu.LoadMenu(m_nid.uID))
		return 0;
	CMenu* pSubMenu = menu.GetSubMenu(0);
	if (!pSubMenu) 
		return 0;

	if (lEvent==WM_RBUTTONUP) 
	{

		// Make first menu item the default (bold font)
		::SetMenuDefaultItem(pSubMenu->m_hMenu, 0, TRUE);

		// Display the menu at the current mouse location. There's a "bug"
		// (Microsoft calls it a feature) in Windows 95 that requires calling
		// SetForegroundWindow. To find out more, search for Q135788 in MSDN.
		//
		CPoint mouse;
		GetCursorPos(&mouse);
		::SetForegroundWindow(m_nid.hWnd);	
		::TrackPopupMenu(pSubMenu->m_hMenu, 0, mouse.x, mouse.y, 0,
			m_nid.hWnd, NULL);

	} else  // double click: execute first menu item
		::SendMessage(m_nid.hWnd, WM_COMMAND, pSubMenu->GetMenuItemID(0), 0);
#endif
	return 1; // handled
}

// ------------------------------- [5/30/2013 Z.t]
BOOL ITrayIcon::IsWow64()  
{  
	BOOL bIsWow64 = FALSE;  

	LPFN_ISWOW64PROCESS  fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(_T("kernel32")),"IsWow64Process");  

	if (NULL != fnIsWow64Process)  
	{  
		if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64))  
		{  
			// handle error   
		}  
	}  
	return bIsWow64;  
}

/************************************************************************/
/* 
	函数:GetTrayRect,获取托盘图标区域;(有待完善,添加各种错误判断)
	参数:None;
	返回:返回指定托盘图标的区域;

	注意:函数的功能有些太复杂,暂时没办法使用更简单的方法获取图标区域;
*/
/************************************************************************/
void ITrayIcon::GetTrayRect(RECT &rc)
{
	HWND hWnd,hWndPaper;
	long ret;
	
	LPVOID lngAddress;
//	long lngTextAdr; //,lngHwndAdr,lngHwnd,lngButtonID;
	TCHAR strBuff[1024]={0};

	TBBUTTON btnData={0};

	hWnd = FindWindow(_T("Shell_TrayWnd"), NULL);				// 获取任务栏句柄 [5/31/2013 Z.t]
	hWnd = FindWindowEx(hWnd, 0, _T("TrayNotifyWnd"), NULL);	// 获取托盘区域句柄 [5/31/2013 Z.t]
	hWndPaper = FindWindowEx(hWnd, 0, _T("SysPager"), NULL);	// 获取系统页句柄 [5/31/2013 Z.t]

	if(!hWndPaper)
		hWnd = FindWindowEx(hWnd, 0, _T("ToolbarWindow32"), NULL);
	else
		hWnd = FindWindowEx(hWndPaper, 0, _T("ToolbarWindow32"), NULL);

	DWORD dwProcessId = 0; 
	GetWindowThreadProcessId(hWnd, &dwProcessId);//LOG4C_NO_FILENUM((LOG_NOTICE,"进程ID%d",dwProcessId));进程其实就是explorer.exe
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS|PROCESS_VM_OPERATION|PROCESS_VM_READ|PROCESS_VM_WRITE,0,dwProcessId);
	if ( hProcess == NULL )
	{
		//LOG4C_NO_FILENUM((LOG_NOTICE,"hProcess == NULL--"));
		return ;
	}
	
	lngAddress = VirtualAllocEx(hProcess,0, 0x4096, MEM_COMMIT, PAGE_READWRITE); 
	if( lngAddress == NULL) 
	{
		//LOG4C_NO_FILENUM((LOG_NOTICE,"lngAddress == NULL--"));
		return;
	}

	DWORD lTextAdr = 0;
	BYTE buff[1024] = {0};
	CString strFilePath;
	CString strTile;
	HWND hMainWnd = NULL;
	int nDataOffset = sizeof(TBBUTTON) - sizeof(INT_PTR) - sizeof(DWORD_PTR);
	int nStrOffset = 18; 
	if ( IsWow64() )
	{
		nDataOffset+=4;
		nStrOffset+=6;
	}

	 // 获取托盘区域的所有图标数量 [5/31/2013 Z.t]      
	int ibtnCount = SendMessage(hWnd, TB_BUTTONCOUNT, 0, 0);               
	LPVOID lngRect = VirtualAllocEx(hProcess,0,sizeof(RECT), MEM_COMMIT, PAGE_READWRITE);
	
	CRect rect;
	for(int i=0 ;i< ibtnCount;i++)
	{
		int j = i;
		ret = SendMessage(hWnd,TB_GETBUTTON,j, (LPARAM)(lngAddress));
		// 读文本地址;
		ret = ReadProcessMemory(hProcess, LPVOID(long(lngAddress) + nDataOffset),&lTextAdr,4,0);
		if(lTextAdr != -1)
		{
			// 读文本;
			ret = ReadProcessMemory(hProcess, LPVOID(lTextAdr),buff,1024,0);

			hMainWnd = (HWND)(*((DWORD*)buff));
			strFilePath = (WCHAR *)buff + nStrOffset;			// 获取托盘图标进程路径 [5/31/2013 Z.t]
			strTile = (WCHAR *)buff + nStrOffset + MAX_PATH;	// 获取托盘图标tip标题 [5/31/2013 Z.t]
			//_tprintf(_T("%s %s\n"),strTile,strFilePath);

			if (strTile.Compare(m_nid.szTip) == 0)
			{
				::SendMessage(hWnd,TB_GETITEMRECT,(WPARAM)j,(LPARAM)lngRect);
				ReadProcessMemory(hProcess,lngRect,&rc, sizeof(rc),0);			// 获取托盘图标区域;
				CWnd::FromHandle(hWnd)->ClientToScreen(&rc);
			}
		}
	}
	VirtualFreeEx( hProcess, lngAddress, 0x4096, MEM_DECOMMIT);
	VirtualFreeEx( hProcess, lngAddress, 0, MEM_RELEASE);
	VirtualFreeEx( hProcess, lngRect, sizeof(RECT), MEM_DECOMMIT);
	VirtualFreeEx( hProcess, lngRect, 0, MEM_RELEASE);
	CloseHandle(hProcess);
}

int ITrayIcon::OpendThread()
{
	//LOG4C_NO_FILENUM((LOG_NOTICE,"创建TrayIcon"));
	m_hThreadCtrl = CreateEvent(NULL,TRUE,FALSE,NULL);	// 无信号事件;
	if ( m_hThreadCtrl == NULL )
	{
		//LOG4C_NO_FILENUM((LOG_NOTICE,"创建TrayIcon事件失败"));
		return -1;
	}

	m_hThreadObj = CreateThread(NULL,0,FlashingThread,this,CREATE_SUSPENDED,&m_dwThreadID);
	if ( m_hThreadObj == NULL )
	{
		//LOG4C_NO_FILENUM((LOG_NOTICE,"创建TrayIcon线程失败"));
		return -1;
	}
	bSuspending = true;
	return 0;
}

void ITrayIcon::TerminalThread()
{
	if ( m_hThreadCtrl )
	{
		SetEvent( m_hThreadCtrl );
	}

	if (WaitForSingleObject(m_hThreadCtrl,INFINITE) != WAIT_TIMEOUT)
	{
		CloseHandle(m_hThreadObj);
		m_hThreadObj = NULL;
	}

	CloseHandle( m_hThreadCtrl );
	m_hThreadCtrl = NULL;

	bSuspending = false;
}

BOOL ITrayIcon::StartFlashing()
{
	if ( !bSuspending ) return FALSE;
	if(::ResumeThread(m_hThreadObj) == 0xFFFFFFFF)
		return FALSE;
	
	bSuspending = false;
	SetInfo(_T("环境监控系统产生新警报,请及时处理！"),_T("警报提示"));
	return TRUE;
}

BOOL ITrayIcon::StopFlashing()
{
	if ( bSuspending ) return FALSE;

	if(::SuspendThread(m_hThreadObj) == 0xFFFFFFFF)
		return FALSE;
	
	bSuspending = true;
	SetIcon(_utIcon[0]);
	return TRUE;
}

/************************************************************************/
/* 
	线程函数:闪烁线程;
*/
/************************************************************************/
DWORD WINAPI ITrayIcon::FlashingThread(LPVOID lpVoid)
{
	ITrayIcon *pTrayIcon = (ITrayIcon*)lpVoid;

	CPoint pt; 
	CRect IconRect;
	bool bChange = FALSE;
	do 
	{
		if ( bChange)
		{
			pTrayIcon->SetIcon(pTrayIcon->_utIcon[1]); 
			bChange = FALSE;
		}
		else
		{
			pTrayIcon->SetIcon(pTrayIcon->_utIcon[2]);
			bChange = TRUE;
		}

		if( pTrayIcon->bShowMinibox )
		{
			GetCursorPos(&pt); 
			pTrayIcon->GetTrayRect(IconRect);
			if ( IconRect.PtInRect( pt) ) 
				::PostMessage(pTrayIcon->m_nid.hWnd,MYWM_SHOWMINBOX,1,0);
			else
				::PostMessage(pTrayIcon->m_nid.hWnd,MYWM_SHOWMINBOX,0,0);
		}
	}while (WaitForSingleObject(pTrayIcon->m_hThreadCtrl,300) == WAIT_TIMEOUT);

	return 0;
}