2020-09-30 17:12:29 +02:00

1101 lines
23 KiB
C++

/*++
Copyright (c) 1994 Microsoft Corporation
All rights reserved.
Module Name:
printui.c
Abstract:
Singleton class that exists when printer queues are open.
Author:
Albert Ting (AlbertT) 22-Jun-1995
Revision History:
--*/
#include "precomp.hxx"
#pragma hdrstop
#define _GLOBALS
#include "globals.hxx"
#include "time.hxx"
#include "shlobj.h"
#include "folder.hxx"
//
// Singleton PrintLib object. This should really be
// a static of TPrintLib, but that would require qualification
// for every usage.
//
TPrintLib* gpPrintLib;
extern "C" {
MODULE_DEBUG_INIT( DBG_ERROR|DBG_WARN, DBG_ERROR );
BOOL
DllMain(
IN HINSTANCE hInst,
IN DWORD dwReason,
IN LPVOID lpRes
);
}
BOOL
DllMain(
IN HINSTANCE hInst,
IN DWORD dwReason,
IN LPVOID lpRes
)
/*++
Routine Description:
Dll entry point.
Arguments:
Return Value:
--*/
{
switch( dwReason ){
case DLL_PROCESS_ATTACH:
ghInst = hInst;
if( !bSplLibInit( )){
DBGMSG( DBG_ERROR,
( "DllEntryPoint: Failed to init SplLib %d\n", GetLastError( )));
return FALSE;
}
if( !bPrintLibInit( )){
DBGMSG( DBG_ERROR,
( "DllEntryPoint: Failed to init PrintLib %d\n", GetLastError( )));
return FALSE;
}
InitCommonControls();
DisableThreadLibraryCalls( hInst );
break;
case DLL_PROCESS_DETACH:
//
// lpRes is NULL if it's a FreeLibrary, non-NULL if it's
// process termination. Don't do cleanup if it's process
// termination.
//
if( !lpRes ){
//
// Unregister the class.
//
UnregisterClass( gszClassName, hInst );
UnregisterClass( gszQueueCreateClassName, hInst );
delete gpCritSec;
vSplLibFree();
}
break;
default:
break;
}
return TRUE;
}
TPrintLib::
TPrintLib(
VOID
) : TExec( gpCritSec ), _cRef( 0 ), _hEventInit( NULL )
/*++
Routine Description:
Initializes application object. This is a singleton object.
This will create the message pump and also the hwndQueueCreate
window which listens for requests.
Arguments:
Return Value:
Notes:
_strComputerName is the valid variable.
--*/
{
SPLASSERT( gpCritSec->bInside( ));
//
// Initialize the singleton global pointer.
//
gpPrintLib = this;
//
// Create init event. This event is used to synchronize
// the message pump initialization and this thread.
//
_hEventInit = CreateEvent( NULL, FALSE, FALSE, NULL );
if( !_hEventInit ){
return;
}
TNotify* pNotify = new TNotify;
if( !VALID_BASE( TExec ) || !VALID_PTR( pNotify )){
return;
}
Notify_vAcquire( pNotify );
//
// The computer name needs to be formatted as "\\computername."
//
TCHAR szComputerName[MAX_COMPUTERNAME_LENGTH+1 + 2];
DWORD cchComputerName = COUNTOF( szComputerName ) - 2;
if( !GetComputerName( &szComputerName[2], &cchComputerName )){
return;
}
szComputerName[0] =
szComputerName[1] = TEXT( '\\' );
if( !_strComputerName.bUpdate( szComputerName ) ){
return;
}
DWORD dwThreadId;
HANDLE hThread;
//
// Start the message pump by spawning a UI thread.
//
hThread = CreateThread( NULL,
0,
(LPTHREAD_START_ROUTINE)TPrintLib::xMessagePump,
NULL,
0,
&dwThreadId );
if( !hThread ){
return;
}
CloseHandle( hThread );
//
// Wait for thread to initialize.
//
WaitForSingleObject( gpPrintLib->_hEventInit, INFINITE );
CloseHandle( gpPrintLib->_hEventInit );
gpPrintLib->_hEventInit = NULL;
//
// _hwndQueueCreate is our valid check. If it failed, cleanup.
// This is set by the worker thread (xMessagePump). We can access
// it only after _hEventInit has been triggered.
//
if( !_hwndQueueCreate ){
PostThreadMessage( dwThreadId,
WM_QUIT,
0,
0 );
}
}
TPrintLib::
~TPrintLib(
VOID
)
/*++
Routine Description:
Destroys the printui.
Arguments:
Return Value:
--*/
{
SPLASSERT( Queue_bEmpty( ));
if( _hEventInit ){
CloseHandle( _hEventInit );
}
//
// We are shutting down. Tell pNotify that it can start shutting
// down too.
//
pNotify()->vDelete();
//
// RL_Notify will automatically shut down when the last
// reference to it has been deleted.
//
}
HWND
TPrintLib::
hwndQueueFind(
IN LPCTSTR pszQueue
)
/*++
Routine Description:
Determines whether a queue window is already open by this process.
!! LATER !!
This should be replaced by shell code so that it is system rather
than process wide.
Arguments:
psQueue - Name of the queue to check.
Return Value:
HWND - Window of the queue, if it exists.
NULL - Queue window does not exist.
--*/
{
SINGLETHREAD( UIThread );
TIter Iter;
TQueue* pQueue;
TCHAR szPrinter[kPrinterBufMax];
LPTSTR pszPrinter;
for( Queue_vIterInit( Iter ), Iter.vNext();
Iter.bValid();
Iter.vNext( )){
pQueue = Queue_pConvert( Iter );
pszPrinter = pQueue->pszPrinterName( szPrinter );
if( !lstrcmpi( szPrinter, pszQueue )){
return pQueue->hwnd();
}
}
return NULL;
}
VOID
TPrintLib::
vHandleCreateQueue(
IN TInfo* pInfo ADOPT
)
/*++
Routine Description:
Handle the creation of a new Queue window.
Arguments:
pInfo - Which queue should be created and extra parms.
Return Value:
--*/
{
DBGMSG( DBG_INFO, ( "PrintLib.vHandleQueueCreate: received pInfo %x\n", pInfo ));
//
// The Add Printer wizard is invoked by double clicking
// a printer called "WinUtils_NewObject." I hope no one
// ever tries to create a printer under this name...
//
if( !lstrcmp( gszNewObject, pInfo->_szPrinter )){
DBGMSG( DBG_ERROR,
( "PrintLib.lrQueueCreateWndProc: WinUtils_NewObject called here!\n" ));
} else {
HWND hwndQueue = NULL;
//
// Check if printer is already open.
//
hwndQueue = gpPrintLib->hwndQueueFind( pInfo->_szPrinter );
if( !hwndQueue ){
TQueue* pQueue = new TQueue( pInfo->_hwndOwner,
pInfo->_szPrinter,
pInfo->_nCmdShow,
pInfo->_hEventClose );
if( VALID_PTR( pQueue )){
hwndQueue = pQueue->hwnd();
} else {
delete pQueue;
//
// !! LATER !!
//
// Put up error message.
//
}
}
if( hwndQueue ){
SetForegroundWindow( hwndQueue );
ShowWindow( hwndQueue, SW_RESTORE );
}
}
delete pInfo;
}
LRESULT
CALLBACK
TPrintLib::
lrQueueCreateWndProc(
IN HWND hwnd,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam
)
{
switch( uMsg ){
case WM_WININICHANGE:
//
// !! HACK !!
//
// Hack to make the default printer icon change correctly.
// The NT spooler doesn't issue default change notifications,
// so create one here.
//
TFolder::vDefaultPrinterChanged();
break;
case WM_PRINTLIB_NEW:
SPLASSERT( gpPrintLib );
gpPrintLib->vHandleCreateQueue( (TInfo*)lParam );
break;
case WM_DESTROY_REQUEST:
DestroyWindow( hwnd );
break;
case WM_DESTROY:
SINGLETHREADRESET( UIThread );
PostQuitMessage( 0 );
break;
default:
return DefWindowProc( hwnd, uMsg, wParam, lParam);
}
return 0;
}
/********************************************************************
Public interface functions
********************************************************************/
VOID
vQueueCreate(
IN HWND hwndOwner,
IN LPCTSTR pszPrinter,
IN INT nCmdShow,
IN LPARAM lParam
)
/*++
Routine Description:
Creates a printer queue.
Arguments:
lParam - TRUE => fModal.
Return Value:
--*/
{
if( lstrlen( pszPrinter ) >= kPrinterBufMax ){
DBGMSG( DBG_ERROR,
( "vQueueCreate: printer name too long "TSTR"\n",
pszPrinter ));
iMessage( hwndOwner,
IDS_DESC,
IDS_ERR_BAD_PRINTER_NAME,
MB_OK|MB_ICONSTOP,
kMsgNone,
NULL );
return;
}
TStatusB bSuccess;
bSuccess DBGNOCHK = TRUE;
HANDLE hEventClose = NULL;
//
// Initialize TInfo and acquire the gpPrintLib.
//
TPrintLib::TInfo* pInfo = new TPrintLib::TInfo;
if( !VALID_PTR( pInfo )){
goto Fail;
}
//
// If modal, send in an event to trigger when the window is closed.
//
if( lParam ){
hEventClose = CreateEvent( NULL, FALSE, FALSE, NULL );
if( !hEventClose ){
goto Fail;
}
}
lstrcpy( pInfo->_szPrinter, pszPrinter );
pInfo->_nCmdShow = nCmdShow;
pInfo->_hwndOwner = hwndOwner;
pInfo->_hEventClose = hEventClose;
//
// Need to grab the critical section, since this call should
// be reentrant.
//
{
TCritSecLock CSL( *gpCritSec );
bSuccess DBGCHK = TPrintLib::bGetSingleton();
//
// Queue creation the first time just passes the TInfo to
// a worker function that serves as the message pump. If
// gpPrintLib has been instantiated, then we'll post a message
// and return immediately (freeing this thread).
//
// If gpPrintLib has not been instantiated, then we create the
// singleton here.
//
if( bSuccess ){
//
// Acquire a reference to gpPrintLib so that
// the pInfo can be posted without worrying about losing
// gpPrintLib. This reference will automatically be
// release when pInfo is destroyed.
//
pInfo->PrintLib_vAcquire( gpPrintLib );
//
// Send a message to the UI thread to create a new window.
// We want all queues to use the same UI thread.
//
DBGMSG( DBG_INFO, ( "vQueueCreate: posted pInfo %x\n", pInfo ));
bSuccess DBGCHK = PostMessage( gpPrintLib->hwndQueueCreate(),
WM_PRINTLIB_NEW,
0,
(LPARAM)pInfo );
}
}
Fail:
if( bSuccess ){
//
// Success - check if needs to be modal. If so, wait until
// window is closed.
//
if( lParam ){
WaitForSingleObject( hEventClose, INFINITE );
}
} else {
//
// Destroy the pInfo data. This will automatically decrement
// the reference count on gpPrintLib (if necessary).
//
delete pInfo;
vShowResourceError( hwndOwner );
}
if( hEventClose ){
CloseHandle( hEventClose );
}
return;
}
BOOL
bPrintLibInit(
VOID
)
/*++
Routine Description:
Initializes the print library.
Arguments:
Return Value:
--*/
{
gpCritSec = new MCritSec;
if( !VALID_PTR( gpCritSec )){
delete gpCritSec;
DBGMSG( DBG_ERROR,
( "bPrintLibInit: gpCritSec creation failed %d\n",
GetLastError( )));
return FALSE;
}
if( !TTime::bInitClass( )){
DBGMSG( DBG_ERROR,
( "bPrintLib: TTime::bInitClass failed %d\n",
GetLastError( )));
return FALSE;
}
ghAccel = LoadAccelerators( ghInst,
(LPCTSTR)MAKEINTRESOURCE( ACCEL_PRINTQUEUE ));
if( !ghAccel ){
DBGMSG( DBG_ERROR,
( "bInitPrintLib: LoadAccelerators failed %d\n",
GetLastError( )));
return FALSE;
}
WNDCLASS WndClass;
WndClass.lpszClassName = gszClassName;
WndClass.style = 0L;
WndClass.lpfnWndProc = (WNDPROC)&MGenericWin::SetupWndProc;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = sizeof( TPrintLib* );
WndClass.hInstance = ghInst;
WndClass.hIcon = NULL;
WndClass.hCursor = LoadCursor( NULL, IDC_ARROW ); // NULL
WndClass.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
WndClass.lpszMenuName = MAKEINTRESOURCE( MENU_PRINTQUEUE );
if( !RegisterClass( &WndClass )){
DBGMSG( DBG_ERROR,
( "bInitPrintLib: RegisterClass failed %d\n",
GetLastError( )));
return FALSE;
}
ZeroMemory( &WndClass, sizeof( WndClass ));
WndClass.lpszClassName = gszQueueCreateClassName;
WndClass.lpfnWndProc = TPrintLib::lrQueueCreateWndProc;
WndClass.hInstance = ghInst;
if( !RegisterClass( &WndClass )){
DBGMSG( DBG_ERROR,
( "bInitPrintLib: RegisterClass 2 failed %d\n",
GetLastError( )));
return FALSE;
}
gcxSmIcon = GetSystemMetrics( SM_CXICON )/2;
gcySmIcon = GetSystemMetrics( SM_CYICON )/2;
return TRUE;
}
/********************************************************************
Private helper routines.
********************************************************************/
BOOL
TPrintLib::
bGetSingleton(
VOID
)
/*++
Routine Description:
Retrieves the singleton object, and stores it in the global.
If the singleton has not been created, then it is instantiated
here.
Must be called in the critical section.
Arguments:
Return Value:
TRUE = success (singleton exists), FALSE = fail.
--*/
{
//
// Must be in CS.
//
SPLASSERT( gpCritSec->bInside( ));
if( !gpPrintLib ){
new TPrintLib();
if( !VALID_PTR( gpPrintLib )){
DBGMSG( DBG_WARN,
( "PrintLib.bCreateSingleton: Abnormal termination %d %d\n",
gpPrintLib, GetLastError( )));
//
// Creation failed, delete the object, null the pointer,
// and prepare to fail.
//
// Normally this is a request to delete the object, which may
// not occur for a while, but since we haven't tried using
// it yet, it immediately deletes the object.
//
// There is no need to delete gpPrintLib since vDelete will
// virtually destruct it.
//
if( gpPrintLib ){
gpPrintLib->TThreadM::vDelete();
}
gpPrintLib = NULL;
SINGLETHREADRESET( UIThread );
return FALSE;
}
}
return TRUE;
}
STATUS
TPrintLib::
xMessagePump(
VOID
)
/*++
Routine Description:
Main message pump. The printer windows are architected slightly
differently than regular explorer windows: there is a single UI
thread, and multiple worker threads. This has two main advantages:
1. Fewer threads even when many printer queues are open (assuming
NT->NT connections).
2. The UI virtually never hangs.
It has this slight disadvantage that sometimes the main UI thread
is busy (e.g., inserting 2,000 print jobs may take a few seconds)
in which all print UI windows are hung.
Arguments:
None.
Return Value:
Win32 status code.
--*/
{
MSG msg;
TStatusB bSuccess;
bSuccess DBGNOCHK = TRUE;
//
// Ensure that functions that must execute in the UI thread don't
// execute elsewhere.
//
SINGLETHREAD( UIThread );
//
// Create our window to listen for Queue Creates.
//
gpPrintLib->_hwndQueueCreate = CreateWindowEx(
0,
gszQueueCreateClassName,
gszQueueCreateClassName,
WS_OVERLAPPED,
0,
0,
0,
0,
NULL,
NULL,
ghInst,
NULL );
if( !gpPrintLib->_hwndQueueCreate ){
bSuccess DBGCHK = FALSE;
}
SetEvent( gpPrintLib->_hEventInit );
if( !bSuccess ){
return 0;
}
while( GetMessage( &msg, NULL, 0, 0 )){
if( !TranslateAccelerator( ghwndActive,
ghAccel,
&msg )){
TranslateMessage( &msg );
DispatchMessage( &msg );
}
}
return (STATUS)msg.wParam;
}
VOID
TPrintLib::
vRefZeroed(
VOID
)
/*++
Routine Description:
Virtual definition from class MRefCom.
The reference count has reached zero; we should cleanup the
object. Immediately zero out gpPrintLib then post a message
instructing it to destory itself.
Arguments:
Return Value:
--*/
{
if( bValid( )){
TCritSecLock CSL( *gpCritSec );
//
// Close a potential race condition: gpPrintLib is still
// non-null, and could have been acquired right before we
// grabbed gpCritSec. If so, the refcount is now non-zero
// and we shouldn't delete the object. (This can happen
// if the last print queue window is closed, then a new one is
// opened quickly.)
//
if( !_cRef ){
PostMessage( _hwndQueueCreate, WM_DESTROY_REQUEST, 0, 0 );
//
// We can't immediately delete ourselves because we may have
// pending work items. Notify TThreadM that it should start
// shutdown, then zero the global out so that no one else will
// try to use us.
//
// When TThreadM has shut down, it will call
// vThreadMDeleteComplete then we can delete ourselves.
//
TThreadM::vDelete();
gpPrintLib = NULL;
SINGLETHREADRESET( UIThread );
}
}
}
VOID
TPrintLib::
vThreadMDeleteComplete(
VOID
)
/*++
Routine Description:
When the base class TExec has completed shutting down, then this
virtual function is called which completes deletion of PrintLib.
At this stage, the gpPrintLib has already been NULL'ed.
Arguments:
Return Value:
--*/
{
delete this;
}
/********************************************************************
Default printer code. Should be moved into spoolss\client
so that SetPrinter w/ attribute_default works.
********************************************************************/
DEFAULT_PRINTER
CheckDefaultPrinter(
IN LPCTSTR pszPrinter OPTIONAL
)
/*++
Routine Description:
Determines the default printer status.
Arguments:
pszPrinter - Check if this printer is the default (optional).
Return Value:
kNoDefault - No default printer exists.
kDefault - pszPrinter is the default printer.
kOtherDefault - Default printer exists, but it's not pszPrinter
(or pszPrinter was not passed in).
--*/
{
TCHAR szPrinterDefault[kPrinterDefaultStringBufMax];
szPrinterDefault[0] = 0;
if( !GetProfileString( gszWindows,
gszDevice,
szPrinterDefault,
szPrinterDefault,
COUNTOF( szPrinterDefault ))){
return kNoDefault;
}
//
// If pszPrinter passed in, see if it's the default.
//
if( pszPrinter ){
LPTSTR psz = lstrchr( szPrinterDefault, TEXT( ',' ));
//
// We should find a comma, but let's be safe and check.
// Convert "superp,winspool,Ne00:" to "superp."
//
if( psz ){
*psz = 0;
if( !lstrcmpi( szPrinterDefault, pszPrinter )){
return kDefault;
}
}
}
return kOtherDefault;
}
BOOL
bSetDefaultPrinter(
IN LPCTSTR pszPrinter OPTIONAL
)
/*++
Routine Description:
Set the default printer to pszPrinter.
Note: assumes spooler has correctly set up szDevices section.
Arguments:
pszPrinter - Printer to set as the default. If not present,
a printer from [devices] is set as the default.
Return Value:
BOOL - TRUE = success, FALSE = fail.
--*/
{
TCHAR szDefaultPrinterString[kPrinterDefaultStringBufMax];
TCHAR szBuffer[kPrinterDefaultStringBufMax];
TCHAR szAnyPrinter[kPrinterBufMax];
//
// If pszPrinter is NULL, look for any printer in the [devices]
// section.
//
if( !pszPrinter ){
if( !GetProfileString( gszDevices,
NULL,
gszNULL,
szAnyPrinter,
COUNTOF( szAnyPrinter ))){
DBGMSG( DBG_WARN,
( "bSetDefaultPrinter: any printer, but none available.\n" ));
return FALSE;
}
//
// Pick the first one to be the default.
//
pszPrinter = szAnyPrinter;
} else {
//
// Avoid broadcasts as much as possible. Se if the printer
// is already the default, and don't do anything if it is.
//
if( CheckDefaultPrinter( pszPrinter ) == kDefault ){
DBGMSG( DBG_WARN,
( "bSetDefaultPrinter: "TSTR" already the default printer.\n",
pszPrinter ));
return TRUE;
}
}
//
// Not the default, set it.
//
if( !GetProfileString( gszDevices,
pszPrinter,
gszNULL,
szBuffer,
COUNTOF( szBuffer ))){
DBGMSG( DBG_WARN,
( "bSetDefaultPrinter: "TSTR" not found in szDevices\n", pszPrinter ));
return FALSE;
}
lstrcpy( szDefaultPrinterString, pszPrinter );
lstrcat( szDefaultPrinterString, gszComma );
lstrcat( szDefaultPrinterString, szBuffer );
SPLASSERT( lstrlen( szDefaultPrinterString ) <
COUNTOF( szDefaultPrinterString ));
if( !WriteProfileString( gszWindows,
gszDevice,
szDefaultPrinterString )){
DBGMSG( DBG_WARN,
( "bSetDefaultPrinter: unable to set default string "TSTR", %d\n",
szDefaultPrinterString, GetLastError( )));
return FALSE;
}
//
// Tell the world and make everyone flash.
//
SendNotifyMessage( HWND_BROADCAST,
WM_WININICHANGE,
0,
(LPARAM)gszWindows );
return TRUE;
}