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.
3256 lines
90 KiB
3256 lines
90 KiB
/**
|
|
|
|
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 <windows.h>
|
|
#include <faxreg.h>
|
|
#include <fxsapip.h>
|
|
#include <faxutil.h>
|
|
#include <shellapi.h>
|
|
#include <winspool.h>
|
|
#include <shlobj.h>
|
|
#include <Mmsystem.h>
|
|
#include <tchar.h>
|
|
#include <DebugEx.h>
|
|
#include <FaxRes.h>
|
|
|
|
#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
|