PrinterSetupWizardSheet.cpp   [plain text]


/* -*- Mode: C; tab-width: 4 -*-
 *
 * Copyright (c) 1997-2004 Apple Computer, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "stdafx.h"
#include "PrinterSetupWizardApp.h"
#include "PrinterSetupWizardSheet.h"
#include "CommonServices.h"
#include "DebugServices.h"
#include "WinServices.h"
#include "About.h"
#include "tcpxcv.h"
#include <winspool.h>
#include <string>
#include <shlwapi.h>

// unreachable code
#pragma warning(disable:4702)


#if( !TARGET_OS_WINDOWS_CE )
#	include	<mswsock.h>
#	include	<process.h>
#endif


#if defined( UNICODE ) || defined( _UNICODE )
#	define GetEnv	_wgetenv
#else
#	define GetEnv	getenv
#endif

static TCHAR*
g_printerDriverFiles[] =		// Printer driver files
{
	TEXT( "ps5ui.dll" ),
	TEXT( "pscript.hlp" ),
	TEXT( "pscript.ntf" ),
	TEXT( "pscript5.dll" ),
	TEXT( "cups6.ini" ),
	TEXT( "cupsui6.dll" ),
	TEXT( "cupsps6.dll" )
};


// Private Messages

#define WM_SOCKET_EVENT		( WM_USER + 0x100 )
#define WM_PROCESS_EVENT	( WM_USER + 0x101 )


static BOOL
Is64BitWindows()
{
#if defined(_WIN64)
	return TRUE;  // 64-bit programs run only on Win64
#else
	typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS)( HANDLE, PBOOL );
	LPFN_ISWOW64PROCESS fnIsWow64Process;
	BOOL bIsWow64 = FALSE;

    fnIsWow64Process = ( LPFN_ISWOW64PROCESS ) GetProcAddress( GetModuleHandle( TEXT( "kernel32" ) ), "IsWow64Process" );
  
    if ( fnIsWow64Process != NULL )
    {
		BOOL ok;

        ok = fnIsWow64Process( GetCurrentProcess(), &bIsWow64 );

		if ( !ok )
		{
			bIsWow64 = FALSE;
		}
	}

	return bIsWow64;
#endif
}


// CPrinterSetupWizardSheet
CPrinterSetupWizardSheet * CPrinterSetupWizardSheet::m_self;

IMPLEMENT_DYNAMIC(CPrinterSetupWizardSheet, CPropertySheet)
CPrinterSetupWizardSheet::CPrinterSetupWizardSheet(UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage)
	:CPropertySheet(nIDCaption, pParentWnd, iSelectPage),
	m_selectedPrinter(NULL),
	m_driverThreadExitCode( 0 ),
	m_driverThreadFinished( false ),
	m_pdlBrowser( NULL ),
	m_ippBrowser( NULL ),
	m_lprBrowser( NULL ),
	m_lastPage( NULL )
{
	m_arrow		=	LoadCursor(0, IDC_ARROW);
	m_wait		=	LoadCursor(0, IDC_APPSTARTING);
	m_active	=	m_arrow;
	m_self		=	this;
	
	Init();

	LoadPrinterNames();
}


CPrinterSetupWizardSheet::~CPrinterSetupWizardSheet()
{
	Printer * printer;

	while ( m_printers.size() > 0 )
	{
		printer = m_printers.front();
		m_printers.pop_front();

		delete printer;
	}

	m_self = NULL;
}


// ------------------------------------------------------
// SetSelectedPrinter
//
// Manages setting a printer as the printer to install.  Stops
// any pending resolves.  
//	
void
CPrinterSetupWizardSheet::SetSelectedPrinter(Printer * printer)
{
	check( !printer || ( printer != m_selectedPrinter ) );

	m_selectedPrinter = printer;
}


OSStatus
CPrinterSetupWizardSheet::LoadPrinterNames()
{
	PBYTE		buffer	=	NULL;
	OSStatus	err		= 0;

	//
	// rdar://problem/3701926 - Printer can't be installed twice
	//
	// First thing we want to do is make sure the printer isn't already installed.
	// If the printer name is found, we'll try and rename it until we
	// find a unique name
	//
	DWORD dwNeeded = 0, dwNumPrinters = 0;

	BOOL ok = EnumPrinters(PRINTER_ENUM_LOCAL, NULL, 4, NULL, 0, &dwNeeded, &dwNumPrinters);
	err = translate_errno( ok, errno_compat(), kUnknownErr );

	if ((err == ERROR_INSUFFICIENT_BUFFER) && (dwNeeded > 0))
	{
		try
		{
			buffer = new unsigned char[dwNeeded];
		}
		catch (...)
		{
			buffer = NULL;
		}
	
		require_action( buffer, exit, kNoMemoryErr );
		ok = EnumPrinters(PRINTER_ENUM_LOCAL, NULL, 4, buffer, dwNeeded, &dwNeeded, &dwNumPrinters);
		err = translate_errno( ok, errno_compat(), kUnknownErr );
		require_noerr( err, exit );

		for (DWORD index = 0; index < dwNumPrinters; index++)
		{
			PRINTER_INFO_4 * lppi4 = (PRINTER_INFO_4*) (buffer + index * sizeof(PRINTER_INFO_4));

			m_printerNames.push_back( lppi4->pPrinterName );
		}
	}

exit:

	if (buffer != NULL)
	{
		delete [] buffer;
	}

	return err;
}



// ------------------------------------------------------
// InstallPrinter
//
// Installs a printer with Windows.
//
// Note: this works one of two ways, depending on whether
// there are drivers already installed for this printer.
// If there are, then we can just create a port with XcvData,
// and then call AddPrinter.  If not, we use the printui.dll
// to install the printer. Actually installing drivers that
// are not currently installed is painful, and it's much
// easier and less error prone to just let printui.dll do
// the hard work for us.
//	

OSStatus
CPrinterSetupWizardSheet::InstallPrinter(Printer * printer)
{
	Logger		log;
	CUPSLibrary	cupsLib;
	Service	*	service		= NULL;
	BOOL		ok;
	OSStatus	err = 0;

	service = printer->services.front();
	check( service );

	if ( printer->isCUPSPrinter && cupsLib.IsInstalled() )
	{
		err = InstallPrinterCUPS( printer, service, cupsLib );
		require_noerr( err, exit );
	}
	else
	{
		//
		// if the driver isn't installed, then install it
		//

		if ( !printer->driverInstalled )
		{
			DWORD		dwResult;
			HANDLE		hThread;
			unsigned	threadID;

			m_driverThreadFinished = false;
		
			//
			// create the thread
			//
			hThread = (HANDLE) _beginthreadex_compat( NULL, 0, InstallDriverThread, printer, 0, &threadID );
			err = translate_errno( hThread, (OSStatus) GetLastError(), kUnknownErr );
			require_noerr_with_log( log, "_beginthreadex_compat()", err, exit );
				
			//
			// go modal
			//
			while (!m_driverThreadFinished)
			{
				MSG msg;
		
				GetMessage( &msg, m_hWnd, 0, 0 );
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}

			//
			// Wait until child process exits.
			//
			dwResult = WaitForSingleObject( hThread, INFINITE );
			err = translate_errno( dwResult == WAIT_OBJECT_0, errno_compat(), err = kUnknownErr );
			require_noerr_with_log( log, "WaitForSingleObject()", err, exit );

			//
			// check the return value of thread
			//
			require_noerr_with_log( log, "thread exit code", m_driverThreadExitCode, exit );

			//
			// now we know that the driver was successfully installed
			//
			printer->driverInstalled = true;
		}

		if ( service->type == kPDLServiceType )
		{
			err = InstallPrinterPort( printer, service, PROTOCOL_RAWTCP_TYPE, log );
			require_noerr_with_log( log, "InstallPrinterPort()", err, exit );
			err = InstallPrinterPDLAndLPR( printer, service, log );
			require_noerr_with_log( log, "InstallPrinterPDLAndLPR()", err, exit );
		}
		else if ( service->type == kLPRServiceType )
		{
			err = InstallPrinterPort( printer, service, PROTOCOL_LPR_TYPE, log );
			require_noerr_with_log( log, "InstallPrinterPort()", err, exit );
			err = InstallPrinterPDLAndLPR( printer, service, log );
			require_noerr_with_log( log, "InstallPrinterPDLAndLPR()", err, exit );
		}
		else if ( service->type == kIPPServiceType )
		{
			// There's no need to install a printer port for IPP printers, because
			// the call to AddPrinter() will do that for us.

			err = InstallPrinterIPP( printer, service, log );
			require_noerr_with_log( log, "InstallPrinterIPP()", err, exit );
		}
		else
		{
			require_action_with_log( log, ( service->type == kPDLServiceType ) || ( service->type == kLPRServiceType ) || ( service->type == kIPPServiceType ), exit, err = kUnknownErr );
		}
	}

	printer->installed = true;

	//
	// if the user specified a default printer, set it
	//
	if (printer->deflt)
	{
		ok = SetDefaultPrinter( printer->actualName );
		err = translate_errno( ok, errno_compat(), err = kUnknownErr );
		require_noerr_with_log( log, "SetDefaultPrinter()", err, exit );
	}

exit:

	return err;
}


OSStatus
CPrinterSetupWizardSheet::InstallPrinterPort( Printer * printer, Service * service, DWORD protocol, Logger & log )
{
	PRINTER_DEFAULTS	printerDefaults =	{ NULL,  NULL, SERVER_ACCESS_ADMINISTER };
	PORT_DATA_1			portData;
	DWORD				dwStatus;
	DWORD				cbInputData		=	100;
	PBYTE				pOutputData		=	NULL;
	DWORD				cbOutputNeeded	=	0;
	HANDLE				hXcv			=	NULL;
	Queue			*	q;
	BOOL				ok;
	OSStatus			err;

	ZeroMemory(&portData, sizeof(PORT_DATA_1));

	require_action_with_log( log, wcslen(printer->portName) < sizeof_array(portData.sztPortName), exit, err = kSizeErr );
	wcscpy_s(portData.sztPortName, printer->portName);

	q = service->queues.front();
	check( q );

	ok = OpenPrinter(L",XcvMonitor Standard TCP/IP Port", &hXcv, &printerDefaults);
	err = translate_errno( ok, errno_compat(), kUnknownErr );
	require_noerr_with_log( log, "OpenPrinter()", err, exit );

	//
	// BUGBUG: MSDN said this is not required, but my experience shows it is required
	//
	try
	{
		pOutputData = new BYTE[cbInputData];
	}
	catch (...)
	{
		pOutputData = NULL;
	}

	require_action_with_log( log, pOutputData, exit, err = kNoMemoryErr );
    	
	portData.dwPortNumber	=	service->portNumber;
	portData.dwVersion		=	1;
	portData.dwDoubleSpool	=	1;
    	
	portData.dwProtocol	= protocol;
	portData.cbSize		= sizeof PORT_DATA_1;
	portData.dwReserved	= 0L;

	require_action_with_log( log, wcslen(q->name) < sizeof_array(portData.sztQueue), exit, err = kSizeErr );
	wcscpy_s(portData.sztQueue, q->name);

	require_action_with_log( log, wcslen( service->hostname ) < sizeof_array(portData.sztHostAddress), exit, err = kSizeErr );
	wcscpy_s( portData.sztHostAddress, service->hostname );

	ok = XcvData(hXcv, L"AddPort", (PBYTE) &portData, sizeof(PORT_DATA_1), pOutputData, cbInputData,  &cbOutputNeeded, &dwStatus);
	err = translate_errno( ok, errno_compat(), kUnknownErr );
	require_noerr_with_log( log, "XcvData()", err, exit );

exit:

	if (hXcv != NULL)
	{
		ClosePrinter(hXcv);
	}

	if (pOutputData != NULL)
	{
		delete [] pOutputData;
	}

	return err;
}


OSStatus
CPrinterSetupWizardSheet::InstallPrinterPDLAndLPR(Printer * printer, Service * service, Logger & log )
{
	PRINTER_INFO_2		pInfo;
	HANDLE				hPrinter = NULL;
	Queue			*	q;
	OSStatus			err;

	check(printer != NULL);
	check(printer->installed == false);

	q = service->queues.front();
	check( q );

	//
	// add the printer
	//
	ZeroMemory(&pInfo, sizeof(pInfo));
		
	pInfo.pPrinterName			=	printer->actualName.GetBuffer();
	pInfo.pServerName			=	NULL;
	pInfo.pShareName			=	NULL;
	pInfo.pPortName				=	printer->portName.GetBuffer();
	pInfo.pDriverName			=	printer->modelName.GetBuffer();
	pInfo.pComment				=	printer->displayModelName.GetBuffer();
	pInfo.pLocation				=	q->location.GetBuffer();
	pInfo.pDevMode				=	NULL;
	pInfo.pDevMode				=	NULL;
	pInfo.pSepFile				=	L"";
	pInfo.pPrintProcessor		=	L"winprint";
	pInfo.pDatatype				=	L"RAW";
	pInfo.pParameters			=	L"";
	pInfo.pSecurityDescriptor	=	NULL;
	pInfo.Attributes			=	PRINTER_ATTRIBUTE_QUEUED;
	pInfo.Priority				=	0;
	pInfo.DefaultPriority		=	0;
	pInfo.StartTime				=	0;
	pInfo.UntilTime				=	0;

	hPrinter = AddPrinter(NULL, 2, (LPBYTE) &pInfo);
	err = translate_errno( hPrinter, errno_compat(), kUnknownErr );
	require_noerr_with_log( log, "AddPrinter()", err, exit );

exit:

	if (hPrinter != NULL)
	{
		ClosePrinter(hPrinter);
	}

	return err;
}


OSStatus
CPrinterSetupWizardSheet::InstallPrinterIPP(Printer * printer, Service * service, Logger & log)
{
	DEBUG_UNUSED( service );

	Queue		*	q		 = service->SelectedQueue();
	HANDLE			hPrinter = NULL;
	PRINTER_INFO_2	pInfo;
	OSStatus		err;

	check( q );
	
	//
	// add the printer
	//
	ZeroMemory(&pInfo, sizeof(PRINTER_INFO_2));
	
	pInfo.pPrinterName		= printer->actualName.GetBuffer();
	pInfo.pPortName			= printer->portName.GetBuffer();
	pInfo.pDriverName		= printer->modelName.GetBuffer();
	pInfo.pPrintProcessor	= L"winprint";
	pInfo.pLocation			= q->location.GetBuffer();
	pInfo.pComment			= printer->displayModelName.GetBuffer();
	pInfo.Attributes		= PRINTER_ATTRIBUTE_NETWORK | PRINTER_ATTRIBUTE_LOCAL;
	
	hPrinter = AddPrinter(NULL, 2, (LPBYTE)&pInfo);
	err = translate_errno( hPrinter, errno_compat(), kUnknownErr );
	require_noerr_with_log( log, "AddPrinter()", err, exit );

exit:

	if ( hPrinter != NULL )
	{
		ClosePrinter(hPrinter);
	}

	return err;
}


OSStatus
CPrinterSetupWizardSheet::InstallPrinterCUPS(Printer * printer, Service * service, CUPSLibrary & cupsLib )
{
	OSStatus err = kNoErr;

	check( printer );
	check( service );
	check( cupsLib.IsInstalled() );

	err = InstallPrinterCUPS( printer, service, cupsLib, TEXT( "Windows NT x86" ) );
	require_noerr( err, exit );

	if ( Is64BitWindows() )
	{
		err = InstallPrinterCUPS( printer, service, cupsLib, TEXT( "Windows x64" ) );
		require_noerr( err, exit );
	}

exit:

	return err;
}


OSStatus
CPrinterSetupWizardSheet::InstallPrinterCUPS(Printer * printer, Service * service, CUPSLibrary & cupsLib, TCHAR * env )
{
	
	Queue		*	q;
	CString			ppdfile;				// PPD file for printer drivers
	TCHAR			driverdir[1024];		// Directory for driver files
	DWORD			needed;					// Bytes needed
	DRIVER_INFO_3	driverinfo;				// Driver information
	PRINTER_INFO_2	printerinfo;			// Printer information
	HANDLE			printerHandle = NULL;	// Handle to printer
	CString			filename;				// Driver filename
	CString			dependentFiles;			// List of dependent files
	CString			portName;				// Port Name
	int				bytes;					// Bytes copied
	TCHAR			datadir[ MAX_PATH ];	// Driver files location
	CFile			in;						// Input file
	CFile			out;					// Output file
	void		*	http;					// Connection to server
	char			buffer[4096];			// Copy/error buffer
	CString			platform;
	char			hostname[ 1024 ];
	CString			dest;
	char			destANSI[ 1024 ];
	int				i;
	DWORD			num;
	OSStatus		err	= 0;
	BOOL			ok;

	check( printer );
	check( service );
	check( cupsLib.IsInstalled() );
	check( env );

	// What do we do here for multiple queues?
	q = service->queues.front();
	require_action( q != NULL, exit, err = kUnknownErr );

	num = GetModuleFileName( NULL, datadir, MAX_PATH );
	err = translate_errno( num > 0, GetLastError(), kUnknownErr );
	require_noerr( err, exit );
	ok = PathRemoveFileSpec( datadir );
	require_action( ok, exit, err = kUnknownErr );

	ok = GetPrinterDriverDirectory(NULL, env, 1, ( LPBYTE ) driverdir, sizeof( driverdir ), &needed );
	err = translate_errno( ok, GetLastError(), kUnknownErr );
	require_noerr( err, exit );

	platform = env;
	platform = platform.Right( 3 );

	// Append the supported banner pages to the PPD file...
	err = StringObjectToUTF8String( service->hostname, hostname, sizeof( hostname ) );
	require_noerr( err, exit );
	http = cupsLib.httpConnectEncrypt( hostname, service->portNumber, cupsLib.cupsEncryption() );
	err = translate_errno( http != NULL, errno, kUnknownErr );
	require_noerr( err, exit );

	if ( ( service->portNumber == 443 ) || ( cupsLib.cupsEncryption() >= HTTP_ENCRYPT_REQUIRED ) )
	{
		// This forces the use the https: URLs below...
		cupsLib.cupsSetEncryption( HTTP_ENCRYPT_ALWAYS );
	}

	// Strip the leading "printers/" or "classes/" from the beginning
	// of the name

	dest = q->name;
	dest.Replace( TEXT( "printers/" ), TEXT( "" ) );
	dest.Replace( TEXT( "classes/" ), TEXT( "" ) );

	err = StringObjectToUTF8String( dest, destANSI, sizeof( destANSI ) );
	require_noerr( err, exit );

	// Get the PPD file...
	for ( i = 0; i < 10; i++ )
	{
		char ppdfileANSI[ 1024 ];

		if ( cupsLib.cupsAdminCreateWindowsPPD( http, destANSI, ppdfileANSI, sizeof( ppdfileANSI ) ) )
		{
			err = UTF8StringToStringObject( ppdfileANSI, ppdfile );
			require_noerr( err, exit );
			break;
		}
	}

	err = translate_errno( i < 10, errno, kUnknownErr );
	require_noerr( err, exit );

	// Copy the PPD file to the Windows driver directory...
	filename.Format( TEXT( "%s/%s.ppd" ), driverdir, dest );

	ok = in.Open( ppdfile, CFile::modeRead | CFile::typeBinary );
	translate_errno( ok, GetLastError(), kUnknownErr );
	require_noerr( err, exit );

	ok = out.Open( filename, CFile::modeCreate | CFile::modeWrite | CFile::typeBinary );
	translate_errno( ok, GetLastError(), kUnknownErr );
	require_noerr( err, exit );

	while ( ( bytes = in.Read( buffer, sizeof(buffer) ) ) > 0 )
	{
		out.Write(buffer, bytes );
	}

	in.Close();
	out.Close();

	// Cleanup temp file...
	CFile::Remove( ppdfile );

	// Copy the driver files to the driver directory...
	for ( i = 0; i < ( sizeof( g_printerDriverFiles ) / sizeof( g_printerDriverFiles[0] ) ); i++ )
	{
		filename.Format( TEXT( "%s/drivers/%s/%s" ), datadir, platform, g_printerDriverFiles[i]);
	
		ok = in.Open(filename, CFile::modeRead | CFile::typeBinary );
		err = translate_errno( ok, GetLastError(), kUnknownErr );
		require_noerr( err, exit );

		filename.Format( TEXT( "%s/%s" ), driverdir, g_printerDriverFiles[i] );
		ok = out.Open(filename, CFile::modeCreate | CFile::modeWrite | CFile::typeBinary );
		err = translate_errno( ok, errno, kUnknownErr );

		while ( ( bytes = in.Read(buffer, sizeof( buffer ) ) ) > 0 )
		{
			out.Write( buffer, bytes );
		}

		in.Close();
		out.Close();
	}

	// Do the Windows system calls needed to add the printer driver...
	filename.Format( TEXT( "%s.ppd" ), dest);
	dependentFiles.Format( TEXT( "pscript5.dll%c" ) TEXT( "%s.ppd%c" ) TEXT( "ps5ui.dll%c" ) TEXT( "pscript.hlp%c" ) TEXT( "pscript.ntf%c" ) TEXT( "cups6.ini%c" ) TEXT( "cupsps6.dll%c" ) TEXT( "cupsui6.dll%c" ), 0, dest, 0, 0, 0, 0, 0, 0, 0);

	driverinfo.cVersion         = 3;
	driverinfo.pName            = printer->actualName.GetBuffer();
	driverinfo.pEnvironment     = env;
	driverinfo.pDriverPath      = TEXT( "pscript5.dll" );
	driverinfo.pDataFile        = filename.GetBuffer();
	driverinfo.pConfigFile      = TEXT( "ps5ui.dll" );
	driverinfo.pHelpFile        = TEXT( "pscript.hlp" );
	driverinfo.pDependentFiles  = dependentFiles.GetBuffer();
	driverinfo.pMonitorName     = NULL;
	driverinfo.pDefaultDataType = TEXT( "raw" );

	ok = AddPrinterDriverEx(NULL, 3, (LPBYTE) &driverinfo, APD_COPY_ALL_FILES );
	err = translate_errno( ok, GetLastError(), kUnknownErr );
	require_noerr( err, exit );

	// See if the printer has already been added?
	if ( OpenPrinter( printer->actualName.GetBuffer(), &printerHandle, NULL ) )
    {
		// Printer already exists, so we are done now...
		goto exit;
    }

    // Add the printer using the HTTP/IPP port...
	portName.Format( TEXT( "%s://%s:%d/printers/%s" ), cupsLib.cupsEncryption() == HTTP_ENCRYPT_ALWAYS ? TEXT( "https" ) : TEXT( "http" ), service->hostname.GetBuffer(), service->portNumber, dest );

    memset(&printerinfo, 0, sizeof(printerinfo));
    printerinfo.pPrinterName	= printer->actualName.GetBuffer();
    printerinfo.pPortName		= portName.GetBuffer();
    printerinfo.pDriverName		= printer->actualName.GetBuffer();
    printerinfo.Attributes		= PRINTER_ATTRIBUTE_NETWORK | PRINTER_ATTRIBUTE_LOCAL;
	printerinfo.pComment		= q->description.GetBuffer();
	printerinfo.pLocation		= q->location.GetBuffer();
	printerinfo.pPrintProcessor = TEXT( "winprint" );

    printerHandle = AddPrinter( NULL, 2, (LPBYTE) &printerinfo );
	err = translate_errno( printerHandle, GetLastError(), kUnknownErr );
	require_noerr( err, exit );

exit:

	if ( printerHandle != NULL )
	{
		ClosePrinter( printerHandle );
		printerHandle = NULL;
	}

	return err;
}

BEGIN_MESSAGE_MAP(CPrinterSetupWizardSheet, CPropertySheet)
ON_MESSAGE( WM_SOCKET_EVENT, OnSocketEvent )
ON_MESSAGE( WM_PROCESS_EVENT, OnProcessEvent )
ON_WM_SETCURSOR()
ON_WM_TIMER()
END_MESSAGE_MAP()


// ------------------------------------------------------
// OnCommand
//
// Traps when the user hits Finish  
//	
BOOL CPrinterSetupWizardSheet::OnCommand(WPARAM wParam, LPARAM lParam)
{
	//
	// Check if this is OK
	//
	if (wParam == ID_WIZFINISH)              // If OK is hit...
	{
		OnOK();
	}
 
	return CPropertySheet::OnCommand(wParam, lParam);
}


// ------------------------------------------------------
// OnInitDialog
//
// Initializes this Dialog object.
//	
BOOL CPrinterSetupWizardSheet::OnInitDialog()
{
	OSStatus err;

	CPropertySheet::OnInitDialog();

	err = StartBrowse();
	require_noerr( err, exit );

exit:

	if ( err )
	{
		StopBrowse();

		if ( err == kDNSServiceErr_Firewall )
		{
			CString text, caption;

			text.LoadString( IDS_FIREWALL );
			caption.LoadString( IDS_FIREWALL_CAPTION );

			MessageBox(text, caption, MB_OK|MB_ICONEXCLAMATION);
		}
		else
		{
			CString text, caption;

			text.LoadString( IDS_NO_MDNSRESPONDER_SERVICE_TEXT );
			caption.LoadString( IDS_ERROR_CAPTION );

			MessageBox(text, caption, MB_OK|MB_ICONEXCLAMATION);

			_exit( 0 );
		}
	}

	return TRUE;
}


// ------------------------------------------------------
// OnSetCursor
//
// This is called when Windows wants to know what cursor
// to display.  So we tell it.  
//	
BOOL
CPrinterSetupWizardSheet::OnSetCursor(CWnd * pWnd, UINT nHitTest, UINT message)
{
	DEBUG_UNUSED(pWnd);
	DEBUG_UNUSED(nHitTest);
	DEBUG_UNUSED(message);

	SetCursor(m_active);
	return TRUE;
}


// ------------------------------------------------------
// OnContextMenu
//
// This is not fully implemented yet.  
//	

void
CPrinterSetupWizardSheet::OnContextMenu(CWnd * pWnd, CPoint pos)
{
	DEBUG_UNUSED(pWnd);
	DEBUG_UNUSED(pos);

	CAbout dlg;

	dlg.DoModal();
}


// ------------------------------------------------------
// OnOK
//
// This is called when the user hits the "Finish" button  
//	
void
CPrinterSetupWizardSheet::OnOK()
{
	CWnd * window;
	OSStatus err;

	check ( m_selectedPrinter != NULL );

	SetWizardButtons( PSWIZB_DISABLEDFINISH );

	window = GetDlgItem( IDCANCEL );
 
	if ( window )
	{
		window->EnableWindow( FALSE );
	}

	m_pgFourth.StartActivityIndicator();
	
	err = InstallPrinter( m_selectedPrinter );

	m_pgFourth.StopActivityIndicator();

	if ( err != kNoErr )
	{
		CString caption;
		CString message;

		caption.LoadString(IDS_INSTALL_ERROR_CAPTION);
		caption.AppendFormat( TEXT( " (%d)" ), err );
		message.LoadString(IDS_INSTALL_ERROR_MESSAGE);
		MessageBox(message, caption, MB_OK|MB_ICONEXCLAMATION);
	}

	StopBrowse();
}


// CPrinterSetupWizardSheet message handlers

void CPrinterSetupWizardSheet::Init(void)
{
	AddPage(&m_pgSecond);
	AddPage(&m_pgThird);
	AddPage(&m_pgFourth);

	m_psh.dwFlags &= (~PSH_HASHELP);

	m_psh.dwFlags |= PSH_WIZARD97|PSH_WATERMARK|PSH_HEADER;
	m_psh.pszbmWatermark = MAKEINTRESOURCE(IDB_WATERMARK);
	m_psh.pszbmHeader = MAKEINTRESOURCE(IDB_BANNER_ICON);

	m_psh.hInstance = GetNonLocalizedResources();

	SetWizardMode();
}


LRESULT
CPrinterSetupWizardSheet::OnSocketEvent(WPARAM inWParam, LPARAM inLParam)
{
	if (WSAGETSELECTERROR(inLParam) && !(HIWORD(inLParam)))
    {
		dlog( kDebugLevelError, "OnServiceEvent: window error\n" );
    }
    else
    {
		SOCKET sock = (SOCKET) inWParam;

		// iterate thru list
		ServiceRefList::iterator begin = m_serviceRefList.begin();
		ServiceRefList::iterator end   = m_serviceRefList.end();

		while (begin != end)
		{
			DNSServiceRef ref = *begin++;

			check(ref != NULL);

			if ((SOCKET) DNSServiceRefSockFD(ref) == sock)
			{
				DNSServiceProcessResult(ref);
				break;
			}
		}
	}

	return ( 0 );
}


LRESULT
CPrinterSetupWizardSheet::OnProcessEvent(WPARAM inWParam, LPARAM inLParam)
{
	DEBUG_UNUSED(inLParam);

	m_driverThreadExitCode	=	(DWORD) inWParam;
	m_driverThreadFinished	=	true;

	return 0;
}


unsigned WINAPI
CPrinterSetupWizardSheet::InstallDriverThread( LPVOID inParam )
{	
	Printer			*	printer = (Printer*) inParam;
	DWORD				exitCode = 0;
	DWORD				dwResult;
	OSStatus			err;
	STARTUPINFO			si;
	PROCESS_INFORMATION pi;
	BOOL				ok;

	check( printer );
	check( m_self );

	//
	// because we're calling endthreadex(), C++ objects won't be cleaned up
	// correctly.  we'll nest the CString 'command' inside a block so
	// that it's destructor will be invoked.
	//
	{
		CString command;

		ZeroMemory( &si, sizeof(si) );
		si.cb = sizeof(si);
		ZeroMemory( &pi, sizeof(pi) );

		command.Format(L"rundll32.exe printui.dll,PrintUIEntry /ia /m \"%s\" /f \"%s\"", (LPCTSTR) printer->modelName, (LPCTSTR) printer->infFileName );

		ok = CreateProcess(NULL, command.GetBuffer(), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
		err = translate_errno( ok, errno_compat(), kUnknownErr );
		require_noerr( err, exit );

		dwResult = WaitForSingleObject( pi.hProcess, INFINITE );
		translate_errno( dwResult == WAIT_OBJECT_0, errno_compat(), err = kUnknownErr );
		require_noerr( err, exit );

		ok = GetExitCodeProcess( pi.hProcess, &exitCode );
		err = translate_errno( ok, errno_compat(), kUnknownErr );
		require_noerr( err, exit );
	}

exit:

	//
	// Close process and thread handles. 
	//
	if ( pi.hProcess )
	{
		CloseHandle( pi.hProcess );
	}

	if ( pi.hThread )
	{
		CloseHandle( pi.hThread );
	}

	//
	// alert the main thread
	//
	m_self->PostMessage( WM_PROCESS_EVENT, err, exitCode );

	_endthreadex_compat( 0 );

	return 0;
}


void DNSSD_API
CPrinterSetupWizardSheet::OnBrowse(
							DNSServiceRef 			inRef,
							DNSServiceFlags 		inFlags,
							uint32_t 				inInterfaceIndex,
							DNSServiceErrorType 	inErrorCode,
							const char *			inName,	
							const char *			inType,	
							const char *			inDomain,	
							void *					inContext )
{
	DEBUG_UNUSED(inRef);

	CPrinterSetupWizardSheet	*	self;
	bool							moreComing = (bool) (inFlags & kDNSServiceFlagsMoreComing);
	CPropertyPage				*	active;
	Printer						*	printer = NULL;
	Service						*	service = NULL;
	OSStatus						err = kNoErr;

	require_noerr( inErrorCode, exit );

	self = reinterpret_cast <CPrinterSetupWizardSheet*>( inContext );
	require_quiet( self, exit );

	active = self->GetActivePage();
	require_quiet( active, exit );

	// Have we seen this printer before?

	printer = self->Lookup( inName );

	if ( printer )
	{
		service = printer->LookupService( inType );
	}

	if ( inFlags & kDNSServiceFlagsAdd )
	{
		BOOL newPrinter = FALSE;

		if ( !printer )
		{
			printer = self->OnAddPrinter( inInterfaceIndex, inName, inType, inDomain, moreComing );
			require_action( printer, exit, err = kUnknownErr );
			newPrinter = TRUE;
		}
		
		// If we're looking at the browse list on page 2, then we need to call
		// CPage2::OnAddPrinter() regardless of whether we've seen the printer
		// or not because the moreComing flag might have changed from a previous
		// call. If we only call CPage2::OnAddPrinter() when there's a new printer,
		// we might not correctly update our UI, so if we've seen the printer before,
		// call OnAddPrinter with a NULL parameter.

		if ( self->GetActivePage() == &self->m_pgSecond )
		{
			self->m_pgSecond.OnAddPrinter( newPrinter ? printer : NULL, moreComing );
		}

		if ( !service )
		{
			err = self->OnAddService( printer, inInterfaceIndex, inName, inType, inDomain );
			require_noerr( err, exit );
		}
		else
		{
			service->refs++;
		}
	}
	else if ( printer )
	{
		check( service );

		err = self->OnRemoveService( service );
		require_noerr( err, exit );

		if ( printer->services.size() == 0 )
		{
			err = self->OnRemovePrinter( printer, moreComing );
			require_noerr( err, exit );
		}
	}

exit:
	
	return;
}


void DNSSD_API
CPrinterSetupWizardSheet::OnResolve(
								DNSServiceRef			inRef,
								DNSServiceFlags			inFlags,
								uint32_t				inInterfaceIndex,
								DNSServiceErrorType		inErrorCode,
								const char *			inFullName,	
								const char *			inHostName, 
								uint16_t 				inPort,
								uint16_t 				inTXTSize,
								const char *			inTXT,
								void *					inContext )
{
	DEBUG_UNUSED(inFullName);
	DEBUG_UNUSED(inInterfaceIndex);
	DEBUG_UNUSED(inFlags);
	DEBUG_UNUSED(inRef);

	CPrinterSetupWizardSheet	*	self;
	Service						*	service;
	Queue						*	q;
	int								idx;
	OSStatus						err;

	require_noerr( inErrorCode, exit );

	service = reinterpret_cast<Service*>( inContext );
	require_quiet( service, exit);

	check( service->refs != 0 );

	self = service->printer->window;
	require_quiet( self, exit );

	err = self->StopOperation( service->serviceRef );
	require_noerr( err, exit );
	
	//
	// hold on to the hostname...
	//
	err = UTF8StringToStringObject( inHostName, service->hostname );
	require_noerr( err, exit );

	//
	// <rdar://problem/3739200> remove the trailing dot on hostname
	//
	idx = service->hostname.ReverseFind('.');

	if ((idx > 1) && ((service->hostname.GetLength() - 1) == idx))
	{
		service->hostname.Delete(idx, 1);
	}

	//
	// hold on to the port
	//
	service->portNumber = ntohs(inPort);

	if ( service->qtotal == 1 )
	{	
		//
		// create a new queue
		//
		try
		{
			q = new Queue;
		}
		catch (...)
		{
			q = NULL;
		}

		require_action( q, exit, err = E_OUTOFMEMORY );

		//
		// parse the text record.
		//

		err = self->ParseTextRecord( service, q, inTXTSize, inTXT );
		require_noerr( err, exit );

		service->queues.push_back( q );

		//
		// we've completely resolved this service
		//

		self->OnResolveService( service );
	}
	else
	{
		//
		// if qtotal is more than 1, then we need to get additional
		// text records.  if not, then this service is considered
		// resolved
		//

		err = DNSServiceQueryRecord(&service->serviceRef, 0, inInterfaceIndex, inFullName, kDNSServiceType_TXT, kDNSServiceClass_IN, OnQuery, (void*) service );
		require_noerr( err, exit );

		err = self->StartOperation( service->serviceRef );
		require_noerr( err, exit );
	}

exit:

	return;
}


void DNSSD_API
CPrinterSetupWizardSheet::OnQuery(
							DNSServiceRef		inRef, 
							DNSServiceFlags		inFlags, 
							uint32_t			inInterfaceIndex, 
							DNSServiceErrorType inErrorCode,
							const char		*	inFullName, 
							uint16_t			inRRType, 
							uint16_t			inRRClass, 
							uint16_t			inRDLen, 
							const void		*	inRData, 
							uint32_t			inTTL, 
							void			*	inContext)
{
	DEBUG_UNUSED( inTTL );
	DEBUG_UNUSED( inRRClass );
	DEBUG_UNUSED( inRRType );
	DEBUG_UNUSED( inFullName );
	DEBUG_UNUSED( inInterfaceIndex );
	DEBUG_UNUSED( inRef );

	Service						*	service = NULL;
	Queue						*	q;
	CPrinterSetupWizardSheet	*	self;
	OSStatus						err = kNoErr;

	require_noerr( inErrorCode, exit );

	service = reinterpret_cast<Service*>( inContext );
	require_quiet( service, exit);

	self = service->printer->window;
	require_quiet( self, exit );

	if ( ( inFlags & kDNSServiceFlagsAdd ) && ( inRDLen > 0 ) && ( inRData != NULL ) )
	{
		const char * inTXT = ( const char * ) inRData;

		//
		// create a new queue
		//
		try
		{
			q = new Queue;
		}
		catch (...)
		{
			q = NULL;
		}

		require_action( q, exit, err = E_OUTOFMEMORY );

		err = service->printer->window->ParseTextRecord( service, q, inRDLen, inTXT );
		require_noerr( err, exit );

		//
		// add this queue
		//

		service->queues.push_back( q );

		if ( service->queues.size() == service->qtotal )
		{
			//
			// else if moreComing is not set, then we're going
			// to assume that we're done
			//

			self->StopOperation( service->serviceRef );

			//
			// sort the queues
			//

			service->queues.sort( OrderQueueFunc );

			//
			// we've completely resolved this service
			//

			self->OnResolveService( service );
		}
	}

exit:

	if ( err && service && ( service->serviceRef != NULL ) )
	{
		service->printer->window->StopOperation( service->serviceRef );
	}

	return;
}


Printer*
CPrinterSetupWizardSheet::OnAddPrinter(
								uint32_t 		inInterfaceIndex,
								const char *	inName,	
								const char *	inType,	
								const char *	inDomain,
								bool			moreComing)
{
	Printer	*	printer = NULL;
	DWORD		printerNameCount;
	OSStatus	err;

	DEBUG_UNUSED( inInterfaceIndex );
	DEBUG_UNUSED( inType );
	DEBUG_UNUSED( inDomain );
	DEBUG_UNUSED( moreComing );

	try
	{
		printer = new Printer;
	}
	catch (...)
	{
		printer = NULL;
	}

	require_action( printer, exit, err = E_OUTOFMEMORY );

	printer->window		=	this;
	printer->name		=	inName;
	
	err = UTF8StringToStringObject(inName, printer->displayName);
	check_noerr( err );
	printer->actualName	=	printer->displayName;
	printer->installed	=	false;
	printer->deflt		=	false;
	printer->resolving	=	0;

	// Compare this name against printers that are already installed
	// to avoid name clashes.  Rename as necessary
	// to come up with a unique name.

	printerNameCount = 2;

	for (;;)
	{
		CPrinterSetupWizardSheet::PrinterNames::iterator it;

		// <rdar://problem/4141221> Don't use find to do comparisons because we need to
		// do a case insensitive string comparison

		for ( it = m_printerNames.begin(); it != m_printerNames.end(); it++ )
		{
			if ( (*it).CompareNoCase( printer->actualName ) == 0 )
			{
				break;
			}
		}

		if (it != m_printerNames.end())
		{
			printer->actualName.Format(L"%s (%d)", printer->displayName, printerNameCount);
		}
		else
		{
			break;
		}

		printerNameCount++;
	}

	m_printers.push_back( printer );

exit:

	return printer;
}


OSStatus
CPrinterSetupWizardSheet::OnAddService(
								Printer		*	printer,
								uint32_t 		inInterfaceIndex,
								const char	*	inName,	
								const char	*	inType,	
								const char	*	inDomain)
{
	Service	*	service = NULL;
	OSStatus	err     = kNoErr;

	DEBUG_UNUSED( inName );
	DEBUG_UNUSED( inDomain );

	try
	{
		service = new Service;
	}
	catch (...)
	{
		service = NULL;
	}

	require_action( service, exit, err = E_OUTOFMEMORY );
	
	service->printer	=	printer;
	service->ifi		=	inInterfaceIndex;
	service->type		=	inType;
	service->domain		=	inDomain;
	service->qtotal		=	1;
	service->refs		=	1;
	service->serviceRef	=	NULL;

	printer->services.push_back( service );

	//
	// if the printer is selected, then we'll want to start a
	// resolve on this guy
	//

	if ( printer == m_selectedPrinter )
	{
		StartResolve( service );
	}

exit:

	return err;
}


OSStatus
CPrinterSetupWizardSheet::OnRemovePrinter( Printer * printer, bool moreComing )
{
	CPropertyPage	*	active	= GetActivePage();
	OSStatus			err		= kNoErr;

	if ( active == &m_pgSecond )
	{
		m_pgSecond.OnRemovePrinter( printer, moreComing );
	}

	m_printers.remove( printer );

	if ( m_selectedPrinter == printer )
	{
		m_selectedPrinter = NULL;

		if ( ( active == &m_pgThird ) || ( active == &m_pgFourth ) )
		{
			CString caption;
			CString message;

			caption.LoadString( IDS_ERROR_CAPTION );
			message.LoadString( IDS_PRINTER_UNAVAILABLE );

			MessageBox(message, caption, MB_OK|MB_ICONEXCLAMATION);

			SetActivePage( &m_pgSecond );
		}
	}

	delete printer;

	return err;
}


OSStatus
CPrinterSetupWizardSheet::OnRemoveService( Service * service )
{
	OSStatus err = kNoErr;

	if ( service && ( --service->refs == 0 ) )
	{
		if ( service->serviceRef != NULL )
		{
			err = StopResolve( service );
			require_noerr( err, exit );
		}

		service->printer->services.remove( service );

		delete service;
	}

exit:

	return err;	
}


void
CPrinterSetupWizardSheet::OnResolveService( Service * service )
{
	// Make sure that the active page is page 2

	require_quiet( GetActivePage() == &m_pgSecond, exit );

	if ( !--service->printer->resolving )
	{
		// sort the services now.  we want the service that
		// has the highest priority queue to be first in
		// the list.

		service->printer->services.sort( OrderServiceFunc );

		// Now we can hit next

		SetWizardButtons( PSWIZB_BACK|PSWIZB_NEXT );
	
		// Reset the cursor	
		
		m_active = m_arrow;

		// And tell page 2 about it

		m_pgSecond.OnResolveService( service );
	}		

exit:

	return;
}


OSStatus
CPrinterSetupWizardSheet::StartBrowse()
{
	OSStatus err;

	//
	// setup the DNS-SD browsing
	//
	err = DNSServiceBrowse( &m_pdlBrowser, 0, 0, kPDLServiceType, NULL, OnBrowse, this );
	require_noerr( err, exit );

	err = StartOperation( m_pdlBrowser );
	require_noerr( err, exit );

	err = DNSServiceBrowse( &m_lprBrowser, 0, 0, kLPRServiceType, NULL, OnBrowse, this );
	require_noerr( err, exit );

	err = StartOperation( m_lprBrowser );
	require_noerr( err, exit );

	err = DNSServiceBrowse( &m_ippBrowser, 0, 0, kIPPServiceType, NULL, OnBrowse, this );
	require_noerr( err, exit );

	err = StartOperation( m_ippBrowser );
	require_noerr( err, exit );

exit:

	return err;
}


OSStatus
CPrinterSetupWizardSheet::StopBrowse()
{
	OSStatus err;

	err = StopOperation( m_pdlBrowser );
	require_noerr( err, exit );

	err = StopOperation( m_lprBrowser );
	require_noerr( err, exit );

	err = StopOperation( m_ippBrowser );
	require_noerr( err, exit );

	while ( m_printers.size() > 0 )
	{
		Printer * printer = m_printers.front();

		m_printers.pop_front();

		if ( printer->resolving )
		{
			StopResolve( printer );
		}

		delete printer;
	}

exit:

	return err;
}


OSStatus
CPrinterSetupWizardSheet::StartResolve( Printer * printer )
{
	OSStatus			err = kNoErr;
	Services::iterator	it;

	check( printer );

	for ( it = printer->services.begin(); it != printer->services.end(); it++ )
	{
		if ( (*it)->serviceRef == NULL )
		{
			err = StartResolve( *it );
			require_noerr( err, exit );
		}
	}

	m_selectedPrinter = printer;

exit:

	return err;
}


OSStatus
CPrinterSetupWizardSheet::StartResolve( Service * service )
{
	OSStatus err = kNoErr;

	check( service->serviceRef == NULL );

	//
	// clean out any queues that were collected during a previous
	// resolve
	//

	service->EmptyQueues();

	//
	// now start the new resolve
	//

	err = DNSServiceResolve( &service->serviceRef, 0, 0, service->printer->name.c_str(), service->type.c_str(), service->domain.c_str(), (DNSServiceResolveReply) OnResolve, service );
	require_noerr( err, exit );

	err = StartOperation( service->serviceRef );
	require_noerr( err, exit );

	//
	// If we're not currently resolving, then disable the next button
	// and set the cursor to hourglass
	//

	if ( !service->printer->resolving )
	{
		SetWizardButtons( PSWIZB_BACK );

		m_active = m_wait;
		SetCursor(m_active);
	}

	service->printer->resolving++;

exit:

	return err;
}


OSStatus
CPrinterSetupWizardSheet::StopResolve(Printer * printer)
{
	OSStatus err = kNoErr;

	check( printer );

	Services::iterator it;

	for ( it = printer->services.begin(); it != printer->services.end(); it++ )
	{
		if ( (*it)->serviceRef )
		{
			err = StopResolve( *it );
			require_noerr( err, exit );
		}
	}

exit:

	return err;
}


OSStatus
CPrinterSetupWizardSheet::StopResolve( Service * service )
{
	OSStatus err;

	check( service->serviceRef );

	err = StopOperation( service->serviceRef );
	require_noerr( err, exit );

	service->printer->resolving--;

exit:

	return err;
}


OSStatus
CPrinterSetupWizardSheet::StartOperation( DNSServiceRef ref )
{
	OSStatus err;

	err = WSAAsyncSelect((SOCKET) DNSServiceRefSockFD(ref), m_hWnd, WM_SOCKET_EVENT, FD_READ|FD_CLOSE);
	require_noerr( err, exit );

	m_serviceRefList.push_back( ref );

exit:

	return err;
}


OSStatus
CPrinterSetupWizardSheet::StopOperation( DNSServiceRef & ref )
{
	OSStatus err = kNoErr;

	if ( ref )
	{
		m_serviceRefList.remove( ref );

		if ( IsWindow( m_hWnd ) )
		{
			err = WSAAsyncSelect((SOCKET) DNSServiceRefSockFD( ref ), m_hWnd, 0, 0 );
			require_noerr( err, exit );
		}

		DNSServiceRefDeallocate( ref );
		ref = NULL;
	}

exit:

	return err;
}


OSStatus
CPrinterSetupWizardSheet::ParseTextRecord( Service * service, Queue * q, uint16_t inTXTSize, const char * inTXT )
{
	check( service );
	check( q );

	// <rdar://problem/3946587> Use TXTRecord APIs declared in dns_sd.h
	
	bool			qtotalDefined	= false;
	const void	*	val;
	char			buf[256];
	uint8_t			len;
	OSStatus		err				= kNoErr;

	// <rdar://problem/3987680> Default to queue "lp"

	q->name = L"lp";

	// <rdar://problem/4003710> Default pdl key to be "application/postscript"

	q->pdl = L"application/postscript";

	if ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "rp", &len ) ) != NULL )
	{
		// Stringize val ( doesn't have trailing '\0' yet )

		memcpy( buf, val, len );
		buf[len] = '\0';

		err = UTF8StringToStringObject( buf, q->name );
		require_noerr( err, exit );
	}
	
	if ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "pdl", &len ) ) != NULL )
	{
		// Stringize val ( doesn't have trailing '\0' yet )

		memcpy( buf, val, len );
		buf[len] = '\0';

		err = UTF8StringToStringObject( buf, q->pdl );
		require_noerr( err, exit );
	}
	
	if ( ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "usb_mfg", &len ) ) != NULL ) ||
	     ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "usb_manufacturer", &len ) ) != NULL ) )
	{
		// Stringize val ( doesn't have trailing '\0' yet )

		memcpy( buf, val, len );
		buf[len] = '\0';

		err = UTF8StringToStringObject( buf, q->usb_MFG );
		require_noerr( err, exit );
	}
	
	if ( ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "usb_mdl", &len ) ) != NULL ) ||
	     ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "usb_model", &len ) ) != NULL ) )
	{
		// Stringize val ( doesn't have trailing '\0' yet )

		memcpy( buf, val, len );
		buf[len] = '\0';

		err = UTF8StringToStringObject( buf, q->usb_MDL );
		require_noerr( err, exit );
	}

	if ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "ty", &len ) ) != NULL )
	{
		// Stringize val ( doesn't have trailing '\0' yet )

		memcpy( buf, val, len );
		buf[len] = '\0';

		err = UTF8StringToStringObject( buf, q->description );
		require_noerr( err, exit );
	}
		
	if ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "product", &len ) ) != NULL )
	{
		// Stringize val ( doesn't have trailing '\0' yet )

		memcpy( buf, val, len );
		buf[len] = '\0';

		err = UTF8StringToStringObject( buf, q->product );
		require_noerr( err, exit );
	}

	if ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "note", &len ) ) != NULL )
	{
		// Stringize val ( doesn't have trailing '\0' yet )

		memcpy( buf, val, len );
		buf[len] = '\0';

		err = UTF8StringToStringObject( buf, q->location );
		require_noerr( err, exit );
	}

	if ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "qtotal", &len ) ) != NULL )
	{
		// Stringize val ( doesn't have trailing '\0' yet )

		memcpy( buf, val, len );
		buf[len] = '\0';

		service->qtotal = (unsigned short) atoi( buf );
		qtotalDefined = true;
	}

	if ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "priority", &len ) ) != NULL )
	{
		// Stringize val ( doesn't have trailing '\0' yet )

		memcpy( buf, val, len );
		buf[len] = '\0';

		q->priority = atoi( buf );
	}

	// <rdar://problem/4124524> Was this printer discovered via OS X Printer Sharing?

	if ( TXTRecordContainsKey( inTXTSize, inTXT, "printer-state" ) || TXTRecordContainsKey( inTXTSize, inTXT, "printer-type" ) )
	{
		service->printer->isCUPSPrinter = true;
	}

exit:

	// The following code is to fix a problem with older HP 
	// printers that don't include "qtotal" in their text
	// record.  We'll check to see if the q->name is "TEXT"
	// and if so, we're going to modify it to be "lp" so
	// that we don't use the wrong queue

	if ( !err && !qtotalDefined && ( q->name == L"TEXT" ) )
	{
		q->name = "lp";
	}

	return err;
}


Printer*
CPrinterSetupWizardSheet::Lookup(const char * inName)
{
	check( inName );

	Printer			*	printer = NULL;
	Printers::iterator	it;

	for ( it = m_printers.begin(); it != m_printers.end(); it++ )
	{
		if ( (*it)->name == inName )
		{
			printer = *it;
			break;
		}
	}

	return printer;
}


bool
CPrinterSetupWizardSheet::OrderServiceFunc( const Service * a, const Service * b )
{
	Queue * q1, * q2;

	q1 = (a->queues.size() > 0) ? a->queues.front() : NULL;

	q2 = (b->queues.size() > 0) ? b->queues.front() : NULL;

	if ( !q1 && !q2 )
	{
		return true;
	}
	else if ( q1 && !q2 )
	{
		return true;
	}
	else if ( !q1 && q2 )
	{
		return false;
	}
	else if ( q1->priority < q2->priority )
	{
		return true;
	}
	else if ( q1->priority > q2->priority )
	{
		return false;
	}
	else if ( ( a->type == kPDLServiceType ) || ( ( a->type == kLPRServiceType ) && ( b->type == kIPPServiceType ) ) )
	{
		return true;
	}
	else
	{
		return false;
	}
}


bool
CPrinterSetupWizardSheet::OrderQueueFunc( const Queue * q1, const Queue * q2 )
{
	return ( q1->priority <= q2->priority ) ? true : false;
}