/** Copyright (c) Microsoft Corporation 1999-2000 Module Name: fxsst.cpp Abstract: This module implements the tray icon for fax. The purpose of the tray icon is to provide status and feedback to the fax user. **/ #include #include #include #include #include #include #include #include #include #include #include #include "monitor.h" #include "resource.h" //////////////////////////////////////////////////////////// // Global data // // // The following message ids are used for internal custom messages. // #define WM_FAX_STARTED (WM_USER + 204) // Message indicating the loca fax service is up and running #define WM_TRAYCALLBACK (WM_USER + 205) // Notification bar icon callback message #define WM_FAX_EVENT (WM_USER + 300) // Fax extended event message #define TRAY_ICON_ID 12345 // Unique enough HINSTANCE g_hModule = NULL; // DLL Global instance HINSTANCE g_hResource = NULL; // Resource DLL handle HANDLE g_hFaxSvcHandle = NULL; // Handle to the fax service (from FaxConnectFaxServer) DWORDLONG g_dwlCurrentMsgID = 0; // ID of current message being monitored DWORD g_dwCurrentJobID = 0; // ID of current queue job being monitored HANDLE g_hServerStartupThread = NULL; // Handle of thread which waits for the server startup event HANDLE g_hStopStartupThreadEvent = NULL; // Event for stop Server Startup Thread BOOL g_bShuttingDown = FALSE; // Are we shutting down now? HWND g_hWndFaxNotify = NULL; // Local (hidden) window handle HANDLE g_hNotification = NULL; // Fax extended notification handle HCALL g_hCall = NULL; // Handle to call (from FAX_EVENT_TYPE_NEW_CALL) DWORDLONG g_dwlNewMsgId; // ID of the last incoming fax DWORDLONG g_dwlSendFailedMsgId; // ID of the last outgoing failed fax DWORDLONG g_dwlSendSuccessMsgId; // ID of the last successfully sent fax TCHAR g_szAddress[MAX_PATH] = {0}; // Current caller ID or recipient number TCHAR g_szRemoteId[MAX_PATH] = {0}; // Sender ID or Recipient ID // // Sender ID (receive): // TSID or // Caller ID or // "unknown caller" // // Recipient ID (send): // Recipient name or // CSID or // Recipient phone number. // BOOL g_bRecipientNameValid = FALSE; // TRUE if the g_szRecipientName has valid data TCHAR g_szRecipientName[MAX_PATH] = {0}; // Keep the recipient name during sending // // Configuration options - read from the registry / Service // Default values are set here. // CONFIG_OPTIONS g_ConfigOptions = {0}; // // Notification bar icon states // typedef enum { ICON_RINGING=0, // Device is ringing ICON_SENDING, // Device is sending ICON_RECEIVING, // Device is receiving ICON_SEND_FAILED, // Send operation failed ICON_RECEIVE_FAILED, // Receive operation failed ICON_NEW_FAX, // New unread fax ICON_SEND_SUCCESS, // Send was successful ICON_IDLE, // Don't display an icon ICONS_COUNT // Number of icons we support } eIconState; eIconState g_CurrentIcon = ICONS_COUNT; // The index of the currently displayed icon #define TOOLTIP_SIZE 128 // Number of characters in the tooltip struct SIconState { BOOL bEnable; // Is the state active? (e.g. are there any new unread faxes?) DWORD dwIconResId; // Resource id of the icon to use HICON hIcon; // Handle to icon to use LPCTSTR pctsSound; // Name of sound event TCHAR tszToolTip[TOOLTIP_SIZE]; // Text to display in icon tooltip DWORD dwBalloonTimeout; // Timeout of balloon (millisecs) DWORD dwBalloonIcon; // The icon to display in the balloon. (see NIIF_* constants) }; // // Fax notification icon state array. // Several states may have the bEnable flag on. // The array is sorted by priority and EvaluateIcon() scans it looking // for the first active state. // SIconState g_Icons[ICONS_COUNT] = { {FALSE, IDI_RINGING_1, NULL, TEXT("FaxLineRings"), TEXT(""), 30000, NIIF_INFO}, // ICON_RINGING {FALSE, IDI_SENDING, NULL, TEXT(""), TEXT(""), 0, NIIF_INFO}, // ICON_SENDING {FALSE, IDI_RECEIVING, NULL, TEXT(""), TEXT(""), 0, NIIF_INFO}, // ICON_RECEIVING {FALSE, IDI_SEND_FAILED, NULL, TEXT("FaxError"), TEXT(""), 15000, NIIF_WARNING}, // ICON_SEND_FAILED {FALSE, IDI_RECEIVE_FAILED, NULL, TEXT("FaxError"), TEXT(""), 15000, NIIF_WARNING}, // ICON_RECEIVE_FAILED {FALSE, IDI_NEW_FAX, NULL, TEXT("FaxNew"), TEXT(""), 15000, NIIF_INFO}, // ICON_NEW_FAX {FALSE, IDI_SEND_SUCCESS, NULL, TEXT("FaxSent"), TEXT(""), 10000, NIIF_INFO}, // ICON_SEND_SUCCESS {FALSE, IDI_FAX_NORMAL, NULL, TEXT(""), TEXT(""), 0, NIIF_NONE} // ICON_IDLE }; // // Icons array for ringing animation // struct SRingIcon { HICON hIcon; // Handle to loaded icon DWORD dwIconResId; // Resource ID of icon }; #define RING_ICONS_NUM 4 // Number of frames (different icons) in ringing animation #define RING_ANIMATION_FRAME_DELAY 300 // Delay (millisecs) between ring animation frames #define RING_ANIMATION_TIMEOUT 10000 // Timeout (millisecs) of ring animation. When the timeout expires, the animation // stops and the icon becomes static. SRingIcon g_RingIcons[RING_ICONS_NUM] = { NULL, IDI_RINGING_1, NULL, IDI_RINGING_2, NULL, IDI_RINGING_3, NULL, IDI_RINGING_4 }; UINT_PTR g_uRingTimerID = 0; // Timer of ringing animation DWORD g_dwCurrRingIconIndex = 0; // Index of current frame (into g_RingIcons) DWORD g_dwRingAnimationStartTick; // Tick count (time) of animation start #define MAX_BALLOON_TEXT_LEN 256 // Max number of character in balloon text #define MAX_BALLOON_TITLE_LEN 64 // Max number of character in balloon title struct SBalloonInfo { BOOL bEnable; // This flag is set when there's a need to display some balloon. // EvaluateIcon() detects this bit, asks for a balloon and turns the bit off. BOOL bDelete; // This flag is set when there's a need to destroy some balloon. eIconState eState; // The current state of the icon TCHAR szInfo[MAX_BALLOON_TEXT_LEN]; // The text to display on the balloon TCHAR szInfoTitle[MAX_BALLOON_TITLE_LEN]; // The title to display on the balloon }; BOOL g_bIconAdded = FALSE; // Do we have an icon on the status bar? SBalloonInfo g_BalloonInfo = {0}; // The current icon + ballon state struct EVENT_INFO { DWORD dwExtStatus; // Extended status code UINT uResourceId; // String for display eIconType eIcon; }; static const EVENT_INFO g_StatusEx[] = { JS_EX_DISCONNECTED, IDS_FAX_DISCONNECTED, LIST_IMAGE_ERROR, JS_EX_INITIALIZING, IDS_FAX_INITIALIZING, LIST_IMAGE_NONE, JS_EX_DIALING, IDS_FAX_DIALING, LIST_IMAGE_NONE, JS_EX_TRANSMITTING, IDS_FAX_SENDING, LIST_IMAGE_NONE, JS_EX_ANSWERED, IDS_FAX_ANSWERED, LIST_IMAGE_NONE, JS_EX_RECEIVING, IDS_FAX_RECEIVING, LIST_IMAGE_NONE, JS_EX_LINE_UNAVAILABLE, IDS_FAX_LINE_UNAVAILABLE, LIST_IMAGE_ERROR, JS_EX_BUSY, IDS_FAX_BUSY, LIST_IMAGE_WARNING, JS_EX_NO_ANSWER, IDS_FAX_NO_ANSWER, LIST_IMAGE_WARNING, JS_EX_BAD_ADDRESS, IDS_FAX_BAD_ADDRESS, LIST_IMAGE_ERROR, JS_EX_NO_DIAL_TONE, IDS_FAX_NO_DIAL_TONE, LIST_IMAGE_ERROR, JS_EX_FATAL_ERROR, IDS_FAX_FATAL_ERROR_SND, LIST_IMAGE_ERROR, JS_EX_CALL_DELAYED, IDS_FAX_CALL_DELAYED, LIST_IMAGE_ERROR, JS_EX_CALL_BLACKLISTED, IDS_FAX_CALL_BLACKLISTED, LIST_IMAGE_ERROR, JS_EX_NOT_FAX_CALL, IDS_FAX_NOT_FAX_CALL, LIST_IMAGE_ERROR, JS_EX_PARTIALLY_RECEIVED, IDS_FAX_PARTIALLY_RECEIVED, LIST_IMAGE_WARNING, JS_EX_CALL_COMPLETED, IDS_FAX_CALL_COMPLETED, LIST_IMAGE_NONE, JS_EX_CALL_ABORTED, IDS_FAX_CALL_ABORTED, LIST_IMAGE_NONE, 0, 0, LIST_IMAGE_NONE }; ///////////////////////////////////////////////////////////////////// // Function prototypes // BOOL WINAPI DllMain(HINSTANCE hModule, DWORD dwReason, void* lpReserved); void GetConfiguration(); DWORD WaitForRestartThread(LPVOID ThreadData); VOID WaitForFaxRestart(HWND hWnd); LRESULT CALLBACK NotifyWndProc (HWND hwnd, UINT msg, WPARAM wp, LPARAM lp); BOOL Connect(); BOOL RegisterForServerEvents(); VOID OnFaxEvent(FAX_EVENT_EX *pEvent); VOID OnNewCall (const FAX_EVENT_NEW_CALL &NewCall); VOID StatusUpdate (PFAX_JOB_STATUS pStatus); BOOL GetStatusEx(PFAX_JOB_STATUS pStatus, eIconType* peIcon, TCHAR* ptsStatusEx, DWORD dwSize); BOOL IsUserGrantedAccess(DWORD); void EvaluateIcon(); void SetIconState(eIconState eIcon, BOOL bEnable, TCHAR* ptsStatus = NULL); VOID AnswerTheCall(); VOID InvokeClientConsole(); VOID DoFaxContextMenu(HWND hwnd); VOID OnTrayCallback (HWND hwnd, WPARAM wp, LPARAM lp); VOID CALLBACK RingTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime); VOID OnDeviceRing(DWORD dwDeviceID); VOID InitGlobals (); VOID GetRemoteId(PFAX_JOB_STATUS pStatus); BOOL InitModule (); BOOL DestroyModule (); DWORD CheckAnswerNowCapability (BOOL bForceReconnect, LPDWORD lpdwDeviceId /* = NULL */); VOID FaxPrinterProperties(DWORD dwPage); VOID CopyLTRString(TCHAR* szDest, LPCTSTR szSource, DWORD dwSize); ////////////////////////////////////////////////////////////////////// // Implementation // extern "C" BOOL FaxMonitorShutdown() { g_bShuttingDown = TRUE; return DestroyModule(); } // FaxMonitorShutdown extern "C" BOOL IsFaxMessage( PMSG pMsg ) /*++ Routine name : IsFaxMessage Routine description: Fax message handle Arguments: pMsg - pointer to a message Return Value: TRUE if the message was handled FALSE otherwise --*/ { BOOL bRes = FALSE; if(g_hMonitorDlg) { bRes = IsDialogMessage(g_hMonitorDlg, pMsg); } return bRes; } // IsFaxMessage VOID InitGlobals () /*++ Routine name : InitGlobals Routine description: Initializes all server connection related global variables Author: Eran Yariv (EranY), Dec, 2000 Arguments: Return Value: None. --*/ { DBG_ENTER(TEXT("InitGlobals")); g_hFaxSvcHandle = NULL; g_dwlCurrentMsgID = 0; g_dwCurrentJobID = 0; g_hNotification = NULL; g_hCall = NULL; g_szAddress[0] = TEXT('\0'); g_szRemoteId[0] = TEXT('\0'); g_bRecipientNameValid = FALSE; g_szRecipientName[0] = TEXT('\0'); BOOL bDesktopSKU = IsDesktopSKU(); g_ConfigOptions.dwMonitorDeviceId = 0; g_ConfigOptions.bSend = FALSE; g_ConfigOptions.bReceive = FALSE; g_ConfigOptions.dwManualAnswerDeviceId = 0; g_ConfigOptions.dwAccessRights = 0; g_ConfigOptions.bNotifyProgress = bDesktopSKU; g_ConfigOptions.bNotifyInCompletion = bDesktopSKU; g_ConfigOptions.bNotifyOutCompletion = bDesktopSKU; g_ConfigOptions.bMonitorOnSend = bDesktopSKU; g_ConfigOptions.bMonitorOnReceive = bDesktopSKU; g_ConfigOptions.bSoundOnRing = bDesktopSKU; g_ConfigOptions.bSoundOnReceive = bDesktopSKU; g_ConfigOptions.bSoundOnSent = bDesktopSKU; g_ConfigOptions.bSoundOnError = bDesktopSKU; for (DWORD dw = 0; dw < ICONS_COUNT; dw++) { g_Icons[dw].bEnable = FALSE; g_Icons[dw].tszToolTip[0] = TEXT('\0'); } g_uRingTimerID = 0; g_dwCurrRingIconIndex = 0; g_dwRingAnimationStartTick = 0; g_BalloonInfo.bEnable = FALSE; g_BalloonInfo.bDelete = FALSE; g_BalloonInfo.szInfo[0] = TEXT('\0'); g_BalloonInfo.szInfoTitle[0] = TEXT('\0'); g_CurrentIcon = ICONS_COUNT; } // InitGlobals BOOL InitModule () /*++ Routine name : InitModule Routine description: Initializes the DLL module. Call only once. Author: Eran Yariv (EranY), Mar, 2001 Arguments: Return Value: TRUE on success --*/ { BOOL bRes = FALSE; DWORD dwRes; DBG_ENTER(TEXT("InitModule"), bRes); InitGlobals (); // // Don't have DllMain called for thread inits and shutdown. // DisableThreadLibraryCalls(g_hModule); // // Load icons // for(DWORD dw=0; dw < ICONS_COUNT; ++dw) { g_Icons[dw].hIcon = LoadIcon(g_hModule, MAKEINTRESOURCE(g_Icons[dw].dwIconResId)); if(!g_Icons[dw].hIcon) { dwRes = GetLastError(); CALL_FAIL (RESOURCE_ERR, TEXT ("LoadIcon"), dwRes); bRes = FALSE; return bRes; } } // // Load animation icons // for(dw=0; dw < RING_ICONS_NUM; ++dw) { g_RingIcons[dw].hIcon = LoadIcon(g_hModule, MAKEINTRESOURCE(g_RingIcons[dw].dwIconResId)); if(!g_RingIcons[dw].hIcon) { dwRes = GetLastError(); CALL_FAIL (RESOURCE_ERR, TEXT ("LoadIcon"), dwRes); bRes = FALSE; return bRes; } } // // Load "new fax" tooltip // if (ERROR_SUCCESS != (dwRes = LoadAndFormatString (IDS_NEW_FAX, g_Icons[ICON_NEW_FAX].tszToolTip, TOOLTIP_SIZE))) { SetLastError (dwRes); bRes = FALSE; return bRes; } // // Register our hidden window and create it // WNDCLASSEX wndclass = {0}; wndclass.cbSize = sizeof(wndclass); wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = NotifyWndProc; wndclass.hInstance = g_hModule; wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = (HBRUSH) (COLOR_INACTIVEBORDER + 1); wndclass.lpszClassName = FAXSTAT_WINCLASS; if(!RegisterClassEx(&wndclass)) { dwRes = GetLastError(); CALL_FAIL (WINDOW_ERR, TEXT ("RegisterClassEx"), dwRes); bRes = FALSE; return bRes; } g_hWndFaxNotify = CreateWindow (FAXSTAT_WINCLASS, TEXT("HiddenFaxWindow"), 0, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, g_hModule, NULL); if(!g_hWndFaxNotify) { dwRes = GetLastError(); CALL_FAIL (WINDOW_ERR, TEXT ("CreateWindow"), dwRes); bRes = FALSE; return bRes; } // // Create stop thread event // g_hStopStartupThreadEvent = CreateEvent (NULL, FALSE, FALSE, NULL); if(!g_hStopStartupThreadEvent) { dwRes = GetLastError(); CALL_FAIL (WINDOW_ERR, TEXT ("CreateEvent"), dwRes); bRes = FALSE; return bRes; } // // Launch a thread which waits for the local fax service startup event. // When the event is set, the thread posts WM_FAX_STARTED to our hidden window. // WaitForFaxRestart(g_hWndFaxNotify); bRes = TRUE; return bRes; } // InitModule DWORD WaitForBackgroundThreadToDie () { DWORD dwRes = ERROR_SUCCESS; DBG_ENTER(TEXT("WaitForBackgroundThreadToDie"), dwRes); ASSERTION (g_hServerStartupThread); DWORD dwWaitRes = WaitForSingleObject (g_hServerStartupThread, INFINITE); switch (dwWaitRes) { case WAIT_OBJECT_0: // // Thread terminated - hooray // VERBOSE (DBG_MSG, TEXT("Background thread terminated successfully")); CloseHandle (g_hServerStartupThread); g_hServerStartupThread = NULL; break; case WAIT_FAILED: // // Error waiting for thread to die // dwRes = GetLastError (); VERBOSE (DBG_MSG, TEXT("Can't wait for background thread: %ld"), dwRes); break; default: // // No other return value from WaitForSingleObject is valid // ASSERTION_FAILURE; dwRes = ERROR_GEN_FAILURE; break; } return dwRes; } // WaitForBackgroundThreadToDie BOOL DestroyModule () /*++ Routine name : DestroyModule Routine description: Destroys the DLL module. Call only once. Author: Eran Yariv (EranY), Mar, 2001 Arguments: Return Value: TRUE on success --*/ { BOOL bRes = FALSE; DBG_ENTER(TEXT("DestroyModule"), bRes); // // Prepare for shutdown - destroy all active windows // if (g_hMonitorDlg) { // // Fake 'hide' key press on the monitor dialog // SendMessage (g_hMonitorDlg, WM_COMMAND, IDCANCEL, 0); } // // Delete the system tray icon if existed // if (g_bIconAdded) { NOTIFYICONDATA iconData = {0}; iconData.cbSize = sizeof(iconData); iconData.hWnd = g_hWndFaxNotify; iconData.uID = TRAY_ICON_ID; Shell_NotifyIcon(NIM_DELETE, &iconData); g_bIconAdded = FALSE; } // // Destory this window // if (!DestroyWindow (g_hWndFaxNotify)) { CALL_FAIL (WINDOW_ERR, TEXT("DestroyWindow"), GetLastError ()); } g_hWndFaxNotify = NULL; // // Signal the DLL shutdown event // ASSERTION (g_hStopStartupThreadEvent); if (SetEvent (g_hStopStartupThreadEvent)) { VERBOSE (DBG_MSG, TEXT("DLL shutdown event signaled")); if (g_hServerStartupThread) { // // Wait for background thread to die // DWORD dwRes = WaitForBackgroundThreadToDie(); if (ERROR_SUCCESS != dwRes) { CALL_FAIL (GENERAL_ERR, TEXT("WaitForBackgroundThreadToDie"), dwRes); } } } else { CALL_FAIL (GENERAL_ERR, TEXT("SetEvent (g_hStopStartupThreadEvent)"), GetLastError ()); } // // Release our DLL shutdown event // CloseHandle (g_hStopStartupThreadEvent); g_hStopStartupThreadEvent = NULL; // // Free the data of the monitor module // FreeMonitorDialogData (TRUE); // // Unregister window class // if (!UnregisterClass (FAXSTAT_WINCLASS, g_hModule)) { CALL_FAIL (WINDOW_ERR, TEXT("UnregisterClass"), GetLastError ()); } // // Unregister from server notifications // if (g_hNotification) { if(!FaxUnregisterForServerEvents(g_hNotification)) { CALL_FAIL (RPC_ERR, TEXT("FaxUnregisterForServerEvents"), GetLastError()); } g_hNotification = NULL; } // // Disconnect from the fax service // if (g_hFaxSvcHandle) { if (!FaxClose (g_hFaxSvcHandle)) { CALL_FAIL (GENERAL_ERR, TEXT("FaxClose"), GetLastError ()); } g_hFaxSvcHandle = NULL; } // // Unload all icons // for (DWORD dw = 0; dw < ICONS_COUNT; dw++) { if (g_Icons[dw].hIcon) { if (!DestroyIcon (g_Icons[dw].hIcon)) { CALL_FAIL (WINDOW_ERR, TEXT("DestroyIcon"), GetLastError ()); } g_Icons[dw].hIcon = NULL; } } for (DWORD dw = 0; dw < RING_ICONS_NUM; dw++) { if (g_RingIcons[dw].hIcon) { if (!DestroyIcon (g_RingIcons[dw].hIcon)) { CALL_FAIL (WINDOW_ERR, TEXT("DestroyIcon"), GetLastError ()); } g_RingIcons[dw].hIcon = NULL; } } // // Kill animation timer // if(g_uRingTimerID) { if (!KillTimer(NULL, g_uRingTimerID)) { CALL_FAIL (GENERAL_ERR, TEXT("KillTimer"), GetLastError ()); } g_uRingTimerID = NULL; } bRes = TRUE; return bRes; } // DestroyModule BOOL WINAPI DllMain( HINSTANCE hModule, DWORD dwReason, void* lpReserved ) /*++ Routine description: Fax notifications startup Arguments: hinstDLL - handle to the DLL module fdwReason - reason for calling function lpvReserved - reserved Return Value: TRUE if success FALSE otherwise --*/ { BOOL bRes = TRUE; DBG_ENTER(TEXT("DllMain"), bRes, TEXT("Reason = %ld"), dwReason); switch (dwReason) { case DLL_PROCESS_ATTACH: g_hModule = hModule; g_hResource = GetResInstance(hModule); if(!g_hResource) { return FALSE; } bRes = InitModule (); return bRes; case DLL_PROCESS_DETACH: // // If g_bShuttingDown is not TRUE, someone (STOBJECT.DLL) forgot to call // FaxMonitorShutdown() (our shutdown procedure) before doing FreeLibrary on us. // This is not the way we're supposed to be used - a bug. // ASSERTION (g_bShuttingDown); HeapCleanup(); FreeResInstance(); return bRes; default: return bRes; } } // DllMain DWORD WaitForRestartThread( LPVOID ThreadData ) { // // Wait for event to be signaled, indicating fax service started // DWORD dwRes = ERROR_SUCCESS; HKEY hKey = NULL; HANDLE hEvents[2] = {0}; DBG_ENTER(TEXT("WaitForRestartThread"), dwRes); // // NOTICE: Events order in the array matters - we want to detect DLL shutdown BEFORE we detect service startup // hEvents[0] = g_hStopStartupThreadEvent; if (hEvents[1]) { CloseHandle (hEvents[1]); } if (hKey) { RegCloseKey (hKey); } // // Obtain service startup event handle. // We need to do this every time before calling WaitForMultipleObjects // because the event returned from CreateSvcStartEvent is a single-shot event. // dwRes = CreateSvcStartEvent (&(hEvents[1]), &hKey); if (ERROR_SUCCESS != dwRes) { CALL_FAIL (GENERAL_ERR, TEXT("CreateSvcStartEvent"), dwRes); goto ExitThisThread; } // // Wait for either the service startup event or the DLL shutdown event // DWORD dwWaitRes = WaitForMultipleObjects(ARR_SIZE(hEvents), hEvents, FALSE, INFINITE); switch (dwWaitRes) { case WAIT_OBJECT_0 + 1: // // Service startup event // VERBOSE (DBG_MSG, TEXT("Service startup event received")); PostMessage((HWND) ThreadData, WM_FAX_STARTED, 0, 0); break; case WAIT_OBJECT_0: // // Stop thread event - exit thread ASAP. // VERBOSE (DBG_MSG, TEXT("DLL shutdown event received")); break; case WAIT_FAILED: dwRes = GetLastError (); CALL_FAIL (GENERAL_ERR, TEXT("WaitForMultipleObjects"), dwRes); break; default: // // No other return value from WaitForMultipleObjects is valid. // ASSERTION_FAILURE; break; } // switch (dwWaitRes) ExitThisThread: if (hEvents[1]) { CloseHandle (hEvents[1]); } if (hKey) { RegCloseKey (hKey); } return dwRes; } // WaitForRestartThread VOID WaitForFaxRestart( HWND hWnd ) { DBG_ENTER(TEXT("WaitForFaxRestart")); if (g_bShuttingDown) { // // Shutting down - no thread creation allowed // return; } if (g_hServerStartupThread) { // // Signal to Startup Thread to stop // if (!SetEvent (g_hStopStartupThreadEvent)) { CALL_FAIL (GENERAL_ERR, TEXT("SetEvent"), GetLastError()); return; } // // A Previous thead exists - wait for it to die // DWORD dwRes = WaitForBackgroundThreadToDie(); if (ERROR_SUCCESS != dwRes) { CALL_FAIL (GENERAL_ERR, TEXT("WaitForBackgroundThreadToDie"), dwRes); return; } } if (!ResetEvent (g_hStopStartupThreadEvent)) { CALL_FAIL (GENERAL_ERR, TEXT("ResetEvent"), GetLastError()); return; } ASSERTION (NULL == g_hServerStartupThread); g_hServerStartupThread = CreateThread(NULL, 0, WaitForRestartThread, (LPVOID) hWnd, 0, NULL); if (g_hServerStartupThread) { VERBOSE (DBG_MSG, TEXT("Background therad created successfully")); } else { CALL_FAIL (GENERAL_ERR, TEXT("CreateThread(WaitForRestartThread)"), GetLastError()); } } // WaitForFaxRestart void GetConfiguration() /*++ Routine description: Read notification configuration from the registry Arguments: none Return Value: none --*/ { DWORD dwRes; DBG_ENTER(TEXT("GetConfiguration")); HKEY hKey; if(Connect()) { if (!FaxAccessCheckEx(g_hFaxSvcHandle, MAXIMUM_ALLOWED, &g_ConfigOptions.dwAccessRights)) { dwRes = GetLastError (); CALL_FAIL (RPC_ERR, TEXT("FaxAccessCheckEx"), dwRes); } } dwRes = RegOpenKeyEx(HKEY_CURRENT_USER, REGKEY_FAX_USERINFO, 0, KEY_READ, &hKey); if (dwRes != ERROR_SUCCESS) { // // Can't open user information key - use defaults // CALL_FAIL (GENERAL_ERR, TEXT("RegOpenKeyEx(REGKEY_FAX_USERINFO)"), dwRes); BOOL bDesktopSKU = IsDesktopSKU(); g_ConfigOptions.dwMonitorDeviceId = 0; g_ConfigOptions.bNotifyProgress = bDesktopSKU; g_ConfigOptions.bNotifyInCompletion = bDesktopSKU; g_ConfigOptions.bNotifyOutCompletion = bDesktopSKU; g_ConfigOptions.bMonitorOnSend = bDesktopSKU; g_ConfigOptions.bMonitorOnReceive = bDesktopSKU; g_ConfigOptions.bSoundOnRing = bDesktopSKU; g_ConfigOptions.bSoundOnReceive = bDesktopSKU; g_ConfigOptions.bSoundOnSent = bDesktopSKU; g_ConfigOptions.bSoundOnError = bDesktopSKU; } else { GetRegistryDwordEx(hKey, REGVAL_NOTIFY_PROGRESS, &g_ConfigOptions.bNotifyProgress); GetRegistryDwordEx(hKey, REGVAL_NOTIFY_IN_COMPLETE, &g_ConfigOptions.bNotifyInCompletion); GetRegistryDwordEx(hKey, REGVAL_NOTIFY_OUT_COMPLETE, &g_ConfigOptions.bNotifyOutCompletion); GetRegistryDwordEx(hKey, REGVAL_MONITOR_ON_SEND, &g_ConfigOptions.bMonitorOnSend); GetRegistryDwordEx(hKey, REGVAL_MONITOR_ON_RECEIVE, &g_ConfigOptions.bMonitorOnReceive); GetRegistryDwordEx(hKey, REGVAL_SOUND_ON_RING, &g_ConfigOptions.bSoundOnRing); GetRegistryDwordEx(hKey, REGVAL_SOUND_ON_RECEIVE, &g_ConfigOptions.bSoundOnReceive); GetRegistryDwordEx(hKey, REGVAL_SOUND_ON_SENT, &g_ConfigOptions.bSoundOnSent); GetRegistryDwordEx(hKey, REGVAL_SOUND_ON_ERROR, &g_ConfigOptions.bSoundOnError); GetRegistryDwordEx(hKey, REGVAL_DEVICE_TO_MONITOR, &g_ConfigOptions.dwMonitorDeviceId); RegCloseKey( hKey ); } g_ConfigOptions.dwManualAnswerDeviceId = 0; if(Connect() && IsUserGrantedAccess(FAX_ACCESS_QUERY_CONFIG)) { PFAX_PORT_INFO_EX pPortsInfo = NULL; DWORD dwPorts = 0; if(!FaxEnumPortsEx(g_hFaxSvcHandle, &pPortsInfo, &dwPorts)) { dwRes = GetLastError (); CALL_FAIL (RPC_ERR, TEXT("FaxEnumPortsEx"), dwRes); } else { if (dwPorts) { DWORD dwDevIndex = 0; for(DWORD dw=0; dw < dwPorts; ++dw) { // // Iterate all fax devices // if ((g_ConfigOptions.dwMonitorDeviceId == pPortsInfo[dw].dwDeviceID) || // Found the monitored device or (!g_ConfigOptions.dwMonitorDeviceId && // No monitored device and (pPortsInfo[dw].bSend || // the device is send-enabled or (FAX_DEVICE_RECEIVE_MODE_OFF != pPortsInfo[dw].ReceiveMode) // the device is receive-enabled ) ) ) { // // Mark the index of the device we use for monitoring. // dwDevIndex = dw; } if (FAX_DEVICE_RECEIVE_MODE_MANUAL == pPortsInfo[dw].ReceiveMode) { // // Mark the id of the device set for manual-answer // g_ConfigOptions.dwManualAnswerDeviceId = pPortsInfo[dw].dwDeviceID; } } // // Update the device used for monitoring from the index we found // g_ConfigOptions.dwMonitorDeviceId = pPortsInfo[dwDevIndex].dwDeviceID; g_ConfigOptions.bSend = pPortsInfo[dwDevIndex].bSend; g_ConfigOptions.bReceive = FAX_DEVICE_RECEIVE_MODE_OFF != pPortsInfo[dwDevIndex].ReceiveMode; } else { // // No devices // g_ConfigOptions.dwMonitorDeviceId = 0; g_ConfigOptions.bSend = FALSE; g_ConfigOptions.bReceive = FALSE; } FaxFreeBuffer(pPortsInfo); } } } // GetConfiguration BOOL Connect( ) { BOOL bRes = FALSE; DBG_ENTER(TEXT("Connect"), bRes); if (g_hFaxSvcHandle) { // // Already connected // bRes = TRUE; return bRes; } if (!FaxConnectFaxServer(NULL, &g_hFaxSvcHandle)) { CALL_FAIL (RPC_ERR, TEXT("FaxConnectFaxServer"), GetLastError()); return bRes; } bRes = TRUE; return bRes; } // Connect VOID CALLBACK WaitForFaxRestartTimerProc( HWND hwnd, // handle to window UINT uMsg, // WM_TIMER message UINT_PTR idEvent, // timer identifier DWORD dwTime // current system time ) /*++ Routine description: Timer proc for restart waiting thread Arguments: hwnd - handle to window uMsg - WM_TIMER message idEvent - timer identifier dwTime - current system time Return Value: none --*/ { DBG_ENTER(TEXT("WaitForFaxRestartTimerProc")); if(!KillTimer(NULL, idEvent)) { CALL_FAIL (GENERAL_ERR, TEXT ("KillTimer"), GetLastError()); } WaitForFaxRestart(g_hWndFaxNotify); } // WaitForFaxRestartTimerProc BOOL RegisterForServerEvents() /*++ Routine description: Register for fax notifications Arguments: none Return Value: none --*/ { BOOL bRes = FALSE; DWORD dwEventTypes; DBG_ENTER(TEXT("RegisterForServerEvents")); if (!Connect()) { goto exit; } // // Load configuration // GetConfiguration (); if(g_hNotification) { if(!FaxUnregisterForServerEvents(g_hNotification)) { CALL_FAIL (RPC_ERR, TEXT("FaxUnregisterForServerEvents"), GetLastError()); } g_hNotification = NULL; } // // Register for the fax events // dwEventTypes = FAX_EVENT_TYPE_FXSSVC_ENDED; VERBOSE (DBG_MSG, TEXT("User has the following rights: %x. Asking for FAX_EVENT_TYPE_FXSSVC_ENDED"), g_ConfigOptions.dwAccessRights); if(IsUserGrantedAccess(FAX_ACCESS_SUBMIT) || IsUserGrantedAccess(FAX_ACCESS_SUBMIT_NORMAL) || IsUserGrantedAccess(FAX_ACCESS_SUBMIT_HIGH)) // User can submit new faxes (and view his own faxes) { dwEventTypes |= FAX_EVENT_TYPE_OUT_QUEUE; VERBOSE (DBG_MSG, TEXT("Also asking for FAX_EVENT_TYPE_OUT_QUEUE")); } if(IsUserGrantedAccess(FAX_ACCESS_QUERY_JOBS)) // User can view all jobs (in and out) { dwEventTypes |= FAX_EVENT_TYPE_OUT_QUEUE | FAX_EVENT_TYPE_IN_QUEUE; VERBOSE (DBG_MSG, TEXT("Also asking for FAX_EVENT_TYPE_OUT_QUEUE & FAX_EVENT_TYPE_IN_QUEUE")); } if(IsUserGrantedAccess(FAX_ACCESS_QUERY_CONFIG)) { dwEventTypes |= FAX_EVENT_TYPE_CONFIG | FAX_EVENT_TYPE_DEVICE_STATUS; VERBOSE (DBG_MSG, TEXT("Also asking for FAX_EVENT_TYPE_CONFIG & FAX_EVENT_TYPE_DEVICE_STATUS")); } if(IsUserGrantedAccess(FAX_ACCESS_QUERY_IN_ARCHIVE)) { dwEventTypes |= FAX_EVENT_TYPE_IN_ARCHIVE | FAX_EVENT_TYPE_NEW_CALL; VERBOSE (DBG_MSG, TEXT("Also asking for FAX_EVENT_TYPE_IN_ARCHIVE")); } if (!FaxRegisterForServerEvents (g_hFaxSvcHandle, dwEventTypes, // Types of events to receive NULL, // Not using completion ports 0, // Not using completion ports g_hWndFaxNotify, // Handle of window to receive notification messages WM_FAX_EVENT, // Message id &g_hNotification)) // Notification handle { DWORD dwRes = GetLastError (); CALL_FAIL (RPC_ERR, TEXT("FaxRegisterForServerEvents"), dwRes); g_hNotification = NULL; } else { bRes = TRUE; } if(!FaxRelease(g_hFaxSvcHandle)) { CALL_FAIL (RPC_ERR, TEXT("FaxRelease"), GetLastError ()); } exit: if(!bRes) { // // FaxRegisterForServerEvents failed, try again 1 minute later // if(!SetTimer(NULL, 0, 60000, WaitForFaxRestartTimerProc)) { CALL_FAIL (GENERAL_ERR, TEXT("SetTimer"), GetLastError ()); } } return bRes; } // RegisterForServerEvents VOID OnFaxEvent(FAX_EVENT_EX* pEvent) /*++ Routine description: Handle fax events Arguments: pEvent - fax event data Return Value: none --*/ { DBG_ENTER(TEXT("OnFaxEvent"), TEXT("%x"), pEvent); if(!pEvent || pEvent->dwSizeOfStruct != sizeof(FAX_EVENT_EX)) { VERBOSE (DBG_MSG, TEXT("Either event is bad or it has bad size")); return; } switch (pEvent->EventType) { case FAX_EVENT_TYPE_NEW_CALL: OnNewCall (pEvent->EventInfo.NewCall); break; case FAX_EVENT_TYPE_IN_QUEUE: case FAX_EVENT_TYPE_OUT_QUEUE: switch (pEvent->EventInfo.JobInfo.Type) { case FAX_JOB_EVENT_TYPE_ADDED: case FAX_JOB_EVENT_TYPE_REMOVED: break; case FAX_JOB_EVENT_TYPE_STATUS: if(pEvent->EventInfo.JobInfo.pJobData && pEvent->EventInfo.JobInfo.pJobData->dwDeviceID && pEvent->EventInfo.JobInfo.pJobData->dwDeviceID == g_ConfigOptions.dwMonitorDeviceId) { if(g_dwlCurrentMsgID != pEvent->EventInfo.JobInfo.dwlMessageId) { g_bRecipientNameValid = FALSE; } g_dwlCurrentMsgID = pEvent->EventInfo.JobInfo.dwlMessageId; } if(g_dwlCurrentMsgID == pEvent->EventInfo.JobInfo.dwlMessageId) { StatusUpdate(pEvent->EventInfo.JobInfo.pJobData); } break; } break; case FAX_EVENT_TYPE_IN_ARCHIVE: if(FAX_JOB_EVENT_TYPE_ADDED == pEvent->EventInfo.JobInfo.Type) { g_dwlNewMsgId = pEvent->EventInfo.JobInfo.dwlMessageId; SetIconState(ICON_NEW_FAX, TRUE); } break; case FAX_EVENT_TYPE_CONFIG: if (FAX_CONFIG_TYPE_SECURITY == pEvent->EventInfo.ConfigType) { // // Security has changed. // We should re-register for events now. // Also re-read the current user rights // RegisterForServerEvents(); } else if (FAX_CONFIG_TYPE_DEVICES == pEvent->EventInfo.ConfigType) { // // Device configuration has changed. // The only reason we need to know that is because the device we were listening on might be gone now. // If that's true, we should pick the first available device as the monitoring device. // GetConfiguration(); UpdateMonitorData(g_hMonitorDlg); } else { // // Non-interesting configuraton change - ignore. // } break; case FAX_EVENT_TYPE_DEVICE_STATUS: if(pEvent->EventInfo.DeviceStatus.dwDeviceId == g_ConfigOptions.dwMonitorDeviceId || pEvent->EventInfo.DeviceStatus.dwDeviceId == g_ConfigOptions.dwManualAnswerDeviceId) { // // we only care about the monitored / manual-answer devices // if ((pEvent->EventInfo.DeviceStatus.dwNewStatus) & FAX_DEVICE_STATUS_RINGING) { // // Device is ringing // OnDeviceRing (pEvent->EventInfo.DeviceStatus.dwDeviceId); } else { if (FAX_RINGING == g_devState) { // // Device is not ringing anymore but the monitor shows 'ringing'. // Set the monitor to idle state. // SetStatusMonitorDeviceState(FAX_IDLE); } } } break; case FAX_EVENT_TYPE_FXSSVC_ENDED: // // Service was stopped // SetIconState(ICON_RINGING, FALSE); SetIconState(ICON_SENDING, FALSE); SetIconState(ICON_RECEIVING, FALSE); SetStatusMonitorDeviceState(FAX_IDLE); // // We just lost our RPC connection handle and our notification handle. Close and zero them. // if (g_hNotification) { FaxUnregisterForServerEvents (g_hNotification); g_hNotification = NULL; } if (g_hFaxSvcHandle) { FaxClose (g_hFaxSvcHandle); g_hFaxSvcHandle = NULL; } WaitForFaxRestart(g_hWndFaxNotify); break; } FaxFreeBuffer (pEvent); } // OnFaxEvent VOID OnDeviceRing( DWORD dwDeviceID ) /*++ Routine description: Called when a device is ringing Arguments: dwDeviceID - device ID Return Value: none --*/ { DBG_ENTER(TEXT("OnDeviceRing"), TEXT("%d"), dwDeviceID); // // It can be monitored or manual answer device // SetStatusMonitorDeviceState(FAX_RINGING); AddStatusMonitorLogEvent(LIST_IMAGE_NONE, IDS_RINGING); if(g_ConfigOptions.bSoundOnRing) { if(!PlaySound(g_Icons[ICON_RINGING].pctsSound, NULL, SND_ASYNC | SND_APPLICATION | SND_NODEFAULT)) { CALL_FAIL (WINDOW_ERR, TEXT ("PlaySound"), 0); } } } VOID OnNewCall ( const FAX_EVENT_NEW_CALL &NewCall ) /*++ Routine description: Handle "new call" fax event Arguments: NewCall - fax event data Return Value: none --*/ { DBG_ENTER(TEXT("OnNewCall")); // // It can be any manual answer device // g_hCall = NewCall.hCall; if(NewCall.hCall) { LPCTSTR lpctstrParam = NULL; DWORD dwStringResId = IDS_INCOMING_CALL; CopyLTRString(g_szAddress, NewCall.lptstrCallerId, ARR_SIZE(g_szAddress) - 1); _tcscpy(g_szRemoteId, g_szAddress); if(NewCall.lptstrCallerId && _tcslen(NewCall.lptstrCallerId)) { // // We know the caller id. // Use another string which formats the caller ID parameter // lpctstrParam = NewCall.lptstrCallerId; dwStringResId = IDS_INCOMING_CALL_FROM; } TCHAR tszEvent[MAX_PATH] = {0}; AddStatusMonitorLogEvent (LIST_IMAGE_NONE, dwStringResId, lpctstrParam, tszEvent, ARR_SIZE(tszEvent)); SetStatusMonitorDeviceState(FAX_RINGING); SetIconState(ICON_RINGING, TRUE, tszEvent); } else { // // Call is gone // SetStatusMonitorDeviceState(FAX_IDLE); SetIconState(ICON_RINGING, FALSE, TEXT("")); } } // OnNewCall VOID GetRemoteId( PFAX_JOB_STATUS pStatus ) /*++ Routine description: Write Sender ID or Recipient ID into g_szRemoteId Sender ID (receive): TSID or Caller ID or "unknown caller" Recipient ID (send): Recipient name or CSID or Recipient phone number. Arguments: pStatus - job status data Return Value: none --*/ { DBG_ENTER(TEXT("GetRemoteId")); if(!pStatus) { return; } if(JT_SEND == pStatus->dwJobType) { // // Recipient ID (send) // if(!g_bRecipientNameValid) { // // Store the recipient name into g_szRecipientName // PFAX_JOB_ENTRY_EX pJobEntry = NULL; if(!FaxGetJobEx(g_hFaxSvcHandle, g_dwlCurrentMsgID, &pJobEntry)) { CALL_FAIL (RPC_ERR, TEXT ("FaxGetJobEx"), GetLastError()); g_szRecipientName[0] = TEXT('\0'); } else { if(pJobEntry->lpctstrRecipientName && _tcslen(pJobEntry->lpctstrRecipientName)) { _tcsncpy(g_szRecipientName, pJobEntry->lpctstrRecipientName, ARR_SIZE(g_szRecipientName) - 1); } else { g_szRecipientName[0] = TEXT('\0'); } g_bRecipientNameValid = TRUE; FaxFreeBuffer(pJobEntry); } } if(_tcslen(g_szRecipientName)) { // // Recipient name // _tcsncpy(g_szRemoteId, g_szRecipientName, ARR_SIZE(g_szRemoteId) - 1); } else if(pStatus->lpctstrCsid && _tcslen(pStatus->lpctstrCsid)) { // // CSID // CopyLTRString(g_szRemoteId, pStatus->lpctstrCsid, ARR_SIZE(g_szRemoteId) - 1); } else if(pStatus->lpctstrCallerID && _tcslen(pStatus->lpctstrCallerID)) { // // Recipient number // For outgoing fax FAX_JOB_STATUS.lpctstrCallerID field // contains a recipient fax number. // CopyLTRString(g_szRemoteId, pStatus->lpctstrCallerID, ARR_SIZE(g_szRemoteId) - 1); } } else if(JT_RECEIVE == pStatus->dwJobType) { // // Sender ID (receive) // if(pStatus->lpctstrTsid && _tcslen(pStatus->lpctstrTsid) && pStatus->lpctstrCallerID && _tcslen(pStatus->lpctstrCallerID)) { // // We have Caller ID and TSID // TCHAR szTmp[MAX_PATH] = {0}; _sntprintf(szTmp, ARR_SIZE(szTmp)-1, TEXT("%s (%s)"), pStatus->lpctstrCallerID, pStatus->lpctstrTsid); CopyLTRString(g_szRemoteId, szTmp, ARR_SIZE(g_szRemoteId) - 1); } else if(pStatus->lpctstrTsid && _tcslen(pStatus->lpctstrTsid)) { // // TSID // CopyLTRString(g_szRemoteId, pStatus->lpctstrTsid, ARR_SIZE(g_szRemoteId) - 1); } else if(pStatus->lpctstrCallerID && _tcslen(pStatus->lpctstrCallerID)) { // // Caller ID // CopyLTRString(g_szRemoteId, pStatus->lpctstrCallerID, ARR_SIZE(g_szRemoteId) - 1); } else { // // unknown caller // _tcsncpy(g_szRemoteId, TEXT(""), ARR_SIZE(g_szRemoteId) - 1); } } } VOID StatusUpdate(PFAX_JOB_STATUS pStatus) /*++ Routine description: Handle "status update" fax event Arguments: pStatus - job status data Return Value: none --*/ { DBG_ENTER(TEXT("StatusUpdate")); DWORD dwRes; if(!pStatus) { return; } VERBOSE (DBG_MSG, TEXT("Job status event - Type=%x, QueueStatus=%x, ExtendedStatus=%x"), pStatus->dwJobType, pStatus->dwQueueStatus, pStatus->dwExtendedStatus); if(JT_RECEIVE != pStatus->dwJobType && JT_SEND != pStatus->dwJobType) { VERBOSE (DBG_MSG, TEXT("Job type (%d) is not JT_RECEIVE or JT_SEND. Ignoring."), pStatus->dwJobType); return; } eIconType eIcon = LIST_IMAGE_NONE; // New icon to set DWORD dwStatusId = 0; // string resource ID TCHAR tszStatus[MAX_PATH] = {0}; // String to show in status monitor BOOL bStatus = FALSE; // TRUE if tszStatus has valid string if(pStatus->dwQueueStatus & JS_PAUSED) { // // The job has been paused in the outbox queue after a failure // g_dwlCurrentMsgID = 0; return; } if(pStatus->dwQueueStatus & JS_COMPLETED || pStatus->dwQueueStatus & JS_ROUTING) { // // Incoming job sends JS_ROUTING status by completion // if(JS_EX_PARTIALLY_RECEIVED == pStatus->dwExtendedStatus) { bStatus = GetStatusEx(pStatus, &eIcon, tszStatus, ARR_SIZE(tszStatus) - 1); } else { eIcon = LIST_IMAGE_SUCCESS; dwStatusId = (JT_SEND == pStatus->dwJobType) ? IDS_FAX_SNT_COMPLETED : IDS_FAX_RCV_COMPLETED; } } else if(pStatus->dwQueueStatus & JS_CANCELING) { dwStatusId = IDS_FAX_CANCELING; } else if(pStatus->dwQueueStatus & JS_CANCELED) { dwStatusId = IDS_FAX_CANCELED; } else if(pStatus->dwQueueStatus & JS_INPROGRESS) { GetRemoteId(pStatus); bStatus = GetStatusEx(pStatus, &eIcon, tszStatus, ARR_SIZE(tszStatus) - 1); g_dwCurrentJobID = pStatus->dwJobID; SetIconState((JT_SEND == pStatus->dwJobType) ? ICON_SENDING : ICON_RECEIVING, TRUE, tszStatus); SetStatusMonitorDeviceState((JT_SEND == pStatus->dwJobType) ? FAX_SENDING : FAX_RECEIVING); } else if(pStatus->dwQueueStatus & JS_FAILED) { if(!(bStatus = GetStatusEx(pStatus, &eIcon, tszStatus, ARR_SIZE(tszStatus) - 1))) { eIcon = LIST_IMAGE_ERROR; dwStatusId = (JT_SEND == pStatus->dwJobType) ? IDS_FAX_FATAL_ERROR_SND : IDS_FAX_FATAL_ERROR_RCV; } } else if(pStatus->dwQueueStatus & JS_RETRIES_EXCEEDED) { // // Add two strings to the log. // The first is extended status. // The second is "Retries exceeded" // if(bStatus = GetStatusEx(pStatus, &eIcon, tszStatus, ARR_SIZE(tszStatus) - 1)) { AddStatusMonitorLogEvent(eIcon, tszStatus); bStatus = FALSE; } eIcon = LIST_IMAGE_ERROR; dwStatusId = IDS_FAX_RETRIES_EXCEEDED; } else if(pStatus->dwQueueStatus & JS_RETRYING) { if(!(bStatus = GetStatusEx(pStatus, &eIcon, tszStatus, ARR_SIZE(tszStatus) - 1))) { eIcon = LIST_IMAGE_ERROR; dwStatusId = IDS_FAX_FATAL_ERROR_SND; } } if(!bStatus && dwStatusId) { if (ERROR_SUCCESS != (dwRes = LoadAndFormatString (dwStatusId, tszStatus, ARR_SIZE(tszStatus)))) { bStatus = FALSE; } else { bStatus = TRUE; } } if(bStatus) { AddStatusMonitorLogEvent (eIcon, tszStatus); } if(!(pStatus->dwQueueStatus & JS_INPROGRESS)) { g_dwCurrentJobID = 0; SetStatusMonitorDeviceState(FAX_IDLE); SetIconState(ICON_SENDING, FALSE); SetIconState(ICON_RECEIVING, FALSE); } if(pStatus->dwQueueStatus & (JS_FAILED | JS_RETRIES_EXCEEDED | JS_RETRYING)) { if(JT_SEND == pStatus->dwJobType) { g_dwlSendFailedMsgId = g_dwlCurrentMsgID; } SetIconState((JT_SEND == pStatus->dwJobType) ? ICON_SEND_FAILED : ICON_RECEIVE_FAILED, TRUE, tszStatus); } if((JT_SEND == pStatus->dwJobType) && (pStatus->dwQueueStatus & JS_COMPLETED)) { SetIconState(ICON_SEND_SUCCESS, TRUE, tszStatus); g_dwlSendSuccessMsgId = g_dwlCurrentMsgID; } } // StatusUpdate /* Unhandled Job Statuses: JS_NOLINE JS_PAUSED JS_PENDING JS_DELETING Unhandled Extneded Job Statuses: JS_EX_HANDLED */ BOOL GetStatusEx( PFAX_JOB_STATUS pStatus, eIconType* peIcon, TCHAR* ptsStatusEx, DWORD dwSize ) /*++ Routine description: Find string description and icon type for a job according to its extended status Arguments: pStatus - [in] job status data peIcon - [out] job icon type ptsStatusEx - [out] job status string dwSize - [in] status string size Return Value: TRUE if success FALSE otherwise --*/ { BOOL bRes = FALSE; DBG_ENTER(TEXT("GetStatusEx"), bRes); ASSERTION (pStatus && peIcon && ptsStatusEx); TCHAR tszFormat[MAX_PATH]={0}; if (pStatus->lpctstrExtendedStatus) { // // FSP provided proprietary status string - use it as is. // *peIcon = LIST_IMAGE_WARNING; _tcsncpy(ptsStatusEx, pStatus->lpctstrExtendedStatus, dwSize); ptsStatusEx[dwSize-1] = TEXT('\0'); bRes = TRUE; return bRes; } // // No extended status string, check for well known status code // if(!(pStatus->dwValidityMask & FAX_JOB_FIELD_STATUS_EX) || !pStatus->dwExtendedStatus) { return FALSE; } *peIcon = LIST_IMAGE_NONE; for(DWORD dw=0; g_StatusEx[dw].dwExtStatus != 0; ++dw) { if(g_StatusEx[dw].dwExtStatus == pStatus->dwExtendedStatus) { DWORD dwRes; *peIcon = g_StatusEx[dw].eIcon; if (ERROR_SUCCESS != (dwRes = LoadAndFormatString (g_StatusEx[dw].uResourceId, tszFormat, ARR_SIZE(tszFormat)))) { return bRes; } break; } } switch(pStatus->dwExtendedStatus) { case JS_EX_DIALING: // // For outgoing fax FAX_JOB_STATUS.lpctstrCallerID field // contains a recipient fax number. // CopyLTRString(g_szAddress, pStatus->lpctstrCallerID, ARR_SIZE(g_szAddress) - 1); _sntprintf(ptsStatusEx, dwSize -1, tszFormat, g_szAddress); ptsStatusEx[dwSize-1] = TEXT('\0'); break; case JS_EX_TRANSMITTING: _sntprintf(ptsStatusEx, dwSize -1, tszFormat, pStatus->dwCurrentPage, pStatus->dwPageCount); ptsStatusEx[dwSize-1] = TEXT('\0'); break; case JS_EX_RECEIVING: _sntprintf(ptsStatusEx, dwSize -1, tszFormat, pStatus->dwCurrentPage); ptsStatusEx[dwSize-1] = TEXT('\0'); break; case JS_EX_FATAL_ERROR: { DWORD dwRes; if (ERROR_SUCCESS != (dwRes = LoadAndFormatString ( (JT_SEND == pStatus->dwJobType) ? IDS_FAX_FATAL_ERROR_SND : IDS_FAX_FATAL_ERROR_RCV, ptsStatusEx, dwSize))) { return bRes; } } break; default: _tcsncpy(ptsStatusEx, tszFormat, dwSize); break; } bRes = TRUE; return bRes; } // GetStatusEx BOOL IsNotifyEnable( eIconState state ) /*++ Routine description: Check if the UI notification is enabled for a specific icon state Arguments: state [in] - icon state Return Value: TRUE if the notification is enabled FASLE otherwise --*/ { BOOL bEnable = TRUE; switch(state) { case ICON_SENDING: case ICON_RECEIVING: bEnable = g_ConfigOptions.bNotifyProgress; break; case ICON_NEW_FAX: case ICON_RECEIVE_FAILED: bEnable = g_ConfigOptions.bNotifyInCompletion; break; case ICON_SEND_SUCCESS: case ICON_SEND_FAILED: bEnable = g_ConfigOptions.bNotifyOutCompletion; break; }; return bEnable; } // IsNotifyEnable eIconState GetVisibleIconType () /*++ Routine name : GetVisibleIconType Routine description: Return the index (type) of the currently visible icon Author: Eran Yariv (EranY), May, 2001 Arguments: Return Value: Icon type --*/ { for(int index = ICON_RINGING; index < ICONS_COUNT; ++index) { if(!IsNotifyEnable(eIconState(index))) { continue; } if(g_Icons[index].bEnable) { return eIconState(index); } } return ICONS_COUNT; } // GetVisibleIconType void EvaluateIcon() /*++ Routine description: Show notification icon, tooltip and balloon according to the current icon state Arguments: Return Value: none --*/ { DBG_ENTER(TEXT("EvaluateIcon")); ASSERTION (g_hWndFaxNotify); NOTIFYICONDATA iconData = {0}; iconData.cbSize = sizeof(iconData); iconData.hWnd = g_hWndFaxNotify; iconData.uID = TRAY_ICON_ID; iconData.uFlags = NIF_MESSAGE | NIF_TIP; iconData.uCallbackMessage = WM_TRAYCALLBACK; g_CurrentIcon = GetVisibleIconType(); if(ICONS_COUNT == g_CurrentIcon) { // // No visible icon // if(g_bIconAdded) { Shell_NotifyIcon(NIM_DELETE, &iconData); g_bIconAdded = FALSE; } // // No icon - no balloon // g_BalloonInfo.bDelete = FALSE; g_BalloonInfo.bEnable = FALSE; return; } iconData.uFlags = iconData.uFlags | NIF_ICON; iconData.hIcon = g_Icons[g_CurrentIcon].hIcon; _tcscpy(iconData.szTip, g_Icons[g_CurrentIcon].tszToolTip); if(g_BalloonInfo.bEnable) { if(IsNotifyEnable(g_BalloonInfo.eState)) { // // Show balloon tooltip // iconData.uTimeout = g_Icons[g_BalloonInfo.eState].dwBalloonTimeout; iconData.uFlags = iconData.uFlags | NIF_INFO; iconData.dwInfoFlags = g_Icons[g_BalloonInfo.eState].dwBalloonIcon | NIIF_NOSOUND; _tcscpy(iconData.szInfo, g_BalloonInfo.szInfo); _tcscpy(iconData.szInfoTitle, g_BalloonInfo.szInfoTitle); } g_BalloonInfo.bEnable = FALSE; } if(g_BalloonInfo.bDelete) { // // Destroy currently open balloon tooltip // iconData.uFlags = iconData.uFlags | NIF_INFO; _tcscpy(iconData.szInfo, TEXT("")); _tcscpy(iconData.szInfoTitle, TEXT("")); g_BalloonInfo.bDelete = FALSE; } Shell_NotifyIcon(g_bIconAdded ? NIM_MODIFY : NIM_ADD, &iconData); g_bIconAdded = TRUE; } // EvaluateIcon void SetIconState( eIconState eIcon, BOOL bEnable, TCHAR* ptsStatus /* = NULL */ ) /*++ Routine description: Change notification bar icon state. Arguments: eIcon - icon type bEnable - icon state (enable/disable) ptsStatus - status string (optional) Return Value: none --*/ { DWORD dwRes; DBG_ENTER(TEXT("SetIconState"), TEXT("Icon id=%d, Enable=%d, Status=%s"), eIcon, bEnable, ptsStatus); ASSERTION (eIcon < ICONS_COUNT); if(!bEnable && eIcon != ICON_RINGING) { // // We're turning off a state - nothing special to do // goto exit; } TCHAR tsFormat[MAX_PATH]= {0}; LPCTSTR strParam = NULL; DWORD dwStringResId = 0; switch(eIcon) { case ICON_RINGING: if(bEnable) { // // Sound, Balloon, and Animation // SetIconState(ICON_SENDING, FALSE); SetIconState(ICON_RECEIVING, FALSE); g_BalloonInfo.bEnable = TRUE; g_BalloonInfo.eState = eIcon; // // Compose the balloon tooltip // strParam = NULL; dwStringResId = IDS_INCOMING_CALL; if(_tcslen(g_szAddress)) { // // Caller id is known - use it in formatted string // strParam = g_szAddress; dwStringResId = IDS_INCOMING_CALL_FROM; } if (ERROR_SUCCESS != LoadAndFormatString(dwStringResId, tsFormat, ARR_SIZE(tsFormat), strParam)) { return; } _tcsncpy(g_BalloonInfo.szInfoTitle, tsFormat, MAX_BALLOON_TITLE_LEN-1); if (ERROR_SUCCESS != LoadAndFormatString(IDS_CLICK_TO_ANSWER, g_BalloonInfo.szInfo, ARR_SIZE(g_BalloonInfo.szInfo))) { return; } // // Set tooltip // _sntprintf(g_Icons[eIcon].tszToolTip, TOOLTIP_SIZE-1, TEXT("%s\n%s"), tsFormat, g_BalloonInfo.szInfo); if(!g_uRingTimerID) { // // Set animation timer // g_uRingTimerID = SetTimer(NULL, 0, RING_ANIMATION_FRAME_DELAY, RingTimerProc); if(!g_uRingTimerID) { dwRes = GetLastError(); CALL_FAIL (GENERAL_ERR, TEXT ("SetTimer"), dwRes); } else { g_dwRingAnimationStartTick = GetTickCount(); } } } else // disable ringing { if(g_Icons[eIcon].bEnable) { // // Remove ringing balloon // g_BalloonInfo.bDelete = TRUE; } if(g_uRingTimerID) { // // kill animation timer // if(!KillTimer(NULL, g_uRingTimerID)) { dwRes = GetLastError(); CALL_FAIL (GENERAL_ERR, TEXT ("KillTimer"), dwRes); } g_uRingTimerID = 0; g_dwRingAnimationStartTick = 0; } } break; case ICON_SENDING: // // Compose tooltip // if (ERROR_SUCCESS != LoadAndFormatString (IDS_SENDING_TO, tsFormat, ARR_SIZE(tsFormat), g_szRemoteId)) { return; } _sntprintf(g_Icons[eIcon].tszToolTip, TOOLTIP_SIZE-1, TEXT("%s\n%s"), tsFormat, ptsStatus ? ptsStatus : TEXT("")); if(!g_Icons[eIcon].bEnable) { // // Turn the icon on // SetIconState(ICON_RINGING, FALSE); SetIconState(ICON_RECEIVING, FALSE); // // Open fax monitor // if(g_ConfigOptions.bMonitorOnSend) { dwRes = OpenFaxMonitor(); if(ERROR_SUCCESS != dwRes) { CALL_FAIL (GENERAL_ERR, TEXT ("OpenFaxMonitor"), dwRes); } } } break; case ICON_RECEIVING: // // Compose tooltip // strParam = NULL; dwStringResId = IDS_RECEIVING; if(_tcslen(g_szRemoteId)) { strParam = g_szRemoteId; dwStringResId = IDS_RECEIVING_FROM; } if (ERROR_SUCCESS != LoadAndFormatString (dwStringResId, tsFormat, ARR_SIZE(tsFormat), strParam)) { return; } _sntprintf(g_Icons[eIcon].tszToolTip, TOOLTIP_SIZE-1, TEXT("%s\n%s"), tsFormat, ptsStatus ? ptsStatus : TEXT("")); if(!g_Icons[eIcon].bEnable) { // // Turn the icon on // SetIconState(ICON_RINGING, FALSE); SetIconState(ICON_SENDING, FALSE); // // open fax monitor // if(g_ConfigOptions.bMonitorOnReceive) { dwRes = OpenFaxMonitor(); if(ERROR_SUCCESS != dwRes) { CALL_FAIL (GENERAL_ERR, TEXT ("OpenFaxMonitor"), dwRes); } } } break; case ICON_SEND_FAILED: // // Compose tooltip // if (ERROR_SUCCESS != LoadAndFormatString (IDS_SEND_ERROR_BALLOON, tsFormat, ARR_SIZE(tsFormat), g_szRemoteId)) { return; } _sntprintf(g_Icons[eIcon].tszToolTip, TOOLTIP_SIZE-1, TEXT("%s\n%s"), tsFormat, ptsStatus ? ptsStatus : TEXT("")); if(!g_Icons[eIcon].bEnable) { // // Turn the icon on // if(g_ConfigOptions.bSoundOnError) { if(!PlaySound(g_Icons[eIcon].pctsSound, NULL, SND_ASYNC | SND_APPLICATION | SND_NODEFAULT)) { CALL_FAIL (WINDOW_ERR, TEXT ("PlaySound"), 0); } } g_BalloonInfo.bEnable = TRUE; g_BalloonInfo.eState = eIcon; // // Compose the balloon // _tcsncpy(g_BalloonInfo.szInfoTitle, tsFormat, MAX_BALLOON_TITLE_LEN-1); _tcsncpy(g_BalloonInfo.szInfo, ptsStatus ? ptsStatus : TEXT(""), MAX_BALLOON_TEXT_LEN-1); } break; case ICON_RECEIVE_FAILED: // // Compose tooltip // strParam = NULL; dwStringResId = IDS_RCV_ERROR_BALLOON; if(_tcslen(g_szRemoteId)) { strParam = g_szRemoteId; dwStringResId = IDS_RCV_FROM_ERROR_BALLOON; } if (ERROR_SUCCESS != LoadAndFormatString (dwStringResId, tsFormat, ARR_SIZE(tsFormat), strParam)) { return; } _sntprintf(g_Icons[eIcon].tszToolTip, TOOLTIP_SIZE-1, TEXT("%s\n%s"), tsFormat, ptsStatus ? ptsStatus : TEXT("")); if(!g_Icons[eIcon].bEnable) { // // Turn the icon on // if(g_ConfigOptions.bSoundOnError) { if(!PlaySound(g_Icons[eIcon].pctsSound, NULL, SND_ASYNC | SND_APPLICATION | SND_NODEFAULT)) { CALL_FAIL (WINDOW_ERR, TEXT ("PlaySound"), 0); } } g_BalloonInfo.bEnable = TRUE; g_BalloonInfo.eState = eIcon; // // Compose the balloon // _tcsncpy(g_BalloonInfo.szInfoTitle, tsFormat, MAX_BALLOON_TITLE_LEN-1); _tcsncpy(g_BalloonInfo.szInfo, ptsStatus ? ptsStatus : TEXT(""), MAX_BALLOON_TEXT_LEN-1); } break; case ICON_NEW_FAX: // // Compose tooltip // strParam = NULL; dwStringResId = IDS_NEW_FAX_BALLOON; if(_tcslen(g_szRemoteId)) { strParam = g_szRemoteId; dwStringResId = IDS_NEW_FAX_FROM_BALLOON; } if (ERROR_SUCCESS != LoadAndFormatString (dwStringResId, tsFormat, ARR_SIZE(tsFormat), strParam)) { return; } if (ERROR_SUCCESS != LoadAndFormatString (IDS_CLICK_TO_VIEW, g_BalloonInfo.szInfo, ARR_SIZE(g_BalloonInfo.szInfo))) { return; } _sntprintf(g_Icons[eIcon].tszToolTip, TOOLTIP_SIZE-1, TEXT("%s\n%s"), tsFormat, g_BalloonInfo.szInfo); if(!g_Icons[eIcon].bEnable) { // // Turn the icon on // if (g_ConfigOptions.bSoundOnReceive) { if(!PlaySound(g_Icons[eIcon].pctsSound, NULL, SND_ASYNC | SND_APPLICATION | SND_NODEFAULT)) { CALL_FAIL (WINDOW_ERR, TEXT ("PlaySound"), 0); } } g_BalloonInfo.bEnable = TRUE; g_BalloonInfo.eState = eIcon; // // Compose the balloon // _tcsncpy(g_BalloonInfo.szInfoTitle, tsFormat, MAX_BALLOON_TITLE_LEN-1); } break; case ICON_SEND_SUCCESS: if(!g_Icons[eIcon].bEnable) { // // Turn the icon on // if(g_ConfigOptions.bSoundOnSent) { if(!PlaySound(g_Icons[eIcon].pctsSound, NULL, SND_ASYNC | SND_APPLICATION | SND_NODEFAULT)) { CALL_FAIL (WINDOW_ERR, TEXT ("PlaySound"), 0); } } g_BalloonInfo.bEnable = TRUE; g_BalloonInfo.eState = eIcon; // // Compose the balloon // if (ERROR_SUCCESS != LoadAndFormatString (IDS_SEND_OK, tsFormat, ARR_SIZE(tsFormat))) { return; } _tcsncpy(g_BalloonInfo.szInfoTitle, tsFormat, MAX_BALLOON_TITLE_LEN-1); if (ERROR_SUCCESS != LoadAndFormatString (IDS_SEND_OK_BALLOON, g_BalloonInfo.szInfo, ARR_SIZE(g_BalloonInfo.szInfo), g_szRemoteId)) { return; } } break; default: break; } exit: g_Icons[eIcon].bEnable = bEnable; g_Icons[eIcon].tszToolTip[TOOLTIP_SIZE -1] = _T('\0'); EvaluateIcon(); } // SetIconState VOID CALLBACK RingTimerProc( HWND hwnd, // handle to window UINT uMsg, // WM_TIMER message UINT_PTR idEvent, // timer identifier DWORD dwTime // current system time ) /*++ Routine description: Animate ringing icon Arguments: hwnd - handle to window uMsg - WM_TIMER message idEvent - timer identifier dwTime - current system time Return Value: none --*/ { DBG_ENTER(TEXT("RingTimerProc")); if ((GetTickCount() - g_dwRingAnimationStartTick) > RING_ANIMATION_TIMEOUT) { // // Animation has expired - keep static icon // g_Icons[ICON_RINGING].hIcon = g_RingIcons[0].hIcon; if(!KillTimer(NULL, g_uRingTimerID)) { CALL_FAIL (GENERAL_ERR, TEXT ("KillTimer"), GetLastError()); } g_uRingTimerID = 0; g_dwRingAnimationStartTick = 0; } else { g_dwCurrRingIconIndex = (g_dwCurrRingIconIndex + 1) % RING_ICONS_NUM; g_Icons[ICON_RINGING].hIcon = g_RingIcons[g_dwCurrRingIconIndex].hIcon; } EvaluateIcon(); } // RingTimerProc VOID InvokeClientConsole () /*++ Routine description: Invoke Client Console Arguments: none Return Value: none --*/ { DBG_ENTER(TEXT("InvokeClientConsole")); TCHAR szCmdLine[MAX_PATH]; static TCHAR szFmtMsg[] = TEXT(" -folder %s -MessageId %I64x"); static TCHAR szFmtNoMsg[] = TEXT(" -folder %s"); DWORDLONG dwlMsgId = 0; LPCWSTR lpcwstrFolder = TEXT(""); switch (g_CurrentIcon) { case ICON_RINGING: // Line is ringing - nothing special to do case ICON_RECEIVE_FAILED: // Receive operation failed - nothing special to do default: // Any other icon state - nothing special to do break; case ICON_SENDING: // // Device is sending - open fax console in Outbox folder // dwlMsgId = g_dwlCurrentMsgID; lpcwstrFolder = CONSOLE_CMD_PRM_STR_OUTBOX; break; case ICON_SEND_FAILED: // // Send operation failed - open fax console in Outbox folder // dwlMsgId = g_dwlSendFailedMsgId; lpcwstrFolder = CONSOLE_CMD_PRM_STR_OUTBOX; break; case ICON_RECEIVING: // // Device is receiving - open fax console in Incoming folder // dwlMsgId = g_dwlCurrentMsgID; lpcwstrFolder = CONSOLE_CMD_PRM_STR_INCOMING; break; break; case ICON_NEW_FAX: // // New unread fax - open fax console in Inbox folder // dwlMsgId = g_dwlNewMsgId; lpcwstrFolder = CONSOLE_CMD_PRM_STR_INBOX; break; case ICON_SEND_SUCCESS: // // Send was successful - open fax console in Sent Items folder // dwlMsgId = g_dwlSendSuccessMsgId; lpcwstrFolder = CONSOLE_CMD_PRM_STR_SENT_ITEMS; break; } if (dwlMsgId) { wsprintf (szCmdLine, szFmtMsg, lpcwstrFolder, dwlMsgId); } else { wsprintf (szCmdLine, szFmtNoMsg, lpcwstrFolder); } HINSTANCE hRes; hRes = ShellExecute(g_hWndFaxNotify, NULL, FAX_CLIENT_CONSOLE_IMAGE_NAME, szCmdLine, NULL, SW_SHOW); if((DWORD_PTR)hRes <= 32) { // // error // CALL_FAIL (GENERAL_ERR, TEXT("ShellExecute"), PtrToUlong(hRes)); } } // InvokeClientConsole VOID AnswerTheCall () /*++ Routine description: Answer the current incoming call Arguments: none Return Value: none --*/ { DBG_ENTER(TEXT("AnswerTheCall")); DWORD dwDeviceId; // // Check for 'Answer now' capabilities and auto-detect the device id. // DWORD dwRes = CheckAnswerNowCapability (TRUE, // Start service if necessary &dwDeviceId); // Get device id for FaxAnswerCall if (ERROR_SUCCESS != dwRes) { // // Can't 'Answer Now' - dwRes has the string resource id for the message to show to the user. // FaxMessageBox (g_hMonitorDlg, dwRes, MB_OK | MB_ICONEXCLAMATION); return; } // // Reset remote ID // _tcscpy(g_szRemoteId, TEXT("")); // // Looks like we have a chance of FaxAnswerCall succeeding - let's try it. // First, open the monitor (or make sure it's already open). // OpenFaxMonitor (); // // Start by disabling the 'Answer Now' button on the monitor dialog // if (g_hMonitorDlg) { // // Monitor dialog is there // HWND hWndAnswerNow = GetDlgItem(g_hMonitorDlg, IDC_DISCONNECT); if(hWndAnswerNow) { EnableWindow(hWndAnswerNow, FALSE); } } // // Call is gone // g_hCall = NULL; SetIconState(ICON_RINGING, FALSE, TEXT("")); if(!FaxAnswerCall(g_hFaxSvcHandle, dwDeviceId)) { CALL_FAIL (RPC_ERR, TEXT ("FaxAnswerCall"), GetLastError()); FaxMessageBox(g_hWndFaxNotify, IDS_CANNOT_ANSWER, MB_OK | MB_ICONEXCLAMATION); SetStatusMonitorDeviceState (FAX_IDLE); } else { g_tszLastEvent[0] = TEXT('\0'); SetStatusMonitorDeviceState(FAX_RECEIVING); } } // AnswerTheCall VOID FaxPrinterProperties(DWORD dwPage) /*++ Routine description: Open Fax Printer Property Sheet Arguments: dwPage - page number Return Value: none --*/ { DBG_ENTER(TEXT("FaxPrinterProperties")); // // open fax printer properties on the Tracking page // TCHAR tsPrinter[MAX_PATH]; typedef VOID (*PRINTER_PROP_PAGES_PROC)(HWND, LPCTSTR, INT, LPARAM); HMODULE hPrintUI = NULL; PRINTER_PROP_PAGES_PROC fpPrnPropPages = NULL; if(!GetFirstLocalFaxPrinterName(tsPrinter, MAX_PATH)) { CALL_FAIL (GENERAL_ERR, TEXT ("GetFirstLocalFaxPrinterName"), GetLastError()); return; } hPrintUI = LoadLibrary(TEXT("printui.dll")); if(!hPrintUI) { CALL_FAIL (GENERAL_ERR, TEXT ("LoadLibrary(printui.dll)"), GetLastError()); return; } fpPrnPropPages = (PRINTER_PROP_PAGES_PROC)GetProcAddress(hPrintUI, "vPrinterPropPages"); if(fpPrnPropPages) { fpPrnPropPages(g_hWndFaxNotify, tsPrinter, SW_SHOWNORMAL, dwPage); } else { CALL_FAIL (GENERAL_ERR, TEXT ("GetProcAddress(vPrinterPropPages)"), GetLastError()); } FreeLibrary(hPrintUI); } // FaxPrinterProperties VOID DoFaxContextMenu (HWND hwnd) /*++ Routine description: Popup and handle context menu Arguments: hwnd - notification window handle Return Value: none --*/ { DBG_ENTER(TEXT("DoFaxContextMenu")); POINT pt; HMENU hm = LoadMenu (g_hResource, MAKEINTRESOURCE (IDM_FAX_MENU)); HMENU hmPopup = GetSubMenu(hm, 0); if (!g_Icons[ICON_RINGING].bEnable) { RemoveMenu (hmPopup, ID_ANSWER_CALL, MF_BYCOMMAND); } if(g_dwCurrentJobID == 0) { RemoveMenu (hmPopup, ID_DISCONNECT_CALL, MF_BYCOMMAND); } if(!g_Icons[ICON_RINGING].bEnable && g_dwCurrentJobID == 0) { // // delete the menu separator // DeleteMenu(hmPopup, 0, MF_BYPOSITION); } SetMenuDefaultItem(hmPopup, ID_FAX_QUEUE, FALSE); GetCursorPos (&pt); SetForegroundWindow(hwnd); INT idCmd = TrackPopupMenu (GetSubMenu(hm, 0), TPM_RETURNCMD | TPM_NONOTIFY, pt.x, pt.y, 0, hwnd, NULL); switch (idCmd) { case ID_ICON_PROPERTIES: FaxPrinterProperties(IsSimpleUI() ? 3 : 5); break; case ID_FAX_QUEUE: InvokeClientConsole (); break; case ID_ANSWER_CALL: AnswerTheCall (); break; case ID_FAX_MONITOR: OpenFaxMonitor (); break; case ID_DISCONNECT_CALL: OnDisconnect(); break; } if (hm) { DestroyMenu (hm); } } // DoFaxContextMenu VOID OnTrayCallback (HWND hwnd, WPARAM wp, LPARAM lp) /*++ Routine description: Handle messages from the notification icon Arguments: hwnd - notification window handle wp - message parameter lp - message parameter Return Value: none --*/ { DBG_ENTER(TEXT("OnTrayCallback"), TEXT("hWnd=%08x, wParam=%08x, lParam=%08x"), hwnd, wp, lp); switch (lp) { case NIN_BALLOONUSERCLICK: // User clicked balloon or (WM_USER + 5 = 1029) case WM_LBUTTONDOWN: // User pressed icon (513) { // // Our behavior depends on the icon currently being displyed // switch (g_CurrentIcon) { case ICON_RINGING: // // Device is ringing - answer the call // AnswerTheCall (); break; case ICON_NEW_FAX: // New unread fax - open fax console in Inbox folder case ICON_SEND_SUCCESS: // Send was successful - open fax console in Sent Items folder case ICON_SEND_FAILED: // Send operation failed - open fax console in Outbox folder // // Turn off the current icon state // InvokeClientConsole (); SetIconState(g_CurrentIcon, FALSE); break; case ICON_SENDING: // Device is sending - open fax console in Outbox folder case ICON_RECEIVING: // Device is receiving - open fax console in Incoming folder InvokeClientConsole (); break; case ICON_RECEIVE_FAILED: // // Receive operation failed // SetIconState(g_CurrentIcon, FALSE); break; default: // // When balloon is opened and the user clicks on the icon we get two notifications // NIN_BALLOONUSERCLICK and WM_LBUTTONDOWN. The first one reset the icon state and the second do nothing. // break; } } // // no break ==> fall-through // case NIN_BALLOONTIMEOUT: if (g_BalloonInfo.eState == ICON_RECEIVE_FAILED || g_BalloonInfo.eState == ICON_SEND_SUCCESS) { SetIconState(g_BalloonInfo.eState, FALSE); } g_BalloonInfo.eState = ICON_IDLE; break; case WM_RBUTTONDOWN: DoFaxContextMenu (hwnd); break; } } // OnTrayCallback BOOL IsUserGrantedAccess( DWORD dwAccess ) { BOOL bRes = FALSE; DBG_ENTER(TEXT("IsUserGrantedAccess"), bRes, TEXT("%d"), dwAccess); if (!g_hFaxSvcHandle) { // // Not connected - no rights // return bRes; } if (dwAccess == (g_ConfigOptions.dwAccessRights & dwAccess)) { bRes = TRUE; } return bRes; } // IsUserGrantedAccess DWORD CheckAnswerNowCapability ( BOOL bForceReconnect, LPDWORD lpdwDeviceId /* = NULL */ ) /*++ Routine name : CheckAnswerNowCapability Routine description: Checks if the 'Answer Now' option can be used Author: Eran Yariv (EranY), Mar, 2001 Arguments: bForceReconnect [in] - If the service is down, should we bring it up now? lpdwDeviceId [out] - The device id to use when calling FaxAnswerCall. If the Manual-Answer-Device is ringing, we use the Manual-Answer-Device id. Otherwise, it's the monitored device id. (Optional) Return Value: ERROR_SUCCESS if the 'Answer Now' can be used. Othewise, returns a string resource id that can be used in a message box to tell the user why 'Answer Now' is not available. --*/ { DWORD dwRes = ERROR_SUCCESS; DBG_ENTER(TEXT("CheckAnswerNowCapability"), dwRes); // // First, let's see if we're connected to the local server // if (NULL == g_hFaxSvcHandle) { // // Service is down // if (!bForceReconnect) { // // We assume the user can 'Answer now' // ASSERTION (NULL == lpdwDeviceId); return dwRes; } // // Try to start up the local fax service // if (!Connect()) { // // Couldn't start up the service // dwRes = GetLastError (); CALL_FAIL (GENERAL_ERR, TEXT("Connect"), dwRes); dwRes = IDS_ERR_CANT_TALK_TO_SERVICE; return dwRes; } // // Now that the service is up - we need to connect. // Send a message to the main window to bring up the connection. // if (!SendMessage (g_hWndFaxNotify, WM_FAX_STARTED, 0, 0)) { // // Failed to connect // dwRes = IDS_ERR_CANT_TALK_TO_SERVICE; return dwRes; } // // Now we're connected !!! // } if (!IsUserGrantedAccess (FAX_ACCESS_QUERY_IN_ARCHIVE)) { // // User can't receive-now // dwRes = IDS_ERR_ANSWER_ACCESS_DENIED; return dwRes; } if (0 == g_ConfigOptions.dwMonitorDeviceId) { // // No devices // dwRes = IDS_ERR_NO_DEVICES; return dwRes; } if (g_hCall) { // // The Manual-Answer-Device is ringing, we use the Manual-Answer-Device id. // ASSERTION (g_ConfigOptions.dwManualAnswerDeviceId); if (lpdwDeviceId) { *lpdwDeviceId = g_ConfigOptions.dwManualAnswerDeviceId; } return dwRes; } // // The Manual-Answer-Device is NOT ringing; we should receive on the monitored device // if ((0 != g_dwCurrentJobID) || (FAX_IDLE != g_devState)) { // // There's a job on the monitored device // dwRes = IDS_ERR_DEVICE_BUSY; return dwRes; } // // One last check - is the monitored device virtual? // BOOL bVirtual; dwRes = IsDeviceVirtual (g_hFaxSvcHandle, g_ConfigOptions.dwMonitorDeviceId, &bVirtual); if (ERROR_SUCCESS != dwRes) { // // Can't tell - assume virtual // bVirtual = TRUE; } if (bVirtual) { // // Sorry, manual answering on virtual devices is NOT supported // dwRes = IDS_ERROR_VIRTUAL_DEVICE; return dwRes; } // // It's ok to call FaxAnswerCall on the monitored device // if (lpdwDeviceId) { *lpdwDeviceId = g_ConfigOptions.dwMonitorDeviceId; } return dwRes; } // CheckAnswerNowCapability LRESULT CALLBACK NotifyWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) /*++ Routine description: Notification window procedure Arguments: hwnd - notification window handle msg - message ID wp - message parameter lp - message parameter Return Value: result --*/ { switch (msg) { case WM_CREATE: break; case WM_FAX_STARTED: // // We get this message after service startup event // return RegisterForServerEvents(); case WM_TRAYCALLBACK: OnTrayCallback (hwnd, wp, lp); break; case WM_FAX_EVENT: #ifndef DEBUG try { #endif OnFaxEvent ((FAX_EVENT_EX*)lp); #ifndef DEBUG } catch(...) { // // Do not handle the exception for the debug version // DBG_ENTER(TEXT("NotifyWndProc")); CALL_FAIL (GENERAL_ERR, TEXT("OnFaxEvent"), 0); return 0; } #endif return 0; case WM_FAXSTAT_CONTROLPANEL: // // configuration has been changed // GetConfiguration (); EvaluateIcon(); UpdateMonitorData(g_hMonitorDlg); return 0; case WM_FAXSTAT_OPEN_MONITOR: OpenFaxMonitor (); return 0; case WM_FAXSTAT_INBOX_VIEWED: // // Client Console Inbox has been viewed // SetIconState(ICON_NEW_FAX, FALSE); return 0; case WM_FAXSTAT_OUTBOX_VIEWED: // // Client Console Outbox has been viewed // SetIconState(ICON_SEND_FAILED, FALSE); return 0; case WM_FAXSTAT_RECEIVE_NOW: // // Start receiving now // AnswerTheCall (); return 0; case WM_FAXSTAT_PRINTER_PROPERTY: // // Open Fax Printer Property Sheet // FaxPrinterProperties((DWORD)(wp)); return 0; default: break; } return CallWindowProc (DefWindowProc, hwnd, msg, wp, lp); } // NotifyWndProc VOID CopyLTRString( TCHAR* szDest, LPCTSTR szSource, DWORD dwSize) /*++ Routine description: Copy the string and add left-to-right Unicode control characters if needed Arguments: szDest - destination string szSource - source string dwSize - destination string maximum size in characters Return Value: none --*/ { DBG_ENTER(TEXT("CopyLTRString")); if(!szDest) { ASSERTION_FAILURE; return; } if(IsRTLUILanguage() && szSource && _tcslen(szSource)) { // // The string always should be LTR // Add LEFT-TO-RIGHT OVERRIDE (LRO) // _sntprintf(szDest, dwSize -1, TEXT("%c%s%c"), UNICODE_LRO, szSource, UNICODE_PDF); szDest[dwSize -1] = _T('\0'); } else { _tcsncpy(szDest, szSource ? szSource : TEXT(""), dwSize); } } // CopyLTRString