Leaked source code of windows server 2003
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.

2097 lines
72 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[UNLEN + 1];
  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. else
  484. {
  485. //
  486. // This should never happen, but we must check and
  487. // free the msg instead of causing a leak.
  488. //
  489. if( WM_PRINTTRAY_PRIVATE_MSG == uMsg )
  490. {
  491. VERIFY(SUCCEEDED(m_heapMsgCache.Free(reinterpret_cast<HANDLE>(wParam))));
  492. }
  493. }
  494. break;
  495. }
  496. // allways call the default processing
  497. return DefWindowProc(hwnd, uMsg, wParam, lParam);
  498. }
  499. ///////////////////
  500. // IFolderNotify
  501. //
  502. STDMETHODIMP_(BOOL) CTrayNotify::ProcessNotify(FOLDER_NOTIFY_TYPE NotifyType, LPCWSTR pszName, LPCWSTR pszNewName)
  503. {
  504. // !IMPORTANT!
  505. // this is a callback from the background threads, so
  506. // we've got to be very carefull about what we are doing here.
  507. // the easiest way to syncronize is to quickly pass a private
  508. // message with the notification data into the foreground thread.
  509. MsgInfo msg = { msgTypePrnNotify, NotifyType };
  510. if( pszName )
  511. {
  512. lstrcpyn(msg.szPrinter, pszName, COUNTOF(msg.szPrinter));
  513. }
  514. if( pszNewName )
  515. {
  516. // this is not NULL only for kFolderRename
  517. lstrcpyn(msg.szAuxName, pszNewName, COUNTOF(msg.szAuxName));
  518. }
  519. // post a private message here...
  520. _PostPrivateMsg(msg);
  521. return TRUE;
  522. }
  523. ///////////////////////////
  524. // IPrinterChangeCallback
  525. //
  526. STDMETHODIMP CTrayNotify::PrinterChange(ULONG_PTR uCookie, DWORD dwChange, const PRINTER_NOTIFY_INFO *pInfo)
  527. {
  528. // !IMPORTANT!
  529. // this is a callback from the background threads, so
  530. // we've got to be very carefull about what we are doing here.
  531. // the easiest way to syncronize is to quickly pass a private
  532. // message with the notification data into the foreground thread.
  533. CPrintNotify *pListener = reinterpret_cast<CPrintNotify*>(uCookie);
  534. return (pListener ? _ProcessJobNotifications(pListener->GetPrinter(), dwChange, pInfo, NULL) : E_INVALIDARG);
  535. }
  536. //////////////////////////////////
  537. // private stuff _*
  538. //
  539. BOOL CTrayNotify::_InternalInit()
  540. {
  541. BOOL bReturn = SUCCEEDED(m_arrWatchList.Create());
  542. if( bReturn )
  543. {
  544. WNDCLASS WndClass;
  545. WndClass.style = 0L;
  546. WndClass.lpfnWndProc = ::DefWindowProc;
  547. WndClass.cbClsExtra = 0;
  548. WndClass.cbWndExtra = 0;
  549. WndClass.hInstance = 0;
  550. WndClass.hIcon = NULL;
  551. WndClass.hCursor = NULL;
  552. WndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
  553. WndClass.lpszMenuName = NULL;
  554. WndClass.lpszClassName = gszTrayListenerClassName;
  555. if( RegisterClass(&WndClass) ||
  556. ERROR_CLASS_ALREADY_EXISTS == GetLastError() )
  557. {
  558. // create the worker window
  559. HWND hwnd = CreateWindowEx(
  560. bIsBiDiLocalizedSystem() ? kExStyleRTLMirrorWnd : 0,
  561. gszTrayListenerClassName, NULL,
  562. WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, NULL, NULL, 0, NULL);
  563. if( hwnd )
  564. {
  565. Attach(hwnd);
  566. }
  567. }
  568. if( IsAttached() )
  569. {
  570. // register for print notifications in the printer's folder cache
  571. if( FAILED(RegisterPrintNotify(NULL, this, &m_hFolder, NULL)) )
  572. {
  573. // it will detach automatically
  574. DestroyWindow(m_hwnd);
  575. }
  576. else
  577. {
  578. // initialize a timer to shutdown the listening thread if there
  579. // is no activity for more than SHUTDOWN_TIMEOUT
  580. SetTimer(m_hwnd, SHUTDOWN_TIMER_ID, SHUTDOWN_TIMEOUT, NULL);
  581. }
  582. }
  583. }
  584. return bReturn;
  585. }
  586. void CTrayNotify::_MsgLoop()
  587. {
  588. // spin the msg loop here
  589. MSG msg;
  590. ASSERT(m_hwnd);
  591. while( GetMessage(&msg, NULL, 0, 0) )
  592. {
  593. TranslateMessage(&msg);
  594. DispatchMessage(&msg);
  595. }
  596. }
  597. void CTrayNotify::_ThreadProc()
  598. {
  599. AddRef();
  600. _InternalInit();
  601. if( m_shEventReady )
  602. {
  603. // notify the foreground thread we are about to start the msg loop
  604. SetEvent(m_shEventReady);
  605. }
  606. if( IsAttached() )
  607. {
  608. // spin a standard windows msg loop here
  609. _MsgLoop();
  610. }
  611. Release();
  612. }
  613. void CTrayNotify::_ProcessPrnNotify(FOLDER_NOTIFY_TYPE NotifyType, LPCWSTR pszName, LPCWSTR pszNewName)
  614. {
  615. DBGMSG(DBG_PROCESSPRNNOTIFY, ("PROCESSPRNNOTIFY: event has arrived, NotifyType=%d\n", NotifyType));
  616. switch( NotifyType )
  617. {
  618. case kFolderUpdate:
  619. case kFolderAttributes:
  620. case kFolderCreate:
  621. case kFolderUpdateAll:
  622. {
  623. CAutoPtrArray<BYTE> spBuffer;
  624. DWORD i, cReturned = 0;
  625. if( kFolderUpdateAll == NotifyType )
  626. {
  627. // reset all listeners...
  628. _ResetAll();
  629. // hide the icon, as this may take some time...
  630. _CheckToUpdateTray(FALSE, NULL);
  631. }
  632. if( SUCCEEDED(_GetPrinter(kFolderUpdateAll == NotifyType ?
  633. NULL : pszName, &spBuffer, &cReturned)) )
  634. {
  635. // walk through the printers to see if we need to add/delete/update printer(s) to our watch list.
  636. PFOLDER_PRINTER_DATA pPrinters = spBuffer.GetPtrAs<PFOLDER_PRINTER_DATA>();
  637. DBGMSG(DBG_PROCESSPRNNOTIFY, ("PROCESSPRNNOTIFY: create/update event, Count=%d\n", cReturned));
  638. for( i=0; i<cReturned; i++ )
  639. {
  640. DBGMSG(DBG_PROCESSPRNNOTIFY, ("PROCESSPRNNOTIFY: process printer: "TSTR", Jobs=%d\n",
  641. DBGSTR(pPrinters[i].pName), pPrinters[i].cJobs));
  642. // important!: we can't watch printers in pending deletion state because once the printer is
  643. // pending deletion it goes away without prior notification (when there is no jobs to print)
  644. int iPos;
  645. if( _FindPrinter(pPrinters[i].pName, &iPos) )
  646. {
  647. //
  648. // we don't want to delete the printer from the watch list when jobs count goes
  649. // to zero (0 == pPrinters[i].cJobs) because we rely on job notifications to show
  650. // balloons and sometimes the job notifications come AFTER the printer notifications
  651. //
  652. // we handle this by posting msgTypePrnCheckDelete when job gets deleted
  653. // so we can monitor the printer when there are no more user jobs in the queue and
  654. // then we stop watching the printer. don't change this behaviour unless you want
  655. // many things broken.
  656. //
  657. // printer found in the watch list
  658. if( PRINTER_STATUS_PENDING_DELETION & pPrinters[i].Status )
  659. {
  660. // no jobs or in pending deletion state - just delete.
  661. DBGMSG(DBG_PROCESSPRNNOTIFY, ("PROCESSPRNNOTIFY:[delete] watched printer with no jobs, pszPrinter="TSTR"\n",
  662. DBGSTR(pPrinters[i].pName)));
  663. _Delete(iPos);
  664. _RequestUpdate(UPDATE_REQUEST_PRN_DELETE, pPrinters[i].pName);
  665. }
  666. else
  667. {
  668. if( m_arrWatchList[iPos].dwStatus != pPrinters[i].Status )
  669. {
  670. // update status
  671. DBGMSG(DBG_PROCESSPRNNOTIFY, ("PROCESSPRNNOTIFY:[update] watched printer with jobs, pszPrinter="TSTR"\n",
  672. DBGSTR(pPrinters[i].pName)));
  673. m_arrWatchList[iPos].dwStatus = pPrinters[i].Status;
  674. _RequestUpdate(UPDATE_REQUEST_PRN_STATUS, pPrinters[i].pName);
  675. }
  676. }
  677. }
  678. else
  679. {
  680. // printer not found in the watch list
  681. if( pPrinters[i].cJobs && !(PRINTER_STATUS_PENDING_DELETION & pPrinters[i].Status) && !_IsFaxPrinter(pPrinters[i]) )
  682. {
  683. // start listening on this printer
  684. DBGMSG(DBG_PROCESSPRNNOTIFY, ("PROCESSPRNNOTIFY:[insert] non-watched printer with jobs, pszPrinter="TSTR"\n",
  685. DBGSTR(pPrinters[i].pName)));
  686. iPos = _Insert(pPrinters[i]);
  687. if( -1 != iPos )
  688. {
  689. _RequestUpdate(UPDATE_REQUEST_PRN_ADD, pPrinters[i].pName);
  690. }
  691. }
  692. }
  693. }
  694. }
  695. }
  696. break;
  697. case kFolderDelete:
  698. case kFolderRename:
  699. {
  700. int iPos;
  701. if( _FindPrinter(pszName, &iPos) )
  702. {
  703. // ranaming is a bit tricky. we need to delete & reinsert the item
  704. // to keep the array sorted & then update the context menu if necessary
  705. DBGMSG(DBG_PROCESSPRNNOTIFY, ("PROCESSPRNNOTIFY:[delete] watched printer deleted, pszPrinter="TSTR"\n",
  706. DBGSTR(pszName)));
  707. // first delete the printer which got renamed or deleted
  708. _Delete(iPos);
  709. _RequestUpdate(UPDATE_REQUEST_PRN_DELETE, pszName);
  710. if( kFolderRename == NotifyType )
  711. {
  712. // if rename, request update, so this printer can be re-added with
  713. // the new name.
  714. MsgInfo msg = { msgTypePrnNotify, kFolderUpdate };
  715. lstrcpyn(msg.szPrinter, pszNewName, COUNTOF(msg.szPrinter));
  716. _PostPrivateMsg(msg);
  717. }
  718. }
  719. }
  720. break;
  721. default:
  722. break;
  723. }
  724. }
  725. void CTrayNotify::_AdjustMenuIDs(HMENU hMenu, UINT uIDFrom, int iAdjustment) const
  726. {
  727. MENUITEMINFO mii = { sizeof(mii), MIIM_ID, 0 };
  728. int i, iCount = GetMenuItemCount(hMenu);
  729. for( i=0; i<iCount; i++ )
  730. {
  731. if( GetMenuItemInfo(hMenu, i, TRUE, &mii) && mii.wID >= uIDFrom )
  732. {
  733. DBGMSG(DBG_MENUADJUST, ("MENUADJUST: %d -> %d\n", mii.wID,
  734. static_cast<UINT>(static_cast<int>(mii.wID) + iAdjustment)));
  735. // adjust the menu item ID
  736. mii.wID = static_cast<UINT>(static_cast<int>(mii.wID) + iAdjustment);
  737. ASSERT(_DecodeMenuID(mii.wID) < m_arrWatchList.Count());
  738. // update menu item here
  739. SetMenuItemInfo(hMenu, i, TRUE, &mii);
  740. }
  741. }
  742. }
  743. int CTrayNotify::_Insert(const FOLDER_PRINTER_DATA &data)
  744. {
  745. CAutoPtr<CJobInfoArray> spUserJobs = new CJobInfoArray;
  746. CAutoPtr<CPrintNotify> spListener = new CPrintNotify(this, COUNTOF(g_Notifications), g_Notifications, PRINTER_CHANGE_JOB);
  747. int iPos = -1;
  748. if( spUserJobs && SUCCEEDED(spUserJobs->Create()) &&
  749. spListener && SUCCEEDED(spListener->Initialize(data.pName)) )
  750. {
  751. PrinterInfo infoPrn = {0};
  752. infoPrn.pListener = spListener;
  753. infoPrn.pUserJobs = spUserJobs;
  754. lstrcpyn(infoPrn.szPrinter, data.pName, COUNTOF(infoPrn.szPrinter));
  755. infoPrn.pListener->SetCookie(reinterpret_cast<ULONG_PTR>(infoPrn.pListener));
  756. ASSERT(!m_arrWatchList.FindItem(infoPrn.szPrinter, &iPos));
  757. iPos = m_arrWatchList.SortedInsert(infoPrn);
  758. if( -1 != iPos )
  759. {
  760. if( m_shCtxMenu )
  761. {
  762. DBGMSG(DBG_MENUADJUST, ("MENUADJUST: insert at pos: %d, Count=%d\n", iPos, m_arrWatchList.Count()));
  763. // if the context menu is opened then adjust the IDs
  764. _AdjustMenuIDs(m_shCtxMenu, _EncodeMenuID(iPos), 1);
  765. }
  766. // start listen on this printer
  767. DBGMSG(DBG_PRNNOTIFY, ("PRNNOTIFY: start listen printer: "TSTR"\n", DBGSTR(data.pName)));
  768. // they are hooked up already, detach from the smart pointers
  769. spListener.Detach();
  770. spUserJobs.Detach();
  771. // do an initial refresh and start listen
  772. _DoRefresh(infoPrn.pListener);
  773. infoPrn.pListener->StartListen();
  774. }
  775. }
  776. return iPos;
  777. }
  778. void CTrayNotify::_Delete(int iPos)
  779. {
  780. // stop listen on this printer
  781. m_arrWatchList[iPos].pListener->StopListen();
  782. DBGMSG(DBG_PRNNOTIFY, ("PRNNOTIFY: stop listen printer: "TSTR"\n", DBGSTR(m_arrWatchList[iPos].szPrinter)));
  783. delete m_arrWatchList[iPos].pListener;
  784. delete m_arrWatchList[iPos].pUserJobs;
  785. m_arrWatchList.Delete(iPos);
  786. DBGMSG(DBG_MENUADJUST, ("MENUADJUST: delete at pos: %d, Count=%d\n", iPos, m_arrWatchList.Count()));
  787. // fix the context menu
  788. if( m_shCtxMenu )
  789. {
  790. MENUITEMINFO mii = { sizeof(mii), MIIM_ID, 0 };
  791. if( GetMenuItemInfo(m_shCtxMenu, _EncodeMenuID(iPos), FALSE, &mii) )
  792. {
  793. // make sure this menu item is deleted
  794. VERIFY(DeleteMenu(m_shCtxMenu, _EncodeMenuID(iPos), MF_BYCOMMAND));
  795. }
  796. // if the context menu is opened then adjust the IDs
  797. _AdjustMenuIDs(m_shCtxMenu, _EncodeMenuID(iPos), -1);
  798. }
  799. }
  800. HRESULT CTrayNotify::_GetPrinter(LPCTSTR pszPrinter, LPBYTE *ppData, PDWORD pcReturned)
  801. {
  802. ASSERT(ppData);
  803. ASSERT(pcReturned);
  804. HRESULT hr = E_OUTOFMEMORY;
  805. int iTry = -1;
  806. DWORD cbNeeded = 0;
  807. DWORD cReturned = 0;
  808. CAutoPtrArray<BYTE> pData;
  809. BOOL bStatus = FALSE;
  810. for( ;; )
  811. {
  812. if( iTry++ >= ENUM_MAX_RETRY )
  813. {
  814. // max retry count reached. this is also
  815. // considered out of memory case
  816. pData = NULL;
  817. break;
  818. }
  819. // call bFolderEnumPrinters/bFolderGetPrinter...
  820. bStatus = pszPrinter ?
  821. bFolderGetPrinter(m_hFolder, pszPrinter, pData.GetPtrAs<PFOLDER_PRINTER_DATA>(), cbNeeded, &cbNeeded) :
  822. bFolderEnumPrinters(m_hFolder, pData.GetPtrAs<PFOLDER_PRINTER_DATA>(), cbNeeded, &cbNeeded, pcReturned);
  823. if( !bStatus && ERROR_INSUFFICIENT_BUFFER == GetLastError() && cbNeeded )
  824. {
  825. // buffer too small case
  826. pData = new BYTE[cbNeeded];
  827. if( pData )
  828. {
  829. continue;
  830. }
  831. else
  832. {
  833. SetLastError(ERROR_OUTOFMEMORY);
  834. break;
  835. }
  836. }
  837. break;
  838. }
  839. // setup the error code properly
  840. hr = bStatus ? S_OK : GetLastError() != ERROR_SUCCESS ? HRESULT_FROM_WIN32(GetLastError()) :
  841. !pData ? E_OUTOFMEMORY : E_FAIL;
  842. if( SUCCEEDED(hr) )
  843. {
  844. *ppData = pData.Detach();
  845. if( pszPrinter )
  846. {
  847. *pcReturned = 1;
  848. }
  849. }
  850. return hr;
  851. }
  852. void CTrayNotify::_CheckToUpdateUserJobs(PrinterInfo &pi, const MsgInfo &msg)
  853. {
  854. int iPos = -1;
  855. BOOL bJobFound = _FindUserJob(msg.jn.Id, pi, &iPos);
  856. // process DBG_JOBNOTIFY
  857. DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: pszPrinter="TSTR", szAuxName="TSTR", Field: %d, dwData: %d\n",
  858. DBGSTR(pi.szPrinter), DBGSTR(msg.szAuxName), msg.jn.Field, msg.jn.dwData));
  859. if( JOB_NOTIFY_FIELD_NOTIFY_NAME == msg.jn.Field )
  860. {
  861. // process JOB_NOTIFY_FIELD_NOTIFY_NAME here
  862. DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: JOB_NOTIFY_FIELD_NOTIFY_NAME, pszPrinter="TSTR"\n",
  863. DBGSTR(pi.szPrinter)));
  864. if( 0 == lstrcmp(msg.szAuxName, m_strUser) )
  865. {
  866. // a user job - check to insert
  867. if( !bJobFound )
  868. {
  869. JobInfo ji = {msg.jn.Id};
  870. if( _UpdateUserJob(pi, ji) && -1 != pi.pUserJobs->SortedInsert(ji) )
  871. {
  872. _RequestUpdate(UPDATE_REQUEST_JOB_ADD, pi.szPrinter);
  873. DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: job added, JobID=%d, pszPrinter="TSTR"\n",
  874. msg.jn.Id, DBGSTR(pi.szPrinter)));
  875. }
  876. }
  877. }
  878. else
  879. {
  880. // not a user job - check to delete
  881. if( bJobFound )
  882. {
  883. DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: job deleted, JobID=%d, pszPrinter="TSTR"\n",
  884. pi.pUserJobs->operator[](iPos).dwID, DBGSTR(pi.szPrinter)));
  885. pi.pUserJobs->Delete(iPos);
  886. _RequestUpdate(UPDATE_REQUEST_JOB_DELETE, pi.szPrinter);
  887. }
  888. }
  889. }
  890. if( bJobFound && JOB_NOTIFY_FIELD_STATUS == msg.jn.Field )
  891. {
  892. // process JOB_NOTIFY_FIELD_STATUS here
  893. DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: JOB_NOTIFY_FIELD_STATUS, Status=%x, pszPrinter="TSTR"\n",
  894. msg.jn.dwData, DBGSTR(pi.szPrinter)));
  895. // update the job status bits here, by saving the old status first
  896. JobInfo &ji = pi.pUserJobs->operator[](iPos);
  897. DWORD dwOldJobStatus = ji.dwStatus;
  898. ji.dwStatus = msg.jn.dwData;
  899. do
  900. {
  901. // dump the job status
  902. DBGMSG(DBG_JOBSTATUS, ("JOBSTATUS: old status=%x, new status=%x\n",
  903. dwOldJobStatus, ji.dwStatus));
  904. if( ji.dwStatus & JOB_STATUS_DELETED )
  905. {
  906. // the job status has the JOB_STATUS_DELETED bit up. delete the job.
  907. DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: job deleted, JobID=%x, pszPrinter="TSTR"\n",
  908. pi.pUserJobs->operator[](iPos).dwID, DBGSTR(pi.szPrinter)));
  909. pi.pUserJobs->Delete(iPos);
  910. _RequestUpdate(UPDATE_REQUEST_JOB_DELETE, pi.szPrinter);
  911. break; // skip everything
  912. }
  913. if( FALSE == m_bInRefresh && (0 == (dwOldJobStatus & JOB_STATUS_PRINTED)) &&
  914. (JOB_STATUS_PRINTED & ji.dwStatus) && (0 == (ji.dwStatus & JOB_ERROR_BITS)) )
  915. {
  916. // JOB_STATUS_PRINTED bit is up, the previous status nas no JOB_STATUS_PRINTED bit up
  917. // and there are no error bits up - consider the job had just printed successfully.
  918. DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: job completed with sucesss, JobID=%x, pszPrinter="TSTR"\n",
  919. pi.pUserJobs->operator[](iPos).dwID, DBGSTR(pi.szPrinter)));
  920. _JobPrinted(pi.szPrinter, ji);
  921. }
  922. if( FALSE == m_bInRefresh && (0 == (dwOldJobStatus & JOB_ERROR_BITS)) &&
  923. (ji.dwStatus & JOB_ERROR_BITS) )
  924. {
  925. // if the job goes from non-error state into an error state
  926. // then we assume the job has failed or a user intervention is
  927. // required. in both cases we show the job-failed balloon.
  928. DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: job failed to print, JobID=%x, pszPrinter="TSTR"\n",
  929. pi.pUserJobs->operator[](iPos).dwID, DBGSTR(pi.szPrinter)));
  930. _JobFailed(pi.szPrinter, ji);
  931. }
  932. // just check to update the job status
  933. if( dwOldJobStatus != ji.dwStatus )
  934. {
  935. ji.dwStatus = msg.jn.dwData;
  936. _RequestUpdate(UPDATE_REQUEST_JOB_STATUS, pi.szPrinter);
  937. DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: status updated, Status=%x, pszPrinter="TSTR"\n",
  938. ji.dwStatus, DBGSTR(pi.szPrinter)));
  939. }
  940. }
  941. while( FALSE );
  942. }
  943. if( bJobFound && JOB_NOTIFY_FIELD_TOTAL_PAGES == msg.jn.Field &&
  944. pi.pUserJobs->operator[](iPos).dwTotalPages != msg.jn.dwData )
  945. {
  946. // save the number of total pages, so we can display this info
  947. // later in the balloon when the job completes successfully.
  948. pi.pUserJobs->operator[](iPos).dwTotalPages = msg.jn.dwData;
  949. DBGMSG(DBG_JOBNOTIFY, ("JOBNOTIFY: total pages updated, TotalPages=%x, pszPrinter="TSTR"\n",
  950. pi.pUserJobs->operator[](iPos).dwTotalPages, DBGSTR(pi.szPrinter)));
  951. }
  952. }
  953. void CTrayNotify::_CheckUserJobs(int *piUserJobs, BOOL *pbUserJobPrinting, BOOL *pbUserInterventionReq)
  954. {
  955. ASSERT(piUserJobs);
  956. ASSERT(pbUserJobPrinting);
  957. ASSERT(pbUserInterventionReq);
  958. *piUserJobs = 0;
  959. *pbUserJobPrinting = *pbUserInterventionReq = FALSE;
  960. // walk through the watch list to see...
  961. int i, iCount = m_arrWatchList.Count();
  962. for( i=0; i<iCount; i++ )
  963. {
  964. m_arrWatchList[i].bUserInterventionReq = FALSE;
  965. CJobInfoArray &arrJobs = *m_arrWatchList[i].pUserJobs;
  966. int j, iJobCount = arrJobs.Count();
  967. for( j=0; j<iJobCount; j++ )
  968. {
  969. DWORD dwStatus = arrJobs[j].dwStatus;
  970. if( 0 == (dwStatus & JOB_IGNORE_BITS) )
  971. {
  972. (*piUserJobs)++;
  973. }
  974. if( dwStatus & JOB_ERROR_BITS )
  975. {
  976. // these job status bits are considered an error
  977. *pbUserInterventionReq = m_arrWatchList[i].bUserInterventionReq = TRUE;
  978. }
  979. if( dwStatus & JOB_STATUS_PRINTING )
  980. {
  981. // check if the job is printing now
  982. *pbUserJobPrinting = TRUE;
  983. }
  984. }
  985. }
  986. }
  987. void CTrayNotify::_CheckToUpdateTray(BOOL bForceUpdate, const BalloonInfo *pBalloon, BOOL bForceDelete)
  988. {
  989. int iUserJobs;
  990. BOOL bUserJobPrinting, bUserInterventionReq;
  991. _CheckUserJobs(&iUserJobs, &bUserJobPrinting, &bUserInterventionReq);
  992. DBGMSG(DBG_TRAYUPDATE, ("TRAYUPDATE: _CheckToUpdateTray called, "
  993. "iUserJobs=%d, bUserJobPrinting=%d, bUserInterventionReq=%d\n",
  994. iUserJobs, bUserJobPrinting, bUserInterventionReq));
  995. UINT uTrayIcon = g_arrIcons[ ((iUserJobs != 0) || bUserJobPrinting || (0 != m_uBalloonID) || (0 != m_uBalloonsCount)) + bUserInterventionReq ];
  996. NOTIFYICONDATA nid = { sizeof(nid), m_hwnd, ICON_ID, NIF_MESSAGE, WM_PRINTTRAY_ICON_NOTIFY, NULL };
  997. // check to delete first
  998. if( bForceDelete || (uTrayIcon == g_arrIcons[0] && m_uTrayIcon != g_arrIcons[0]) )
  999. {
  1000. if( m_bIconShown )
  1001. {
  1002. Shell_NotifyIcon(NIM_DELETE, &nid);
  1003. // reset the shutdown timer
  1004. Touch();
  1005. DBGMSG(DBG_NTFYICON, ("NTFYICON: icon deleted.\n"));
  1006. }
  1007. m_uTrayIcon = g_arrIcons[0];
  1008. m_cxSmIcon = m_cySmIcon = m_iUserJobs = 0;
  1009. m_bIconShown = FALSE;
  1010. }
  1011. else
  1012. {
  1013. // check to add/modify the icon
  1014. if( uTrayIcon != g_arrIcons[0] )
  1015. {
  1016. BOOL bPlayBalloonSound = FALSE;
  1017. BOOL bBalloonRequested = FALSE;
  1018. DWORD dwMsg = (m_uTrayIcon == g_arrIcons[0]) ? NIM_ADD : NIM_MODIFY;
  1019. int cxSmIcon = GetSystemMetrics(SM_CXSMICON);
  1020. int cySmIcon = GetSystemMetrics(SM_CYSMICON);
  1021. // check to sync the icon
  1022. if( uTrayIcon != m_uTrayIcon || cxSmIcon != m_cxSmIcon || m_cySmIcon != cySmIcon )
  1023. {
  1024. m_cxSmIcon = cxSmIcon;
  1025. m_cySmIcon = cySmIcon;
  1026. m_uTrayIcon = uTrayIcon;
  1027. m_shIconShown = (HICON)LoadImage(ghInst, MAKEINTRESOURCE(m_uTrayIcon), IMAGE_ICON, m_cxSmIcon, m_cySmIcon, 0);
  1028. nid.uFlags |= NIF_ICON;
  1029. nid.hIcon = m_shIconShown;
  1030. }
  1031. // check to sync the tip (if the ctx menu is not open)
  1032. if( bForceUpdate || m_iUserJobs != iUserJobs )
  1033. {
  1034. TString strTemplate, strTooltip;
  1035. m_iUserJobs = iUserJobs;
  1036. if( strTemplate.bLoadString(ghInst, IDS_TOOLTIP_TRAY) &&
  1037. strTooltip.bFormat(strTemplate, m_iUserJobs, static_cast<LPCTSTR>(m_strUser)) )
  1038. {
  1039. nid.uFlags |= NIF_TIP;
  1040. if( m_shCtxMenu )
  1041. {
  1042. // clear the tip
  1043. nid.szTip[0] = 0;
  1044. }
  1045. else
  1046. {
  1047. // update the tip
  1048. lstrcpyn(nid.szTip, strTooltip, COUNTOF(nid.szTip));
  1049. }
  1050. }
  1051. }
  1052. // check to sync the balloon (if the ctx menu is not open)
  1053. if( bForceUpdate || pBalloon )
  1054. {
  1055. nid.uFlags |= NIF_INFO;
  1056. if( m_shCtxMenu || NULL == pBalloon )
  1057. {
  1058. // hide the ballon
  1059. nid.szInfoTitle[0] = 0;
  1060. nid.szInfo[0] = 0;
  1061. }
  1062. else
  1063. {
  1064. // show up the balloon
  1065. nid.dwInfoFlags = pBalloon->dwFlags;
  1066. nid.uTimeout = pBalloon->uTimeout;
  1067. lstrcpyn(nid.szInfoTitle, pBalloon->pszCaption, COUNTOF(nid.szInfoTitle));
  1068. lstrcpyn(nid.szInfo, pBalloon->pszText, COUNTOF(nid.szInfo));
  1069. if( pBalloon->pszSound && pBalloon->pszSound[0] )
  1070. {
  1071. nid.dwInfoFlags |= NIIF_NOSOUND;
  1072. bPlayBalloonSound = TRUE;
  1073. }
  1074. bBalloonRequested = TRUE;
  1075. }
  1076. }
  1077. if( bForceUpdate || !m_bIconShown || nid.uFlags != NIF_MESSAGE )
  1078. {
  1079. // sync icon data
  1080. Shell_NotifyIcon(dwMsg, &nid);
  1081. if( bPlayBalloonSound )
  1082. {
  1083. PlaySound(pBalloon->pszSound, NULL,
  1084. SND_ALIAS | SND_APPLICATION | SND_ASYNC | SND_NODEFAULT | SND_NOSTOP);
  1085. }
  1086. if( bBalloonRequested )
  1087. {
  1088. m_uBalloonsCount++;
  1089. }
  1090. if( NIM_ADD == dwMsg )
  1091. {
  1092. DBGMSG(DBG_NTFYICON, ("NTFYICON: icon added.\n"));
  1093. }
  1094. else
  1095. {
  1096. DBGMSG(DBG_NTFYICON, ("NTFYICON: icon modified.\n"));
  1097. }
  1098. if( NIM_ADD == dwMsg )
  1099. {
  1100. // make sure we use the correct version
  1101. nid.uVersion = NOTIFYICON_VERSION;
  1102. Shell_NotifyIcon(NIM_SETVERSION, &nid);
  1103. DBGMSG(DBG_NTFYICON, ("NTFYICON: icon set version.\n"));
  1104. }
  1105. }
  1106. m_bIconShown = TRUE;
  1107. }
  1108. }
  1109. }
  1110. void CTrayNotify::_ShowMenu()
  1111. {
  1112. // no need to synchronize as m_arrWatchList size can be
  1113. // changed only in the message loop
  1114. int i, iCount = m_arrWatchList.Count();
  1115. if( iCount )
  1116. {
  1117. // when loaded from the resource, the context menu contains only one
  1118. if( m_shCtxMenu )
  1119. {
  1120. EndMenu();
  1121. }
  1122. // command - "Open Active Printers" corresponding to IDM_TRAYNOTIFY_DEFAULT
  1123. m_shCtxMenu = ShellServices::LoadPopupMenu(ghInst, POPUP_TRAYNOTIFY_PRINTERS);
  1124. if( m_shCtxMenu )
  1125. {
  1126. // build the context menu here
  1127. InsertMenu(m_shCtxMenu, (UINT)-1, MF_SEPARATOR|MF_BYPOSITION, 0, NULL);
  1128. for( i=0; i<iCount; i++ )
  1129. {
  1130. if( m_arrWatchList[i].pUserJobs->Count() )
  1131. {
  1132. _AddPrinterToCtxMenu(m_shCtxMenu, i);
  1133. }
  1134. }
  1135. // show up the context menu
  1136. if( GetMenuItemCount(m_shCtxMenu) )
  1137. {
  1138. POINT pt;
  1139. GetCursorPos(&pt);
  1140. SetMenuDefaultItem(m_shCtxMenu, IDM_TRAYNOTIFY_DEFAULT, MF_BYCOMMAND);
  1141. SetForegroundWindow(m_hwnd);
  1142. // now after m_shCtxMenu is not NULL, disable the tooltips,
  1143. // while the menu is open
  1144. _CheckToUpdateTray(TRUE, NULL);
  1145. // show up the context menu here...
  1146. // popup menus follows it's window owner when it comes to mirroring,
  1147. // so we should pass a an owner window which is mirrored.
  1148. int idCmd = TrackPopupMenu(m_shCtxMenu,
  1149. TPM_NONOTIFY|TPM_RETURNCMD|TPM_RIGHTBUTTON|TPM_HORNEGANIMATION,
  1150. pt.x, pt.y, 0, m_hwnd, NULL);
  1151. if( idCmd != 0 )
  1152. {
  1153. switch(idCmd)
  1154. {
  1155. case IDM_TRAYNOTIFY_DEFAULT:
  1156. {
  1157. // open the queues of all active printers
  1158. _ShowAllActivePrinters();
  1159. }
  1160. break;
  1161. case IDM_TRAYNOTIFY_PRNFOLDER:
  1162. {
  1163. // open the printer's folder
  1164. _ShowPrnFolder();
  1165. }
  1166. break;
  1167. case IDM_TRAYNOTIFY_REFRESH:
  1168. {
  1169. // just refresh the whole thing...
  1170. MsgInfo msg = { msgTypePrnNotify, kFolderUpdateAll };
  1171. _PostPrivateMsg(msg);
  1172. }
  1173. break;
  1174. default:
  1175. {
  1176. // open the selected printer
  1177. vQueueCreate(NULL, m_arrWatchList[_DecodeMenuID(idCmd)].szPrinter,
  1178. SW_SHOWNORMAL, static_cast<LPARAM>(FALSE));
  1179. }
  1180. break;
  1181. }
  1182. }
  1183. }
  1184. }
  1185. // destroy the menu
  1186. m_shCtxMenu = NULL;
  1187. // re-enable the tooltips after the menu is closed
  1188. _CheckToUpdateTray(TRUE, NULL);
  1189. }
  1190. }
  1191. void CTrayNotify::_ShowPrnFolder() const
  1192. {
  1193. // find the printer's folder PIDL
  1194. CAutoPtrPIDL pidlPrinters;
  1195. HRESULT hr = SHGetSpecialFolderLocation(NULL, CSIDL_PRINTERS, &pidlPrinters);
  1196. if( SUCCEEDED(hr) )
  1197. {
  1198. // invoke ShellExecuteEx on that PIDL
  1199. SHELLEXECUTEINFO seInfo;
  1200. memset(&seInfo, 0, sizeof(seInfo));
  1201. seInfo.cbSize = sizeof(seInfo);
  1202. seInfo.fMask = SEE_MASK_IDLIST;
  1203. seInfo.hwnd = m_hwnd;
  1204. seInfo.nShow = SW_SHOWDEFAULT;
  1205. seInfo.lpIDList = pidlPrinters;
  1206. ShellExecuteEx(&seInfo);
  1207. }
  1208. }
  1209. void CTrayNotify::_ShowBalloon(
  1210. UINT uBalloonID,
  1211. LPCTSTR pszCaption,
  1212. LPCTSTR pszText,
  1213. LPCTSTR pszSound,
  1214. DWORD dwFlags,
  1215. UINT uTimeout)
  1216. {
  1217. // just show up the balloon...
  1218. m_uBalloonID = uBalloonID;
  1219. BalloonInfo bi = {m_uBalloonID, pszCaption, pszText, pszSound, dwFlags, uTimeout};
  1220. _CheckToUpdateTray(FALSE, &bi);
  1221. }
  1222. void CTrayNotify::_JobFailed(LPCTSTR pszPrinter, JobInfo &ji)
  1223. {
  1224. ASSERT(pszPrinter);
  1225. // since the baloon text length is limited to 255 characters we need to abbreviate the printer name
  1226. // and the document name if they are too long by adding ellipses at the end.
  1227. TString strPrinter, strDocName;
  1228. if( SUCCEEDED(AbbreviateText(pszPrinter, MAX_PRINTER_DISPLAYNAME, &strPrinter)) &&
  1229. SUCCEEDED(AbbreviateText(ji.szDocName, MAX_DOC_DISPLAYNAME, &strDocName)) )
  1230. {
  1231. TString strTimeSubmitted, strTemplate, strTitle, strText;
  1232. TStatusB bStatus;
  1233. bStatus DBGCHK = (ji.dwStatus & JOB_STATUS_PAPEROUT) ?
  1234. strTitle.bLoadString(ghInst, IDS_BALLOON_TITLE_JOB_FAILED_OOP) :
  1235. strTitle.bLoadString(ghInst, IDS_BALLOON_TITLE_JOB_FAILED);
  1236. if( bStatus &&
  1237. _TimeToString(ji.timeSubmitted, &strTimeSubmitted) &&
  1238. strTemplate.bLoadString(ghInst, IDS_BALLOON_TEXT_JOB_FAILED) &&
  1239. strText.bFormat(strTemplate,
  1240. static_cast<LPCTSTR>(strDocName),
  1241. static_cast<LPCTSTR>(strPrinter),
  1242. static_cast<LPCTSTR>(strTimeSubmitted)) )
  1243. {
  1244. _ShowBalloon(BALLOON_ID_JOB_FAILED, strTitle, strText, NULL, NIIF_WARNING);
  1245. m_strLastBalloonPrinter.bUpdate(pszPrinter);
  1246. }
  1247. }
  1248. }
  1249. void CTrayNotify::_JobPrinted(LPCTSTR pszPrinter, JobInfo &ji)
  1250. {
  1251. ASSERT(pszPrinter);
  1252. // since the baloon text length is limited to 255 characters we need to abbreviate the printer name
  1253. // and the document name if they are too long, by adding ellipses at the end.
  1254. TString strPrinter, strDocName;
  1255. if( SUCCEEDED(AbbreviateText(pszPrinter, MAX_PRINTER_DISPLAYNAME, &strPrinter)) &&
  1256. SUCCEEDED(AbbreviateText(ji.szDocName, MAX_DOC_DISPLAYNAME, &strDocName)) )
  1257. {
  1258. BOOL bCanNotify;
  1259. TString strTimeSubmitted, strTemplate, strTitle, strText;
  1260. // total pages can be zero when a downlevel document is printed (directly to the port) in
  1261. // this case StartDoc/EndDoc are not called and the spooler doesn't know the total pages of
  1262. // the document. in this case just display the balloon without total pages info.
  1263. UINT uTextID = ji.dwTotalPages ? IDS_BALLOON_TEXT_JOB_PRINTED : IDS_BALLOON_TEXT_JOB_PRINTED_NOPAGES;
  1264. if( SUCCEEDED(_CanNotify(pszPrinter, &bCanNotify)) && bCanNotify &&
  1265. _TimeToString(ji.timeSubmitted, &strTimeSubmitted) &&
  1266. strTitle.bLoadString(ghInst, IDS_BALLOON_TITLE_JOB_PRINTED) &&
  1267. strTemplate.bLoadString(ghInst, uTextID) &&
  1268. strText.bFormat(strTemplate,
  1269. static_cast<LPCTSTR>(strDocName),
  1270. static_cast<LPCTSTR>(strPrinter),
  1271. static_cast<LPCTSTR>(strTimeSubmitted),
  1272. ji.dwTotalPages) )
  1273. {
  1274. _ShowBalloon(BALLOON_ID_JOB_PRINTED, strTitle, strText, gszBalloonSoundPrintComplete, NIIF_INFO);
  1275. m_strLastBalloonPrinter.bUpdate(pszPrinter);
  1276. }
  1277. }
  1278. }
  1279. BOOL CTrayNotify::_TimeToString(const SYSTEMTIME &time, TString *pTime) const
  1280. {
  1281. ASSERT(pTime);
  1282. BOOL bReturn = FALSE;
  1283. TCHAR szText[255];
  1284. SYSTEMTIME timeLocal;
  1285. if( SystemTimeToTzSpecificLocalTime(NULL, const_cast<LPSYSTEMTIME>(&time), &timeLocal) &&
  1286. GetTimeFormat(LOCALE_USER_DEFAULT, 0, &timeLocal, NULL, szText, COUNTOF(szText)) )
  1287. {
  1288. pTime->bCat(szText);
  1289. pTime->bCat(TEXT(" "));
  1290. if( GetDateFormat(LOCALE_USER_DEFAULT, 0, &timeLocal, NULL, szText, COUNTOF(szText)) )
  1291. {
  1292. pTime->bCat(szText);
  1293. bReturn = TRUE;
  1294. }
  1295. }
  1296. return bReturn;
  1297. }
  1298. void CTrayNotify::_DoRefresh(CPrintNotify *pListener)
  1299. {
  1300. m_bInRefresh = TRUE;
  1301. pListener->Refresh(this, _RefreshCallback);
  1302. m_bInRefresh = FALSE;
  1303. // check to update the tray
  1304. _CheckToUpdateTray(FALSE, NULL);
  1305. }
  1306. void CTrayNotify::_PostPrivateMsg(const MsgInfo &msg)
  1307. {
  1308. HANDLE hItem;
  1309. if( SUCCEEDED(m_heapMsgCache.Alloc(msg, &hItem)) )
  1310. {
  1311. // request a balloon to show up...
  1312. if (!PostMessage(m_hwnd, WM_PRINTTRAY_PRIVATE_MSG, reinterpret_cast<LPARAM>(hItem), 0))
  1313. {
  1314. VERIFY(SUCCEEDED(m_heapMsgCache.Free(hItem)));
  1315. }
  1316. }
  1317. }
  1318. void CTrayNotify::_RequestUpdate(int iEvent, LPCTSTR pszPrinter, LPCTSTR pszAux)
  1319. {
  1320. // check to update the context menu if shown
  1321. int i = -1;
  1322. switch( iEvent )
  1323. {
  1324. case UPDATE_REQUEST_JOB_ADD:
  1325. {
  1326. // check to add to the context menu
  1327. MENUITEMINFO mii = { sizeof(mii), MIIM_ID, 0 };
  1328. if( m_shCtxMenu && _FindPrinter(pszPrinter, &i) && 0 != m_arrWatchList[i].pUserJobs->Count() &&
  1329. FALSE == GetMenuItemInfo(m_shCtxMenu, _EncodeMenuID(i), FALSE, &mii) )
  1330. {
  1331. // add this printer to the context menu
  1332. _AddPrinterToCtxMenu(m_shCtxMenu, i);
  1333. }
  1334. }
  1335. break;
  1336. case UPDATE_REQUEST_JOB_DELETE:
  1337. {
  1338. // check this printer to be deleted later
  1339. MsgInfo msg = { msgTypePrnCheckDelete, kFolderNone };
  1340. lstrcpyn(msg.szPrinter, pszPrinter, COUNTOF(msg.szPrinter));
  1341. _PostPrivateMsg(msg);
  1342. // check to delete from the context menu
  1343. MENUITEMINFO mii = { sizeof(mii), MIIM_ID, 0 };
  1344. if( m_shCtxMenu && _FindPrinter(pszPrinter, &i) && 0 == m_arrWatchList[i].pUserJobs->Count() &&
  1345. TRUE == GetMenuItemInfo(m_shCtxMenu, _EncodeMenuID(i), FALSE, &mii) )
  1346. {
  1347. // delete this printer from the context menu
  1348. VERIFY(DeleteMenu(m_shCtxMenu, _EncodeMenuID(i), MF_BYCOMMAND));
  1349. }
  1350. }
  1351. break;
  1352. default:
  1353. break;
  1354. }
  1355. // check to update tray if not in refresh
  1356. if( !m_bInRefresh )
  1357. {
  1358. _CheckToUpdateTray(FALSE, NULL);
  1359. }
  1360. }
  1361. void CTrayNotify::_BalloonClicked(UINT uBalloonID) const
  1362. {
  1363. switch( uBalloonID )
  1364. {
  1365. case BALLOON_ID_JOB_FAILED:
  1366. vQueueCreate(NULL, m_strLastBalloonPrinter, SW_SHOWNORMAL, static_cast<LPARAM>(FALSE));
  1367. break;
  1368. case BALLOON_ID_JOB_PRINTED:
  1369. // do nothing
  1370. break;
  1371. case BALLOON_ID_PRN_CREATED:
  1372. _ShowPrnFolder();
  1373. break;
  1374. default:
  1375. ASSERT(FALSE);
  1376. break;
  1377. }
  1378. }
  1379. BOOL CTrayNotify::_UpdateUserJob(PrinterInfo &pi, JobInfo &ji)
  1380. {
  1381. // get job info at level 1
  1382. BOOL bReturn = FALSE;
  1383. DWORD cbJob = 0;
  1384. CAutoPtrSpl<JOB_INFO_2> spJob;
  1385. if( VDataRefresh::bGetJob(pi.pListener->GetPrinterHandle(), ji.dwID, 2, spJob.GetPPV(), &cbJob) )
  1386. {
  1387. // the only thing we care about is the document name & job status
  1388. ji.dwStatus = spJob->Status;
  1389. lstrcpyn(ji.szDocName, spJob->pDocument, COUNTOF(ji.szDocName));
  1390. ji.timeSubmitted = spJob->Submitted;
  1391. ji.dwTotalPages = spJob->TotalPages;
  1392. bReturn = TRUE;
  1393. }
  1394. return bReturn;
  1395. }
  1396. void CTrayNotify::_ShowAllActivePrinters() const
  1397. {
  1398. HRESULT hr = S_OK;
  1399. int i, iCount;
  1400. PrinterInfo pi = {0};
  1401. CPrnInfoArray arrActivePrinters;
  1402. hr = arrActivePrinters.Create();
  1403. if (SUCCEEDED(hr))
  1404. {
  1405. for (i=0, iCount = m_arrWatchList.Count(); i<iCount; i++)
  1406. {
  1407. if (m_arrWatchList[i].pUserJobs->Count())
  1408. {
  1409. lstrcpyn(pi.szPrinter, m_arrWatchList[i].szPrinter, COUNTOF(pi.szPrinter));
  1410. //
  1411. // Ignore failures... SortedInsert can return -1 in case
  1412. // of lack of memory, but we can just safely ignore this.
  1413. // The worst case will be that some of the printer queues
  1414. // won't be opened.
  1415. //
  1416. hr = (-1 == arrActivePrinters.SortedInsert(pi) ? E_OUTOFMEMORY : S_OK);
  1417. }
  1418. }
  1419. //
  1420. // Open the queues of all active printers.
  1421. //
  1422. for (i=0, iCount = arrActivePrinters.Count(); i<iCount; i++)
  1423. {
  1424. vQueueCreate(NULL, arrActivePrinters[i].szPrinter, SW_SHOWNORMAL, static_cast<LPARAM>(FALSE));
  1425. }
  1426. }
  1427. }
  1428. void CTrayNotify::_AddPrinterToCtxMenu(HMENU hMenu, int i)
  1429. {
  1430. // build the context menu here
  1431. TString strPrinter;
  1432. MENUITEMINFO mii = { sizeof(mii), MIIM_TYPE|MIIM_ID, MF_STRING };
  1433. if( m_arrWatchList[i].bUserInterventionReq )
  1434. {
  1435. // this printer is in error state, add an (error) suffix
  1436. TString strTemplate;
  1437. strTemplate.bLoadString(ghInst, IDS_TRAY_TEXT_ERROR);
  1438. strPrinter.bFormat(strTemplate, m_arrWatchList[i].szPrinter);
  1439. }
  1440. else
  1441. {
  1442. strPrinter.bUpdate(m_arrWatchList[i].szPrinter);
  1443. }
  1444. mii.wID = _EncodeMenuID(i);
  1445. mii.dwTypeData = const_cast<LPTSTR>(static_cast<LPCTSTR>(strPrinter));
  1446. mii.cch = lstrlen(mii.dwTypeData);
  1447. InsertMenuItem(hMenu, (UINT)-1, MF_BYPOSITION, &mii);
  1448. }
  1449. HRESULT CTrayNotify::_ProcessJobNotifications(LPCTSTR pszPrinter, DWORD dwChange,
  1450. const PRINTER_NOTIFY_INFO *pInfo, PrinterInfo *ppi)
  1451. {
  1452. if( pInfo && (PRINTER_NOTIFY_INFO_DISCARDED & pInfo->Flags) )
  1453. {
  1454. MsgInfo msg = { msgTypeJobNotifyLost, kFolderNone };
  1455. lstrcpyn(msg.szPrinter, pszPrinter, COUNTOF(msg.szPrinter));
  1456. if( ppi )
  1457. {
  1458. // called from the foreground thread: sync processing
  1459. _CheckToUpdateUserJobs(*ppi, msg);
  1460. }
  1461. else
  1462. {
  1463. // called from the background threads: async processing
  1464. _PostPrivateMsg(msg);
  1465. }
  1466. }
  1467. else
  1468. {
  1469. // regular job notifications have arrived
  1470. if( pInfo && pInfo->Count )
  1471. {
  1472. MsgInfo msg = { msgTypeJobNotify, kFolderNone };
  1473. lstrcpyn(msg.szPrinter, pszPrinter, COUNTOF(msg.szPrinter));
  1474. for( DWORD i=0; i<pInfo->Count; i++ )
  1475. {
  1476. if( JOB_NOTIFY_TYPE != pInfo->aData[i].Type )
  1477. {
  1478. // we only care about job notifications here
  1479. continue;
  1480. }
  1481. msg.jn.Type = pInfo->aData[i].Type;
  1482. msg.jn.Field = pInfo->aData[i].Field;
  1483. msg.jn.Id = pInfo->aData[i].Id;
  1484. msg.jn.dwData = pInfo->aData[i].NotifyData.adwData[0];
  1485. if( JOB_NOTIFY_FIELD_NOTIFY_NAME == pInfo->aData[i].Field )
  1486. {
  1487. // need to copy pBuf into the aux buffer (szAuxName)
  1488. lstrcpyn(msg.szAuxName, reinterpret_cast<LPCTSTR>(pInfo->aData[i].NotifyData.Data.pBuf),
  1489. COUNTOF(msg.szAuxName));
  1490. }
  1491. if( ppi )
  1492. {
  1493. // called from the foreground thread: sync processing
  1494. _CheckToUpdateUserJobs(*ppi, msg);
  1495. }
  1496. else
  1497. {
  1498. // called from the background threads: async processing
  1499. _PostPrivateMsg(msg);
  1500. }
  1501. }
  1502. }
  1503. }
  1504. return S_OK;
  1505. }
  1506. void CTrayNotify::_ResetAll()
  1507. {
  1508. // close the context menu
  1509. m_shCtxMenu = NULL;
  1510. // cleanup the watch list (unregister all job notifications listeners)
  1511. while( m_arrWatchList.Count() )
  1512. {
  1513. _Delete(0);
  1514. }
  1515. // flush the message queue here...
  1516. MSG msg;
  1517. while( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) )
  1518. {
  1519. if( WM_PRINTTRAY_PRIVATE_MSG == msg.message )
  1520. {
  1521. // just free up the private message
  1522. VERIFY(SUCCEEDED(m_heapMsgCache.Free(reinterpret_cast<HANDLE>(msg.wParam))));
  1523. }
  1524. }
  1525. // update the tray
  1526. _CheckToUpdateTray(FALSE, NULL);
  1527. }
  1528. BOOL CTrayNotify::_IsFaxPrinter(const FOLDER_PRINTER_DATA &data)
  1529. {
  1530. return ((0 == lstrcmp(data.pDriverName, FAX_DRIVER_NAME)) ||
  1531. (data.Attributes & PRINTER_ATTRIBUTE_FAX));
  1532. }
  1533. HRESULT CTrayNotify::_CanNotify(LPCTSTR pszPrinterName, BOOL *pbCanNotify)
  1534. {
  1535. HRESULT hr = E_INVALIDARG;
  1536. BOOL bIsNetworkPrinter;
  1537. LPCTSTR pszServer;
  1538. LPCTSTR pszPrinter;
  1539. TCHAR szScratch[kStrMax+kPrinterBufMax];
  1540. UINT nSize = COUNTOF(szScratch);
  1541. if( pszPrinterName && *pszPrinterName && pbCanNotify )
  1542. {
  1543. //
  1544. // Split the printer name into its components.
  1545. //
  1546. vPrinterSplitFullName(szScratch, ARRAYSIZE(szScratch), pszPrinterName, &pszServer, &pszPrinter);
  1547. bIsNetworkPrinter = bIsRemote(pszServer);
  1548. // set default value
  1549. *pbCanNotify = bIsNetworkPrinter ? TRUE : FALSE;
  1550. TPersist NotifyUser(gszPrinterPositions, TPersist::kCreate|TPersist::kRead);
  1551. if( NotifyUser.bValid() )
  1552. {
  1553. if( NotifyUser.bRead(bIsNetworkPrinter ? gszNetworkPrintNotification : gszLocalPrintNotification, *pbCanNotify) )
  1554. {
  1555. hr = S_OK;
  1556. }
  1557. else
  1558. {
  1559. hr = CreateHRFromWin32();
  1560. }
  1561. }
  1562. else
  1563. {
  1564. hr = CreateHRFromWin32();
  1565. }
  1566. }
  1567. return hr;
  1568. }
  1569. LRESULT CTrayNotify::_ProcessUserMsg(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  1570. {
  1571. switch( uMsg )
  1572. {
  1573. case WM_PRINTTRAY_PRIVATE_MSG:
  1574. {
  1575. MsgInfo *pmsg = NULL;
  1576. HANDLE hItem = reinterpret_cast<HANDLE>(wParam);
  1577. VERIFY(SUCCEEDED(m_heapMsgCache.GetItem(hItem, &pmsg)));
  1578. switch( pmsg->iType )
  1579. {
  1580. case msgTypePrnNotify:
  1581. {
  1582. _ProcessPrnNotify(pmsg->NotifyType, pmsg->szPrinter, pmsg->szAuxName);
  1583. }
  1584. break;
  1585. case msgTypePrnCheckDelete:
  1586. {
  1587. int iPos = -1;
  1588. if( _FindPrinter(pmsg->szPrinter, &iPos) && 0 == m_arrWatchList[iPos].pUserJobs->Count() )
  1589. {
  1590. // this printer has no active user jobs, so delete it.
  1591. _Delete(iPos);
  1592. _RequestUpdate(UPDATE_REQUEST_PRN_DELETE, pmsg->szPrinter);
  1593. }
  1594. }
  1595. break;
  1596. case msgTypeJobNotify:
  1597. {
  1598. int iPos = -1;
  1599. if( _FindPrinter(pmsg->szPrinter, &iPos) )
  1600. {
  1601. _CheckToUpdateUserJobs(m_arrWatchList[iPos], *pmsg);
  1602. }
  1603. }
  1604. break;
  1605. case msgTypeJobNotifyLost:
  1606. {
  1607. int iPos = -1;
  1608. if( _FindPrinter(pmsg->szPrinter, &iPos) )
  1609. {
  1610. PrinterInfo &pi = m_arrWatchList[iPos];
  1611. // do a full refresh here
  1612. pi.dwStatus = 0;
  1613. pi.pUserJobs->DeleteAll();
  1614. _RequestUpdate(UPDATE_REQUEST_JOB_DELETE, pi.szPrinter);
  1615. // update all
  1616. _DoRefresh(m_arrWatchList[iPos].pListener);
  1617. }
  1618. }
  1619. break;
  1620. default:
  1621. ASSERT(FALSE);
  1622. break;
  1623. }
  1624. // free up the message
  1625. VERIFY(SUCCEEDED(m_heapMsgCache.Free(hItem)));
  1626. }
  1627. break;
  1628. case WM_PRINTTRAY_ICON_NOTIFY:
  1629. {
  1630. // the real uMsg is in lParam
  1631. switch( lParam )
  1632. {
  1633. case WM_CONTEXTMENU:
  1634. _ShowMenu();
  1635. break;
  1636. case WM_LBUTTONDBLCLK:
  1637. _ShowAllActivePrinters();
  1638. break;
  1639. case NIN_BALLOONUSERCLICK:
  1640. case NIN_BALLOONTIMEOUT:
  1641. {
  1642. m_uBalloonsCount--;
  1643. if( NIN_BALLOONUSERCLICK == lParam && m_uBalloonID )
  1644. {
  1645. _BalloonClicked(m_uBalloonID);
  1646. }
  1647. if( 0 == m_uBalloonsCount )
  1648. {
  1649. m_uBalloonID = 0;
  1650. m_strLastBalloonPrinter.bUpdate(NULL);
  1651. _CheckToUpdateTray(FALSE, NULL);
  1652. }
  1653. }
  1654. break;
  1655. default:
  1656. break;
  1657. }
  1658. }
  1659. break;
  1660. case WM_WININICHANGE:
  1661. {
  1662. // check to re-do the icon
  1663. _CheckToUpdateTray(FALSE, NULL);
  1664. }
  1665. break;
  1666. default:
  1667. break;
  1668. }
  1669. return 0;
  1670. }
  1671. DWORD WINAPI CTrayNotify::_ThreadProc_MsgLoop(LPVOID lpParameter)
  1672. {
  1673. CTrayNotify *pFolderNotify = (CTrayNotify *)lpParameter;
  1674. if( pFolderNotify )
  1675. {
  1676. pFolderNotify->_ThreadProc();
  1677. return EXIT_SUCCESS;
  1678. }
  1679. return EXIT_FAILURE;
  1680. }
  1681. // the callback called on refresh
  1682. HRESULT WINAPI CTrayNotify::_RefreshCallback(
  1683. LPVOID lpCookie, ULONG_PTR uCookie, DWORD dwChange, const PRINTER_NOTIFY_INFO *pInfo)
  1684. {
  1685. int iPos;
  1686. HRESULT hr = E_INVALIDARG;
  1687. CTrayNotify *pThis = reinterpret_cast<CTrayNotify*>(lpCookie);
  1688. CPrintNotify *pListener = reinterpret_cast<CPrintNotify*>(uCookie);
  1689. if( pThis && pListener && pThis->_FindPrinter(pListener->GetPrinter(), &iPos) )
  1690. {
  1691. hr = pThis->_ProcessJobNotifications(
  1692. pListener->GetPrinter(), dwChange, pInfo, &pThis->m_arrWatchList[iPos]);
  1693. }
  1694. return hr;
  1695. }
  1696. //////////////////////////////////
  1697. // global & exported functions
  1698. //
  1699. static CTrayNotify *gpTrayNotify = NULL;
  1700. extern "C" {
  1701. BOOL WINAPI PrintNotifyTray_Init()
  1702. {
  1703. ASSERT(gpTrayLock);
  1704. // synchonize this with gpTrayLock
  1705. CCSLock::Locker lock(*gpTrayLock);
  1706. BOOL bReturn = FALSE;
  1707. if( NULL == gpTrayNotify )
  1708. {
  1709. if( SUCCEEDED(CTrayNotify_CreateInstance(&gpTrayNotify)) )
  1710. {
  1711. bReturn = gpTrayNotify->Initialize();
  1712. if( !bReturn )
  1713. {
  1714. gpTrayNotify->Release();
  1715. gpTrayNotify = NULL;
  1716. }
  1717. else
  1718. {
  1719. DBGMSG(DBG_INITDONE, ("INITDONE: PrintTray - start listen! \n"));
  1720. }
  1721. }
  1722. }
  1723. else
  1724. {
  1725. // reset the shutdown timer
  1726. gpTrayNotify->Touch();
  1727. }
  1728. return bReturn;
  1729. }
  1730. BOOL WINAPI PrintNotifyTray_Exit()
  1731. {
  1732. ASSERT(gpTrayLock);
  1733. // synchonize this with gpTrayLock
  1734. CCSLock::Locker lock(*gpTrayLock);
  1735. BOOL bReturn = FALSE;
  1736. if( gpTrayNotify )
  1737. {
  1738. bReturn = gpTrayNotify->Shutdown();
  1739. gpTrayNotify->Release();
  1740. gpTrayNotify = NULL;
  1741. DBGMSG(DBG_INITDONE, ("INITDONE: PrintTray - stop listen! \n"));
  1742. }
  1743. return bReturn;
  1744. }
  1745. BOOL WINAPI PrintNotifyTray_SelfShutdown()
  1746. {
  1747. ASSERT(gpTrayLock);
  1748. BOOL bReturn = FALSE;
  1749. CTrayNotify *pTrayNotify = NULL;
  1750. {
  1751. // synchonize this with gpTrayLock
  1752. CCSLock::Locker lock(*gpTrayLock);
  1753. if( gpTrayNotify )
  1754. {
  1755. if( gpTrayNotify->CanShutdown() )
  1756. {
  1757. // mark for deletion and continue to exit the CS
  1758. pTrayNotify = gpTrayNotify;
  1759. gpTrayNotify = NULL;
  1760. DBGMSG(DBG_INITDONE, ("INITDONE: PrintTray - stop listen! \n"));
  1761. }
  1762. else
  1763. {
  1764. // restart the shutdown timer
  1765. gpTrayNotify->Resurrect();
  1766. }
  1767. }
  1768. }
  1769. if( pTrayNotify )
  1770. {
  1771. // marked for shutdown & release - shutdown without holding the CS
  1772. bReturn = pTrayNotify->Shutdown();
  1773. pTrayNotify->Release();
  1774. }
  1775. return bReturn;
  1776. }
  1777. } // extern "C"