2059 lines
68 KiB
C++
2059 lines
68 KiB
C++
/*++
|
|
|
|
Copyright (C) Microsoft Corporation, 1995 - 1999
|
|
All rights reserved.
|
|
|
|
Module Name:
|
|
|
|
traynot.cxx
|
|
|
|
Abstract:
|
|
|
|
tray notifications and balloon help
|
|
|
|
Author:
|
|
|
|
Lazar Ivanov (LazarI) 25-Apr-2000
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "precomp.hxx"
|
|
#pragma hdrstop
|
|
|
|
#include "prids.h"
|
|
#include "spllibex.hxx"
|
|
#include "persist.hxx"
|
|
#include "rtlmir.hxx"
|
|
|
|
////////////////////////////////////////////////////////
|
|
// debugging stuff
|
|
//
|
|
|
|
#if DBG
|
|
// #define DBG_TRAYNOTIFY DBG_INFO
|
|
#define DBG_TRAYNOTIFY DBG_NONE
|
|
#define DBG_PROCESSPRNNOTIFY DBG_NONE
|
|
#define DBG_JOBNOTIFY DBG_NONE
|
|
#define DBG_PRNNOTIFY DBG_NONE
|
|
#define DBG_TRAYUPDATE DBG_NONE
|
|
#define DBG_BALLOON DBG_NONE
|
|
#define DBG_NTFYICON DBG_NONE
|
|
#define DBG_MENUADJUST DBG_NONE
|
|
#define DBG_INITDONE DBG_NONE
|
|
#define DBG_JOBSTATUS DBG_NONE
|
|
#endif
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
BOOL WINAPI PrintNotifyTray_Init();
|
|
BOOL WINAPI PrintNotifyTray_Exit();
|
|
BOOL WINAPI PrintNotifyTray_SelfShutdown();
|
|
|
|
} // extern "C"
|
|
|
|
//////////////////////////////////////////////
|
|
// CTrayNotify - tray notifications class
|
|
//
|
|
|
|
QITABLE_DECLARE(CTrayNotify)
|
|
class CTrayNotify: public CUnknownMT<QITABLE_GET(CTrayNotify)>, // MT impl. of IUnknown
|
|
public IFolderNotify,
|
|
public IPrinterChangeCallback,
|
|
public CSimpleWndSubclass<CTrayNotify>
|
|
{
|
|
public:
|
|
CTrayNotify();
|
|
~CTrayNotify();
|
|
|
|
//////////////////
|
|
// IUnknown
|
|
//
|
|
IMPLEMENT_IUNKNOWN()
|
|
|
|
void SetUser(LPCTSTR pszUser = NULL); // NULL means the current user
|
|
void Touch(); // resets the shutdown timer
|
|
void Resurrect(); // resurrects the tray after shutdown has been initiated
|
|
BOOL Initialize(); // initialize & start listening
|
|
BOOL Shutdown(); // stop listening & force shutdown
|
|
BOOL CanShutdown(); // check if shutdown criteria is met (SHUTDOWN_TIMEOUT sec. inactivity)
|
|
|
|
// implement CSimpleWndSubclass<...> - has to be public
|
|
LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
|
|
|
///////////////////
|
|
// IFolderNotify
|
|
//
|
|
STDMETHODIMP_(BOOL) ProcessNotify(FOLDER_NOTIFY_TYPE NotifyType, LPCWSTR pszName, LPCWSTR pszNewName);
|
|
|
|
///////////////////////////
|
|
// IPrinterChangeCallback
|
|
//
|
|
STDMETHODIMP PrinterChange(ULONG_PTR uCookie, DWORD dwChange, const PRINTER_NOTIFY_INFO *pInfo);
|
|
|
|
private:
|
|
// internal stuff, enums/data types/methods/members
|
|
enum
|
|
{
|
|
// possible private messages coming to our special window
|
|
WM_PRINTTRAY_FIRST = WM_APP,
|
|
WM_PRINTTRAY_PRIVATE_MSG, // private msg (posted data by background threads)
|
|
WM_PRINTTRAY_REQUEST_SHUTDOWN, // request shutdown
|
|
WM_PRINTTRAY_ICON_NOTIFY, // message comming back from shell
|
|
};
|
|
|
|
enum
|
|
{
|
|
// possible update events (see _RequestUpdate), we don't really care now,
|
|
// but it is useful for future extensibility
|
|
UPDATE_REQUEST_JOB_ADD, // lParam = prnCookie
|
|
UPDATE_REQUEST_JOB_DELETE, // lParam = prnCookie
|
|
UPDATE_REQUEST_JOB_STATUS, // lParam = prnCookie
|
|
|
|
UPDATE_REQUEST_PRN_FIRST,
|
|
UPDATE_REQUEST_PRN_ADD, // lParam = prnCookie
|
|
UPDATE_REQUEST_PRN_DELETE, // lParam = 0, not used
|
|
UPDATE_REQUEST_PRN_STATUS, // lParam = prnCookie
|
|
UPDATE_REQUEST_PRN_RENAME, // lParam = prnCookie
|
|
};
|
|
|
|
enum
|
|
{
|
|
ENUM_MAX_RETRY = 5, // max attempts to call bFolderEnumPrinters/bFolderGetPrinter
|
|
ICON_ID = 1, // our icon ID
|
|
DEFAULT_BALLOON_TIMEOUT = 10000, // in miliseconds
|
|
JOB_ERROR_BITS = (JOB_STATUS_OFFLINE|JOB_STATUS_USER_INTERVENTION|JOB_STATUS_ERROR|JOB_STATUS_PAPEROUT),
|
|
JOB_IGNORE_BITS = (JOB_STATUS_DELETED|JOB_STATUS_PRINTED|JOB_STATUS_COMPLETE),
|
|
};
|
|
|
|
enum
|
|
{
|
|
SHUTDOWN_TIMER_ID = 100, // shutdown timer ID
|
|
//SHUTDOWN_TIMEOUT = 3*1000, // timeout to shutdown the tray code when the user
|
|
SHUTDOWN_TIMEOUT = 30*1000, // timeout to shutdown the tray code when the user
|
|
// is not printing - 1 min.
|
|
};
|
|
|
|
enum
|
|
{
|
|
// balloon IDs
|
|
BALLOON_ID_JOB_FAILED = 1, // balloon when a job failed to print
|
|
BALLOON_ID_JOB_PRINTED = 2, // balloon when a job printed
|
|
BALLOON_ID_PRN_CREATED = 3, // balloon when a local printer is created
|
|
};
|
|
|
|
enum
|
|
{
|
|
MAX_PRINTER_DISPLAYNAME = 48,
|
|
MAX_DOC_DISPLAYNAME = 32,
|
|
};
|
|
|
|
enum
|
|
{
|
|
// message types, see MsgInfo declaration below
|
|
msgTypePrnNotify = 100, // why not?
|
|
msgTypePrnCheckDelete,
|
|
msgTypeJobNotify,
|
|
msgTypeJobNotifyLost,
|
|
};
|
|
|
|
// job info
|
|
typedef struct
|
|
{
|
|
DWORD dwID; // job ID
|
|
DWORD dwStatus; // job status
|
|
TCHAR szDocName[255]; // document name
|
|
SYSTEMTIME timeSubmitted; // when submited
|
|
DWORD dwTotalPages; // Total number of pages
|
|
} JobInfo;
|
|
|
|
// define JobInfo adaptor class
|
|
class CJobInfoAdaptor: public Alg::CDefaultAdaptor<JobInfo, DWORD>
|
|
{
|
|
public:
|
|
static DWORD Key(const JobInfo &i) { return i.dwID; }
|
|
};
|
|
|
|
// CJobInfoArray definition
|
|
typedef CSortedArray<JobInfo, DWORD, CJobInfoAdaptor> CJobInfoArray;
|
|
|
|
// printer info
|
|
typedef struct
|
|
{
|
|
TCHAR szPrinter[kPrinterBufMax]; // the printer name
|
|
DWORD dwStatus; // printer status
|
|
CJobInfoArray *pUserJobs; // the jobs pending on this printer
|
|
CPrintNotify *pListener; // notifications listener
|
|
BOOL bUserInterventionReq; // this printer requires user intervention
|
|
} PrinterInfo;
|
|
|
|
// define PrinterInfo adaptor class
|
|
class CPrinterInfoAdaptor: public Alg::CDefaultAdaptor<PrinterInfo, LPCTSTR>
|
|
{
|
|
public:
|
|
static LPCTSTR Key(const PrinterInfo &i) { return i.szPrinter; }
|
|
static int Compare(LPCTSTR pszK1, LPCTSTR pszK2) { return lstrcmp(pszK1, pszK2); }
|
|
};
|
|
|
|
// CPrnInfoArray definition
|
|
typedef CSortedArray<PrinterInfo, LPCTSTR, CPrinterInfoAdaptor> CPrnInfoArray;
|
|
|
|
// balloon info
|
|
typedef struct
|
|
{
|
|
UINT uBalloonID; // balloon ID (what action to take when clicked)
|
|
LPCTSTR pszCaption; // balloon caption
|
|
LPCTSTR pszText; // balloon text
|
|
LPCTSTR pszSound; // canonical name of a special sound (can be NULL)
|
|
DWORD dwFlags; // flags (NIIF_INFO, NIIF_WARNING, NIIF_ERROR)
|
|
UINT uTimeout; // timeout
|
|
} BalloonInfo;
|
|
|
|
// private message info
|
|
typedef struct
|
|
{
|
|
int iType; // private message type (msgTypePrnNotifymsgTypeJobNotify, msgTypeJobNotifyLost)
|
|
FOLDER_NOTIFY_TYPE NotifyType; // printer notify type (kFolder* constants defined in winprtp.h)
|
|
TCHAR szPrinter[kPrinterBufMax]; // printer name
|
|
|
|
// job notification fields
|
|
struct
|
|
{
|
|
WORD Type; // PRINTER_NOTIFY_INFO_DATA.Type
|
|
WORD Field; // PRINTER_NOTIFY_INFO_DATA.Field
|
|
DWORD Id; // PRINTER_NOTIFY_INFO_DATA.Id
|
|
DWORD dwData; // PRINTER_NOTIFY_INFO_DATA.NotifyData.adwData[0]
|
|
} jn;
|
|
|
|
// auxiliary buffer
|
|
TCHAR szAuxName[kPrinterBufMax]; // PRINTER_NOTIFY_INFO_DATA.NotifyData.Data.pBuf or pszNewName
|
|
} MsgInfo;
|
|
|
|
// internal APIs
|
|
BOOL _InternalInit();
|
|
void _MsgLoop();
|
|
void _ThreadProc();
|
|
void _ProcessPrnNotify(FOLDER_NOTIFY_TYPE NotifyType, LPCWSTR pszName, LPCWSTR pszNewName);
|
|
BOOL _FindPrinter(LPCTSTR pszPrinter, int *pi) const;
|
|
BOOL _FindUserJob(DWORD dwID, const PrinterInfo &info, int *pi) const;
|
|
UINT _LastReservedMenuID() const;
|
|
void _AdjustMenuIDs(HMENU hMenu, UINT uIDFrom, int iAdjustment) const;
|
|
int _Insert(const FOLDER_PRINTER_DATA &data);
|
|
void _Delete(int iPos);
|
|
HRESULT _GetPrinter(LPCTSTR pszPrinter, LPBYTE *ppData, PDWORD pcReturned);
|
|
void _CheckToUpdateUserJobs(PrinterInfo &pi, const MsgInfo &msg);
|
|
void _CheckUserJobs(int *piUserJobs, BOOL *pbUserJobPrinting, BOOL *pbUserInterventionReq);
|
|
void _CheckToUpdateTray(BOOL bForceUpdate, const BalloonInfo *pBalloon, BOOL bForceDelete = FALSE);
|
|
LRESULT _ProcessUserMsg(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
|
void _ShowMenu();
|
|
void _ShowPrnFolder() const;
|
|
void _ShowBalloon(UINT uBalloonID, LPCTSTR pszCaption, LPCTSTR pszText, LPCTSTR pszSound,
|
|
DWORD dwFlags = NIIF_INFO, UINT uTimeout = DEFAULT_BALLOON_TIMEOUT);
|
|
void _JobFailed(LPCTSTR pszPrinter, JobInfo &ji);
|
|
void _JobPrinted(LPCTSTR pszPrinter, JobInfo &ji);
|
|
BOOL _TimeToString(const SYSTEMTIME &time, TString *pTime) const;
|
|
void _DoRefresh(CPrintNotify *pListener);
|
|
void _PostPrivateMsg(const MsgInfo &msg);
|
|
void _RequestUpdate(int iEvent, LPCTSTR pszPrinter, LPCTSTR pszAux = NULL);
|
|
void _BalloonClicked(UINT uBalloonID) const;
|
|
BOOL _UpdateUserJob(PrinterInfo &pi, JobInfo &ji);
|
|
void _ShowAllActivePrinters() const;
|
|
void _AddPrinterToCtxMenu(HMENU hMenu, int i);
|
|
HRESULT _ProcessJobNotifications(LPCTSTR pszPrinter, DWORD dwChange,
|
|
const PRINTER_NOTIFY_INFO *pInfo, PrinterInfo *ppi = NULL);
|
|
void _ResetAll();
|
|
BOOL _IsFaxPrinter(const FOLDER_PRINTER_DATA &data);
|
|
HRESULT _CanNotify(LPCTSTR pszPrinterName, BOOL *pbCanNotify);
|
|
|
|
// msg loop thread proc
|
|
static DWORD WINAPI _ThreadProc_MsgLoop(LPVOID lpParameter);
|
|
|
|
// the callback called on refresh
|
|
static HRESULT WINAPI _RefreshCallback(
|
|
LPVOID lpCookie, ULONG_PTR uCookie, DWORD dwChange, const PRINTER_NOTIFY_INFO *pInfo);
|
|
|
|
// some inlines
|
|
int _EncodeMenuID(int i) const { return (i + _LastReservedMenuID() + 1); }
|
|
int _DecodeMenuID(int i) const { return (i - _LastReservedMenuID() - 1); }
|
|
|
|
// internal members
|
|
int m_cxSmIcon; // the icon size X
|
|
int m_cySmIcon; // the icon size Y
|
|
UINT m_uTrayIcon; // the current tray icon
|
|
int m_iUserJobs; // the user jobs (need to keep track of this to update the tooltip)
|
|
BOOL m_bInRefresh; // if we are in refresh
|
|
BOOL m_bIconShown; // if the icon is visible or not
|
|
UINT m_uBalloonID; // the ID of the last balloon shown up
|
|
UINT m_uBalloonsCount; // how many balloons has been currently displayed
|
|
CAutoHandleNT m_shEventReady; // event to sync Initialize & Shutdown
|
|
CAutoHandleNT m_shThread; // notifications thread listener
|
|
CAutoHandleIcon m_shIconShown; // the tray icon shown
|
|
CAutoHandleMenu m_shCtxMenu; // the context menu
|
|
HANDLE m_hFolder; // printer's folder cache
|
|
CFastHeap<MsgInfo> m_heapMsgCache; // messages cache
|
|
CPrnInfoArray m_arrWatchList; // printers holding documents for our (the current) user
|
|
TString m_strUser; // our (usually the current) user
|
|
TString m_strLastBalloonPrinter; // the printer name of the last balloon
|
|
DWORD m_dwLastTimeInactive; // the last time since the print icon has been inactive
|
|
BOOL m_bSelfShutdownInitiated; // is self-shutdown initiated?
|
|
};
|
|
|
|
// QueryInterface table
|
|
QITABLE_BEGIN(CTrayNotify)
|
|
QITABENT(CTrayNotify, IFolderNotify), // IID_IFolderNotify
|
|
QITABLE_END()
|
|
|
|
HRESULT CTrayNotify_CreateInstance(CTrayNotify **ppObj)
|
|
{
|
|
HRESULT hr = E_INVALIDARG;
|
|
|
|
if( ppObj )
|
|
{
|
|
*ppObj = new CTrayNotify;
|
|
hr = (*ppObj) ? S_OK : E_OUTOFMEMORY;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/////////////////////////////
|
|
// globals & constants
|
|
//
|
|
#define gszTrayListenerClassName TEXT("PrintTray_Notify_WndClass")
|
|
|
|
static WORD g_JobFields[] =
|
|
{ // Job Fields we want notifications for
|
|
JOB_NOTIFY_FIELD_STATUS, // Status bits
|
|
JOB_NOTIFY_FIELD_NOTIFY_NAME, // Name of the user who should be notified
|
|
JOB_NOTIFY_FIELD_TOTAL_PAGES, // Total number of pages
|
|
};
|
|
|
|
static PRINTER_NOTIFY_OPTIONS_TYPE g_Notifications[2] =
|
|
{
|
|
{
|
|
JOB_NOTIFY_TYPE, // We want notifications on print jobs
|
|
0, 0, 0, // Reserved, must be zeros
|
|
sizeof(g_JobFields)/sizeof(g_JobFields[0]), // We specified 9 fields in the JobFields array
|
|
g_JobFields // Precisely which fields we want notifications for
|
|
}
|
|
};
|
|
|
|
static const UINT g_arrIcons[] = { 0, IDI_PRINTER, IDI_PRINTER_ERROR };
|
|
|
|
static const UINT g_arrReservedMenuIDs[] =
|
|
{
|
|
IDM_TRAYNOTIFY_DEFAULT,
|
|
IDM_TRAYNOTIFY_PRNFOLDER,
|
|
IDM_TRAYNOTIFY_REFRESH,
|
|
};
|
|
|
|
/////////////////////////////
|
|
// inlines
|
|
//
|
|
inline UINT CTrayNotify::_LastReservedMenuID() const
|
|
{
|
|
UINT uMax = 0;
|
|
for( int i=0; i<ARRAYSIZE(g_arrReservedMenuIDs); i++ )
|
|
{
|
|
if( g_arrReservedMenuIDs[i] > uMax )
|
|
{
|
|
uMax = g_arrReservedMenuIDs[i];
|
|
}
|
|
}
|
|
return uMax;
|
|
}
|
|
|
|
inline BOOL CTrayNotify::_FindPrinter(LPCTSTR pszPrinter, int *pi) const
|
|
{
|
|
return m_arrWatchList.FindItem(pszPrinter, pi);
|
|
}
|
|
|
|
inline BOOL CTrayNotify::_FindUserJob(DWORD dwID, const PrinterInfo &info, int *pi) const
|
|
{
|
|
return info.pUserJobs->FindItem(dwID, pi);
|
|
}
|
|
|
|
/////////////////////////////
|
|
// CTrayNotify
|
|
//
|
|
|
|
CTrayNotify::CTrayNotify()
|
|
: m_hFolder(NULL),
|
|
m_cxSmIcon(0),
|
|
m_cySmIcon(0),
|
|
m_uTrayIcon(g_arrIcons[0]),
|
|
m_iUserJobs(0),
|
|
m_bInRefresh(FALSE),
|
|
m_bIconShown(FALSE),
|
|
m_uBalloonID(0),
|
|
m_uBalloonsCount(0),
|
|
m_dwLastTimeInactive(GetTickCount()),
|
|
m_bSelfShutdownInitiated(FALSE)
|
|
{
|
|
// nothing special
|
|
DBGMSG(DBG_TRAYNOTIFY, ("TRAYNOTIFY: CTrayNotify::CTrayNotify()\n"));
|
|
}
|
|
|
|
CTrayNotify::~CTrayNotify()
|
|
{
|
|
ASSERT(FALSE == IsAttached());
|
|
ASSERT(NULL == m_hFolder);
|
|
ASSERT(0 == m_arrWatchList.Count());
|
|
|
|
DBGMSG(DBG_TRAYNOTIFY, ("TRAYNOTIFY: CTrayNotify::~CTrayNotify()\n"));
|
|
}
|
|
|
|
void CTrayNotify::SetUser(LPCTSTR pszUser)
|
|
{
|
|
TCHAR szUserName[64];
|
|
szUserName[0] = 0;
|
|
|
|
if( NULL == pszUser || 0 == pszUser[0] )
|
|
{
|
|
// set the current user
|
|
DWORD dwSize = COUNTOF(szUserName);
|
|
if( GetUserName(szUserName, &dwSize) )
|
|
{
|
|
pszUser = szUserName;
|
|
}
|
|
}
|
|
|
|
m_strUser.bUpdate(pszUser);
|
|
}
|
|
|
|
void CTrayNotify::Touch()
|
|
{
|
|
m_dwLastTimeInactive = GetTickCount();
|
|
}
|
|
|
|
void CTrayNotify::Resurrect()
|
|
{
|
|
if( m_bSelfShutdownInitiated )
|
|
{
|
|
// restart the shutdown timer & reset the state
|
|
m_bSelfShutdownInitiated = FALSE;
|
|
SetTimer(m_hwnd, SHUTDOWN_TIMER_ID, SHUTDOWN_TIMEOUT, NULL);
|
|
}
|
|
}
|
|
|
|
BOOL CTrayNotify::Initialize()
|
|
{
|
|
BOOL bReturn = FALSE;
|
|
|
|
if( 0 == m_strUser.uLen() )
|
|
{
|
|
// assume listening for the currently logged on user
|
|
SetUser(NULL);
|
|
}
|
|
|
|
m_shEventReady = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
if( m_shEventReady )
|
|
{
|
|
DWORD dwThreadId;
|
|
m_shThread = TSafeThread::Create(NULL, 0, (LPTHREAD_START_ROUTINE)_ThreadProc_MsgLoop, this, 0, &dwThreadId);
|
|
|
|
if( m_shThread )
|
|
{
|
|
// wait the background thread to kick off
|
|
WaitForSingleObject(m_shEventReady, INFINITE);
|
|
|
|
if( IsAttached() )
|
|
{
|
|
// the message loop has started successfully, request full refresh
|
|
m_shEventReady = NULL;
|
|
|
|
// request an initial refresh
|
|
MsgInfo msg = { msgTypePrnNotify, kFolderUpdateAll };
|
|
_PostPrivateMsg(msg);
|
|
|
|
// we are fine here
|
|
bReturn = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bReturn;
|
|
}
|
|
|
|
BOOL CTrayNotify::Shutdown()
|
|
{
|
|
if( IsAttached() )
|
|
{
|
|
PostMessage(m_hwnd, WM_PRINTTRAY_REQUEST_SHUTDOWN, 0, 0);
|
|
|
|
// wait the background thread to cleanup
|
|
WaitForSingleObject(m_shThread, INFINITE);
|
|
return !IsAttached();
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL CTrayNotify::CanShutdown()
|
|
{
|
|
return ( !m_bIconShown &&
|
|
(GetTickCount() > m_dwLastTimeInactive) &&
|
|
(GetTickCount() - m_dwLastTimeInactive) > SHUTDOWN_TIMEOUT );
|
|
}
|
|
|
|
// private worker proc to shutdown the tray
|
|
static DWORD WINAPI ShutdownTray_WorkerProc(LPVOID lpParameter)
|
|
{
|
|
// shutdown the tray code.
|
|
PrintNotifyTray_SelfShutdown();
|
|
return 0;
|
|
}
|
|
|
|
// implement CSimpleWndSubclass<...>
|
|
LRESULT CTrayNotify::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch( uMsg )
|
|
{
|
|
case WM_TIMER:
|
|
{
|
|
switch( wParam )
|
|
{
|
|
case SHUTDOWN_TIMER_ID:
|
|
{
|
|
if( CanShutdown() && !m_bSelfShutdownInitiated )
|
|
{
|
|
// the tray icon has been inactive for more than SHUTDOWN_TIMEOUT
|
|
// initiate shutdown
|
|
if( SHQueueUserWorkItem(reinterpret_cast<LPTHREAD_START_ROUTINE>(ShutdownTray_WorkerProc),
|
|
NULL, 0, 0, NULL, "printui.dll", 0) )
|
|
{
|
|
m_bSelfShutdownInitiated = TRUE;
|
|
KillTimer(hwnd, SHUTDOWN_TIMER_ID);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_PRINTTRAY_REQUEST_SHUTDOWN:
|
|
{
|
|
// unregister the folder notifications
|
|
ASSERT(m_hFolder);
|
|
UnregisterPrintNotify(NULL, this, &m_hFolder);
|
|
m_hFolder = NULL;
|
|
|
|
// reset all listeners
|
|
_ResetAll();
|
|
|
|
// force to delete the tray icon
|
|
_CheckToUpdateTray(TRUE, NULL, TRUE);
|
|
|
|
// detach from the window
|
|
VERIFY(Detach());
|
|
|
|
// our object is now detached - destroy the window
|
|
// and post a quit msg to terminate the thread.
|
|
DestroyWindow(hwnd);
|
|
PostQuitMessage(0);
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if( m_hFolder )
|
|
{
|
|
// do not process user msgs if shutdown is in progress
|
|
_ProcessUserMsg(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
break;
|
|
}
|
|
|
|
// allways call the default processing
|
|
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
///////////////////
|
|
// IFolderNotify
|
|
//
|
|
STDMETHODIMP_(BOOL) CTrayNotify::ProcessNotify(FOLDER_NOTIFY_TYPE NotifyType, LPCWSTR pszName, LPCWSTR pszNewName)
|
|
{
|
|
// !IMPORTANT!
|
|
// this is a callback from the background threads, so
|
|
// we've got to be very carefull about what we are doing here.
|
|
// the easiest way to syncronize is to quickly pass a private
|
|
// message with the notification data into the foreground thread.
|
|
MsgInfo msg = { msgTypePrnNotify, NotifyType };
|
|
|
|
if( pszName )
|
|
{
|
|
lstrcpyn(msg.szPrinter, pszName, COUNTOF(msg.szPrinter));
|
|
}
|
|
|
|
if( pszNewName )
|
|
{
|
|
// this is not NULL only for kFolderRename
|
|
lstrcpyn(msg.szAuxName, pszNewName, COUNTOF(msg.szAuxName));
|
|
}
|
|
|
|
// post a private message here...
|
|
_PostPrivateMsg(msg);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
///////////////////////////
|
|
// IPrinterChangeCallback
|
|
//
|
|
STDMETHODIMP CTrayNotify::PrinterChange(ULONG_PTR uCookie, DWORD dwChange, const PRINTER_NOTIFY_INFO *pInfo)
|
|
{
|
|
// !IMPORTANT!
|
|
// this is a callback from the background threads, so
|
|
// we've got to be very carefull about what we are doing here.
|
|
// the easiest way to syncronize is to quickly pass a private
|
|
// message with the notification data into the foreground thread.
|
|
CPrintNotify *pListener = reinterpret_cast<CPrintNotify*>(uCookie);
|
|
return (pListener ? _ProcessJobNotifications(pListener->GetPrinter(), dwChange, pInfo, NULL) : E_INVALIDARG);
|
|
}
|
|
|
|
//////////////////////////////////
|
|
// private stuff _*
|
|
//
|
|
BOOL CTrayNotify::_InternalInit()
|
|
{
|
|
BOOL bReturn = SUCCEEDED(m_arrWatchList.Create());
|
|
|
|
if( bReturn )
|
|
{
|
|
WNDCLASS WndClass;
|
|
|
|
WndClass.style = 0L;
|
|
WndClass.lpfnWndProc = (WNDPROC)&::DefWindowProc;
|
|
WndClass.cbClsExtra = 0;
|
|
WndClass.cbWndExtra = 0;
|
|
WndClass.hInstance = 0;
|
|
WndClass.hIcon = NULL;
|
|
WndClass.hCursor = NULL;
|
|
WndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
|
|
WndClass.lpszMenuName = NULL;
|
|
WndClass.lpszClassName = gszTrayListenerClassName;
|
|
|
|
if( RegisterClass(&WndClass) ||
|
|
ERROR_CLASS_ALREADY_EXISTS == GetLastError() )
|
|
{
|
|
// create the worker window
|
|
HWND hwnd = CreateWindowEx(
|
|
bIsBiDiLocalizedSystem() ? kExStyleRTLMirrorWnd : 0,
|
|
gszTrayListenerClassName, NULL,
|
|
WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, NULL, NULL, 0, NULL);
|
|
|
|
if( hwnd )
|
|
{
|
|
Attach(hwnd);
|
|
}
|
|
}
|
|
|
|
if( IsAttached() )
|
|
{
|
|
// register for print notifications in the printer's folder cache
|
|
if( FAILED(RegisterPrintNotify(NULL, this, &m_hFolder, NULL)) )
|
|
{
|
|
// it will detach automatically
|
|
DestroyWindow(m_hwnd);
|
|
}
|
|
else
|
|
{
|
|
// initialize a timer to shutdown the listening thread if there
|
|
// is no activity for more than SHUTDOWN_TIMEOUT
|
|
SetTimer(m_hwnd, SHUTDOWN_TIMER_ID, SHUTDOWN_TIMEOUT, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
return bReturn;
|
|
}
|
|
|
|
void CTrayNotify::_MsgLoop()
|
|
{
|
|
// spin the msg loop here
|
|
MSG msg;
|
|
ASSERT(m_hwnd);
|
|
|
|
while( GetMessage(&msg, NULL, 0, 0) )
|
|
{
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
}
|
|
|
|
void CTrayNotify::_ThreadProc()
|
|
{
|
|
AddRef();
|
|
_InternalInit();
|
|
|
|
if( m_shEventReady )
|
|
{
|
|
// notify the foreground thread we are about to start the msg loop
|
|
SetEvent(m_shEventReady);
|
|
}
|
|
|
|
if( IsAttached() )
|
|
{
|
|
// spin a standard windows msg loop here
|
|
_MsgLoop();
|
|
}
|
|
|
|
Release();
|
|
}
|
|
|
|
void CTrayNotify::_ProcessPrnNotify(FOLDER_NOTIFY_TYPE NotifyType, LPCWSTR pszName, LPCWSTR pszNewName)
|
|
{
|
|
DBGMSG(DBG_PROCESSPRNNOTIFY, ("PROCESSPRNNOTIFY: event has arrived, NotifyType=%d\n", NotifyType));
|
|
switch( NotifyType )
|
|
{
|
|
case kFolderUpdate:
|
|
case kFolderAttributes:
|
|
case kFolderCreate:
|
|
case kFolderUpdateAll:
|
|
{
|
|
CAutoPtrArray<BYTE> spBuffer;
|
|
DWORD i, cReturned = 0;
|
|
|
|
if( kFolderUpdateAll == NotifyType )
|
|
{
|
|
// reset all listeners...
|
|
_ResetAll();
|
|
|
|
// hide the icon, as this may take some time...
|
|
_CheckToUpdateTray(FALSE, NULL);
|
|
}
|
|
|
|
if( SUCCEEDED(_GetPrinter(kFolderUpdateAll == NotifyType ?
|
|
NULL : pszName, &spBuffer, &cReturned)) )
|
|
{
|
|
// walk through the printers to see if we need to add/delete/update printer(s) to our watch list.
|
|
PFOLDER_PRINTER_DATA pPrinters = spBuffer.GetPtrAs<PFOLDER_PRINTER_DATA>();
|
|
DBGMSG(DBG_PROCESSPRNNOTIFY, ("PROCESSPRNNOTIFY: create/update event, Count=%d\n", cReturned));
|
|
|
|
for( i=0; i<cReturned; i++ )
|
|
{
|
|
DBGMSG(DBG_PROCESSPRNNOTIFY, ("PROCESSPRNNOTIFY: process printer: "TSTR", Jobs=%d\n",
|
|
DBGSTR(pPrinters[i].pName), pPrinters[i].cJobs));
|
|
|
|
// important!: we can't watch printers in pending deletion state because once the printer is
|
|
// pending deletion it goes away without prior notification (when there is no jobs to print)
|
|
|
|
int iPos;
|
|
if( _FindPrinter(pPrinters[i].pName, &iPos) )
|
|
{
|
|
//
|
|
// we don't want to delete the printer from the watch list when jobs count goes
|
|
// to zero (0 == pPrinters[i].cJobs) because we rely on job notifications to show
|
|
// balloons and sometimes the job notifications come AFTER the printer notifications
|
|
//
|
|
// we handle this by posting msgTypePrnCheckDelete when job gets deleted
|
|
// so we can monitor the printer when there are no more user jobs in the queue and
|
|
// then we stop watching the printer. don't change this behaviour unless you want
|
|
// many things broken.
|
|
//
|
|
|
|
// printer found in the watch list
|
|
if( PRINTER_STATUS_PENDING_DELETION & pPrinters[i].Status )
|
|
{
|
|
// no jobs or in pending deletion state - just delete.
|
|
DBGMSG(DBG_PROCESSPRNNOTIFY, ("PROCESSPRNNOTIFY:[delete] watched printer with no jobs, pszPrinter="TSTR"\n",
|
|
DBGSTR(pPrinters[i].pName)));
|
|
|
|
_Delete(iPos);
|
|
_RequestUpdate(UPDATE_REQUEST_PRN_DELETE, pPrinters[i].pName);
|
|
}
|
|
else
|
|
{
|
|
if( m_arrWatchList[iPos].dwStatus != pPrinters[i].Status )
|
|
{
|
|
// update status
|
|
DBGMSG(DBG_PROCESSPRNNOTIFY, ("PROCESSPRNNOTIFY:[update] watched printer with jobs, pszPrinter="TSTR"\n",
|
|
DBGSTR(pPrinters[i].pName)));
|
|
|
|
m_arrWatchList[iPos].dwStatus = pPrinters[i].Status;
|
|
_RequestUpdate(UPDATE_REQUEST_PRN_STATUS, pPrinters[i].pName);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// printer not found in the watch list
|
|
if( pPrinters[i].cJobs && !(PRINTER_STATUS_PENDING_DELETION & pPrinters[i].Status) && !_IsFaxPrinter(pPrinters[i]) )
|
|
{
|
|
// start listening on this printer
|
|
DBGMSG(DBG_PROCESSPRNNOTIFY, ("PROCESSPRNNOTIFY:[insert] non-watched printer with jobs, pszPrinter="TSTR"\n",
|
|
DBGSTR(pPrinters[i].pName)));
|
|
|
|
iPos = _Insert(pPrinters[i]);
|
|
if( -1 != iPos )
|
|
{
|
|
_RequestUpdate(UPDATE_REQUEST_PRN_ADD, pPrinters[i].pName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case kFolderDelete:
|
|
case kFolderRename:
|
|
{
|
|
int iPos;
|
|
if( _FindPrinter(pszName, &iPos) )
|
|
{
|
|
// ranaming is a bit tricky. we need to delete & reinsert the item
|
|
// to keep the array sorted & then update the context menu if necessary
|
|
DBGMSG(DBG_PROCESSPRNNOTIFY, ("PROCESSPRNNOTIFY:[delete] watched printer deleted, pszPrinter="TSTR"\n",
|
|
DBGSTR(pszName)));
|
|
|
|
// first delete the printer which got renamed or deleted
|
|
_Delete(iPos);
|
|
_RequestUpdate(UPDATE_REQUEST_PRN_DELETE, pszName);
|
|
|
|
if( kFolderRename == NotifyType )
|
|
{
|
|
// if rename, request update, so this printer can be re-added with
|
|
// the new name.
|
|
MsgInfo msg = { msgTypePrnNotify, kFolderUpdate };
|
|
lstrcpyn(msg.szPrinter, pszNewName, COUNTOF(msg.szPrinter));
|
|
_PostPrivateMsg(msg);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CTrayNotify::_AdjustMenuIDs(HMENU hMenu, UINT uIDFrom, int iAdjustment) const
|
|
{
|
|
MENUITEMINFO mii = { sizeof(mii), MIIM_ID, 0 };
|
|
int i, iCount = GetMenuItemCount(hMenu);
|
|
|
|
for( i=0; i<iCount; i++ )
|
|
{
|
|
if( GetMenuItemInfo(hMenu, i, TRUE, &mii) && mii.wID >= uIDFrom )
|
|
{
|
|
DBGMSG(DBG_MENUADJUST, ("MENUADJUST: %d -> %d\n", mii.wID,
|
|
static_cast<UINT>(static_cast<int>(mii.wID) + iAdjustment)));
|
|
|
|
// adjust the menu item ID
|
|
mii.wID = static_cast<UINT>(static_cast<int>(mii.wID) + iAdjustment);
|
|
ASSERT(_DecodeMenuID(mii.wID) < m_arrWatchList.Count());
|
|
|
|
// update menu item here
|
|
SetMenuItemInfo(hMenu, i, TRUE, &mii);
|
|
}
|
|
}
|
|
}
|
|
|
|
int CTrayNotify::_Insert(const FOLDER_PRINTER_DATA &data)
|
|
{
|
|
CAutoPtr<CJobInfoArray> spUserJobs = new CJobInfoArray;
|
|
CAutoPtr<CPrintNotify> spListener = new CPrintNotify(this, COUNTOF(g_Notifications), g_Notifications, PRINTER_CHANGE_JOB);
|
|
|
|
int iPos = -1;
|
|
if( spUserJobs && SUCCEEDED(spUserJobs->Create()) &&
|
|
spListener && SUCCEEDED(spListener->Initialize(data.pName)) )
|
|
{
|
|
PrinterInfo infoPrn = {0};
|
|
infoPrn.pListener = spListener;
|
|
infoPrn.pUserJobs = spUserJobs;
|
|
lstrcpyn(infoPrn.szPrinter, data.pName, COUNTOF(infoPrn.szPrinter));
|
|
infoPrn.pListener->SetCookie(reinterpret_cast<ULONG_PTR>(infoPrn.pListener));
|
|
|
|
ASSERT(!m_arrWatchList.FindItem(infoPrn.szPrinter, &iPos));
|
|
iPos = m_arrWatchList.SortedInsert(infoPrn);
|
|
|
|
if( -1 != iPos )
|
|
{
|
|
if( m_shCtxMenu )
|
|
{
|
|
DBGMSG(DBG_MENUADJUST, ("MENUADJUST: insert at pos: %d, Count=%d\n", iPos, m_arrWatchList.Count()));
|
|
|
|
// if the context menu is opened then adjust the IDs
|
|
_AdjustMenuIDs(m_shCtxMenu, _EncodeMenuID(iPos), 1);
|
|
}
|
|
|
|
// start listen on this printer
|
|
DBGMSG(DBG_PRNNOTIFY, ("PRNNOTIFY: start listen printer: "TSTR"\n", DBGSTR(data.pName)));
|
|
|
|
// they are hooked up already, detach from the smart pointers
|
|
spListener.Detach();
|
|
spUserJobs.Detach();
|
|
|
|
// do an initial refresh and start listen
|
|
_DoRefresh(infoPrn.pListener);
|
|
infoPrn.pListener->StartListen();
|
|
}
|
|
}
|
|
return iPos;
|
|
}
|
|
|
|
void CTrayNotify::_Delete(int iPos)
|
|
{
|
|
// stop listen on this printer
|
|
m_arrWatchList[iPos].pListener->StopListen();
|
|
DBGMSG(DBG_PRNNOTIFY, ("PRNNOTIFY: stop listen printer: "TSTR"\n", DBGSTR(m_arrWatchList[iPos].szPrinter)));
|
|
|
|
delete m_arrWatchList[iPos].pListener;
|
|
delete m_arrWatchList[iPos].pUserJobs;
|
|
m_arrWatchList.Delete(iPos);
|
|
|
|
DBGMSG(DBG_MENUADJUST, ("MENUADJUST: delete at pos: %d, Count=%d\n", iPos, m_arrWatchList.Count()));
|
|
|
|
// fix the context menu
|
|
if( m_shCtxMenu )
|
|
{
|
|
MENUITEMINFO mii = { sizeof(mii), MIIM_ID, 0 };
|
|
if( GetMenuItemInfo(m_shCtxMenu, _EncodeMenuID(iPos), FALSE, &mii) )
|
|
{
|
|
// make sure this menu item is deleted
|
|
VERIFY(DeleteMenu(m_shCtxMenu, _EncodeMenuID(iPos), MF_BYCOMMAND));
|
|
}
|
|
|
|
// if the context menu is opened then adjust the IDs
|
|
_AdjustMenuIDs(m_shCtxMenu, _EncodeMenuID(iPos), -1);
|
|
}
|
|
}
|
|
|
|
HRESULT CTrayNotify::_GetPrinter(LPCTSTR pszPrinter, LPBYTE *ppData, PDWORD pcReturned)
|
|
{
|
|
ASSERT(ppData);
|
|
ASSERT(pcReturned);
|
|
HRESULT hr = E_OUTOFMEMORY;
|
|
|
|
int iTry = -1;
|
|
DWORD cbNeeded = 0;
|
|
DWORD cReturned = 0;
|
|
CAutoPtrArray<BYTE> pData;
|
|
BOOL bStatus = FALSE;
|
|
|
|
for( ;; )
|
|
{
|
|
if( iTry++ >= ENUM_MAX_RETRY )
|
|
{
|
|
// max retry count reached. this is also
|
|
// considered out of memory case
|
|
pData = NULL;
|
|
break;
|
|
}
|
|
|
|
// call bFolderEnumPrinters/bFolderGetPrinter...
|
|
bStatus = pszPrinter ?
|
|
bFolderGetPrinter(m_hFolder, pszPrinter, pData.GetPtrAs<PFOLDER_PRINTER_DATA>(), cbNeeded, &cbNeeded) :
|
|
bFolderEnumPrinters(m_hFolder, pData.GetPtrAs<PFOLDER_PRINTER_DATA>(), cbNeeded, &cbNeeded, pcReturned);
|
|
|
|
if( !bStatus && ERROR_INSUFFICIENT_BUFFER == GetLastError() && cbNeeded )
|
|
{
|
|
// buffer too small case
|
|
pData = new BYTE[cbNeeded];
|
|
|
|
if( pData )
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
SetLastError(ERROR_OUTOFMEMORY);
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// setup the error code properly
|
|
hr = bStatus ? S_OK : GetLastError() != ERROR_SUCCESS ? HRESULT_FROM_WIN32(GetLastError()) :
|
|
!pData ? E_OUTOFMEMORY : E_FAIL;
|
|
|
|
if( SUCCEEDED(hr) )
|
|
{
|
|
*ppData = pData.Detach();
|
|
if( pszPrinter )
|
|
{
|
|
*pcReturned = 1;
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
void CTrayNotify::_CheckToUpdateUserJobs(PrinterInfo &pi, const MsgInfo &msg)
|
|
{
|
|
int iPos = -1;
|
|
BOOL bJobFound = _FindUserJob(msg.jn.Id, pi, &iPos);
|
|
|
|
// process DBG_JOBNOTIFY
|
|
DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: pszPrinter="TSTR", szAuxName="TSTR", Field: %d, dwData: %d\n",
|
|
DBGSTR(pi.szPrinter), DBGSTR(msg.szAuxName), msg.jn.Field, msg.jn.dwData));
|
|
|
|
if( JOB_NOTIFY_FIELD_NOTIFY_NAME == msg.jn.Field )
|
|
{
|
|
// process JOB_NOTIFY_FIELD_NOTIFY_NAME here
|
|
DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: JOB_NOTIFY_FIELD_NOTIFY_NAME, pszPrinter="TSTR"\n",
|
|
DBGSTR(pi.szPrinter)));
|
|
|
|
if( 0 == lstrcmp(msg.szAuxName, m_strUser) )
|
|
{
|
|
// a user job - check to insert
|
|
if( !bJobFound )
|
|
{
|
|
JobInfo ji = {msg.jn.Id};
|
|
if( _UpdateUserJob(pi, ji) && -1 != pi.pUserJobs->SortedInsert(ji) )
|
|
{
|
|
_RequestUpdate(UPDATE_REQUEST_JOB_ADD, pi.szPrinter);
|
|
|
|
DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: job added, JobID=%d, pszPrinter="TSTR"\n",
|
|
msg.jn.Id, DBGSTR(pi.szPrinter)));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// not a user job - check to delete
|
|
if( bJobFound )
|
|
{
|
|
DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: job deleted, JobID=%d, pszPrinter="TSTR"\n",
|
|
pi.pUserJobs->operator[](iPos).dwID, DBGSTR(pi.szPrinter)));
|
|
|
|
pi.pUserJobs->Delete(iPos);
|
|
_RequestUpdate(UPDATE_REQUEST_JOB_DELETE, pi.szPrinter);
|
|
}
|
|
}
|
|
}
|
|
|
|
if( bJobFound && JOB_NOTIFY_FIELD_STATUS == msg.jn.Field )
|
|
{
|
|
// process JOB_NOTIFY_FIELD_STATUS here
|
|
DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: JOB_NOTIFY_FIELD_STATUS, Status=%x, pszPrinter="TSTR"\n",
|
|
msg.jn.dwData, DBGSTR(pi.szPrinter)));
|
|
|
|
// update the job status bits here, by saving the old status first
|
|
JobInfo &ji = pi.pUserJobs->operator[](iPos);
|
|
DWORD dwOldJobStatus = ji.dwStatus;
|
|
ji.dwStatus = msg.jn.dwData;
|
|
|
|
do
|
|
{
|
|
// dump the job status
|
|
DBGMSG(DBG_JOBSTATUS, ("JOBSTATUS: old status=%x, new status=%x\n",
|
|
dwOldJobStatus, ji.dwStatus));
|
|
|
|
if( ji.dwStatus & JOB_STATUS_DELETED )
|
|
{
|
|
// the job status has the JOB_STATUS_DELETED bit up. delete the job.
|
|
DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: job deleted, JobID=%x, pszPrinter="TSTR"\n",
|
|
pi.pUserJobs->operator[](iPos).dwID, DBGSTR(pi.szPrinter)));
|
|
|
|
pi.pUserJobs->Delete(iPos);
|
|
_RequestUpdate(UPDATE_REQUEST_JOB_DELETE, pi.szPrinter);
|
|
break; // skip everything
|
|
}
|
|
|
|
if( FALSE == m_bInRefresh && (0 == (dwOldJobStatus & JOB_STATUS_PRINTED)) &&
|
|
(JOB_STATUS_PRINTED & ji.dwStatus) && (0 == (ji.dwStatus & JOB_ERROR_BITS)) )
|
|
{
|
|
// JOB_STATUS_PRINTED bit is up, the previous status nas no JOB_STATUS_PRINTED bit up
|
|
// and there are no error bits up - consider the job had just printed successfully.
|
|
DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: job completed with sucesss, JobID=%x, pszPrinter="TSTR"\n",
|
|
pi.pUserJobs->operator[](iPos).dwID, DBGSTR(pi.szPrinter)));
|
|
|
|
_JobPrinted(pi.szPrinter, ji);
|
|
}
|
|
|
|
if( FALSE == m_bInRefresh && (0 == (dwOldJobStatus & JOB_ERROR_BITS)) &&
|
|
(ji.dwStatus & JOB_ERROR_BITS) )
|
|
{
|
|
// if the job goes from non-error state into an error state
|
|
// then we assume the job has failed or a user intervention is
|
|
// required. in both cases we show the job-failed balloon.
|
|
DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: job failed to print, JobID=%x, pszPrinter="TSTR"\n",
|
|
pi.pUserJobs->operator[](iPos).dwID, DBGSTR(pi.szPrinter)));
|
|
|
|
_JobFailed(pi.szPrinter, ji);
|
|
}
|
|
|
|
// just check to update the job status
|
|
if( dwOldJobStatus != ji.dwStatus )
|
|
{
|
|
ji.dwStatus = msg.jn.dwData;
|
|
_RequestUpdate(UPDATE_REQUEST_JOB_STATUS, pi.szPrinter);
|
|
|
|
DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: status updated, Status=%x, pszPrinter="TSTR"\n",
|
|
ji.dwStatus, DBGSTR(pi.szPrinter)));
|
|
}
|
|
}
|
|
while( FALSE );
|
|
}
|
|
|
|
if( bJobFound && JOB_NOTIFY_FIELD_TOTAL_PAGES == msg.jn.Field &&
|
|
pi.pUserJobs->operator[](iPos).dwTotalPages != msg.jn.dwData )
|
|
|
|
{
|
|
// save the number of total pages, so we can display this info
|
|
// later in the balloon when the job completes successfully.
|
|
pi.pUserJobs->operator[](iPos).dwTotalPages = msg.jn.dwData;
|
|
|
|
DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: total pages updated, TotalPages=%x, pszPrinter="TSTR"\n",
|
|
pi.pUserJobs->operator[](iPos).dwTotalPages, DBGSTR(pi.szPrinter)));
|
|
}
|
|
}
|
|
|
|
void CTrayNotify::_CheckUserJobs(int *piUserJobs, BOOL *pbUserJobPrinting, BOOL *pbUserInterventionReq)
|
|
{
|
|
ASSERT(piUserJobs);
|
|
ASSERT(pbUserJobPrinting);
|
|
ASSERT(pbUserInterventionReq);
|
|
|
|
*piUserJobs = 0;
|
|
*pbUserJobPrinting = *pbUserInterventionReq = FALSE;
|
|
|
|
// walk through the watch list to see...
|
|
int i, iCount = m_arrWatchList.Count();
|
|
for( i=0; i<iCount; i++ )
|
|
{
|
|
m_arrWatchList[i].bUserInterventionReq = FALSE;
|
|
CJobInfoArray &arrJobs = *m_arrWatchList[i].pUserJobs;
|
|
|
|
int j, iJobCount = arrJobs.Count();
|
|
for( j=0; j<iJobCount; j++ )
|
|
{
|
|
DWORD dwStatus = arrJobs[j].dwStatus;
|
|
|
|
if( 0 == (dwStatus & JOB_IGNORE_BITS) )
|
|
{
|
|
(*piUserJobs)++;
|
|
}
|
|
|
|
if( dwStatus & JOB_ERROR_BITS )
|
|
{
|
|
// these job status bits are considered an error
|
|
*pbUserInterventionReq = m_arrWatchList[i].bUserInterventionReq = TRUE;
|
|
}
|
|
|
|
if( dwStatus & JOB_STATUS_PRINTING )
|
|
{
|
|
// check if the job is printing now
|
|
*pbUserJobPrinting = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CTrayNotify::_CheckToUpdateTray(BOOL bForceUpdate, const BalloonInfo *pBalloon, BOOL bForceDelete)
|
|
{
|
|
int iUserJobs;
|
|
BOOL bUserJobPrinting, bUserInterventionReq;
|
|
_CheckUserJobs(&iUserJobs, &bUserJobPrinting, &bUserInterventionReq);
|
|
|
|
DBGMSG(DBG_TRAYUPDATE, ("TRAYUPDATE: _CheckToUpdateTray called, "
|
|
"iUserJobs=%d, bUserJobPrinting=%d, bUserInterventionReq=%d\n",
|
|
iUserJobs, bUserJobPrinting, bUserInterventionReq));
|
|
|
|
UINT uTrayIcon = g_arrIcons[ ((iUserJobs != 0) || bUserJobPrinting || (0 != m_uBalloonID) || (0 != m_uBalloonsCount)) + bUserInterventionReq ];
|
|
NOTIFYICONDATA nid = { sizeof(nid), m_hwnd, ICON_ID, NIF_MESSAGE, WM_PRINTTRAY_ICON_NOTIFY, NULL };
|
|
|
|
// check to delete first
|
|
if( bForceDelete || (uTrayIcon == g_arrIcons[0] && m_uTrayIcon != g_arrIcons[0]) )
|
|
{
|
|
if( m_bIconShown )
|
|
{
|
|
Shell_NotifyIcon(NIM_DELETE, &nid);
|
|
|
|
// reset the shutdown timer
|
|
Touch();
|
|
|
|
DBGMSG(DBG_NTFYICON, ("NTFYICON: icon deleted.\n"));
|
|
}
|
|
|
|
m_uTrayIcon = g_arrIcons[0];
|
|
m_cxSmIcon = m_cySmIcon = m_iUserJobs = 0;
|
|
m_bIconShown = FALSE;
|
|
}
|
|
else
|
|
{
|
|
// check to add/modify the icon
|
|
if( uTrayIcon != g_arrIcons[0] )
|
|
{
|
|
BOOL bPlayBalloonSound = FALSE;
|
|
BOOL bBalloonRequested = FALSE;
|
|
|
|
DWORD dwMsg = (m_uTrayIcon == g_arrIcons[0]) ? NIM_ADD : NIM_MODIFY;
|
|
|
|
int cxSmIcon = GetSystemMetrics(SM_CXSMICON);
|
|
int cySmIcon = GetSystemMetrics(SM_CYSMICON);
|
|
|
|
// check to sync the icon
|
|
if( uTrayIcon != m_uTrayIcon || cxSmIcon != m_cxSmIcon || m_cySmIcon != cySmIcon )
|
|
{
|
|
m_cxSmIcon = cxSmIcon;
|
|
m_cySmIcon = cySmIcon;
|
|
m_uTrayIcon = uTrayIcon;
|
|
m_shIconShown = (HICON)LoadImage(ghInst, MAKEINTRESOURCE(m_uTrayIcon), IMAGE_ICON, m_cxSmIcon, m_cySmIcon, 0);
|
|
nid.uFlags |= NIF_ICON;
|
|
nid.hIcon = m_shIconShown;
|
|
}
|
|
|
|
// check to sync the tip (if the ctx menu is not open)
|
|
if( bForceUpdate || m_iUserJobs != iUserJobs )
|
|
{
|
|
TString strTemplate, strTooltip;
|
|
m_iUserJobs = iUserJobs;
|
|
if( strTemplate.bLoadString(ghInst, IDS_TOOLTIP_TRAY) &&
|
|
strTooltip.bFormat(strTemplate, m_iUserJobs, static_cast<LPCTSTR>(m_strUser)) )
|
|
{
|
|
nid.uFlags |= NIF_TIP;
|
|
|
|
if( m_shCtxMenu )
|
|
{
|
|
// clear the tip
|
|
nid.szTip[0] = 0;
|
|
}
|
|
else
|
|
{
|
|
// update the tip
|
|
lstrcpyn(nid.szTip, strTooltip, COUNTOF(nid.szTip));
|
|
}
|
|
}
|
|
}
|
|
|
|
// check to sync the balloon (if the ctx menu is not open)
|
|
if( bForceUpdate || pBalloon )
|
|
{
|
|
nid.uFlags |= NIF_INFO;
|
|
|
|
if( m_shCtxMenu || NULL == pBalloon )
|
|
{
|
|
// hide the ballon
|
|
nid.szInfoTitle[0] = 0;
|
|
nid.szInfo[0] = 0;
|
|
}
|
|
else
|
|
{
|
|
// show up the balloon
|
|
nid.dwInfoFlags = pBalloon->dwFlags;
|
|
nid.uTimeout = pBalloon->uTimeout;
|
|
lstrcpyn(nid.szInfoTitle, pBalloon->pszCaption, COUNTOF(nid.szInfoTitle));
|
|
lstrcpyn(nid.szInfo, pBalloon->pszText, COUNTOF(nid.szInfo));
|
|
|
|
if( pBalloon->pszSound && pBalloon->pszSound[0] )
|
|
{
|
|
nid.dwInfoFlags |= NIIF_NOSOUND;
|
|
bPlayBalloonSound = TRUE;
|
|
}
|
|
|
|
bBalloonRequested = TRUE;
|
|
}
|
|
}
|
|
|
|
if( bForceUpdate || !m_bIconShown || nid.uFlags != NIF_MESSAGE )
|
|
{
|
|
// sync icon data
|
|
Shell_NotifyIcon(dwMsg, &nid);
|
|
|
|
if( bPlayBalloonSound )
|
|
{
|
|
PlaySound(pBalloon->pszSound, NULL,
|
|
SND_ALIAS | SND_APPLICATION | SND_ASYNC | SND_NODEFAULT | SND_NOSTOP);
|
|
}
|
|
|
|
if( bBalloonRequested )
|
|
{
|
|
m_uBalloonsCount++;
|
|
}
|
|
|
|
if( NIM_ADD == dwMsg )
|
|
{
|
|
DBGMSG(DBG_NTFYICON, ("NTFYICON: icon added.\n"));
|
|
}
|
|
else
|
|
{
|
|
DBGMSG(DBG_NTFYICON, ("NTFYICON: icon modified.\n"));
|
|
}
|
|
|
|
if( NIM_ADD == dwMsg )
|
|
{
|
|
// make sure we use the correct version
|
|
nid.uVersion = NOTIFYICON_VERSION;
|
|
Shell_NotifyIcon(NIM_SETVERSION, &nid);
|
|
|
|
DBGMSG(DBG_NTFYICON, ("NTFYICON: icon set version.\n"));
|
|
}
|
|
}
|
|
|
|
m_bIconShown = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CTrayNotify::_ShowMenu()
|
|
{
|
|
// no need to synchronize as m_arrWatchList size can be
|
|
// changed only in the message loop
|
|
int i, iCount = m_arrWatchList.Count();
|
|
|
|
if( iCount )
|
|
{
|
|
// when loaded from the resource, the context menu contains only one
|
|
if( m_shCtxMenu )
|
|
{
|
|
EndMenu();
|
|
}
|
|
|
|
// command - "Open Active Printers" corresponding to IDM_TRAYNOTIFY_DEFAULT
|
|
m_shCtxMenu = ShellServices::LoadPopupMenu(ghInst, POPUP_TRAYNOTIFY_PRINTERS);
|
|
|
|
if( m_shCtxMenu )
|
|
{
|
|
// build the context menu here
|
|
InsertMenu(m_shCtxMenu, (UINT)-1, MF_SEPARATOR|MF_BYPOSITION, 0, NULL);
|
|
|
|
for( i=0; i<iCount; i++ )
|
|
{
|
|
if( m_arrWatchList[i].pUserJobs->Count() )
|
|
{
|
|
_AddPrinterToCtxMenu(m_shCtxMenu, i);
|
|
}
|
|
}
|
|
|
|
// show up the context menu
|
|
if( GetMenuItemCount(m_shCtxMenu) )
|
|
{
|
|
POINT pt;
|
|
GetCursorPos(&pt);
|
|
SetMenuDefaultItem(m_shCtxMenu, IDM_TRAYNOTIFY_DEFAULT, MF_BYCOMMAND);
|
|
SetForegroundWindow(m_hwnd);
|
|
|
|
// now after m_shCtxMenu is not NULL, disable the tooltips,
|
|
// while the menu is open
|
|
_CheckToUpdateTray(TRUE, NULL);
|
|
|
|
// show up the context menu here...
|
|
// popup menus follows it's window owner when it comes to mirroring,
|
|
// so we should pass a an owner window which is mirrored.
|
|
int idCmd = TrackPopupMenu(m_shCtxMenu,
|
|
TPM_NONOTIFY|TPM_RETURNCMD|TPM_RIGHTBUTTON|TPM_HORNEGANIMATION,
|
|
pt.x, pt.y, 0, m_hwnd, NULL);
|
|
|
|
if( idCmd != 0 )
|
|
{
|
|
switch(idCmd)
|
|
{
|
|
case IDM_TRAYNOTIFY_DEFAULT:
|
|
{
|
|
// open the queues of all active printers
|
|
_ShowAllActivePrinters();
|
|
}
|
|
break;
|
|
|
|
case IDM_TRAYNOTIFY_PRNFOLDER:
|
|
{
|
|
// open the printer's folder
|
|
_ShowPrnFolder();
|
|
}
|
|
break;
|
|
|
|
case IDM_TRAYNOTIFY_REFRESH:
|
|
{
|
|
// just refresh the whole thing...
|
|
MsgInfo msg = { msgTypePrnNotify, kFolderUpdateAll };
|
|
_PostPrivateMsg(msg);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
// open the selected printer
|
|
vQueueCreate(NULL, m_arrWatchList[_DecodeMenuID(idCmd)].szPrinter,
|
|
SW_SHOWNORMAL, static_cast<LPARAM>(FALSE));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// destroy the menu
|
|
m_shCtxMenu = NULL;
|
|
|
|
// re-enable the tooltips after the menu is closed
|
|
_CheckToUpdateTray(TRUE, NULL);
|
|
}
|
|
}
|
|
|
|
void CTrayNotify::_ShowPrnFolder() const
|
|
{
|
|
// find the printer's folder PIDL
|
|
CAutoPtrPIDL pidlPrinters;
|
|
HRESULT hr = SHGetSpecialFolderLocation(NULL, CSIDL_PRINTERS, &pidlPrinters);
|
|
|
|
if( SUCCEEDED(hr) )
|
|
{
|
|
// invoke ShellExecuteEx on that PIDL
|
|
SHELLEXECUTEINFO seInfo;
|
|
memset(&seInfo, 0, sizeof(seInfo));
|
|
|
|
seInfo.cbSize = sizeof(seInfo);
|
|
seInfo.fMask = SEE_MASK_IDLIST;
|
|
seInfo.hwnd = m_hwnd;
|
|
seInfo.nShow = SW_SHOWDEFAULT;
|
|
seInfo.lpIDList = pidlPrinters;
|
|
|
|
ShellExecuteEx(&seInfo);
|
|
}
|
|
}
|
|
|
|
void CTrayNotify::_ShowBalloon(
|
|
UINT uBalloonID,
|
|
LPCTSTR pszCaption,
|
|
LPCTSTR pszText,
|
|
LPCTSTR pszSound,
|
|
DWORD dwFlags,
|
|
UINT uTimeout)
|
|
{
|
|
// just show up the balloon...
|
|
m_uBalloonID = uBalloonID;
|
|
BalloonInfo bi = {m_uBalloonID, pszCaption, pszText, pszSound, dwFlags, uTimeout};
|
|
_CheckToUpdateTray(FALSE, &bi);
|
|
}
|
|
|
|
void CTrayNotify::_JobFailed(LPCTSTR pszPrinter, JobInfo &ji)
|
|
{
|
|
ASSERT(pszPrinter);
|
|
|
|
// since the baloon text length is limited to 255 characters we need to abbreviate the printer name
|
|
// and the document name if they are too long by adding ellipses at the end.
|
|
TString strPrinter, strDocName;
|
|
if( SUCCEEDED(AbbreviateText(pszPrinter, MAX_PRINTER_DISPLAYNAME, &strPrinter)) &&
|
|
SUCCEEDED(AbbreviateText(ji.szDocName, MAX_DOC_DISPLAYNAME, &strDocName)) )
|
|
{
|
|
TString strTimeSubmitted, strTemplate, strTitle, strText;
|
|
|
|
TStatusB bStatus;
|
|
bStatus DBGCHK = (ji.dwStatus & JOB_STATUS_PAPEROUT) ?
|
|
strTitle.bLoadString(ghInst, IDS_BALLOON_TITLE_JOB_FAILED_OOP) :
|
|
strTitle.bLoadString(ghInst, IDS_BALLOON_TITLE_JOB_FAILED);
|
|
|
|
if( bStatus &&
|
|
_TimeToString(ji.timeSubmitted, &strTimeSubmitted) &&
|
|
strTemplate.bLoadString(ghInst, IDS_BALLOON_TEXT_JOB_FAILED) &&
|
|
strText.bFormat(strTemplate,
|
|
static_cast<LPCTSTR>(strDocName),
|
|
static_cast<LPCTSTR>(strPrinter),
|
|
static_cast<LPCTSTR>(strTimeSubmitted)) )
|
|
{
|
|
_ShowBalloon(BALLOON_ID_JOB_FAILED, strTitle, strText, NULL, NIIF_WARNING);
|
|
m_strLastBalloonPrinter.bUpdate(pszPrinter);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CTrayNotify::_JobPrinted(LPCTSTR pszPrinter, JobInfo &ji)
|
|
{
|
|
ASSERT(pszPrinter);
|
|
|
|
// since the baloon text length is limited to 255 characters we need to abbreviate the printer name
|
|
// and the document name if they are too long, by adding ellipses at the end.
|
|
TString strPrinter, strDocName;
|
|
if( SUCCEEDED(AbbreviateText(pszPrinter, MAX_PRINTER_DISPLAYNAME, &strPrinter)) &&
|
|
SUCCEEDED(AbbreviateText(ji.szDocName, MAX_DOC_DISPLAYNAME, &strDocName)) )
|
|
{
|
|
BOOL bCanNotify;
|
|
TString strTimeSubmitted, strTemplate, strTitle, strText;
|
|
|
|
// total pages can be zero when a downlevel document is printed (directly to the port) in
|
|
// this case StartDoc/EndDoc are not called and the spooler doesn't know the total pages of
|
|
// the document. in this case just display the balloon without total pages info.
|
|
UINT uTextID = ji.dwTotalPages ? IDS_BALLOON_TEXT_JOB_PRINTED : IDS_BALLOON_TEXT_JOB_PRINTED_NOPAGES;
|
|
|
|
if( SUCCEEDED(_CanNotify(pszPrinter, &bCanNotify)) && bCanNotify &&
|
|
_TimeToString(ji.timeSubmitted, &strTimeSubmitted) &&
|
|
strTitle.bLoadString(ghInst, IDS_BALLOON_TITLE_JOB_PRINTED) &&
|
|
strTemplate.bLoadString(ghInst, uTextID) &&
|
|
strText.bFormat(strTemplate,
|
|
static_cast<LPCTSTR>(strDocName),
|
|
static_cast<LPCTSTR>(strPrinter),
|
|
static_cast<LPCTSTR>(strTimeSubmitted),
|
|
ji.dwTotalPages) )
|
|
{
|
|
_ShowBalloon(BALLOON_ID_JOB_PRINTED, strTitle, strText, gszBalloonSoundPrintComplete, NIIF_INFO);
|
|
m_strLastBalloonPrinter.bUpdate(pszPrinter);
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL CTrayNotify::_TimeToString(const SYSTEMTIME &time, TString *pTime) const
|
|
{
|
|
ASSERT(pTime);
|
|
|
|
BOOL bReturn = FALSE;
|
|
TCHAR szText[255];
|
|
SYSTEMTIME timeLocal;
|
|
|
|
if( SystemTimeToTzSpecificLocalTime(NULL, const_cast<LPSYSTEMTIME>(&time), &timeLocal) &&
|
|
GetTimeFormat(LOCALE_USER_DEFAULT, 0, &timeLocal, NULL, szText, COUNTOF(szText)) )
|
|
{
|
|
pTime->bCat(szText);
|
|
pTime->bCat(TEXT(" "));
|
|
|
|
if( GetDateFormat(LOCALE_USER_DEFAULT, dwDateFormatFlags(m_hwnd),
|
|
&timeLocal, NULL, szText, COUNTOF(szText)) )
|
|
{
|
|
pTime->bCat(szText);
|
|
bReturn = TRUE;
|
|
}
|
|
}
|
|
|
|
return bReturn;
|
|
}
|
|
|
|
void CTrayNotify::_DoRefresh(CPrintNotify *pListener)
|
|
{
|
|
m_bInRefresh = TRUE;
|
|
pListener->Refresh(this, _RefreshCallback);
|
|
m_bInRefresh = FALSE;
|
|
|
|
// check to update the tray
|
|
_CheckToUpdateTray(FALSE, NULL);
|
|
}
|
|
|
|
void CTrayNotify::_PostPrivateMsg(const MsgInfo &msg)
|
|
{
|
|
HANDLE hItem;
|
|
if( SUCCEEDED(m_heapMsgCache.Alloc(msg, &hItem)) )
|
|
{
|
|
// request a balloon to show up...
|
|
PostMessage(m_hwnd, WM_PRINTTRAY_PRIVATE_MSG, reinterpret_cast<LPARAM>(hItem), 0);
|
|
}
|
|
}
|
|
|
|
void CTrayNotify::_RequestUpdate(int iEvent, LPCTSTR pszPrinter, LPCTSTR pszAux)
|
|
{
|
|
// check to update the context menu if shown
|
|
int i = -1;
|
|
switch( iEvent )
|
|
{
|
|
case UPDATE_REQUEST_JOB_ADD:
|
|
{
|
|
// check to add to the context menu
|
|
MENUITEMINFO mii = { sizeof(mii), MIIM_ID, 0 };
|
|
if( m_shCtxMenu && _FindPrinter(pszPrinter, &i) && 0 != m_arrWatchList[i].pUserJobs->Count() &&
|
|
FALSE == GetMenuItemInfo(m_shCtxMenu, _EncodeMenuID(i), FALSE, &mii) )
|
|
{
|
|
// add this printer to the context menu
|
|
_AddPrinterToCtxMenu(m_shCtxMenu, i);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case UPDATE_REQUEST_JOB_DELETE:
|
|
{
|
|
// check this printer to be deleted later
|
|
MsgInfo msg = { msgTypePrnCheckDelete, kFolderNone };
|
|
lstrcpyn(msg.szPrinter, pszPrinter, COUNTOF(msg.szPrinter));
|
|
_PostPrivateMsg(msg);
|
|
|
|
// check to delete from the context menu
|
|
MENUITEMINFO mii = { sizeof(mii), MIIM_ID, 0 };
|
|
if( m_shCtxMenu && _FindPrinter(pszPrinter, &i) && 0 == m_arrWatchList[i].pUserJobs->Count() &&
|
|
TRUE == GetMenuItemInfo(m_shCtxMenu, _EncodeMenuID(i), FALSE, &mii) )
|
|
{
|
|
// delete this printer from the context menu
|
|
VERIFY(DeleteMenu(m_shCtxMenu, _EncodeMenuID(i), MF_BYCOMMAND));
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// check to update tray if not in refresh
|
|
if( !m_bInRefresh )
|
|
{
|
|
_CheckToUpdateTray(FALSE, NULL);
|
|
}
|
|
}
|
|
|
|
void CTrayNotify::_BalloonClicked(UINT uBalloonID) const
|
|
{
|
|
switch( uBalloonID )
|
|
{
|
|
case BALLOON_ID_JOB_FAILED:
|
|
vQueueCreate(NULL, m_strLastBalloonPrinter, SW_SHOWNORMAL, static_cast<LPARAM>(FALSE));
|
|
break;
|
|
|
|
case BALLOON_ID_JOB_PRINTED:
|
|
// do nothing
|
|
break;
|
|
|
|
case BALLOON_ID_PRN_CREATED:
|
|
_ShowPrnFolder();
|
|
break;
|
|
|
|
default:
|
|
ASSERT(FALSE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
BOOL CTrayNotify::_UpdateUserJob(PrinterInfo &pi, JobInfo &ji)
|
|
{
|
|
// get job info at level 1
|
|
BOOL bReturn = FALSE;
|
|
DWORD cbJob = 0;
|
|
CAutoPtrSpl<JOB_INFO_2> spJob;
|
|
|
|
if( VDataRefresh::bGetJob(pi.pListener->GetPrinterHandle(), ji.dwID, 2, spJob.GetPPV(), &cbJob) )
|
|
{
|
|
// the only thing we care about is the document name & job status
|
|
ji.dwStatus = spJob->Status;
|
|
lstrcpyn(ji.szDocName, spJob->pDocument, COUNTOF(ji.szDocName));
|
|
ji.timeSubmitted = spJob->Submitted;
|
|
ji.dwTotalPages = spJob->TotalPages;
|
|
bReturn = TRUE;
|
|
}
|
|
return bReturn;
|
|
}
|
|
|
|
void CTrayNotify::_ShowAllActivePrinters() const
|
|
{
|
|
// open all the active printers
|
|
int i, iCount = m_arrWatchList.Count();
|
|
for( i=0; i<iCount; i++ )
|
|
{
|
|
if( m_arrWatchList[i].pUserJobs->Count() )
|
|
{
|
|
vQueueCreate(NULL, m_arrWatchList[i].szPrinter, SW_SHOWNORMAL, static_cast<LPARAM>(FALSE));
|
|
}
|
|
}
|
|
}
|
|
|
|
void CTrayNotify::_AddPrinterToCtxMenu(HMENU hMenu, int i)
|
|
{
|
|
// build the context menu here
|
|
TString strPrinter;
|
|
MENUITEMINFO mii = { sizeof(mii), MIIM_TYPE|MIIM_ID, MF_STRING };
|
|
|
|
if( m_arrWatchList[i].bUserInterventionReq )
|
|
{
|
|
// this printer is in error state, add an (error) suffix
|
|
TString strTemplate;
|
|
strTemplate.bLoadString(ghInst, IDS_TRAY_TEXT_ERROR);
|
|
strPrinter.bFormat(strTemplate, m_arrWatchList[i].szPrinter);
|
|
}
|
|
else
|
|
{
|
|
strPrinter.bUpdate(m_arrWatchList[i].szPrinter);
|
|
}
|
|
|
|
mii.wID = _EncodeMenuID(i);
|
|
mii.dwTypeData = const_cast<LPTSTR>(static_cast<LPCTSTR>(strPrinter));
|
|
mii.cch = lstrlen(mii.dwTypeData);
|
|
|
|
InsertMenuItem(hMenu, (UINT)-1, MF_BYPOSITION, &mii);
|
|
}
|
|
|
|
HRESULT CTrayNotify::_ProcessJobNotifications(LPCTSTR pszPrinter, DWORD dwChange,
|
|
const PRINTER_NOTIFY_INFO *pInfo, PrinterInfo *ppi)
|
|
{
|
|
if( pInfo && (PRINTER_NOTIFY_INFO_DISCARDED & pInfo->Flags) )
|
|
{
|
|
MsgInfo msg = { msgTypeJobNotifyLost, kFolderNone };
|
|
lstrcpyn(msg.szPrinter, pszPrinter, COUNTOF(msg.szPrinter));
|
|
|
|
if( ppi )
|
|
{
|
|
// called from the foreground thread: sync processing
|
|
_CheckToUpdateUserJobs(*ppi, msg);
|
|
}
|
|
else
|
|
{
|
|
// called from the background threads: async processing
|
|
_PostPrivateMsg(msg);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// regular job notifications have arrived
|
|
if( pInfo && pInfo->Count )
|
|
{
|
|
MsgInfo msg = { msgTypeJobNotify, kFolderNone };
|
|
lstrcpyn(msg.szPrinter, pszPrinter, COUNTOF(msg.szPrinter));
|
|
|
|
for( DWORD i=0; i<pInfo->Count; i++ )
|
|
{
|
|
if( JOB_NOTIFY_TYPE != pInfo->aData[i].Type )
|
|
{
|
|
// we only care about job notifications here
|
|
continue;
|
|
}
|
|
|
|
msg.jn.Type = pInfo->aData[i].Type;
|
|
msg.jn.Field = pInfo->aData[i].Field;
|
|
msg.jn.Id = pInfo->aData[i].Id;
|
|
msg.jn.dwData = pInfo->aData[i].NotifyData.adwData[0];
|
|
|
|
if( JOB_NOTIFY_FIELD_NOTIFY_NAME == pInfo->aData[i].Field )
|
|
{
|
|
// need to copy pBuf into the aux buffer (szAuxName)
|
|
lstrcpyn(msg.szAuxName, reinterpret_cast<LPCTSTR>(pInfo->aData[i].NotifyData.Data.pBuf),
|
|
COUNTOF(msg.szAuxName));
|
|
}
|
|
|
|
if( ppi )
|
|
{
|
|
// called from the foreground thread: sync processing
|
|
_CheckToUpdateUserJobs(*ppi, msg);
|
|
}
|
|
else
|
|
{
|
|
// called from the background threads: async processing
|
|
_PostPrivateMsg(msg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
void CTrayNotify::_ResetAll()
|
|
{
|
|
// close the context menu
|
|
m_shCtxMenu = NULL;
|
|
|
|
// cleanup the watch list (unregister all job notifications listeners)
|
|
while( m_arrWatchList.Count() )
|
|
{
|
|
_Delete(0);
|
|
}
|
|
|
|
// flush the message queue here...
|
|
MSG msg;
|
|
while( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) )
|
|
{
|
|
if( WM_PRINTTRAY_PRIVATE_MSG == msg.message )
|
|
{
|
|
// just free up the private message
|
|
VERIFY(SUCCEEDED(m_heapMsgCache.Free(reinterpret_cast<HANDLE>(msg.wParam))));
|
|
}
|
|
}
|
|
|
|
// update the tray
|
|
_CheckToUpdateTray(FALSE, NULL);
|
|
}
|
|
|
|
BOOL CTrayNotify::_IsFaxPrinter(const FOLDER_PRINTER_DATA &data)
|
|
{
|
|
return ((0 == lstrcmp(data.pDriverName, FAX_DRIVER_NAME)) ||
|
|
(data.Attributes & PRINTER_ATTRIBUTE_FAX));
|
|
}
|
|
|
|
HRESULT CTrayNotify::_CanNotify(LPCTSTR pszPrinterName, BOOL *pbCanNotify)
|
|
{
|
|
HRESULT hr = E_INVALIDARG;
|
|
BOOL bIsNetworkPrinter;
|
|
LPCTSTR pszServer;
|
|
LPCTSTR pszPrinter;
|
|
TCHAR szScratch[kStrMax+kPrinterBufMax];
|
|
UINT nSize = COUNTOF(szScratch);
|
|
|
|
if( pszPrinterName && *pszPrinterName && pbCanNotify )
|
|
{
|
|
//
|
|
// Split the printer name into its components.
|
|
//
|
|
vPrinterSplitFullName(szScratch, pszPrinterName, &pszServer, &pszPrinter);
|
|
bIsNetworkPrinter = bIsRemote(pszServer);
|
|
|
|
// set default value
|
|
*pbCanNotify = bIsNetworkPrinter ? TRUE : FALSE;
|
|
|
|
TPersist NotifyUser(gszPrinterPositions, TPersist::kCreate|TPersist::kRead);
|
|
|
|
if( NotifyUser.bValid() )
|
|
{
|
|
if( NotifyUser.bRead(bIsNetworkPrinter ? gszNetworkPrintNotification : gszLocalPrintNotification, *pbCanNotify) )
|
|
{
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
{
|
|
hr = CreateHRFromWin32();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = CreateHRFromWin32();
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
LRESULT CTrayNotify::_ProcessUserMsg(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch( uMsg )
|
|
{
|
|
case WM_PRINTTRAY_PRIVATE_MSG:
|
|
{
|
|
MsgInfo *pmsg = NULL;
|
|
HANDLE hItem = reinterpret_cast<HANDLE>(wParam);
|
|
VERIFY(SUCCEEDED(m_heapMsgCache.GetItem(hItem, &pmsg)));
|
|
|
|
switch( pmsg->iType )
|
|
{
|
|
case msgTypePrnNotify:
|
|
{
|
|
_ProcessPrnNotify(pmsg->NotifyType, pmsg->szPrinter, pmsg->szAuxName);
|
|
}
|
|
break;
|
|
|
|
case msgTypePrnCheckDelete:
|
|
{
|
|
int iPos = -1;
|
|
if( _FindPrinter(pmsg->szPrinter, &iPos) && 0 == m_arrWatchList[iPos].pUserJobs->Count() )
|
|
{
|
|
// this printer has no active user jobs, so delete it.
|
|
_Delete(iPos);
|
|
_RequestUpdate(UPDATE_REQUEST_PRN_DELETE, pmsg->szPrinter);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case msgTypeJobNotify:
|
|
{
|
|
int iPos = -1;
|
|
if( _FindPrinter(pmsg->szPrinter, &iPos) )
|
|
{
|
|
_CheckToUpdateUserJobs(m_arrWatchList[iPos], *pmsg);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case msgTypeJobNotifyLost:
|
|
{
|
|
int iPos = -1;
|
|
if( _FindPrinter(pmsg->szPrinter, &iPos) )
|
|
{
|
|
PrinterInfo &pi = m_arrWatchList[iPos];
|
|
|
|
// do a full refresh here
|
|
pi.dwStatus = 0;
|
|
pi.pUserJobs->DeleteAll();
|
|
_RequestUpdate(UPDATE_REQUEST_JOB_DELETE, pi.szPrinter);
|
|
|
|
// update all
|
|
_DoRefresh(m_arrWatchList[iPos].pListener);
|
|
}
|
|
}
|
|
break;
|
|
|
|
|
|
default:
|
|
ASSERT(FALSE);
|
|
break;
|
|
}
|
|
|
|
// free up the message
|
|
VERIFY(SUCCEEDED(m_heapMsgCache.Free(hItem)));
|
|
}
|
|
break;
|
|
|
|
case WM_PRINTTRAY_ICON_NOTIFY:
|
|
{
|
|
// the real uMsg is in lParam
|
|
switch( lParam )
|
|
{
|
|
case WM_CONTEXTMENU:
|
|
_ShowMenu();
|
|
break;
|
|
|
|
case WM_LBUTTONDBLCLK:
|
|
_ShowAllActivePrinters();
|
|
break;
|
|
|
|
case NIN_BALLOONUSERCLICK:
|
|
case NIN_BALLOONTIMEOUT:
|
|
{
|
|
m_uBalloonsCount--;
|
|
|
|
if( NIN_BALLOONUSERCLICK == lParam && m_uBalloonID )
|
|
{
|
|
_BalloonClicked(m_uBalloonID);
|
|
}
|
|
|
|
if( 0 == m_uBalloonsCount )
|
|
{
|
|
m_uBalloonID = 0;
|
|
m_strLastBalloonPrinter.bUpdate(NULL);
|
|
_CheckToUpdateTray(FALSE, NULL);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_WININICHANGE:
|
|
{
|
|
// check to re-do the icon
|
|
_CheckToUpdateTray(FALSE, NULL);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
DWORD WINAPI CTrayNotify::_ThreadProc_MsgLoop(LPVOID lpParameter)
|
|
{
|
|
CTrayNotify *pFolderNotify = (CTrayNotify *)lpParameter;
|
|
|
|
if( pFolderNotify )
|
|
{
|
|
pFolderNotify->_ThreadProc();
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
// the callback called on refresh
|
|
HRESULT WINAPI CTrayNotify::_RefreshCallback(
|
|
LPVOID lpCookie, ULONG_PTR uCookie, DWORD dwChange, const PRINTER_NOTIFY_INFO *pInfo)
|
|
{
|
|
int iPos;
|
|
HRESULT hr = E_INVALIDARG;
|
|
CTrayNotify *pThis = reinterpret_cast<CTrayNotify*>(lpCookie);
|
|
CPrintNotify *pListener = reinterpret_cast<CPrintNotify*>(uCookie);
|
|
|
|
if( pThis && pListener && pThis->_FindPrinter(pListener->GetPrinter(), &iPos) )
|
|
{
|
|
hr = pThis->_ProcessJobNotifications(
|
|
pListener->GetPrinter(), dwChange, pInfo, &pThis->m_arrWatchList[iPos]);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//////////////////////////////////
|
|
// global & exported functions
|
|
//
|
|
static CTrayNotify *gpTrayNotify = NULL;
|
|
|
|
extern "C" {
|
|
|
|
BOOL WINAPI PrintNotifyTray_Init()
|
|
{
|
|
ASSERT(gpTrayLock);
|
|
|
|
// synchonize this with gpTrayLock
|
|
CCSLock::Locker lock(*gpTrayLock);
|
|
|
|
BOOL bReturn = FALSE;
|
|
if( NULL == gpTrayNotify )
|
|
{
|
|
if( SUCCEEDED(CTrayNotify_CreateInstance(&gpTrayNotify)) )
|
|
{
|
|
bReturn = gpTrayNotify->Initialize();
|
|
|
|
if( !bReturn )
|
|
{
|
|
gpTrayNotify->Release();
|
|
gpTrayNotify = NULL;
|
|
}
|
|
else
|
|
{
|
|
DBGMSG(DBG_INITDONE, ("INITDONE: PrintTray - start listen! \n"));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// reset the shutdown timer
|
|
gpTrayNotify->Touch();
|
|
}
|
|
|
|
return bReturn;
|
|
}
|
|
|
|
BOOL WINAPI PrintNotifyTray_Exit()
|
|
{
|
|
ASSERT(gpTrayLock);
|
|
|
|
// synchonize this with gpTrayLock
|
|
CCSLock::Locker lock(*gpTrayLock);
|
|
|
|
BOOL bReturn = FALSE;
|
|
if( gpTrayNotify )
|
|
{
|
|
bReturn = gpTrayNotify->Shutdown();
|
|
gpTrayNotify->Release();
|
|
gpTrayNotify = NULL;
|
|
|
|
DBGMSG(DBG_INITDONE, ("INITDONE: PrintTray - stop listen! \n"));
|
|
}
|
|
|
|
return bReturn;
|
|
}
|
|
|
|
BOOL WINAPI PrintNotifyTray_SelfShutdown()
|
|
{
|
|
ASSERT(gpTrayLock);
|
|
|
|
BOOL bReturn = FALSE;
|
|
CTrayNotify *pTrayNotify = NULL;
|
|
|
|
{
|
|
// synchonize this with gpTrayLock
|
|
CCSLock::Locker lock(*gpTrayLock);
|
|
|
|
if( gpTrayNotify )
|
|
{
|
|
if( gpTrayNotify->CanShutdown() )
|
|
{
|
|
// mark for deletion and continue to exit the CS
|
|
pTrayNotify = gpTrayNotify;
|
|
gpTrayNotify = NULL;
|
|
|
|
DBGMSG(DBG_INITDONE, ("INITDONE: PrintTray - stop listen! \n"));
|
|
}
|
|
else
|
|
{
|
|
// restart the shutdown timer
|
|
gpTrayNotify->Resurrect();
|
|
}
|
|
}
|
|
}
|
|
|
|
if( pTrayNotify )
|
|
{
|
|
// marked for shutdown & release - shutdown without holding the CS
|
|
bReturn = pTrayNotify->Shutdown();
|
|
pTrayNotify->Release();
|
|
}
|
|
|
|
return bReturn;
|
|
}
|
|
|
|
} // extern "C"
|
|
|