Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2058 lines
68 KiB

  1. /*++
  2. Copyright (C) Microsoft Corporation, 1995 - 1999
  3. All rights reserved.
  4. Module Name:
  5. traynot.cxx
  6. Abstract:
  7. tray notifications and balloon help
  8. Author:
  9. Lazar Ivanov (LazarI) 25-Apr-2000
  10. Revision History:
  11. --*/
  12. #include "precomp.hxx"
  13. #pragma hdrstop
  14. #include "prids.h"
  15. #include "spllibex.hxx"
  16. #include "persist.hxx"
  17. #include "rtlmir.hxx"
  18. ////////////////////////////////////////////////////////
  19. // debugging stuff
  20. //
  21. #if DBG
  22. // #define DBG_TRAYNOTIFY DBG_INFO
  23. #define DBG_TRAYNOTIFY DBG_NONE
  24. #define DBG_PROCESSPRNNOTIFY DBG_NONE
  25. #define DBG_JOBNOTIFY DBG_NONE
  26. #define DBG_PRNNOTIFY DBG_NONE
  27. #define DBG_TRAYUPDATE DBG_NONE
  28. #define DBG_BALLOON DBG_NONE
  29. #define DBG_NTFYICON DBG_NONE
  30. #define DBG_MENUADJUST DBG_NONE
  31. #define DBG_INITDONE DBG_NONE
  32. #define DBG_JOBSTATUS DBG_NONE
  33. #endif
  34. #ifdef __cplusplus
  35. extern "C" {
  36. #endif
  37. BOOL WINAPI PrintNotifyTray_Init();
  38. BOOL WINAPI PrintNotifyTray_Exit();
  39. BOOL WINAPI PrintNotifyTray_SelfShutdown();
  40. } // extern "C"
  41. //////////////////////////////////////////////
  42. // CTrayNotify - tray notifications class
  43. //
  44. QITABLE_DECLARE(CTrayNotify)
  45. class CTrayNotify: public CUnknownMT<QITABLE_GET(CTrayNotify)>, // MT impl. of IUnknown
  46. public IFolderNotify,
  47. public IPrinterChangeCallback,
  48. public CSimpleWndSubclass<CTrayNotify>
  49. {
  50. public:
  51. CTrayNotify();
  52. ~CTrayNotify();
  53. //////////////////
  54. // IUnknown
  55. //
  56. IMPLEMENT_IUNKNOWN()
  57. void SetUser(LPCTSTR pszUser = NULL); // NULL means the current user
  58. void Touch(); // resets the shutdown timer
  59. void Resurrect(); // resurrects the tray after shutdown has been initiated
  60. BOOL Initialize(); // initialize & start listening
  61. BOOL Shutdown(); // stop listening & force shutdown
  62. BOOL CanShutdown(); // check if shutdown criteria is met (SHUTDOWN_TIMEOUT sec. inactivity)
  63. // implement CSimpleWndSubclass<...> - has to be public
  64. LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
  65. ///////////////////
  66. // IFolderNotify
  67. //
  68. STDMETHODIMP_(BOOL) ProcessNotify(FOLDER_NOTIFY_TYPE NotifyType, LPCWSTR pszName, LPCWSTR pszNewName);
  69. ///////////////////////////
  70. // IPrinterChangeCallback
  71. //
  72. STDMETHODIMP PrinterChange(ULONG_PTR uCookie, DWORD dwChange, const PRINTER_NOTIFY_INFO *pInfo);
  73. private:
  74. // internal stuff, enums/data types/methods/members
  75. enum
  76. {
  77. // possible private messages coming to our special window
  78. WM_PRINTTRAY_FIRST = WM_APP,
  79. WM_PRINTTRAY_PRIVATE_MSG, // private msg (posted data by background threads)
  80. WM_PRINTTRAY_REQUEST_SHUTDOWN, // request shutdown
  81. WM_PRINTTRAY_ICON_NOTIFY, // message comming back from shell
  82. };
  83. enum
  84. {
  85. // possible update events (see _RequestUpdate), we don't really care now,
  86. // but it is useful for future extensibility
  87. UPDATE_REQUEST_JOB_ADD, // lParam = prnCookie
  88. UPDATE_REQUEST_JOB_DELETE, // lParam = prnCookie
  89. UPDATE_REQUEST_JOB_STATUS, // lParam = prnCookie
  90. UPDATE_REQUEST_PRN_FIRST,
  91. UPDATE_REQUEST_PRN_ADD, // lParam = prnCookie
  92. UPDATE_REQUEST_PRN_DELETE, // lParam = 0, not used
  93. UPDATE_REQUEST_PRN_STATUS, // lParam = prnCookie
  94. UPDATE_REQUEST_PRN_RENAME, // lParam = prnCookie
  95. };
  96. enum
  97. {
  98. ENUM_MAX_RETRY = 5, // max attempts to call bFolderEnumPrinters/bFolderGetPrinter
  99. ICON_ID = 1, // our icon ID
  100. DEFAULT_BALLOON_TIMEOUT = 10000, // in miliseconds
  101. JOB_ERROR_BITS = (JOB_STATUS_OFFLINE|JOB_STATUS_USER_INTERVENTION|JOB_STATUS_ERROR|JOB_STATUS_PAPEROUT),
  102. JOB_IGNORE_BITS = (JOB_STATUS_DELETED|JOB_STATUS_PRINTED|JOB_STATUS_COMPLETE),
  103. };
  104. enum
  105. {
  106. SHUTDOWN_TIMER_ID = 100, // shutdown timer ID
  107. //SHUTDOWN_TIMEOUT = 3*1000, // timeout to shutdown the tray code when the user
  108. SHUTDOWN_TIMEOUT = 30*1000, // timeout to shutdown the tray code when the user
  109. // is not printing - 1 min.
  110. };
  111. enum
  112. {
  113. // balloon IDs
  114. BALLOON_ID_JOB_FAILED = 1, // balloon when a job failed to print
  115. BALLOON_ID_JOB_PRINTED = 2, // balloon when a job printed
  116. BALLOON_ID_PRN_CREATED = 3, // balloon when a local printer is created
  117. };
  118. enum
  119. {
  120. MAX_PRINTER_DISPLAYNAME = 48,
  121. MAX_DOC_DISPLAYNAME = 32,
  122. };
  123. enum
  124. {
  125. // message types, see MsgInfo declaration below
  126. msgTypePrnNotify = 100, // why not?
  127. msgTypePrnCheckDelete,
  128. msgTypeJobNotify,
  129. msgTypeJobNotifyLost,
  130. };
  131. // job info
  132. typedef struct
  133. {
  134. DWORD dwID; // job ID
  135. DWORD dwStatus; // job status
  136. TCHAR szDocName[255]; // document name
  137. SYSTEMTIME timeSubmitted; // when submited
  138. DWORD dwTotalPages; // Total number of pages
  139. } JobInfo;
  140. // define JobInfo adaptor class
  141. class CJobInfoAdaptor: public Alg::CDefaultAdaptor<JobInfo, DWORD>
  142. {
  143. public:
  144. static DWORD Key(const JobInfo &i) { return i.dwID; }
  145. };
  146. // CJobInfoArray definition
  147. typedef CSortedArray<JobInfo, DWORD, CJobInfoAdaptor> CJobInfoArray;
  148. // printer info
  149. typedef struct
  150. {
  151. TCHAR szPrinter[kPrinterBufMax]; // the printer name
  152. DWORD dwStatus; // printer status
  153. CJobInfoArray *pUserJobs; // the jobs pending on this printer
  154. CPrintNotify *pListener; // notifications listener
  155. BOOL bUserInterventionReq; // this printer requires user intervention
  156. } PrinterInfo;
  157. // define PrinterInfo adaptor class
  158. class CPrinterInfoAdaptor: public Alg::CDefaultAdaptor<PrinterInfo, LPCTSTR>
  159. {
  160. public:
  161. static LPCTSTR Key(const PrinterInfo &i) { return i.szPrinter; }
  162. static int Compare(LPCTSTR pszK1, LPCTSTR pszK2) { return lstrcmp(pszK1, pszK2); }
  163. };
  164. // CPrnInfoArray definition
  165. typedef CSortedArray<PrinterInfo, LPCTSTR, CPrinterInfoAdaptor> CPrnInfoArray;
  166. // balloon info
  167. typedef struct
  168. {
  169. UINT uBalloonID; // balloon ID (what action to take when clicked)
  170. LPCTSTR pszCaption; // balloon caption
  171. LPCTSTR pszText; // balloon text
  172. LPCTSTR pszSound; // canonical name of a special sound (can be NULL)
  173. DWORD dwFlags; // flags (NIIF_INFO, NIIF_WARNING, NIIF_ERROR)
  174. UINT uTimeout; // timeout
  175. } BalloonInfo;
  176. // private message info
  177. typedef struct
  178. {
  179. int iType; // private message type (msgTypePrnNotifymsgTypeJobNotify, msgTypeJobNotifyLost)
  180. FOLDER_NOTIFY_TYPE NotifyType; // printer notify type (kFolder* constants defined in winprtp.h)
  181. TCHAR szPrinter[kPrinterBufMax]; // printer name
  182. // job notification fields
  183. struct
  184. {
  185. WORD Type; // PRINTER_NOTIFY_INFO_DATA.Type
  186. WORD Field; // PRINTER_NOTIFY_INFO_DATA.Field
  187. DWORD Id; // PRINTER_NOTIFY_INFO_DATA.Id
  188. DWORD dwData; // PRINTER_NOTIFY_INFO_DATA.NotifyData.adwData[0]
  189. } jn;
  190. // auxiliary buffer
  191. TCHAR szAuxName[kPrinterBufMax]; // PRINTER_NOTIFY_INFO_DATA.NotifyData.Data.pBuf or pszNewName
  192. } MsgInfo;
  193. // internal APIs
  194. BOOL _InternalInit();
  195. void _MsgLoop();
  196. void _ThreadProc();
  197. void _ProcessPrnNotify(FOLDER_NOTIFY_TYPE NotifyType, LPCWSTR pszName, LPCWSTR pszNewName);
  198. BOOL _FindPrinter(LPCTSTR pszPrinter, int *pi) const;
  199. BOOL _FindUserJob(DWORD dwID, const PrinterInfo &info, int *pi) const;
  200. UINT _LastReservedMenuID() const;
  201. void _AdjustMenuIDs(HMENU hMenu, UINT uIDFrom, int iAdjustment) const;
  202. int _Insert(const FOLDER_PRINTER_DATA &data);
  203. void _Delete(int iPos);
  204. HRESULT _GetPrinter(LPCTSTR pszPrinter, LPBYTE *ppData, PDWORD pcReturned);
  205. void _CheckToUpdateUserJobs(PrinterInfo &pi, const MsgInfo &msg);
  206. void _CheckUserJobs(int *piUserJobs, BOOL *pbUserJobPrinting, BOOL *pbUserInterventionReq);
  207. void _CheckToUpdateTray(BOOL bForceUpdate, const BalloonInfo *pBalloon, BOOL bForceDelete = FALSE);
  208. LRESULT _ProcessUserMsg(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
  209. void _ShowMenu();
  210. void _ShowPrnFolder() const;
  211. void _ShowBalloon(UINT uBalloonID, LPCTSTR pszCaption, LPCTSTR pszText, LPCTSTR pszSound,
  212. DWORD dwFlags = NIIF_INFO, UINT uTimeout = DEFAULT_BALLOON_TIMEOUT);
  213. void _JobFailed(LPCTSTR pszPrinter, JobInfo &ji);
  214. void _JobPrinted(LPCTSTR pszPrinter, JobInfo &ji);
  215. BOOL _TimeToString(const SYSTEMTIME &time, TString *pTime) const;
  216. void _DoRefresh(CPrintNotify *pListener);
  217. void _PostPrivateMsg(const MsgInfo &msg);
  218. void _RequestUpdate(int iEvent, LPCTSTR pszPrinter, LPCTSTR pszAux = NULL);
  219. void _BalloonClicked(UINT uBalloonID) const;
  220. BOOL _UpdateUserJob(PrinterInfo &pi, JobInfo &ji);
  221. void _ShowAllActivePrinters() const;
  222. void _AddPrinterToCtxMenu(HMENU hMenu, int i);
  223. HRESULT _ProcessJobNotifications(LPCTSTR pszPrinter, DWORD dwChange,
  224. const PRINTER_NOTIFY_INFO *pInfo, PrinterInfo *ppi = NULL);
  225. void _ResetAll();
  226. BOOL _IsFaxPrinter(const FOLDER_PRINTER_DATA &data);
  227. HRESULT _CanNotify(LPCTSTR pszPrinterName, BOOL *pbCanNotify);
  228. // msg loop thread proc
  229. static DWORD WINAPI _ThreadProc_MsgLoop(LPVOID lpParameter);
  230. // the callback called on refresh
  231. static HRESULT WINAPI _RefreshCallback(
  232. LPVOID lpCookie, ULONG_PTR uCookie, DWORD dwChange, const PRINTER_NOTIFY_INFO *pInfo);
  233. // some inlines
  234. int _EncodeMenuID(int i) const { return (i + _LastReservedMenuID() + 1); }
  235. int _DecodeMenuID(int i) const { return (i - _LastReservedMenuID() - 1); }
  236. // internal members
  237. int m_cxSmIcon; // the icon size X
  238. int m_cySmIcon; // the icon size Y
  239. UINT m_uTrayIcon; // the current tray icon
  240. int m_iUserJobs; // the user jobs (need to keep track of this to update the tooltip)
  241. BOOL m_bInRefresh; // if we are in refresh
  242. BOOL m_bIconShown; // if the icon is visible or not
  243. UINT m_uBalloonID; // the ID of the last balloon shown up
  244. UINT m_uBalloonsCount; // how many balloons has been currently displayed
  245. CAutoHandleNT m_shEventReady; // event to sync Initialize & Shutdown
  246. CAutoHandleNT m_shThread; // notifications thread listener
  247. CAutoHandleIcon m_shIconShown; // the tray icon shown
  248. CAutoHandleMenu m_shCtxMenu; // the context menu
  249. HANDLE m_hFolder; // printer's folder cache
  250. CFastHeap<MsgInfo> m_heapMsgCache; // messages cache
  251. CPrnInfoArray m_arrWatchList; // printers holding documents for our (the current) user
  252. TString m_strUser; // our (usually the current) user
  253. TString m_strLastBalloonPrinter; // the printer name of the last balloon
  254. DWORD m_dwLastTimeInactive; // the last time since the print icon has been inactive
  255. BOOL m_bSelfShutdownInitiated; // is self-shutdown initiated?
  256. };
  257. // QueryInterface table
  258. QITABLE_BEGIN(CTrayNotify)
  259. QITABENT(CTrayNotify, IFolderNotify), // IID_IFolderNotify
  260. QITABLE_END()
  261. HRESULT CTrayNotify_CreateInstance(CTrayNotify **ppObj)
  262. {
  263. HRESULT hr = E_INVALIDARG;
  264. if( ppObj )
  265. {
  266. *ppObj = new CTrayNotify;
  267. hr = (*ppObj) ? S_OK : E_OUTOFMEMORY;
  268. }
  269. return hr;
  270. }
  271. /////////////////////////////
  272. // globals & constants
  273. //
  274. #define gszTrayListenerClassName TEXT("PrintTray_Notify_WndClass")
  275. static WORD g_JobFields[] =
  276. { // Job Fields we want notifications for
  277. JOB_NOTIFY_FIELD_STATUS, // Status bits
  278. JOB_NOTIFY_FIELD_NOTIFY_NAME, // Name of the user who should be notified
  279. JOB_NOTIFY_FIELD_TOTAL_PAGES, // Total number of pages
  280. };
  281. static PRINTER_NOTIFY_OPTIONS_TYPE g_Notifications[2] =
  282. {
  283. {
  284. JOB_NOTIFY_TYPE, // We want notifications on print jobs
  285. 0, 0, 0, // Reserved, must be zeros
  286. sizeof(g_JobFields)/sizeof(g_JobFields[0]), // We specified 9 fields in the JobFields array
  287. g_JobFields // Precisely which fields we want notifications for
  288. }
  289. };
  290. static const UINT g_arrIcons[] = { 0, IDI_PRINTER, IDI_PRINTER_ERROR };
  291. static const UINT g_arrReservedMenuIDs[] =
  292. {
  293. IDM_TRAYNOTIFY_DEFAULT,
  294. IDM_TRAYNOTIFY_PRNFOLDER,
  295. IDM_TRAYNOTIFY_REFRESH,
  296. };
  297. /////////////////////////////
  298. // inlines
  299. //
  300. inline UINT CTrayNotify::_LastReservedMenuID() const
  301. {
  302. UINT uMax = 0;
  303. for( int i=0; i<ARRAYSIZE(g_arrReservedMenuIDs); i++ )
  304. {
  305. if( g_arrReservedMenuIDs[i] > uMax )
  306. {
  307. uMax = g_arrReservedMenuIDs[i];
  308. }
  309. }
  310. return uMax;
  311. }
  312. inline BOOL CTrayNotify::_FindPrinter(LPCTSTR pszPrinter, int *pi) const
  313. {
  314. return m_arrWatchList.FindItem(pszPrinter, pi);
  315. }
  316. inline BOOL CTrayNotify::_FindUserJob(DWORD dwID, const PrinterInfo &info, int *pi) const
  317. {
  318. return info.pUserJobs->FindItem(dwID, pi);
  319. }
  320. /////////////////////////////
  321. // CTrayNotify
  322. //
  323. CTrayNotify::CTrayNotify()
  324. : m_hFolder(NULL),
  325. m_cxSmIcon(0),
  326. m_cySmIcon(0),
  327. m_uTrayIcon(g_arrIcons[0]),
  328. m_iUserJobs(0),
  329. m_bInRefresh(FALSE),
  330. m_bIconShown(FALSE),
  331. m_uBalloonID(0),
  332. m_uBalloonsCount(0),
  333. m_dwLastTimeInactive(GetTickCount()),
  334. m_bSelfShutdownInitiated(FALSE)
  335. {
  336. // nothing special
  337. DBGMSG(DBG_TRAYNOTIFY, ("TRAYNOTIFY: CTrayNotify::CTrayNotify()\n"));
  338. }
  339. CTrayNotify::~CTrayNotify()
  340. {
  341. ASSERT(FALSE == IsAttached());
  342. ASSERT(NULL == m_hFolder);
  343. ASSERT(0 == m_arrWatchList.Count());
  344. DBGMSG(DBG_TRAYNOTIFY, ("TRAYNOTIFY: CTrayNotify::~CTrayNotify()\n"));
  345. }
  346. void CTrayNotify::SetUser(LPCTSTR pszUser)
  347. {
  348. TCHAR szUserName[64];
  349. szUserName[0] = 0;
  350. if( NULL == pszUser || 0 == pszUser[0] )
  351. {
  352. // set the current user
  353. DWORD dwSize = COUNTOF(szUserName);
  354. if( GetUserName(szUserName, &dwSize) )
  355. {
  356. pszUser = szUserName;
  357. }
  358. }
  359. m_strUser.bUpdate(pszUser);
  360. }
  361. void CTrayNotify::Touch()
  362. {
  363. m_dwLastTimeInactive = GetTickCount();
  364. }
  365. void CTrayNotify::Resurrect()
  366. {
  367. if( m_bSelfShutdownInitiated )
  368. {
  369. // restart the shutdown timer & reset the state
  370. m_bSelfShutdownInitiated = FALSE;
  371. SetTimer(m_hwnd, SHUTDOWN_TIMER_ID, SHUTDOWN_TIMEOUT, NULL);
  372. }
  373. }
  374. BOOL CTrayNotify::Initialize()
  375. {
  376. BOOL bReturn = FALSE;
  377. if( 0 == m_strUser.uLen() )
  378. {
  379. // assume listening for the currently logged on user
  380. SetUser(NULL);
  381. }
  382. m_shEventReady = CreateEvent(NULL, FALSE, FALSE, NULL);
  383. if( m_shEventReady )
  384. {
  385. DWORD dwThreadId;
  386. m_shThread = TSafeThread::Create(NULL, 0, (LPTHREAD_START_ROUTINE)_ThreadProc_MsgLoop, this, 0, &dwThreadId);
  387. if( m_shThread )
  388. {
  389. // wait the background thread to kick off
  390. WaitForSingleObject(m_shEventReady, INFINITE);
  391. if( IsAttached() )
  392. {
  393. // the message loop has started successfully, request full refresh
  394. m_shEventReady = NULL;
  395. // request an initial refresh
  396. MsgInfo msg = { msgTypePrnNotify, kFolderUpdateAll };
  397. _PostPrivateMsg(msg);
  398. // we are fine here
  399. bReturn = TRUE;
  400. }
  401. }
  402. }
  403. return bReturn;
  404. }
  405. BOOL CTrayNotify::Shutdown()
  406. {
  407. if( IsAttached() )
  408. {
  409. PostMessage(m_hwnd, WM_PRINTTRAY_REQUEST_SHUTDOWN, 0, 0);
  410. // wait the background thread to cleanup
  411. WaitForSingleObject(m_shThread, INFINITE);
  412. return !IsAttached();
  413. }
  414. return FALSE;
  415. }
  416. BOOL CTrayNotify::CanShutdown()
  417. {
  418. return ( !m_bIconShown &&
  419. (GetTickCount() > m_dwLastTimeInactive) &&
  420. (GetTickCount() - m_dwLastTimeInactive) > SHUTDOWN_TIMEOUT );
  421. }
  422. // private worker proc to shutdown the tray
  423. static DWORD WINAPI ShutdownTray_WorkerProc(LPVOID lpParameter)
  424. {
  425. // shutdown the tray code.
  426. PrintNotifyTray_SelfShutdown();
  427. return 0;
  428. }
  429. // implement CSimpleWndSubclass<...>
  430. LRESULT CTrayNotify::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  431. {
  432. switch( uMsg )
  433. {
  434. case WM_TIMER:
  435. {
  436. switch( wParam )
  437. {
  438. case SHUTDOWN_TIMER_ID:
  439. {
  440. if( CanShutdown() && !m_bSelfShutdownInitiated )
  441. {
  442. // the tray icon has been inactive for more than SHUTDOWN_TIMEOUT
  443. // initiate shutdown
  444. if( SHQueueUserWorkItem(reinterpret_cast<LPTHREAD_START_ROUTINE>(ShutdownTray_WorkerProc),
  445. NULL, 0, 0, NULL, "printui.dll", 0) )
  446. {
  447. m_bSelfShutdownInitiated = TRUE;
  448. KillTimer(hwnd, SHUTDOWN_TIMER_ID);
  449. }
  450. }
  451. }
  452. break;
  453. default:
  454. break;
  455. }
  456. }
  457. break;
  458. case WM_PRINTTRAY_REQUEST_SHUTDOWN:
  459. {
  460. // unregister the folder notifications
  461. ASSERT(m_hFolder);
  462. UnregisterPrintNotify(NULL, this, &m_hFolder);
  463. m_hFolder = NULL;
  464. // reset all listeners
  465. _ResetAll();
  466. // force to delete the tray icon
  467. _CheckToUpdateTray(TRUE, NULL, TRUE);
  468. // detach from the window
  469. VERIFY(Detach());
  470. // our object is now detached - destroy the window
  471. // and post a quit msg to terminate the thread.
  472. DestroyWindow(hwnd);
  473. PostQuitMessage(0);
  474. return 0;
  475. }
  476. break;
  477. default:
  478. if( m_hFolder )
  479. {
  480. // do not process user msgs if shutdown is in progress
  481. _ProcessUserMsg(hwnd, uMsg, wParam, lParam);
  482. }
  483. break;
  484. }
  485. // allways call the default processing
  486. return DefWindowProc(hwnd, uMsg, wParam, lParam);
  487. }
  488. ///////////////////
  489. // IFolderNotify
  490. //
  491. STDMETHODIMP_(BOOL) CTrayNotify::ProcessNotify(FOLDER_NOTIFY_TYPE NotifyType, LPCWSTR pszName, LPCWSTR pszNewName)
  492. {
  493. // !IMPORTANT!
  494. // this is a callback from the background threads, so
  495. // we've got to be very carefull about what we are doing here.
  496. // the easiest way to syncronize is to quickly pass a private
  497. // message with the notification data into the foreground thread.
  498. MsgInfo msg = { msgTypePrnNotify, NotifyType };
  499. if( pszName )
  500. {
  501. lstrcpyn(msg.szPrinter, pszName, COUNTOF(msg.szPrinter));
  502. }
  503. if( pszNewName )
  504. {
  505. // this is not NULL only for kFolderRename
  506. lstrcpyn(msg.szAuxName, pszNewName, COUNTOF(msg.szAuxName));
  507. }
  508. // post a private message here...
  509. _PostPrivateMsg(msg);
  510. return TRUE;
  511. }
  512. ///////////////////////////
  513. // IPrinterChangeCallback
  514. //
  515. STDMETHODIMP CTrayNotify::PrinterChange(ULONG_PTR uCookie, DWORD dwChange, const PRINTER_NOTIFY_INFO *pInfo)
  516. {
  517. // !IMPORTANT!
  518. // this is a callback from the background threads, so
  519. // we've got to be very carefull about what we are doing here.
  520. // the easiest way to syncronize is to quickly pass a private
  521. // message with the notification data into the foreground thread.
  522. CPrintNotify *pListener = reinterpret_cast<CPrintNotify*>(uCookie);
  523. return (pListener ? _ProcessJobNotifications(pListener->GetPrinter(), dwChange, pInfo, NULL) : E_INVALIDARG);
  524. }
  525. //////////////////////////////////
  526. // private stuff _*
  527. //
  528. BOOL CTrayNotify::_InternalInit()
  529. {
  530. BOOL bReturn = SUCCEEDED(m_arrWatchList.Create());
  531. if( bReturn )
  532. {
  533. WNDCLASS WndClass;
  534. WndClass.style = 0L;
  535. WndClass.lpfnWndProc = (WNDPROC)&::DefWindowProc;
  536. WndClass.cbClsExtra = 0;
  537. WndClass.cbWndExtra = 0;
  538. WndClass.hInstance = 0;
  539. WndClass.hIcon = NULL;
  540. WndClass.hCursor = NULL;
  541. WndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
  542. WndClass.lpszMenuName = NULL;
  543. WndClass.lpszClassName = gszTrayListenerClassName;
  544. if( RegisterClass(&WndClass) ||
  545. ERROR_CLASS_ALREADY_EXISTS == GetLastError() )
  546. {
  547. // create the worker window
  548. HWND hwnd = CreateWindowEx(
  549. bIsBiDiLocalizedSystem() ? kExStyleRTLMirrorWnd : 0,
  550. gszTrayListenerClassName, NULL,
  551. WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, NULL, NULL, 0, NULL);
  552. if( hwnd )
  553. {
  554. Attach(hwnd);
  555. }
  556. }
  557. if( IsAttached() )
  558. {
  559. // register for print notifications in the printer's folder cache
  560. if( FAILED(RegisterPrintNotify(NULL, this, &m_hFolder, NULL)) )
  561. {
  562. // it will detach automatically
  563. DestroyWindow(m_hwnd);
  564. }
  565. else
  566. {
  567. // initialize a timer to shutdown the listening thread if there
  568. // is no activity for more than SHUTDOWN_TIMEOUT
  569. SetTimer(m_hwnd, SHUTDOWN_TIMER_ID, SHUTDOWN_TIMEOUT, NULL);
  570. }
  571. }
  572. }
  573. return bReturn;
  574. }
  575. void CTrayNotify::_MsgLoop()
  576. {
  577. // spin the msg loop here
  578. MSG msg;
  579. ASSERT(m_hwnd);
  580. while( GetMessage(&msg, NULL, 0, 0) )
  581. {
  582. TranslateMessage(&msg);
  583. DispatchMessage(&msg);
  584. }
  585. }
  586. void CTrayNotify::_ThreadProc()
  587. {
  588. AddRef();
  589. _InternalInit();
  590. if( m_shEventReady )
  591. {
  592. // notify the foreground thread we are about to start the msg loop
  593. SetEvent(m_shEventReady);
  594. }
  595. if( IsAttached() )
  596. {
  597. // spin a standard windows msg loop here
  598. _MsgLoop();
  599. }
  600. Release();
  601. }
  602. void CTrayNotify::_ProcessPrnNotify(FOLDER_NOTIFY_TYPE NotifyType, LPCWSTR pszName, LPCWSTR pszNewName)
  603. {
  604. DBGMSG(DBG_PROCESSPRNNOTIFY, ("PROCESSPRNNOTIFY: event has arrived, NotifyType=%d\n", NotifyType));
  605. switch( NotifyType )
  606. {
  607. case kFolderUpdate:
  608. case kFolderAttributes:
  609. case kFolderCreate:
  610. case kFolderUpdateAll:
  611. {
  612. CAutoPtrArray<BYTE> spBuffer;
  613. DWORD i, cReturned = 0;
  614. if( kFolderUpdateAll == NotifyType )
  615. {
  616. // reset all listeners...
  617. _ResetAll();
  618. // hide the icon, as this may take some time...
  619. _CheckToUpdateTray(FALSE, NULL);
  620. }
  621. if( SUCCEEDED(_GetPrinter(kFolderUpdateAll == NotifyType ?
  622. NULL : pszName, &spBuffer, &cReturned)) )
  623. {
  624. // walk through the printers to see if we need to add/delete/update printer(s) to our watch list.
  625. PFOLDER_PRINTER_DATA pPrinters = spBuffer.GetPtrAs<PFOLDER_PRINTER_DATA>();
  626. DBGMSG(DBG_PROCESSPRNNOTIFY, ("PROCESSPRNNOTIFY: create/update event, Count=%d\n", cReturned));
  627. for( i=0; i<cReturned; i++ )
  628. {
  629. DBGMSG(DBG_PROCESSPRNNOTIFY, ("PROCESSPRNNOTIFY: process printer: "TSTR", Jobs=%d\n",
  630. DBGSTR(pPrinters[i].pName), pPrinters[i].cJobs));
  631. // important!: we can't watch printers in pending deletion state because once the printer is
  632. // pending deletion it goes away without prior notification (when there is no jobs to print)
  633. int iPos;
  634. if( _FindPrinter(pPrinters[i].pName, &iPos) )
  635. {
  636. //
  637. // we don't want to delete the printer from the watch list when jobs count goes
  638. // to zero (0 == pPrinters[i].cJobs) because we rely on job notifications to show
  639. // balloons and sometimes the job notifications come AFTER the printer notifications
  640. //
  641. // we handle this by posting msgTypePrnCheckDelete when job gets deleted
  642. // so we can monitor the printer when there are no more user jobs in the queue and
  643. // then we stop watching the printer. don't change this behaviour unless you want
  644. // many things broken.
  645. //
  646. // printer found in the watch list
  647. if( PRINTER_STATUS_PENDING_DELETION & pPrinters[i].Status )
  648. {
  649. // no jobs or in pending deletion state - just delete.
  650. DBGMSG(DBG_PROCESSPRNNOTIFY, ("PROCESSPRNNOTIFY:[delete] watched printer with no jobs, pszPrinter="TSTR"\n",
  651. DBGSTR(pPrinters[i].pName)));
  652. _Delete(iPos);
  653. _RequestUpdate(UPDATE_REQUEST_PRN_DELETE, pPrinters[i].pName);
  654. }
  655. else
  656. {
  657. if( m_arrWatchList[iPos].dwStatus != pPrinters[i].Status )
  658. {
  659. // update status
  660. DBGMSG(DBG_PROCESSPRNNOTIFY, ("PROCESSPRNNOTIFY:[update] watched printer with jobs, pszPrinter="TSTR"\n",
  661. DBGSTR(pPrinters[i].pName)));
  662. m_arrWatchList[iPos].dwStatus = pPrinters[i].Status;
  663. _RequestUpdate(UPDATE_REQUEST_PRN_STATUS, pPrinters[i].pName);
  664. }
  665. }
  666. }
  667. else
  668. {
  669. // printer not found in the watch list
  670. if( pPrinters[i].cJobs && !(PRINTER_STATUS_PENDING_DELETION & pPrinters[i].Status) && !_IsFaxPrinter(pPrinters[i]) )
  671. {
  672. // start listening on this printer
  673. DBGMSG(DBG_PROCESSPRNNOTIFY, ("PROCESSPRNNOTIFY:[insert] non-watched printer with jobs, pszPrinter="TSTR"\n",
  674. DBGSTR(pPrinters[i].pName)));
  675. iPos = _Insert(pPrinters[i]);
  676. if( -1 != iPos )
  677. {
  678. _RequestUpdate(UPDATE_REQUEST_PRN_ADD, pPrinters[i].pName);
  679. }
  680. }
  681. }
  682. }
  683. }
  684. }
  685. break;
  686. case kFolderDelete:
  687. case kFolderRename:
  688. {
  689. int iPos;
  690. if( _FindPrinter(pszName, &iPos) )
  691. {
  692. // ranaming is a bit tricky. we need to delete & reinsert the item
  693. // to keep the array sorted & then update the context menu if necessary
  694. DBGMSG(DBG_PROCESSPRNNOTIFY, ("PROCESSPRNNOTIFY:[delete] watched printer deleted, pszPrinter="TSTR"\n",
  695. DBGSTR(pszName)));
  696. // first delete the printer which got renamed or deleted
  697. _Delete(iPos);
  698. _RequestUpdate(UPDATE_REQUEST_PRN_DELETE, pszName);
  699. if( kFolderRename == NotifyType )
  700. {
  701. // if rename, request update, so this printer can be re-added with
  702. // the new name.
  703. MsgInfo msg = { msgTypePrnNotify, kFolderUpdate };
  704. lstrcpyn(msg.szPrinter, pszNewName, COUNTOF(msg.szPrinter));
  705. _PostPrivateMsg(msg);
  706. }
  707. }
  708. }
  709. break;
  710. default:
  711. break;
  712. }
  713. }
  714. void CTrayNotify::_AdjustMenuIDs(HMENU hMenu, UINT uIDFrom, int iAdjustment) const
  715. {
  716. MENUITEMINFO mii = { sizeof(mii), MIIM_ID, 0 };
  717. int i, iCount = GetMenuItemCount(hMenu);
  718. for( i=0; i<iCount; i++ )
  719. {
  720. if( GetMenuItemInfo(hMenu, i, TRUE, &mii) && mii.wID >= uIDFrom )
  721. {
  722. DBGMSG(DBG_MENUADJUST, ("MENUADJUST: %d -> %d\n", mii.wID,
  723. static_cast<UINT>(static_cast<int>(mii.wID) + iAdjustment)));
  724. // adjust the menu item ID
  725. mii.wID = static_cast<UINT>(static_cast<int>(mii.wID) + iAdjustment);
  726. ASSERT(_DecodeMenuID(mii.wID) < m_arrWatchList.Count());
  727. // update menu item here
  728. SetMenuItemInfo(hMenu, i, TRUE, &mii);
  729. }
  730. }
  731. }
  732. int CTrayNotify::_Insert(const FOLDER_PRINTER_DATA &data)
  733. {
  734. CAutoPtr<CJobInfoArray> spUserJobs = new CJobInfoArray;
  735. CAutoPtr<CPrintNotify> spListener = new CPrintNotify(this, COUNTOF(g_Notifications), g_Notifications, PRINTER_CHANGE_JOB);
  736. int iPos = -1;
  737. if( spUserJobs && SUCCEEDED(spUserJobs->Create()) &&
  738. spListener && SUCCEEDED(spListener->Initialize(data.pName)) )
  739. {
  740. PrinterInfo infoPrn = {0};
  741. infoPrn.pListener = spListener;
  742. infoPrn.pUserJobs = spUserJobs;
  743. lstrcpyn(infoPrn.szPrinter, data.pName, COUNTOF(infoPrn.szPrinter));
  744. infoPrn.pListener->SetCookie(reinterpret_cast<ULONG_PTR>(infoPrn.pListener));
  745. ASSERT(!m_arrWatchList.FindItem(infoPrn.szPrinter, &iPos));
  746. iPos = m_arrWatchList.SortedInsert(infoPrn);
  747. if( -1 != iPos )
  748. {
  749. if( m_shCtxMenu )
  750. {
  751. DBGMSG(DBG_MENUADJUST, ("MENUADJUST: insert at pos: %d, Count=%d\n", iPos, m_arrWatchList.Count()));
  752. // if the context menu is opened then adjust the IDs
  753. _AdjustMenuIDs(m_shCtxMenu, _EncodeMenuID(iPos), 1);
  754. }
  755. // start listen on this printer
  756. DBGMSG(DBG_PRNNOTIFY, ("PRNNOTIFY: start listen printer: "TSTR"\n", DBGSTR(data.pName)));
  757. // they are hooked up already, detach from the smart pointers
  758. spListener.Detach();
  759. spUserJobs.Detach();
  760. // do an initial refresh and start listen
  761. _DoRefresh(infoPrn.pListener);
  762. infoPrn.pListener->StartListen();
  763. }
  764. }
  765. return iPos;
  766. }
  767. void CTrayNotify::_Delete(int iPos)
  768. {
  769. // stop listen on this printer
  770. m_arrWatchList[iPos].pListener->StopListen();
  771. DBGMSG(DBG_PRNNOTIFY, ("PRNNOTIFY: stop listen printer: "TSTR"\n", DBGSTR(m_arrWatchList[iPos].szPrinter)));
  772. delete m_arrWatchList[iPos].pListener;
  773. delete m_arrWatchList[iPos].pUserJobs;
  774. m_arrWatchList.Delete(iPos);
  775. DBGMSG(DBG_MENUADJUST, ("MENUADJUST: delete at pos: %d, Count=%d\n", iPos, m_arrWatchList.Count()));
  776. // fix the context menu
  777. if( m_shCtxMenu )
  778. {
  779. MENUITEMINFO mii = { sizeof(mii), MIIM_ID, 0 };
  780. if( GetMenuItemInfo(m_shCtxMenu, _EncodeMenuID(iPos), FALSE, &mii) )
  781. {
  782. // make sure this menu item is deleted
  783. VERIFY(DeleteMenu(m_shCtxMenu, _EncodeMenuID(iPos), MF_BYCOMMAND));
  784. }
  785. // if the context menu is opened then adjust the IDs
  786. _AdjustMenuIDs(m_shCtxMenu, _EncodeMenuID(iPos), -1);
  787. }
  788. }
  789. HRESULT CTrayNotify::_GetPrinter(LPCTSTR pszPrinter, LPBYTE *ppData, PDWORD pcReturned)
  790. {
  791. ASSERT(ppData);
  792. ASSERT(pcReturned);
  793. HRESULT hr = E_OUTOFMEMORY;
  794. int iTry = -1;
  795. DWORD cbNeeded = 0;
  796. DWORD cReturned = 0;
  797. CAutoPtrArray<BYTE> pData;
  798. BOOL bStatus = FALSE;
  799. for( ;; )
  800. {
  801. if( iTry++ >= ENUM_MAX_RETRY )
  802. {
  803. // max retry count reached. this is also
  804. // considered out of memory case
  805. pData = NULL;
  806. break;
  807. }
  808. // call bFolderEnumPrinters/bFolderGetPrinter...
  809. bStatus = pszPrinter ?
  810. bFolderGetPrinter(m_hFolder, pszPrinter, pData.GetPtrAs<PFOLDER_PRINTER_DATA>(), cbNeeded, &cbNeeded) :
  811. bFolderEnumPrinters(m_hFolder, pData.GetPtrAs<PFOLDER_PRINTER_DATA>(), cbNeeded, &cbNeeded, pcReturned);
  812. if( !bStatus && ERROR_INSUFFICIENT_BUFFER == GetLastError() && cbNeeded )
  813. {
  814. // buffer too small case
  815. pData = new BYTE[cbNeeded];
  816. if( pData )
  817. {
  818. continue;
  819. }
  820. else
  821. {
  822. SetLastError(ERROR_OUTOFMEMORY);
  823. break;
  824. }
  825. }
  826. break;
  827. }
  828. // setup the error code properly
  829. hr = bStatus ? S_OK : GetLastError() != ERROR_SUCCESS ? HRESULT_FROM_WIN32(GetLastError()) :
  830. !pData ? E_OUTOFMEMORY : E_FAIL;
  831. if( SUCCEEDED(hr) )
  832. {
  833. *ppData = pData.Detach();
  834. if( pszPrinter )
  835. {
  836. *pcReturned = 1;
  837. }
  838. }
  839. return hr;
  840. }
  841. void CTrayNotify::_CheckToUpdateUserJobs(PrinterInfo &pi, const MsgInfo &msg)
  842. {
  843. int iPos = -1;
  844. BOOL bJobFound = _FindUserJob(msg.jn.Id, pi, &iPos);
  845. // process DBG_JOBNOTIFY
  846. DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: pszPrinter="TSTR", szAuxName="TSTR", Field: %d, dwData: %d\n",
  847. DBGSTR(pi.szPrinter), DBGSTR(msg.szAuxName), msg.jn.Field, msg.jn.dwData));
  848. if( JOB_NOTIFY_FIELD_NOTIFY_NAME == msg.jn.Field )
  849. {
  850. // process JOB_NOTIFY_FIELD_NOTIFY_NAME here
  851. DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: JOB_NOTIFY_FIELD_NOTIFY_NAME, pszPrinter="TSTR"\n",
  852. DBGSTR(pi.szPrinter)));
  853. if( 0 == lstrcmp(msg.szAuxName, m_strUser) )
  854. {
  855. // a user job - check to insert
  856. if( !bJobFound )
  857. {
  858. JobInfo ji = {msg.jn.Id};
  859. if( _UpdateUserJob(pi, ji) && -1 != pi.pUserJobs->SortedInsert(ji) )
  860. {
  861. _RequestUpdate(UPDATE_REQUEST_JOB_ADD, pi.szPrinter);
  862. DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: job added, JobID=%d, pszPrinter="TSTR"\n",
  863. msg.jn.Id, DBGSTR(pi.szPrinter)));
  864. }
  865. }
  866. }
  867. else
  868. {
  869. // not a user job - check to delete
  870. if( bJobFound )
  871. {
  872. DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: job deleted, JobID=%d, pszPrinter="TSTR"\n",
  873. pi.pUserJobs->operator[](iPos).dwID, DBGSTR(pi.szPrinter)));
  874. pi.pUserJobs->Delete(iPos);
  875. _RequestUpdate(UPDATE_REQUEST_JOB_DELETE, pi.szPrinter);
  876. }
  877. }
  878. }
  879. if( bJobFound && JOB_NOTIFY_FIELD_STATUS == msg.jn.Field )
  880. {
  881. // process JOB_NOTIFY_FIELD_STATUS here
  882. DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: JOB_NOTIFY_FIELD_STATUS, Status=%x, pszPrinter="TSTR"\n",
  883. msg.jn.dwData, DBGSTR(pi.szPrinter)));
  884. // update the job status bits here, by saving the old status first
  885. JobInfo &ji = pi.pUserJobs->operator[](iPos);
  886. DWORD dwOldJobStatus = ji.dwStatus;
  887. ji.dwStatus = msg.jn.dwData;
  888. do
  889. {
  890. // dump the job status
  891. DBGMSG(DBG_JOBSTATUS, ("JOBSTATUS: old status=%x, new status=%x\n",
  892. dwOldJobStatus, ji.dwStatus));
  893. if( ji.dwStatus & JOB_STATUS_DELETED )
  894. {
  895. // the job status has the JOB_STATUS_DELETED bit up. delete the job.
  896. DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: job deleted, JobID=%x, pszPrinter="TSTR"\n",
  897. pi.pUserJobs->operator[](iPos).dwID, DBGSTR(pi.szPrinter)));
  898. pi.pUserJobs->Delete(iPos);
  899. _RequestUpdate(UPDATE_REQUEST_JOB_DELETE, pi.szPrinter);
  900. break; // skip everything
  901. }
  902. if( FALSE == m_bInRefresh && (0 == (dwOldJobStatus & JOB_STATUS_PRINTED)) &&
  903. (JOB_STATUS_PRINTED & ji.dwStatus) && (0 == (ji.dwStatus & JOB_ERROR_BITS)) )
  904. {
  905. // JOB_STATUS_PRINTED bit is up, the previous status nas no JOB_STATUS_PRINTED bit up
  906. // and there are no error bits up - consider the job had just printed successfully.
  907. DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: job completed with sucesss, JobID=%x, pszPrinter="TSTR"\n",
  908. pi.pUserJobs->operator[](iPos).dwID, DBGSTR(pi.szPrinter)));
  909. _JobPrinted(pi.szPrinter, ji);
  910. }
  911. if( FALSE == m_bInRefresh && (0 == (dwOldJobStatus & JOB_ERROR_BITS)) &&
  912. (ji.dwStatus & JOB_ERROR_BITS) )
  913. {
  914. // if the job goes from non-error state into an error state
  915. // then we assume the job has failed or a user intervention is
  916. // required. in both cases we show the job-failed balloon.
  917. DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: job failed to print, JobID=%x, pszPrinter="TSTR"\n",
  918. pi.pUserJobs->operator[](iPos).dwID, DBGSTR(pi.szPrinter)));
  919. _JobFailed(pi.szPrinter, ji);
  920. }
  921. // just check to update the job status
  922. if( dwOldJobStatus != ji.dwStatus )
  923. {
  924. ji.dwStatus = msg.jn.dwData;
  925. _RequestUpdate(UPDATE_REQUEST_JOB_STATUS, pi.szPrinter);
  926. DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: status updated, Status=%x, pszPrinter="TSTR"\n",
  927. ji.dwStatus, DBGSTR(pi.szPrinter)));
  928. }
  929. }
  930. while( FALSE );
  931. }
  932. if( bJobFound && JOB_NOTIFY_FIELD_TOTAL_PAGES == msg.jn.Field &&
  933. pi.pUserJobs->operator[](iPos).dwTotalPages != msg.jn.dwData )
  934. {
  935. // save the number of total pages, so we can display this info
  936. // later in the balloon when the job completes successfully.
  937. pi.pUserJobs->operator[](iPos).dwTotalPages = msg.jn.dwData;
  938. DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: total pages updated, TotalPages=%x, pszPrinter="TSTR"\n",
  939. pi.pUserJobs->operator[](iPos).dwTotalPages, DBGSTR(pi.szPrinter)));
  940. }
  941. }
  942. void CTrayNotify::_CheckUserJobs(int *piUserJobs, BOOL *pbUserJobPrinting, BOOL *pbUserInterventionReq)
  943. {
  944. ASSERT(piUserJobs);
  945. ASSERT(pbUserJobPrinting);
  946. ASSERT(pbUserInterventionReq);
  947. *piUserJobs = 0;
  948. *pbUserJobPrinting = *pbUserInterventionReq = FALSE;
  949. // walk through the watch list to see...
  950. int i, iCount = m_arrWatchList.Count();
  951. for( i=0; i<iCount; i++ )
  952. {
  953. m_arrWatchList[i].bUserInterventionReq = FALSE;
  954. CJobInfoArray &arrJobs = *m_arrWatchList[i].pUserJobs;
  955. int j, iJobCount = arrJobs.Count();
  956. for( j=0; j<iJobCount; j++ )
  957. {
  958. DWORD dwStatus = arrJobs[j].dwStatus;
  959. if( 0 == (dwStatus & JOB_IGNORE_BITS) )
  960. {
  961. (*piUserJobs)++;
  962. }
  963. if( dwStatus & JOB_ERROR_BITS )
  964. {
  965. // these job status bits are considered an error
  966. *pbUserInterventionReq = m_arrWatchList[i].bUserInterventionReq = TRUE;
  967. }
  968. if( dwStatus & JOB_STATUS_PRINTING )
  969. {
  970. // check if the job is printing now
  971. *pbUserJobPrinting = TRUE;
  972. }
  973. }
  974. }
  975. }
  976. void CTrayNotify::_CheckToUpdateTray(BOOL bForceUpdate, const BalloonInfo *pBalloon, BOOL bForceDelete)
  977. {
  978. int iUserJobs;
  979. BOOL bUserJobPrinting, bUserInterventionReq;
  980. _CheckUserJobs(&iUserJobs, &bUserJobPrinting, &bUserInterventionReq);
  981. DBGMSG(DBG_TRAYUPDATE, ("TRAYUPDATE: _CheckToUpdateTray called, "
  982. "iUserJobs=%d, bUserJobPrinting=%d, bUserInterventionReq=%d\n",
  983. iUserJobs, bUserJobPrinting, bUserInterventionReq));
  984. UINT uTrayIcon = g_arrIcons[ ((iUserJobs != 0) || bUserJobPrinting || (0 != m_uBalloonID) || (0 != m_uBalloonsCount)) + bUserInterventionReq ];
  985. NOTIFYICONDATA nid = { sizeof(nid), m_hwnd, ICON_ID, NIF_MESSAGE, WM_PRINTTRAY_ICON_NOTIFY, NULL };
  986. // check to delete first
  987. if( bForceDelete || (uTrayIcon == g_arrIcons[0] && m_uTrayIcon != g_arrIcons[0]) )
  988. {
  989. if( m_bIconShown )
  990. {
  991. Shell_NotifyIcon(NIM_DELETE, &nid);
  992. // reset the shutdown timer
  993. Touch();
  994. DBGMSG(DBG_NTFYICON, ("NTFYICON: icon deleted.\n"));
  995. }
  996. m_uTrayIcon = g_arrIcons[0];
  997. m_cxSmIcon = m_cySmIcon = m_iUserJobs = 0;
  998. m_bIconShown = FALSE;
  999. }
  1000. else
  1001. {
  1002. // check to add/modify the icon
  1003. if( uTrayIcon != g_arrIcons[0] )
  1004. {
  1005. BOOL bPlayBalloonSound = FALSE;
  1006. BOOL bBalloonRequested = FALSE;
  1007. DWORD dwMsg = (m_uTrayIcon == g_arrIcons[0]) ? NIM_ADD : NIM_MODIFY;
  1008. int cxSmIcon = GetSystemMetrics(SM_CXSMICON);
  1009. int cySmIcon = GetSystemMetrics(SM_CYSMICON);
  1010. // check to sync the icon
  1011. if( uTrayIcon != m_uTrayIcon || cxSmIcon != m_cxSmIcon || m_cySmIcon != cySmIcon )
  1012. {
  1013. m_cxSmIcon = cxSmIcon;
  1014. m_cySmIcon = cySmIcon;
  1015. m_uTrayIcon = uTrayIcon;
  1016. m_shIconShown = (HICON)LoadImage(ghInst, MAKEINTRESOURCE(m_uTrayIcon), IMAGE_ICON, m_cxSmIcon, m_cySmIcon, 0);
  1017. nid.uFlags |= NIF_ICON;
  1018. nid.hIcon = m_shIconShown;
  1019. }
  1020. // check to sync the tip (if the ctx menu is not open)
  1021. if( bForceUpdate || m_iUserJobs != iUserJobs )
  1022. {
  1023. TString strTemplate, strTooltip;
  1024. m_iUserJobs = iUserJobs;
  1025. if( strTemplate.bLoadString(ghInst, IDS_TOOLTIP_TRAY) &&
  1026. strTooltip.bFormat(strTemplate, m_iUserJobs, static_cast<LPCTSTR>(m_strUser)) )
  1027. {
  1028. nid.uFlags |= NIF_TIP;
  1029. if( m_shCtxMenu )
  1030. {
  1031. // clear the tip
  1032. nid.szTip[0] = 0;
  1033. }
  1034. else
  1035. {
  1036. // update the tip
  1037. lstrcpyn(nid.szTip, strTooltip, COUNTOF(nid.szTip));
  1038. }
  1039. }
  1040. }
  1041. // check to sync the balloon (if the ctx menu is not open)
  1042. if( bForceUpdate || pBalloon )
  1043. {
  1044. nid.uFlags |= NIF_INFO;
  1045. if( m_shCtxMenu || NULL == pBalloon )
  1046. {
  1047. // hide the ballon
  1048. nid.szInfoTitle[0] = 0;
  1049. nid.szInfo[0] = 0;
  1050. }
  1051. else
  1052. {
  1053. // show up the balloon
  1054. nid.dwInfoFlags = pBalloon->dwFlags;
  1055. nid.uTimeout = pBalloon->uTimeout;
  1056. lstrcpyn(nid.szInfoTitle, pBalloon->pszCaption, COUNTOF(nid.szInfoTitle));
  1057. lstrcpyn(nid.szInfo, pBalloon->pszText, COUNTOF(nid.szInfo));
  1058. if( pBalloon->pszSound && pBalloon->pszSound[0] )
  1059. {
  1060. nid.dwInfoFlags |= NIIF_NOSOUND;
  1061. bPlayBalloonSound = TRUE;
  1062. }
  1063. bBalloonRequested = TRUE;
  1064. }
  1065. }
  1066. if( bForceUpdate || !m_bIconShown || nid.uFlags != NIF_MESSAGE )
  1067. {
  1068. // sync icon data
  1069. Shell_NotifyIcon(dwMsg, &nid);
  1070. if( bPlayBalloonSound )
  1071. {
  1072. PlaySound(pBalloon->pszSound, NULL,
  1073. SND_ALIAS | SND_APPLICATION | SND_ASYNC | SND_NODEFAULT | SND_NOSTOP);
  1074. }
  1075. if( bBalloonRequested )
  1076. {
  1077. m_uBalloonsCount++;
  1078. }
  1079. if( NIM_ADD == dwMsg )
  1080. {
  1081. DBGMSG(DBG_NTFYICON, ("NTFYICON: icon added.\n"));
  1082. }
  1083. else
  1084. {
  1085. DBGMSG(DBG_NTFYICON, ("NTFYICON: icon modified.\n"));
  1086. }
  1087. if( NIM_ADD == dwMsg )
  1088. {
  1089. // make sure we use the correct version
  1090. nid.uVersion = NOTIFYICON_VERSION;
  1091. Shell_NotifyIcon(NIM_SETVERSION, &nid);
  1092. DBGMSG(DBG_NTFYICON, ("NTFYICON: icon set version.\n"));
  1093. }
  1094. }
  1095. m_bIconShown = TRUE;
  1096. }
  1097. }
  1098. }
  1099. void CTrayNotify::_ShowMenu()
  1100. {
  1101. // no need to synchronize as m_arrWatchList size can be
  1102. // changed only in the message loop
  1103. int i, iCount = m_arrWatchList.Count();
  1104. if( iCount )
  1105. {
  1106. // when loaded from the resource, the context menu contains only one
  1107. if( m_shCtxMenu )
  1108. {
  1109. EndMenu();
  1110. }
  1111. // command - "Open Active Printers" corresponding to IDM_TRAYNOTIFY_DEFAULT
  1112. m_shCtxMenu = ShellServices::LoadPopupMenu(ghInst, POPUP_TRAYNOTIFY_PRINTERS);
  1113. if( m_shCtxMenu )
  1114. {
  1115. // build the context menu here
  1116. InsertMenu(m_shCtxMenu, (UINT)-1, MF_SEPARATOR|MF_BYPOSITION, 0, NULL);
  1117. for( i=0; i<iCount; i++ )
  1118. {
  1119. if( m_arrWatchList[i].pUserJobs->Count() )
  1120. {
  1121. _AddPrinterToCtxMenu(m_shCtxMenu, i);
  1122. }
  1123. }
  1124. // show up the context menu
  1125. if( GetMenuItemCount(m_shCtxMenu) )
  1126. {
  1127. POINT pt;
  1128. GetCursorPos(&pt);
  1129. SetMenuDefaultItem(m_shCtxMenu, IDM_TRAYNOTIFY_DEFAULT, MF_BYCOMMAND);
  1130. SetForegroundWindow(m_hwnd);
  1131. // now after m_shCtxMenu is not NULL, disable the tooltips,
  1132. // while the menu is open
  1133. _CheckToUpdateTray(TRUE, NULL);
  1134. // show up the context menu here...
  1135. // popup menus follows it's window owner when it comes to mirroring,
  1136. // so we should pass a an owner window which is mirrored.
  1137. int idCmd = TrackPopupMenu(m_shCtxMenu,
  1138. TPM_NONOTIFY|TPM_RETURNCMD|TPM_RIGHTBUTTON|TPM_HORNEGANIMATION,
  1139. pt.x, pt.y, 0, m_hwnd, NULL);
  1140. if( idCmd != 0 )
  1141. {
  1142. switch(idCmd)
  1143. {
  1144. case IDM_TRAYNOTIFY_DEFAULT:
  1145. {
  1146. // open the queues of all active printers
  1147. _ShowAllActivePrinters();
  1148. }
  1149. break;
  1150. case IDM_TRAYNOTIFY_PRNFOLDER:
  1151. {
  1152. // open the printer's folder
  1153. _ShowPrnFolder();
  1154. }
  1155. break;
  1156. case IDM_TRAYNOTIFY_REFRESH:
  1157. {
  1158. // just refresh the whole thing...
  1159. MsgInfo msg = { msgTypePrnNotify, kFolderUpdateAll };
  1160. _PostPrivateMsg(msg);
  1161. }
  1162. break;
  1163. default:
  1164. {
  1165. // open the selected printer
  1166. vQueueCreate(NULL, m_arrWatchList[_DecodeMenuID(idCmd)].szPrinter,
  1167. SW_SHOWNORMAL, static_cast<LPARAM>(FALSE));
  1168. }
  1169. break;
  1170. }
  1171. }
  1172. }
  1173. }
  1174. // destroy the menu
  1175. m_shCtxMenu = NULL;
  1176. // re-enable the tooltips after the menu is closed
  1177. _CheckToUpdateTray(TRUE, NULL);
  1178. }
  1179. }
  1180. void CTrayNotify::_ShowPrnFolder() const
  1181. {
  1182. // find the printer's folder PIDL
  1183. CAutoPtrPIDL pidlPrinters;
  1184. HRESULT hr = SHGetSpecialFolderLocation(NULL, CSIDL_PRINTERS, &pidlPrinters);
  1185. if( SUCCEEDED(hr) )
  1186. {
  1187. // invoke ShellExecuteEx on that PIDL
  1188. SHELLEXECUTEINFO seInfo;
  1189. memset(&seInfo, 0, sizeof(seInfo));
  1190. seInfo.cbSize = sizeof(seInfo);
  1191. seInfo.fMask = SEE_MASK_IDLIST;
  1192. seInfo.hwnd = m_hwnd;
  1193. seInfo.nShow = SW_SHOWDEFAULT;
  1194. seInfo.lpIDList = pidlPrinters;
  1195. ShellExecuteEx(&seInfo);
  1196. }
  1197. }
  1198. void CTrayNotify::_ShowBalloon(
  1199. UINT uBalloonID,
  1200. LPCTSTR pszCaption,
  1201. LPCTSTR pszText,
  1202. LPCTSTR pszSound,
  1203. DWORD dwFlags,
  1204. UINT uTimeout)
  1205. {
  1206. // just show up the balloon...
  1207. m_uBalloonID = uBalloonID;
  1208. BalloonInfo bi = {m_uBalloonID, pszCaption, pszText, pszSound, dwFlags, uTimeout};
  1209. _CheckToUpdateTray(FALSE, &bi);
  1210. }
  1211. void CTrayNotify::_JobFailed(LPCTSTR pszPrinter, JobInfo &ji)
  1212. {
  1213. ASSERT(pszPrinter);
  1214. // since the baloon text length is limited to 255 characters we need to abbreviate the printer name
  1215. // and the document name if they are too long by adding ellipses at the end.
  1216. TString strPrinter, strDocName;
  1217. if( SUCCEEDED(AbbreviateText(pszPrinter, MAX_PRINTER_DISPLAYNAME, &strPrinter)) &&
  1218. SUCCEEDED(AbbreviateText(ji.szDocName, MAX_DOC_DISPLAYNAME, &strDocName)) )
  1219. {
  1220. TString strTimeSubmitted, strTemplate, strTitle, strText;
  1221. TStatusB bStatus;
  1222. bStatus DBGCHK = (ji.dwStatus & JOB_STATUS_PAPEROUT) ?
  1223. strTitle.bLoadString(ghInst, IDS_BALLOON_TITLE_JOB_FAILED_OOP) :
  1224. strTitle.bLoadString(ghInst, IDS_BALLOON_TITLE_JOB_FAILED);
  1225. if( bStatus &&
  1226. _TimeToString(ji.timeSubmitted, &strTimeSubmitted) &&
  1227. strTemplate.bLoadString(ghInst, IDS_BALLOON_TEXT_JOB_FAILED) &&
  1228. strText.bFormat(strTemplate,
  1229. static_cast<LPCTSTR>(strDocName),
  1230. static_cast<LPCTSTR>(strPrinter),
  1231. static_cast<LPCTSTR>(strTimeSubmitted)) )
  1232. {
  1233. _ShowBalloon(BALLOON_ID_JOB_FAILED, strTitle, strText, NULL, NIIF_WARNING);
  1234. m_strLastBalloonPrinter.bUpdate(pszPrinter);
  1235. }
  1236. }
  1237. }
  1238. void CTrayNotify::_JobPrinted(LPCTSTR pszPrinter, JobInfo &ji)
  1239. {
  1240. ASSERT(pszPrinter);
  1241. // since the baloon text length is limited to 255 characters we need to abbreviate the printer name
  1242. // and the document name if they are too long, by adding ellipses at the end.
  1243. TString strPrinter, strDocName;
  1244. if( SUCCEEDED(AbbreviateText(pszPrinter, MAX_PRINTER_DISPLAYNAME, &strPrinter)) &&
  1245. SUCCEEDED(AbbreviateText(ji.szDocName, MAX_DOC_DISPLAYNAME, &strDocName)) )
  1246. {
  1247. BOOL bCanNotify;
  1248. TString strTimeSubmitted, strTemplate, strTitle, strText;
  1249. // total pages can be zero when a downlevel document is printed (directly to the port) in
  1250. // this case StartDoc/EndDoc are not called and the spooler doesn't know the total pages of
  1251. // the document. in this case just display the balloon without total pages info.
  1252. UINT uTextID = ji.dwTotalPages ? IDS_BALLOON_TEXT_JOB_PRINTED : IDS_BALLOON_TEXT_JOB_PRINTED_NOPAGES;
  1253. if( SUCCEEDED(_CanNotify(pszPrinter, &bCanNotify)) && bCanNotify &&
  1254. _TimeToString(ji.timeSubmitted, &strTimeSubmitted) &&
  1255. strTitle.bLoadString(ghInst, IDS_BALLOON_TITLE_JOB_PRINTED) &&
  1256. strTemplate.bLoadString(ghInst, uTextID) &&
  1257. strText.bFormat(strTemplate,
  1258. static_cast<LPCTSTR>(strDocName),
  1259. static_cast<LPCTSTR>(strPrinter),
  1260. static_cast<LPCTSTR>(strTimeSubmitted),
  1261. ji.dwTotalPages) )
  1262. {
  1263. _ShowBalloon(BALLOON_ID_JOB_PRINTED, strTitle, strText, gszBalloonSoundPrintComplete, NIIF_INFO);
  1264. m_strLastBalloonPrinter.bUpdate(pszPrinter);
  1265. }
  1266. }
  1267. }
  1268. BOOL CTrayNotify::_TimeToString(const SYSTEMTIME &time, TString *pTime) const
  1269. {
  1270. ASSERT(pTime);
  1271. BOOL bReturn = FALSE;
  1272. TCHAR szText[255];
  1273. SYSTEMTIME timeLocal;
  1274. if( SystemTimeToTzSpecificLocalTime(NULL, const_cast<LPSYSTEMTIME>(&time), &timeLocal) &&
  1275. GetTimeFormat(LOCALE_USER_DEFAULT, 0, &timeLocal, NULL, szText, COUNTOF(szText)) )
  1276. {
  1277. pTime->bCat(szText);
  1278. pTime->bCat(TEXT(" "));
  1279. if( GetDateFormat(LOCALE_USER_DEFAULT, dwDateFormatFlags(m_hwnd),
  1280. &timeLocal, NULL, szText, COUNTOF(szText)) )
  1281. {
  1282. pTime->bCat(szText);
  1283. bReturn = TRUE;
  1284. }
  1285. }
  1286. return bReturn;
  1287. }
  1288. void CTrayNotify::_DoRefresh(CPrintNotify *pListener)
  1289. {
  1290. m_bInRefresh = TRUE;
  1291. pListener->Refresh(this, _RefreshCallback);
  1292. m_bInRefresh = FALSE;
  1293. // check to update the tray
  1294. _CheckToUpdateTray(FALSE, NULL);
  1295. }
  1296. void CTrayNotify::_PostPrivateMsg(const MsgInfo &msg)
  1297. {
  1298. HANDLE hItem;
  1299. if( SUCCEEDED(m_heapMsgCache.Alloc(msg, &hItem)) )
  1300. {
  1301. // request a balloon to show up...
  1302. PostMessage(m_hwnd, WM_PRINTTRAY_PRIVATE_MSG, reinterpret_cast<LPARAM>(hItem), 0);
  1303. }
  1304. }
  1305. void CTrayNotify::_RequestUpdate(int iEvent, LPCTSTR pszPrinter, LPCTSTR pszAux)
  1306. {
  1307. // check to update the context menu if shown
  1308. int i = -1;
  1309. switch( iEvent )
  1310. {
  1311. case UPDATE_REQUEST_JOB_ADD:
  1312. {
  1313. // check to add to the context menu
  1314. MENUITEMINFO mii = { sizeof(mii), MIIM_ID, 0 };
  1315. if( m_shCtxMenu && _FindPrinter(pszPrinter, &i) && 0 != m_arrWatchList[i].pUserJobs->Count() &&
  1316. FALSE == GetMenuItemInfo(m_shCtxMenu, _EncodeMenuID(i), FALSE, &mii) )
  1317. {
  1318. // add this printer to the context menu
  1319. _AddPrinterToCtxMenu(m_shCtxMenu, i);
  1320. }
  1321. }
  1322. break;
  1323. case UPDATE_REQUEST_JOB_DELETE:
  1324. {
  1325. // check this printer to be deleted later
  1326. MsgInfo msg = { msgTypePrnCheckDelete, kFolderNone };
  1327. lstrcpyn(msg.szPrinter, pszPrinter, COUNTOF(msg.szPrinter));
  1328. _PostPrivateMsg(msg);
  1329. // check to delete from the context menu
  1330. MENUITEMINFO mii = { sizeof(mii), MIIM_ID, 0 };
  1331. if( m_shCtxMenu && _FindPrinter(pszPrinter, &i) && 0 == m_arrWatchList[i].pUserJobs->Count() &&
  1332. TRUE == GetMenuItemInfo(m_shCtxMenu, _EncodeMenuID(i), FALSE, &mii) )
  1333. {
  1334. // delete this printer from the context menu
  1335. VERIFY(DeleteMenu(m_shCtxMenu, _EncodeMenuID(i), MF_BYCOMMAND));
  1336. }
  1337. }
  1338. break;
  1339. default:
  1340. break;
  1341. }
  1342. // check to update tray if not in refresh
  1343. if( !m_bInRefresh )
  1344. {
  1345. _CheckToUpdateTray(FALSE, NULL);
  1346. }
  1347. }
  1348. void CTrayNotify::_BalloonClicked(UINT uBalloonID) const
  1349. {
  1350. switch( uBalloonID )
  1351. {
  1352. case BALLOON_ID_JOB_FAILED:
  1353. vQueueCreate(NULL, m_strLastBalloonPrinter, SW_SHOWNORMAL, static_cast<LPARAM>(FALSE));
  1354. break;
  1355. case BALLOON_ID_JOB_PRINTED:
  1356. // do nothing
  1357. break;
  1358. case BALLOON_ID_PRN_CREATED:
  1359. _ShowPrnFolder();
  1360. break;
  1361. default:
  1362. ASSERT(FALSE);
  1363. break;
  1364. }
  1365. }
  1366. BOOL CTrayNotify::_UpdateUserJob(PrinterInfo &pi, JobInfo &ji)
  1367. {
  1368. // get job info at level 1
  1369. BOOL bReturn = FALSE;
  1370. DWORD cbJob = 0;
  1371. CAutoPtrSpl<JOB_INFO_2> spJob;
  1372. if( VDataRefresh::bGetJob(pi.pListener->GetPrinterHandle(), ji.dwID, 2, spJob.GetPPV(), &cbJob) )
  1373. {
  1374. // the only thing we care about is the document name & job status
  1375. ji.dwStatus = spJob->Status;
  1376. lstrcpyn(ji.szDocName, spJob->pDocument, COUNTOF(ji.szDocName));
  1377. ji.timeSubmitted = spJob->Submitted;
  1378. ji.dwTotalPages = spJob->TotalPages;
  1379. bReturn = TRUE;
  1380. }
  1381. return bReturn;
  1382. }
  1383. void CTrayNotify::_ShowAllActivePrinters() const
  1384. {
  1385. // open all the active printers
  1386. int i, iCount = m_arrWatchList.Count();
  1387. for( i=0; i<iCount; i++ )
  1388. {
  1389. if( m_arrWatchList[i].pUserJobs->Count() )
  1390. {
  1391. vQueueCreate(NULL, m_arrWatchList[i].szPrinter, SW_SHOWNORMAL, static_cast<LPARAM>(FALSE));
  1392. }
  1393. }
  1394. }
  1395. void CTrayNotify::_AddPrinterToCtxMenu(HMENU hMenu, int i)
  1396. {
  1397. // build the context menu here
  1398. TString strPrinter;
  1399. MENUITEMINFO mii = { sizeof(mii), MIIM_TYPE|MIIM_ID, MF_STRING };
  1400. if( m_arrWatchList[i].bUserInterventionReq )
  1401. {
  1402. // this printer is in error state, add an (error) suffix
  1403. TString strTemplate;
  1404. strTemplate.bLoadString(ghInst, IDS_TRAY_TEXT_ERROR);
  1405. strPrinter.bFormat(strTemplate, m_arrWatchList[i].szPrinter);
  1406. }
  1407. else
  1408. {
  1409. strPrinter.bUpdate(m_arrWatchList[i].szPrinter);
  1410. }
  1411. mii.wID = _EncodeMenuID(i);
  1412. mii.dwTypeData = const_cast<LPTSTR>(static_cast<LPCTSTR>(strPrinter));
  1413. mii.cch = lstrlen(mii.dwTypeData);
  1414. InsertMenuItem(hMenu, (UINT)-1, MF_BYPOSITION, &mii);
  1415. }
  1416. HRESULT CTrayNotify::_ProcessJobNotifications(LPCTSTR pszPrinter, DWORD dwChange,
  1417. const PRINTER_NOTIFY_INFO *pInfo, PrinterInfo *ppi)
  1418. {
  1419. if( pInfo && (PRINTER_NOTIFY_INFO_DISCARDED & pInfo->Flags) )
  1420. {
  1421. MsgInfo msg = { msgTypeJobNotifyLost, kFolderNone };
  1422. lstrcpyn(msg.szPrinter, pszPrinter, COUNTOF(msg.szPrinter));
  1423. if( ppi )
  1424. {
  1425. // called from the foreground thread: sync processing
  1426. _CheckToUpdateUserJobs(*ppi, msg);
  1427. }
  1428. else
  1429. {
  1430. // called from the background threads: async processing
  1431. _PostPrivateMsg(msg);
  1432. }
  1433. }
  1434. else
  1435. {
  1436. // regular job notifications have arrived
  1437. if( pInfo && pInfo->Count )
  1438. {
  1439. MsgInfo msg = { msgTypeJobNotify, kFolderNone };
  1440. lstrcpyn(msg.szPrinter, pszPrinter, COUNTOF(msg.szPrinter));
  1441. for( DWORD i=0; i<pInfo->Count; i++ )
  1442. {
  1443. if( JOB_NOTIFY_TYPE != pInfo->aData[i].Type )
  1444. {
  1445. // we only care about job notifications here
  1446. continue;
  1447. }
  1448. msg.jn.Type = pInfo->aData[i].Type;
  1449. msg.jn.Field = pInfo->aData[i].Field;
  1450. msg.jn.Id = pInfo->aData[i].Id;
  1451. msg.jn.dwData = pInfo->aData[i].NotifyData.adwData[0];
  1452. if( JOB_NOTIFY_FIELD_NOTIFY_NAME == pInfo->aData[i].Field )
  1453. {
  1454. // need to copy pBuf into the aux buffer (szAuxName)
  1455. lstrcpyn(msg.szAuxName, reinterpret_cast<LPCTSTR>(pInfo->aData[i].NotifyData.Data.pBuf),
  1456. COUNTOF(msg.szAuxName));
  1457. }
  1458. if( ppi )
  1459. {
  1460. // called from the foreground thread: sync processing
  1461. _CheckToUpdateUserJobs(*ppi, msg);
  1462. }
  1463. else
  1464. {
  1465. // called from the background threads: async processing
  1466. _PostPrivateMsg(msg);
  1467. }
  1468. }
  1469. }
  1470. }
  1471. return S_OK;
  1472. }
  1473. void CTrayNotify::_ResetAll()
  1474. {
  1475. // close the context menu
  1476. m_shCtxMenu = NULL;
  1477. // cleanup the watch list (unregister all job notifications listeners)
  1478. while( m_arrWatchList.Count() )
  1479. {
  1480. _Delete(0);
  1481. }
  1482. // flush the message queue here...
  1483. MSG msg;
  1484. while( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) )
  1485. {
  1486. if( WM_PRINTTRAY_PRIVATE_MSG == msg.message )
  1487. {
  1488. // just free up the private message
  1489. VERIFY(SUCCEEDED(m_heapMsgCache.Free(reinterpret_cast<HANDLE>(msg.wParam))));
  1490. }
  1491. }
  1492. // update the tray
  1493. _CheckToUpdateTray(FALSE, NULL);
  1494. }
  1495. BOOL CTrayNotify::_IsFaxPrinter(const FOLDER_PRINTER_DATA &data)
  1496. {
  1497. return ((0 == lstrcmp(data.pDriverName, FAX_DRIVER_NAME)) ||
  1498. (data.Attributes & PRINTER_ATTRIBUTE_FAX));
  1499. }
  1500. HRESULT CTrayNotify::_CanNotify(LPCTSTR pszPrinterName, BOOL *pbCanNotify)
  1501. {
  1502. HRESULT hr = E_INVALIDARG;
  1503. BOOL bIsNetworkPrinter;
  1504. LPCTSTR pszServer;
  1505. LPCTSTR pszPrinter;
  1506. TCHAR szScratch[kStrMax+kPrinterBufMax];
  1507. UINT nSize = COUNTOF(szScratch);
  1508. if( pszPrinterName && *pszPrinterName && pbCanNotify )
  1509. {
  1510. //
  1511. // Split the printer name into its components.
  1512. //
  1513. vPrinterSplitFullName(szScratch, pszPrinterName, &pszServer, &pszPrinter);
  1514. bIsNetworkPrinter = bIsRemote(pszServer);
  1515. // set default value
  1516. *pbCanNotify = bIsNetworkPrinter ? TRUE : FALSE;
  1517. TPersist NotifyUser(gszPrinterPositions, TPersist::kCreate|TPersist::kRead);
  1518. if( NotifyUser.bValid() )
  1519. {
  1520. if( NotifyUser.bRead(bIsNetworkPrinter ? gszNetworkPrintNotification : gszLocalPrintNotification, *pbCanNotify) )
  1521. {
  1522. hr = S_OK;
  1523. }
  1524. else
  1525. {
  1526. hr = CreateHRFromWin32();
  1527. }
  1528. }
  1529. else
  1530. {
  1531. hr = CreateHRFromWin32();
  1532. }
  1533. }
  1534. return hr;
  1535. }
  1536. LRESULT CTrayNotify::_ProcessUserMsg(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  1537. {
  1538. switch( uMsg )
  1539. {
  1540. case WM_PRINTTRAY_PRIVATE_MSG:
  1541. {
  1542. MsgInfo *pmsg = NULL;
  1543. HANDLE hItem = reinterpret_cast<HANDLE>(wParam);
  1544. VERIFY(SUCCEEDED(m_heapMsgCache.GetItem(hItem, &pmsg)));
  1545. switch( pmsg->iType )
  1546. {
  1547. case msgTypePrnNotify:
  1548. {
  1549. _ProcessPrnNotify(pmsg->NotifyType, pmsg->szPrinter, pmsg->szAuxName);
  1550. }
  1551. break;
  1552. case msgTypePrnCheckDelete:
  1553. {
  1554. int iPos = -1;
  1555. if( _FindPrinter(pmsg->szPrinter, &iPos) && 0 == m_arrWatchList[iPos].pUserJobs->Count() )
  1556. {
  1557. // this printer has no active user jobs, so delete it.
  1558. _Delete(iPos);
  1559. _RequestUpdate(UPDATE_REQUEST_PRN_DELETE, pmsg->szPrinter);
  1560. }
  1561. }
  1562. break;
  1563. case msgTypeJobNotify:
  1564. {
  1565. int iPos = -1;
  1566. if( _FindPrinter(pmsg->szPrinter, &iPos) )
  1567. {
  1568. _CheckToUpdateUserJobs(m_arrWatchList[iPos], *pmsg);
  1569. }
  1570. }
  1571. break;
  1572. case msgTypeJobNotifyLost:
  1573. {
  1574. int iPos = -1;
  1575. if( _FindPrinter(pmsg->szPrinter, &iPos) )
  1576. {
  1577. PrinterInfo &pi = m_arrWatchList[iPos];
  1578. // do a full refresh here
  1579. pi.dwStatus = 0;
  1580. pi.pUserJobs->DeleteAll();
  1581. _RequestUpdate(UPDATE_REQUEST_JOB_DELETE, pi.szPrinter);
  1582. // update all
  1583. _DoRefresh(m_arrWatchList[iPos].pListener);
  1584. }
  1585. }
  1586. break;
  1587. default:
  1588. ASSERT(FALSE);
  1589. break;
  1590. }
  1591. // free up the message
  1592. VERIFY(SUCCEEDED(m_heapMsgCache.Free(hItem)));
  1593. }
  1594. break;
  1595. case WM_PRINTTRAY_ICON_NOTIFY:
  1596. {
  1597. // the real uMsg is in lParam
  1598. switch( lParam )
  1599. {
  1600. case WM_CONTEXTMENU:
  1601. _ShowMenu();
  1602. break;
  1603. case WM_LBUTTONDBLCLK:
  1604. _ShowAllActivePrinters();
  1605. break;
  1606. case NIN_BALLOONUSERCLICK:
  1607. case NIN_BALLOONTIMEOUT:
  1608. {
  1609. m_uBalloonsCount--;
  1610. if( NIN_BALLOONUSERCLICK == lParam && m_uBalloonID )
  1611. {
  1612. _BalloonClicked(m_uBalloonID);
  1613. }
  1614. if( 0 == m_uBalloonsCount )
  1615. {
  1616. m_uBalloonID = 0;
  1617. m_strLastBalloonPrinter.bUpdate(NULL);
  1618. _CheckToUpdateTray(FALSE, NULL);
  1619. }
  1620. }
  1621. break;
  1622. default:
  1623. break;
  1624. }
  1625. }
  1626. break;
  1627. case WM_WININICHANGE:
  1628. {
  1629. // check to re-do the icon
  1630. _CheckToUpdateTray(FALSE, NULL);
  1631. }
  1632. break;
  1633. default:
  1634. break;
  1635. }
  1636. return 0;
  1637. }
  1638. DWORD WINAPI CTrayNotify::_ThreadProc_MsgLoop(LPVOID lpParameter)
  1639. {
  1640. CTrayNotify *pFolderNotify = (CTrayNotify *)lpParameter;
  1641. if( pFolderNotify )
  1642. {
  1643. pFolderNotify->_ThreadProc();
  1644. return EXIT_SUCCESS;
  1645. }
  1646. return EXIT_FAILURE;
  1647. }
  1648. // the callback called on refresh
  1649. HRESULT WINAPI CTrayNotify::_RefreshCallback(
  1650. LPVOID lpCookie, ULONG_PTR uCookie, DWORD dwChange, const PRINTER_NOTIFY_INFO *pInfo)
  1651. {
  1652. int iPos;
  1653. HRESULT hr = E_INVALIDARG;
  1654. CTrayNotify *pThis = reinterpret_cast<CTrayNotify*>(lpCookie);
  1655. CPrintNotify *pListener = reinterpret_cast<CPrintNotify*>(uCookie);
  1656. if( pThis && pListener && pThis->_FindPrinter(pListener->GetPrinter(), &iPos) )
  1657. {
  1658. hr = pThis->_ProcessJobNotifications(
  1659. pListener->GetPrinter(), dwChange, pInfo, &pThis->m_arrWatchList[iPos]);
  1660. }
  1661. return hr;
  1662. }
  1663. //////////////////////////////////
  1664. // global & exported functions
  1665. //
  1666. static CTrayNotify *gpTrayNotify = NULL;
  1667. extern "C" {
  1668. BOOL WINAPI PrintNotifyTray_Init()
  1669. {
  1670. ASSERT(gpTrayLock);
  1671. // synchonize this with gpTrayLock
  1672. CCSLock::Locker lock(*gpTrayLock);
  1673. BOOL bReturn = FALSE;
  1674. if( NULL == gpTrayNotify )
  1675. {
  1676. if( SUCCEEDED(CTrayNotify_CreateInstance(&gpTrayNotify)) )
  1677. {
  1678. bReturn = gpTrayNotify->Initialize();
  1679. if( !bReturn )
  1680. {
  1681. gpTrayNotify->Release();
  1682. gpTrayNotify = NULL;
  1683. }
  1684. else
  1685. {
  1686. DBGMSG(DBG_INITDONE, ("INITDONE: PrintTray - start listen! \n"));
  1687. }
  1688. }
  1689. }
  1690. else
  1691. {
  1692. // reset the shutdown timer
  1693. gpTrayNotify->Touch();
  1694. }
  1695. return bReturn;
  1696. }
  1697. BOOL WINAPI PrintNotifyTray_Exit()
  1698. {
  1699. ASSERT(gpTrayLock);
  1700. // synchonize this with gpTrayLock
  1701. CCSLock::Locker lock(*gpTrayLock);
  1702. BOOL bReturn = FALSE;
  1703. if( gpTrayNotify )
  1704. {
  1705. bReturn = gpTrayNotify->Shutdown();
  1706. gpTrayNotify->Release();
  1707. gpTrayNotify = NULL;
  1708. DBGMSG(DBG_INITDONE, ("INITDONE: PrintTray - stop listen! \n"));
  1709. }
  1710. return bReturn;
  1711. }
  1712. BOOL WINAPI PrintNotifyTray_SelfShutdown()
  1713. {
  1714. ASSERT(gpTrayLock);
  1715. BOOL bReturn = FALSE;
  1716. CTrayNotify *pTrayNotify = NULL;
  1717. {
  1718. // synchonize this with gpTrayLock
  1719. CCSLock::Locker lock(*gpTrayLock);
  1720. if( gpTrayNotify )
  1721. {
  1722. if( gpTrayNotify->CanShutdown() )
  1723. {
  1724. // mark for deletion and continue to exit the CS
  1725. pTrayNotify = gpTrayNotify;
  1726. gpTrayNotify = NULL;
  1727. DBGMSG(DBG_INITDONE, ("INITDONE: PrintTray - stop listen! \n"));
  1728. }
  1729. else
  1730. {
  1731. // restart the shutdown timer
  1732. gpTrayNotify->Resurrect();
  1733. }
  1734. }
  1735. }
  1736. if( pTrayNotify )
  1737. {
  1738. // marked for shutdown & release - shutdown without holding the CS
  1739. bReturn = pTrayNotify->Shutdown();
  1740. pTrayNotify->Release();
  1741. }
  1742. return bReturn;
  1743. }
  1744. } // extern "C"