1/* -*- Mode: C; tab-width: 4 -*-
2 *
3 * Copyright (c) 1997-2004 Apple Computer, Inc. All rights reserved.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18#include "stdafx.h"
19#include "PrinterSetupWizardApp.h"
20#include "PrinterSetupWizardSheet.h"
21#include "ThirdPage.h"
22#include "tcpxcv.h"
23#include <dns_sd.h>
24#include <winspool.h>
25#include <setupapi.h>
26
27// local variable is initialize but not referenced
28#pragma warning(disable:4189)
29
30//
31// This is the printer description file that is shipped
32// with Windows XP and below
33//
34#define kNTPrintFile		L"inf\\ntprint.inf"
35
36//
37// Windows Vista ships with a set of prn*.inf files
38//
39#define kVistaPrintFiles	L"inf\\prn*.inf"
40
41//
42// These are pre-defined names for Generic manufacturer and model
43//
44#define kGenericManufacturer		L"Generic"
45#define kGenericText				L"Generic / Text Only"
46#define kGenericPostscript			L"Generic / Postscript"
47#define kGenericPCL					L"Generic / PCL"
48#define kPDLPostscriptKey			L"application/postscript"
49#define kPDLPCLKey					L"application/vnd.hp-pcl"
50#define kGenericPSColorDriver		L"HP Color LaserJet 4550 PS"
51#define kGenericPSDriver			L"HP LaserJet 4050 Series PS"
52#define kGenericPCLColorDriver		L"HP Color LaserJet 4550 PCL"
53#define kGenericPCLDriver			L"HP LaserJet 4050 Series PCL"
54
55
56// CThirdPage dialog
57
58IMPLEMENT_DYNAMIC(CThirdPage, CPropertyPage)
59CThirdPage::CThirdPage()
60	: CPropertyPage(CThirdPage::IDD),
61		m_manufacturerSelected( NULL ),
62		m_modelSelected( NULL ),
63		m_genericPostscript( NULL ),
64		m_genericPCL( NULL ),
65		m_initialized(false),
66		m_printerImage( NULL )
67{
68	static const int	bufferSize	= 32768;
69	TCHAR				windowsDirectory[bufferSize];
70	CString				header;
71	WIN32_FIND_DATA		findFileData;
72	HANDLE				findHandle;
73	CString				prnFiles;
74	CString				ntPrint;
75	OSStatus			err;
76	BOOL				ok;
77
78	m_psp.dwFlags &= ~(PSP_HASHELP);
79	m_psp.dwFlags |= PSP_DEFAULT|PSP_USEHEADERTITLE|PSP_USEHEADERSUBTITLE;
80
81	m_psp.pszHeaderTitle = MAKEINTRESOURCE(IDS_INSTALL_TITLE);
82	m_psp.pszHeaderSubTitle = MAKEINTRESOURCE(IDS_INSTALL_SUBTITLE);
83
84	//
85	// load printers from ntprint.inf
86	//
87	ok = GetWindowsDirectory( windowsDirectory, bufferSize );
88	err = translate_errno( ok, errno_compat(), kUnknownErr );
89	require_noerr( err, exit );
90
91	//
92	// <rdar://problem/4826126>
93	//
94	// If there are no *prn.inf files, we'll assume that the information
95	// is in ntprint.inf
96	//
97	prnFiles.Format( L"%s\\%s", windowsDirectory, kVistaPrintFiles );
98	findHandle = FindFirstFile( prnFiles, &findFileData );
99
100	if ( findHandle != INVALID_HANDLE_VALUE )
101	{
102		CString absolute;
103
104		absolute.Format( L"%s\\inf\\%s", windowsDirectory, findFileData.cFileName );
105		err = LoadPrintDriverDefsFromFile( m_manufacturers, absolute, false );
106		require_noerr( err, exit );
107
108		while ( FindNextFile( findHandle, &findFileData ) )
109		{
110			absolute.Format( L"%s\\inf\\%s", windowsDirectory, findFileData.cFileName );
111			err = LoadPrintDriverDefsFromFile( m_manufacturers, absolute, false );
112			require_noerr( err, exit );
113		}
114
115		FindClose( findHandle );
116	}
117	else
118	{
119		ntPrint.Format(L"%s\\%s", windowsDirectory, kNTPrintFile);
120		err = LoadPrintDriverDefsFromFile( m_manufacturers, ntPrint, false );
121		require_noerr(err, exit);
122	}
123
124	//
125	// load printer drivers that have been installed on this machine
126	//
127	err = LoadPrintDriverDefs( m_manufacturers );
128	require_noerr(err, exit);
129
130	//
131	// load our own special generic printer defs
132	//
133	err = LoadGenericPrintDriverDefs( m_manufacturers );
134	require_noerr( err, exit );
135
136exit:
137
138	return;
139}
140
141
142void
143CThirdPage::FreeManufacturers( Manufacturers & manufacturers )
144{
145	for ( Manufacturers::iterator it = manufacturers.begin(); it != manufacturers.end(); it++ )
146	{
147		for ( Models::iterator it2 = it->second->models.begin(); it2 != it->second->models.end(); it2++ )
148		{
149			delete *it2;
150		}
151
152		delete it->second;
153	}
154}
155
156
157CThirdPage::~CThirdPage()
158{
159	FreeManufacturers( m_manufacturers );
160}
161
162// ----------------------------------------------------
163// SelectMatch
164//
165// SelectMatch will do all the UI work associated with
166// selected a manufacturer and model of printer.  It also
167// makes sure the printer object is update with the
168// latest settings
169//
170// ----------------------------------------------------
171void
172CThirdPage::SelectMatch(Printer * printer, Service * service, Manufacturer * manufacturer, Model * model)
173{
174	LVFINDINFO	info;
175	int			nIndex;
176
177	check( printer != NULL );
178	check( manufacturer != NULL );
179	check( model != NULL );
180
181	//
182	// select the manufacturer
183	//
184	info.flags	= LVFI_STRING;
185	info.psz	= manufacturer->name;
186
187	nIndex = m_manufacturerListCtrl.FindItem(&info);
188
189	if (nIndex != -1)
190	{
191		m_manufacturerListCtrl.SetItemState(nIndex, LVIS_SELECTED, LVIS_SELECTED);
192		//
193		//<rdar://problem/4528853> mDNS: When auto-highlighting items in lists, scroll list so highlighted item is in the middle
194		//
195		AutoScroll(m_manufacturerListCtrl, nIndex);
196	}
197
198	//
199	// select the model
200	//
201	info.flags	= LVFI_STRING;
202	info.psz	= model->displayName;
203
204	nIndex = m_modelListCtrl.FindItem(&info);
205
206	if (nIndex != -1)
207	{
208		m_modelListCtrl.SetItemState(nIndex, LVIS_SELECTED, LVIS_SELECTED);
209		AutoScroll( m_modelListCtrl, nIndex );
210
211		m_modelListCtrl.SetFocus();
212	}
213
214	CopyPrinterSettings( printer, service, manufacturer, model );
215}
216
217void
218CThirdPage::SelectMatch(Manufacturers & manufacturers, Printer * printer, Service * service, Manufacturer * manufacturer, Model * model)
219{
220	PopulateUI( manufacturers );
221
222	SelectMatch( printer, service, manufacturer, model );
223}
224
225// --------------------------------------------------------
226// CopyPrinterSettings
227//
228// This function makes sure that the printer object has the
229// latest settings from the manufacturer and model objects
230// --------------------------------------------------------
231
232void
233CThirdPage::CopyPrinterSettings( Printer * printer, Service * service, Manufacturer * manufacturer, Model * model )
234{
235	DWORD portNameLen;
236
237	printer->manufacturer		=	manufacturer->name;
238	printer->displayModelName	=	model->displayName;
239	printer->modelName			=	model->name;
240	printer->driverInstalled	=	model->driverInstalled;
241	printer->infFileName		=	model->infFileName;
242
243	if ( service->type == kPDLServiceType )
244	{
245		printer->portName.Format(L"IP_%s.%d", static_cast<LPCTSTR>(service->hostname), service->portNumber);
246		service->protocol = L"Raw";
247	}
248	else if ( service->type == kLPRServiceType )
249	{
250		Queue * q = service->queues.front();
251		check( q );
252
253		if ( q->name.GetLength() > 0 )
254		{
255			printer->portName.Format(L"LPR_%s.%d.%s", static_cast<LPCTSTR>(service->hostname), service->portNumber, static_cast<LPCTSTR>(q->name) );
256		}
257		else
258		{
259			printer->portName.Format(L"LPR_%s.%d", static_cast<LPCTSTR>(service->hostname), service->portNumber);
260		}
261
262		service->protocol = L"LPR";
263	}
264	else if ( service->type == kIPPServiceType )
265	{
266		Queue * q = service->queues.front();
267		check( q );
268
269		if ( q->name.GetLength() > 0 )
270		{
271			printer->portName.Format(L"http://%s:%d/%s", static_cast<LPCTSTR>(service->hostname), service->portNumber, static_cast<LPCTSTR>(q->name) );
272		}
273		else
274		{
275			printer->portName.Format(L"http://%s:%d/", static_cast<LPCTSTR>(service->hostname), service->portNumber );
276		}
277
278		service->protocol = L"IPP";
279	}
280
281	// If it's not an IPP printr, truncate the portName so that it's valid
282
283	if ( service->type != kIPPServiceType )
284	{
285		portNameLen = printer->portName.GetLength() + 1;
286
287		if ( portNameLen > MAX_PORTNAME_LEN )
288		{
289			printer->portName.Delete( MAX_PORTNAME_LEN - 1, ( portNameLen - MAX_PORTNAME_LEN ) );
290		}
291	}
292}
293
294// --------------------------------------------------------
295// DefaultPrinterExists
296//
297// Checks to see if a default printer has been configured
298// on this machine
299// --------------------------------------------------------
300BOOL
301CThirdPage::DefaultPrinterExists()
302{
303	CPrintDialog dlg(FALSE);
304
305	dlg.m_pd.Flags |= PD_RETURNDEFAULT;
306
307	return dlg.GetDefaults();
308}
309
310// --------------------------------------------------------
311// AutoScroll
312//
313// Ensure selected item is in middle of list
314// --------------------------------------------------------
315void
316CThirdPage::AutoScroll( CListCtrl & list, int nIndex )
317{
318	//
319	//<rdar://problem/4528853> mDNS: When auto-highlighting items in lists, scroll list so highlighted item is in the middle
320	//
321
322	int		top;
323	int		count;
324
325	list.EnsureVisible( nIndex, FALSE );
326
327	top		= list.GetTopIndex();
328	count	= list.GetCountPerPage();
329
330	if ( ( nIndex == top ) || ( ( nIndex + 1 ) == ( top + count ) ) )
331	{
332		CRect	rect;
333		int		rows;
334
335		rows = ( count / 2 );
336
337		if ( nIndex == top )
338		{
339			list.GetItemRect(0, rect, LVIR_BOUNDS);
340			list.Scroll( CPoint( 0, rows * rect.Height() * -1 ) );
341		}
342		else
343		{
344			list.GetItemRect(0, rect, LVIR_BOUNDS);
345			list.Scroll( CPoint( 0, rows * rect.Height() ) );
346		}
347	}
348}
349
350// ------------------------------------------------------
351// LoadPrintDriverDefsFromFile
352//
353// The only potentially opaque thing about this function is the
354// checkForDuplicateModels flag.  The problem here is that ntprint.inf
355// doesn't contain duplicate models, and it has hundreds of models
356// listed.  You wouldn't check for duplicates there.  But oftentimes,
357// loading different windows print driver files contain multiple
358// entries for the same printer.  You don't want the UI to display
359// the same printer multiple times, so in that case, you would ask
360// this function to check for multiple models.
361
362OSStatus
363CThirdPage::LoadPrintDriverDefsFromFile(Manufacturers & manufacturers, const CString & filename, bool checkForDuplicateModels )
364{
365	HINF			handle	= INVALID_HANDLE_VALUE;
366	const TCHAR *	section = TEXT( "Manufacturer" );
367	LONG			sectionCount;
368	TCHAR			line[ 1000 ];
369	CString			klass;
370	INFCONTEXT		manufacturerContext;
371	BOOL			ok;
372	OSStatus		err		= 0;
373
374	// Make sure we can open the file
375	handle = SetupOpenInfFile( filename, NULL, INF_STYLE_WIN4, NULL );
376	translate_errno( handle != INVALID_HANDLE_VALUE, GetLastError(), kUnknownErr );
377	require_noerr( err, exit );
378
379	// Make sure it's a printer file
380	ok = SetupGetLineText( NULL, handle, TEXT( "Version" ), TEXT( "Class" ), line, sizeof( line ), NULL );
381	translate_errno( ok, GetLastError(), kUnknownErr );
382	require_noerr( err, exit );
383	klass = line;
384	require_action( klass == TEXT( "Printer" ), exit, err = kUnknownErr );
385
386	sectionCount = SetupGetLineCount( handle, section );
387	translate_errno( sectionCount != -1, GetLastError(), kUnknownErr );
388	require_noerr( err, exit );
389
390	memset( &manufacturerContext, 0, sizeof( manufacturerContext ) );
391
392	for ( LONG i = 0; i < sectionCount; i++ )
393	{
394		Manufacturers::iterator	iter;
395		Manufacturer	*	manufacturer;
396		CString				manufacturerName;
397		CString				temp;
398		CStringList			modelSectionNameDecl;
399		CString				modelSectionName;
400		CString				baseModelName;
401		CString				model;
402		INFCONTEXT			modelContext;
403		LONG				modelCount;
404		POSITION			p;
405
406		if ( i == 0 )
407		{
408			ok = SetupFindFirstLine( handle, section, NULL, &manufacturerContext );
409			err = translate_errno( ok, GetLastError(), kUnknownErr );
410			require_noerr( err, exit );
411		}
412		else
413		{
414			ok = SetupFindNextLine( &manufacturerContext, &manufacturerContext );
415			err = translate_errno( ok, GetLastError(), kUnknownErr );
416			require_noerr( err, exit );
417		}
418
419		ok = SetupGetStringField( &manufacturerContext, 0, line, sizeof( line ), NULL );
420		err = translate_errno( ok, GetLastError(), kUnknownErr );
421		require_noerr( err, exit );
422		manufacturerName = line;
423
424		ok = SetupGetLineText( &manufacturerContext, handle, NULL, NULL, line, sizeof( line ), NULL );
425		err = translate_errno( ok, GetLastError(), kUnknownErr );
426		require_noerr( err, exit );
427
428		// Try to find some model section name that has entries. Explanation of int file structure
429		// can be found at:
430		//
431		// <http://msdn.microsoft.com/en-us/library/ms794359.aspx>
432		Split( line, ',', modelSectionNameDecl );
433
434		p					= modelSectionNameDecl.GetHeadPosition();
435		modelSectionName	= modelSectionNameDecl.GetNext( p );
436		modelCount			= SetupGetLineCount( handle, modelSectionName );
437		baseModelName		= modelSectionName;
438
439		while ( modelCount <= 0 && p )
440		{
441			CString targetOSVersion;
442
443			targetOSVersion		= modelSectionNameDecl.GetNext( p );
444			modelSectionName	= baseModelName + TEXT( "." ) + targetOSVersion;
445			modelCount			= SetupGetLineCount( handle, modelSectionName );
446		}
447
448		if ( modelCount > 0 )
449		{
450			manufacturerName = NormalizeManufacturerName( manufacturerName );
451
452			iter = manufacturers.find( manufacturerName );
453
454			if ( iter != manufacturers.end() )
455			{
456				manufacturer = iter->second;
457				require_action( manufacturer, exit, err = kUnknownErr );
458			}
459			else
460			{
461				try
462				{
463					manufacturer = new Manufacturer;
464				}
465				catch (...)
466				{
467					manufacturer = NULL;
468				}
469
470				require_action( manufacturer, exit, err = kNoMemoryErr );
471
472				manufacturer->name					= manufacturerName;
473				manufacturers[ manufacturerName ]	= manufacturer;
474			}
475
476			memset( &modelContext, 0, sizeof( modelContext ) );
477
478			for ( LONG j = 0; j < modelCount; j++ )
479			{
480				CString modelName;
481				Model * model;
482
483				if ( j == 0 )
484				{
485					ok = SetupFindFirstLine( handle, modelSectionName, NULL, &modelContext );
486					err = translate_errno( ok, GetLastError(), kUnknownErr );
487					require_noerr( err, exit );
488				}
489				else
490				{
491					SetupFindNextLine( &modelContext, &modelContext );
492					err = translate_errno( ok, GetLastError(), kUnknownErr );
493					require_noerr( err, exit );
494				}
495
496				ok = SetupGetStringField( &modelContext, 0, line, sizeof( line ), NULL );
497				err = translate_errno( ok, GetLastError(), kUnknownErr );
498				require_noerr( err, exit );
499
500				modelName = line;
501
502				if (checkForDuplicateModels == true)
503				{
504					if ( MatchModel( manufacturer, ConvertToModelName( modelName ) ) != NULL )
505					{
506						continue;
507					}
508				}
509
510				//
511				// Stock Vista printer inf files embed guids in the model
512				// declarations for Epson printers. Let's ignore those.
513				//
514				if ( modelName.Find( TEXT( "{" ), 0 ) != -1 )
515				{
516					continue;
517				}
518
519				try
520				{
521					model = new Model;
522				}
523				catch (...)
524				{
525					model = NULL;
526				}
527
528				require_action( model, exit, err = kNoMemoryErr );
529
530				model->infFileName		=	filename;
531				model->displayName		=	modelName;
532				model->name				=	modelName;
533				model->driverInstalled	=	false;
534
535				manufacturer->models.push_back(model);
536			}
537		}
538	}
539
540exit:
541
542	if ( handle != INVALID_HANDLE_VALUE )
543	{
544		SetupCloseInfFile( handle );
545		handle = NULL;
546	}
547
548	return err;
549}
550
551
552// -------------------------------------------------------
553// LoadPrintDriverDefs
554//
555// This function is responsible for loading the print driver
556// definitions of all print drivers that have been installed
557// on this machine.
558// -------------------------------------------------------
559OSStatus
560CThirdPage::LoadPrintDriverDefs( Manufacturers & manufacturers )
561{
562	BYTE	*	buffer			=	NULL;
563	DWORD		bytesReceived	=	0;
564	DWORD		numPrinters		=	0;
565	OSStatus	err				=	0;
566	BOOL		ok;
567
568	//
569	// like a lot of win32 calls, we call this first to get the
570	// size of the buffer we need.
571	//
572	EnumPrinterDrivers(NULL, L"all", 6, NULL, 0, &bytesReceived, &numPrinters);
573
574	if (bytesReceived > 0)
575	{
576		try
577		{
578			buffer = new BYTE[bytesReceived];
579		}
580		catch (...)
581		{
582			buffer = NULL;
583		}
584
585		require_action( buffer, exit, err = kNoMemoryErr );
586
587		//
588		// this call gets the real info
589		//
590		ok = EnumPrinterDrivers(NULL, L"all", 6, buffer, bytesReceived, &bytesReceived, &numPrinters);
591		err = translate_errno( ok, errno_compat(), kUnknownErr );
592		require_noerr( err, exit );
593
594		DRIVER_INFO_6 * info = (DRIVER_INFO_6*) buffer;
595
596		for (DWORD i = 0; i < numPrinters; i++)
597		{
598			Manufacturer	*	manufacturer;
599			Model			*	model;
600			CString				name;
601
602			//
603			// skip over anything that doesn't have a manufacturer field.  This
604			// fixes a bug that I noticed that occurred after I installed
605			// ProComm.  This program add a print driver with no manufacturer
606			// that screwed up this wizard.
607			//
608			if (info[i].pszMfgName == NULL)
609			{
610				continue;
611			}
612
613			//
614			// look for manufacturer
615			//
616			Manufacturers::iterator iter;
617
618			//
619			// save the name
620			//
621			name = NormalizeManufacturerName( info[i].pszMfgName );
622
623			iter = manufacturers.find(name);
624
625			if (iter != manufacturers.end())
626			{
627				manufacturer = iter->second;
628			}
629			else
630			{
631				try
632				{
633					manufacturer = new Manufacturer;
634				}
635				catch (...)
636				{
637					manufacturer = NULL;
638				}
639
640				require_action( manufacturer, exit, err = kNoMemoryErr );
641
642				manufacturer->name	=	name;
643
644				manufacturers[name]	=	manufacturer;
645			}
646
647			//
648			// now look to see if we have already seen this guy.  this could
649			// happen if we have already installed printers that are described
650			// in ntprint.inf.  the extant drivers will show up in EnumPrinterDrivers
651			// but we have already loaded their info
652			//
653			//
654			if ( MatchModel( manufacturer, ConvertToModelName( info[i].pName ) ) == NULL )
655			{
656				try
657				{
658					model = new Model;
659				}
660				catch (...)
661				{
662					model = NULL;
663				}
664
665				require_action( model, exit, err = kNoMemoryErr );
666
667				model->displayName		=	info[i].pName;
668				model->name				=	info[i].pName;
669				model->driverInstalled	=	true;
670
671				manufacturer->models.push_back(model);
672			}
673		}
674	}
675
676exit:
677
678	if (buffer != NULL)
679	{
680		delete [] buffer;
681	}
682
683	return err;
684}
685
686// -------------------------------------------------------
687// LoadGenericPrintDriverDefs
688//
689// This function is responsible for loading polymorphic
690// generic print drivers defs.  The UI will read
691// something like "Generic / Postscript" and we can map
692// that to any print driver we want.
693// -------------------------------------------------------
694OSStatus
695CThirdPage::LoadGenericPrintDriverDefs( Manufacturers & manufacturers )
696{
697	Manufacturer		*	manufacturer;
698	Model				*	model;
699	Manufacturers::iterator	iter;
700	CString					psDriverName;
701	CString					pclDriverName;
702	OSStatus				err	= 0;
703
704	// <rdar://problem/4030388> Generic drivers don't do color
705
706	// First try and find our generic driver names
707
708	iter = m_manufacturers.find(L"HP");
709	require_action( iter != m_manufacturers.end(), exit, err = kUnknownErr );
710	manufacturer = iter->second;
711
712	// Look for Postscript
713
714	model = manufacturer->find( kGenericPSColorDriver );
715
716	if ( !model )
717	{
718		model = manufacturer->find( kGenericPSDriver );
719	}
720
721	if ( model )
722	{
723		psDriverName = model->name;
724	}
725
726	// Look for PCL
727
728	model = manufacturer->find( kGenericPCLColorDriver );
729
730	if ( !model )
731	{
732		model = manufacturer->find( kGenericPCLDriver );
733	}
734
735	if ( model )
736	{
737		pclDriverName = model->name;
738	}
739
740	// If we found either a generic PS driver, or a generic PCL driver,
741	// then add them to the list
742
743	if ( psDriverName.GetLength() || pclDriverName.GetLength() )
744	{
745		// Try and find generic manufacturer if there is one
746
747		iter = manufacturers.find(L"Generic");
748
749		if (iter != manufacturers.end())
750		{
751			manufacturer = iter->second;
752		}
753		else
754		{
755			try
756			{
757				manufacturer = new Manufacturer;
758			}
759			catch (...)
760			{
761				manufacturer = NULL;
762			}
763
764			require_action( manufacturer, exit, err = kNoMemoryErr );
765
766			manufacturer->name					=	"Generic";
767			manufacturers[manufacturer->name]	=	manufacturer;
768		}
769
770		if ( psDriverName.GetLength() > 0 )
771		{
772			try
773			{
774				m_genericPostscript = new Model;
775			}
776			catch (...)
777			{
778				m_genericPostscript = NULL;
779			}
780
781			require_action( m_genericPostscript, exit, err = kNoMemoryErr );
782
783			m_genericPostscript->displayName		=	kGenericPostscript;
784			m_genericPostscript->name				=	psDriverName;
785			m_genericPostscript->driverInstalled	=	false;
786
787			manufacturer->models.push_back( m_genericPostscript );
788		}
789
790		if ( pclDriverName.GetLength() > 0 )
791		{
792			try
793			{
794				m_genericPCL = new Model;
795			}
796			catch (...)
797			{
798				m_genericPCL = NULL;
799			}
800
801			require_action( m_genericPCL, exit, err = kNoMemoryErr );
802
803			m_genericPCL->displayName		=	kGenericPCL;
804			m_genericPCL->name				=	pclDriverName;
805			m_genericPCL->driverInstalled	=	false;
806
807			manufacturer->models.push_back( m_genericPCL );
808		}
809	}
810
811exit:
812
813	return err;
814}
815
816// ------------------------------------------------------
817// ConvertToManufacturerName
818//
819// This function is responsible for tweaking the
820// name so that subsequent string operations won't fail because
821// of capitalizations/different names for the same manufacturer
822// (i.e.  Hewlett-Packard/HP/Hewlett Packard)
823//
824CString
825CThirdPage::ConvertToManufacturerName( const CString & name )
826{
827	//
828	// first we're going to convert all the characters to lower
829	// case
830	//
831	CString lower = name;
832	lower.MakeLower();
833
834	//
835	// now we're going to check to see if the string says "hewlett-packard",
836	// because sometimes they refer to themselves as "hewlett-packard", and
837	// sometimes they refer to themselves as "hp".
838	//
839	if ( lower == L"hewlett-packard")
840	{
841		lower = "hp";
842	}
843
844	//
845	// tweak for Xerox Phaser, which doesn't announce itself
846	// as a xerox
847	//
848	else if ( lower.Find( L"phaser", 0 ) != -1 )
849	{
850		lower = "xerox";
851	}
852
853	return lower;
854}
855
856// ------------------------------------------------------
857// ConvertToModelName
858//
859// This function is responsible for ensuring that subsequent
860// string operations don't fail because of differing capitalization
861// schemes and the like
862// ------------------------------------------------------
863
864CString
865CThirdPage::ConvertToModelName( const CString & name )
866{
867	//
868	// convert it to lowercase
869	//
870	CString lower = name;
871	lower.MakeLower();
872
873	return lower;
874}
875
876// ------------------------------------------------------
877// NormalizeManufacturerName
878//
879// This function is responsible for tweaking the manufacturer
880// name so that there are no aliases for vendors
881//
882CString
883CThirdPage::NormalizeManufacturerName( const CString & name )
884{
885	CString normalized = name;
886
887	//
888	// now we're going to check to see if the string says "hewlett-packard",
889	// because sometimes they refer to themselves as "hewlett-packard", and
890	// sometimes they refer to themselves as "hp".
891	//
892	if ( normalized == L"Hewlett-Packard")
893	{
894		normalized = "HP";
895	}
896
897	return normalized;
898}
899
900// -------------------------------------------------------
901// MatchPrinter
902//
903// This function is responsible for matching a printer
904// to a list of manufacturers and models.  It calls
905// MatchManufacturer and MatchModel in turn.
906//
907
908OSStatus CThirdPage::MatchPrinter(Manufacturers & manufacturers, Printer * printer, Service * service, bool useCUPSWorkaround)
909{
910	CString					normalizedProductName;
911	Manufacturer		*	manufacturer		=	NULL;
912	Manufacturer		*	genericManufacturer	=	NULL;
913	Model				*	model				=	NULL;
914	Model				*	genericModel		=	NULL;
915	bool					found				=	false;
916	CString					text;
917	OSStatus				err					=	kNoErr;
918
919	check( printer );
920	check( service );
921
922	Queue * q = service->SelectedQueue();
923
924	check( q );
925
926	//
927	// first look to see if we have a usb_MFG descriptor
928	//
929	if ( q->usb_MFG.GetLength() > 0)
930	{
931		manufacturer = MatchManufacturer( manufacturers, ConvertToManufacturerName ( q->usb_MFG ) );
932	}
933
934	if ( manufacturer == NULL )
935	{
936		q->product.Remove('(');
937		q->product.Remove(')');
938
939		manufacturer = MatchManufacturer( manufacturers, ConvertToManufacturerName ( q->product ) );
940	}
941
942	//
943	// if we found the manufacturer, then start looking for the model
944	//
945	if ( manufacturer != NULL )
946	{
947		if ( q->usb_MDL.GetLength() > 0 )
948		{
949			model = MatchModel ( manufacturer, ConvertToModelName ( q->usb_MDL ) );
950		}
951
952		if ( ( model == NULL ) && ( q->product.GetLength() > 0 ) )
953		{
954			q->product.Remove('(');
955			q->product.Remove(')');
956
957			model = MatchModel ( manufacturer, ConvertToModelName ( q->product ) );
958		}
959
960		if ( model != NULL )
961		{
962			// <rdar://problem/4124524> Offer Generic printers if printer advertises Postscript or PCL.  Workaround
963			// bug in OS X CUPS printer sharing by selecting Generic driver instead of matched printer.
964
965			bool hasGenericDriver = false;
966
967			if ( MatchGeneric( manufacturers, printer, service, &genericManufacturer, &genericModel ) )
968			{
969				hasGenericDriver = true;
970			}
971
972			// <rdar://problem/4190104> Use "application/octet-stream" to determine if CUPS
973			// shared queue supports raw
974
975			if ( q->pdl.Find( L"application/octet-stream" ) != -1 )
976			{
977				useCUPSWorkaround = false;
978			}
979
980			if ( useCUPSWorkaround && printer->isCUPSPrinter && hasGenericDriver )
981			{
982				//
983				// <rdar://problem/4496652> mDNS: Don't allow user to choose non-working driver
984				//
985				Manufacturers genericManufacturers;
986
987				LoadGenericPrintDriverDefs( genericManufacturers );
988
989				SelectMatch( genericManufacturers, printer, service, genericManufacturer, genericModel );
990
991				FreeManufacturers( genericManufacturers );
992			}
993			else
994			{
995				SelectMatch(manufacturers, printer, service, manufacturer, model);
996			}
997
998			found = true;
999		}
1000	}
1001
1002	//
1003	// display a message to the user based on whether we could match
1004	// this printer
1005	//
1006	if (found)
1007	{
1008		text.LoadString(IDS_PRINTER_MATCH_GOOD);
1009		err = kNoErr;
1010	}
1011	else if ( MatchGeneric( manufacturers, printer, service, &genericManufacturer, &genericModel ) )
1012	{
1013		if ( printer->isCUPSPrinter )
1014		{
1015			//
1016			// <rdar://problem/4496652> mDNS: Don't allow user to choose non-working driver
1017			//
1018			Manufacturers genericManufacturers;
1019
1020			LoadGenericPrintDriverDefs( genericManufacturers );
1021
1022			SelectMatch( genericManufacturers, printer, service, genericManufacturer, genericModel );
1023
1024			text.LoadString(IDS_PRINTER_MATCH_GOOD);
1025
1026			FreeManufacturers( genericManufacturers );
1027		}
1028		else
1029		{
1030			SelectMatch( manufacturers, printer, service, genericManufacturer, genericModel );
1031			text.LoadString(IDS_PRINTER_MATCH_MAYBE);
1032		}
1033
1034		err = kNoErr;
1035	}
1036	else
1037	{
1038		text.LoadString(IDS_PRINTER_MATCH_BAD);
1039
1040		//
1041		// if there was any crud in this list from before, get rid of it now
1042		//
1043		m_modelListCtrl.DeleteAllItems();
1044
1045		//
1046		// select the manufacturer if we found one
1047		//
1048		if (manufacturer != NULL)
1049		{
1050			LVFINDINFO	info;
1051			int			nIndex;
1052
1053			//
1054			// select the manufacturer
1055			//
1056			info.flags	= LVFI_STRING;
1057			info.psz	= manufacturer->name;
1058
1059			nIndex = m_manufacturerListCtrl.FindItem(&info);
1060
1061			if (nIndex != -1)
1062			{
1063				m_manufacturerListCtrl.SetItemState(nIndex, LVIS_SELECTED, LVIS_SELECTED);
1064
1065				//
1066				//<rdar://problem/4528853> mDNS: When auto-highlighting items in lists, scroll list so highlighted item is in the middle
1067				//
1068				AutoScroll(m_manufacturerListCtrl, nIndex);
1069			}
1070		}
1071
1072		err = kUnknownErr;
1073	}
1074
1075	m_printerSelectionText.SetWindowText(text);
1076
1077	return err;
1078}
1079
1080// ------------------------------------------------------
1081// MatchManufacturer
1082//
1083// This function is responsible for finding a manufacturer
1084// object from a string name.  It does a CString::Find, which
1085// is like strstr, so it doesn't have to do an exact match
1086//
1087// If it can't find a match, NULL is returned
1088// ------------------------------------------------------
1089
1090Manufacturer*
1091CThirdPage::MatchManufacturer( Manufacturers & manufacturers, const CString & name)
1092{
1093	Manufacturers::iterator iter;
1094
1095	for (iter = manufacturers.begin(); iter != manufacturers.end(); iter++)
1096	{
1097		//
1098		// we're going to convert all the manufacturer names to lower case,
1099		// so we match the name passed in.
1100		//
1101		CString lower = iter->second->name;
1102		lower.MakeLower();
1103
1104		//
1105		// now try and find the lowered string in the name passed in.
1106		//
1107		if (name.Find(lower) != -1)
1108		{
1109			return iter->second;
1110		}
1111	}
1112
1113	return NULL;
1114}
1115
1116// -------------------------------------------------------
1117// MatchModel
1118//
1119// This function is responsible for matching a model from
1120// a name.  It does a CString::Find(), which works like strstr,
1121// so it doesn't rely on doing an exact string match.
1122//
1123
1124Model*
1125CThirdPage::MatchModel(Manufacturer * manufacturer, const CString & name)
1126{
1127	Models::iterator iter;
1128
1129	iter = manufacturer->models.begin();
1130
1131	for (iter = manufacturer->models.begin(); iter != manufacturer->models.end(); iter++)
1132	{
1133		Model * model = *iter;
1134
1135		//
1136		// convert the model name to lower case
1137		//
1138		CString lowered = model->name;
1139		lowered.MakeLower();
1140
1141		if (lowered.Find( name ) != -1)
1142		{
1143			return model;
1144		}
1145
1146		//
1147		// <rdar://problem/3841218>
1148		// try removing the first substring and search again
1149		//
1150
1151		if ( name.Find(' ') != -1 )
1152		{
1153			CString altered = name;
1154			altered.Delete( 0, altered.Find(' ') + 1 );
1155
1156			if ( lowered.Find( altered ) != -1 )
1157			{
1158				return model;
1159			}
1160		}
1161	}
1162
1163	return NULL;
1164}
1165
1166// -------------------------------------------------------
1167// MatchGeneric
1168//
1169// This function will attempt to find a generic printer
1170// driver for a printer that we weren't able to match
1171// specifically
1172//
1173BOOL
1174CThirdPage::MatchGeneric( Manufacturers & manufacturers, Printer * printer, Service * service, Manufacturer ** manufacturer, Model ** model )
1175{
1176	CString	pdl;
1177	BOOL	ok = FALSE;
1178
1179	DEBUG_UNUSED( printer );
1180
1181	check( service );
1182
1183	Queue * q = service->SelectedQueue();
1184
1185	check( q );
1186
1187	Manufacturers::iterator iter = manufacturers.find( kGenericManufacturer );
1188	require_action_quiet( iter != manufacturers.end(), exit, ok = FALSE );
1189
1190	*manufacturer = iter->second;
1191
1192	pdl = q->pdl;
1193	pdl.MakeLower();
1194
1195	if ( m_genericPCL && ( pdl.Find( kPDLPCLKey ) != -1 ) )
1196	{
1197		*model	= m_genericPCL;
1198		ok		= TRUE;
1199	}
1200	else if ( m_genericPostscript && ( pdl.Find( kPDLPostscriptKey ) != -1 ) )
1201	{
1202		*model	= m_genericPostscript;
1203		ok		= TRUE;
1204	}
1205
1206exit:
1207
1208	return ok;
1209}
1210
1211// -----------------------------------------------------------
1212// OnInitPage
1213//
1214// This function is responsible for doing initialization that
1215// only occurs once during a run of the wizard
1216//
1217
1218OSStatus CThirdPage::OnInitPage()
1219{
1220	CString		header;
1221	CString		ntPrint;
1222	OSStatus	err = kNoErr;
1223
1224	// Load printer icon
1225	check( m_printerImage == NULL );
1226
1227	m_printerImage = (CStatic*) GetDlgItem( 1 );	// 1 == IDR_MANIFEST
1228	check( m_printerImage );
1229
1230	if ( m_printerImage != NULL )
1231	{
1232		m_printerImage->SetIcon( LoadIcon( GetNonLocalizedResources(), MAKEINTRESOURCE( IDI_PRINTER ) ) );
1233	}
1234
1235	//
1236	// The CTreeCtrl widget automatically sends a selection changed
1237	// message which initially we want to ignore, because the user
1238	// hasn't selected anything
1239	//
1240	// this flag gets reset in the message handler.  Every subsequent
1241	// message gets handled.
1242	//
1243
1244	//
1245	// we have to make sure that we only do this once.  Typically,
1246	// we would do this in something like OnInitDialog, but we don't
1247	// have this in Wizards, because the window is a PropertySheet.
1248	// We're considered fully initialized when we receive the first
1249	// selection notice
1250	//
1251	header.LoadString(IDS_MANUFACTURER_HEADING);
1252	m_manufacturerListCtrl.InsertColumn(0, header, LVCFMT_LEFT, -1 );
1253	m_manufacturerSelected = NULL;
1254
1255	header.LoadString(IDS_MODEL_HEADING);
1256	m_modelListCtrl.InsertColumn(0, header, LVCFMT_LEFT, -1 );
1257	m_modelSelected = NULL;
1258
1259	return (err);
1260}
1261
1262void CThirdPage::DoDataExchange(CDataExchange* pDX)
1263{
1264	CPropertyPage::DoDataExchange(pDX);
1265	DDX_Control(pDX, IDC_PRINTER_MANUFACTURER, m_manufacturerListCtrl);
1266	DDX_Control(pDX, IDC_PRINTER_MODEL, m_modelListCtrl);
1267	DDX_Control(pDX, IDC_PRINTER_NAME, m_printerName);
1268	DDX_Control(pDX, IDC_DEFAULT_PRINTER, m_defaultPrinterCtrl);
1269	DDX_Control(pDX, IDC_PRINTER_SELECTION_TEXT, m_printerSelectionText);
1270
1271}
1272
1273// ----------------------------------------------------------
1274// OnSetActive
1275//
1276// This function is called by MFC after the window has been
1277// activated.
1278//
1279
1280BOOL
1281CThirdPage::OnSetActive()
1282{
1283	CPrinterSetupWizardSheet	*	psheet;
1284	Printer						*	printer;
1285	Service						*	service;
1286
1287	psheet = reinterpret_cast<CPrinterSetupWizardSheet*>(GetParent());
1288	require_quiet( psheet, exit );
1289
1290	psheet->SetWizardButtons( PSWIZB_BACK );
1291
1292	printer = psheet->GetSelectedPrinter();
1293	require_quiet( printer, exit );
1294
1295	service = printer->services.front();
1296	require_quiet( service, exit );
1297
1298	//
1299	// call OnInitPage once
1300	//
1301	if (!m_initialized)
1302	{
1303		OnInitPage();
1304		m_initialized = true;
1305	}
1306
1307	//
1308	// <rdar://problem/4580061> mDNS: Printers added using Bonjour should be set as the default printer.
1309	//
1310	if ( DefaultPrinterExists() )
1311	{
1312		m_defaultPrinterCtrl.SetCheck( BST_UNCHECKED );
1313		printer->deflt = false;
1314	}
1315	else
1316	{
1317		m_defaultPrinterCtrl.SetCheck( BST_CHECKED );
1318		printer->deflt = true;
1319	}
1320
1321	//
1322	// update the UI with the printer name
1323	//
1324	m_printerName.SetWindowText(printer->displayName);
1325
1326	//
1327	// populate the list controls with the manufacturers and models
1328	// from ntprint.inf
1329	//
1330	PopulateUI( m_manufacturers );
1331
1332	//
1333	// and try and match the printer
1334	//
1335
1336	if ( psheet->GetLastPage() == psheet->GetPage(0) )
1337	{
1338		MatchPrinter( m_manufacturers, printer, service, true );
1339
1340		if ( ( m_manufacturerSelected != NULL ) && ( m_modelSelected != NULL  ) )
1341		{
1342			GetParent()->PostMessage(PSM_SETCURSEL, 2 );
1343		}
1344	}
1345	else
1346	{
1347		SelectMatch(printer, service, m_manufacturerSelected, m_modelSelected);
1348	}
1349
1350exit:
1351
1352	return CPropertyPage::OnSetActive();
1353}
1354
1355BOOL
1356CThirdPage::OnKillActive()
1357{
1358	CPrinterSetupWizardSheet * psheet;
1359
1360	psheet = reinterpret_cast<CPrinterSetupWizardSheet*>(GetParent());
1361	require_quiet( psheet, exit );
1362
1363	psheet->SetLastPage(this);
1364
1365exit:
1366
1367	return CPropertyPage::OnKillActive();
1368}
1369
1370// -------------------------------------------------------
1371// PopulateUI
1372//
1373// This function is called to populate the list of manufacturers
1374//
1375OSStatus
1376CThirdPage::PopulateUI(Manufacturers & manufacturers)
1377{
1378	Manufacturers::iterator iter;
1379
1380	m_manufacturerListCtrl.DeleteAllItems();
1381
1382	for (iter = manufacturers.begin(); iter != manufacturers.end(); iter++)
1383	{
1384		int nIndex;
1385
1386		Manufacturer * manufacturer = iter->second;
1387
1388		nIndex = m_manufacturerListCtrl.InsertItem(0, manufacturer->name);
1389
1390		m_manufacturerListCtrl.SetItemData(nIndex, (DWORD_PTR) manufacturer);
1391
1392		m_manufacturerListCtrl.SetColumnWidth( 0, LVSCW_AUTOSIZE_USEHEADER );
1393	}
1394
1395	return 0;
1396}
1397
1398BEGIN_MESSAGE_MAP(CThirdPage, CPropertyPage)
1399	ON_NOTIFY(LVN_ITEMCHANGED, IDC_PRINTER_MANUFACTURER, OnLvnItemchangedManufacturer)
1400	ON_NOTIFY(LVN_ITEMCHANGED, IDC_PRINTER_MODEL, OnLvnItemchangedPrinterModel)
1401	ON_BN_CLICKED(IDC_DEFAULT_PRINTER, OnBnClickedDefaultPrinter)
1402	ON_BN_CLICKED(IDC_HAVE_DISK, OnBnClickedHaveDisk)
1403END_MESSAGE_MAP()
1404
1405// CThirdPage message handlers
1406void CThirdPage::OnLvnItemchangedManufacturer(NMHDR *pNMHDR, LRESULT *pResult)
1407{
1408	LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1409
1410	POSITION p = m_manufacturerListCtrl.GetFirstSelectedItemPosition();
1411	int nSelected = m_manufacturerListCtrl.GetNextSelectedItem(p);
1412
1413	if (nSelected != -1)
1414	{
1415		m_manufacturerSelected = (Manufacturer*) m_manufacturerListCtrl.GetItemData(nSelected);
1416
1417		m_modelListCtrl.SetRedraw(FALSE);
1418
1419		m_modelListCtrl.DeleteAllItems();
1420		m_modelSelected = NULL;
1421
1422		Models::iterator iter;
1423
1424		for (iter = m_manufacturerSelected->models.begin(); iter != m_manufacturerSelected->models.end(); iter++)
1425		{
1426			Model * model = *iter;
1427
1428			int nItem = m_modelListCtrl.InsertItem( 0, model->displayName );
1429
1430			m_modelListCtrl.SetItemData(nItem, (DWORD_PTR) model);
1431
1432			m_modelListCtrl.SetColumnWidth( 0, LVSCW_AUTOSIZE_USEHEADER );
1433		}
1434
1435		m_modelListCtrl.SetRedraw(TRUE);
1436	}
1437
1438	*pResult = 0;
1439}
1440
1441void CThirdPage::OnLvnItemchangedPrinterModel(NMHDR *pNMHDR, LRESULT *pResult)
1442{
1443	LPNMLISTVIEW					pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1444
1445	CPrinterSetupWizardSheet	*	psheet;
1446	Printer						*	printer;
1447	Service						*	service;
1448
1449	psheet = reinterpret_cast<CPrinterSetupWizardSheet*>(GetParent());
1450	require_quiet( psheet, exit );
1451
1452	printer = psheet->GetSelectedPrinter();
1453	require_quiet( printer, exit );
1454
1455	service = printer->services.front();
1456	require_quiet( service, exit );
1457
1458	check ( m_manufacturerSelected );
1459
1460	POSITION p = m_modelListCtrl.GetFirstSelectedItemPosition();
1461	int nSelected = m_modelListCtrl.GetNextSelectedItem(p);
1462
1463	if (nSelected != -1)
1464	{
1465		m_modelSelected = (Model*) m_modelListCtrl.GetItemData(nSelected);
1466
1467		CopyPrinterSettings( printer, service, m_manufacturerSelected, m_modelSelected );
1468
1469		psheet->SetWizardButtons(PSWIZB_BACK|PSWIZB_NEXT);
1470	}
1471	else
1472	{
1473		psheet->SetWizardButtons(PSWIZB_BACK);
1474	}
1475
1476exit:
1477
1478	*pResult = 0;
1479}
1480
1481void CThirdPage::OnBnClickedDefaultPrinter()
1482{
1483	CPrinterSetupWizardSheet	*	psheet;
1484	Printer						*	printer;
1485
1486	psheet = reinterpret_cast<CPrinterSetupWizardSheet*>(GetParent());
1487	require_quiet( psheet, exit );
1488
1489	printer = psheet->GetSelectedPrinter();
1490	require_quiet( printer, exit );
1491
1492	printer->deflt = ( m_defaultPrinterCtrl.GetCheck() == BST_CHECKED ) ? true : false;
1493
1494exit:
1495
1496	return;
1497}
1498
1499void CThirdPage::OnBnClickedHaveDisk()
1500{
1501	CPrinterSetupWizardSheet	*	psheet;
1502	Printer						*	printer;
1503	Service						*	service;
1504	Manufacturers					manufacturers;
1505
1506	CFileDialog dlg(TRUE, NULL, NULL, OFN_HIDEREADONLY|OFN_FILEMUSTEXIST, L"Setup Information (*.inf)|*.inf||", this);
1507
1508	psheet = reinterpret_cast<CPrinterSetupWizardSheet*>(GetParent());
1509	require_quiet( psheet, exit );
1510
1511	printer = psheet->GetSelectedPrinter();
1512	require_quiet( printer, exit );
1513
1514	service = printer->services.front();
1515	require_quiet( service, exit );
1516
1517	for ( ;; )
1518	{
1519		if ( dlg.DoModal() == IDOK )
1520		{
1521			CString filename = dlg.GetPathName();
1522
1523			LoadPrintDriverDefsFromFile( manufacturers, filename, true );
1524
1525			// Sanity check
1526
1527			if ( manufacturers.size() > 0 )
1528			{
1529				PopulateUI( manufacturers );
1530
1531				if ( MatchPrinter( manufacturers, printer, service, false ) != kNoErr )
1532				{
1533					CString errorMessage;
1534					CString errorCaption;
1535
1536					errorMessage.LoadString( IDS_NO_MATCH_INF_FILE );
1537					errorCaption.LoadString( IDS_NO_MATCH_INF_FILE_CAPTION );
1538
1539					MessageBox( errorMessage, errorCaption, MB_OK );
1540				}
1541
1542				break;
1543			}
1544			else
1545			{
1546				CString errorMessage;
1547				CString errorCaption;
1548
1549				errorMessage.LoadString( IDS_BAD_INF_FILE );
1550				errorCaption.LoadString( IDS_BAD_INF_FILE_CAPTION );
1551
1552				MessageBox( errorMessage, errorCaption, MB_OK );
1553			}
1554		}
1555		else
1556		{
1557			break;
1558		}
1559	}
1560
1561exit:
1562
1563	FreeManufacturers( manufacturers );
1564	return;
1565}
1566
1567
1568void
1569CThirdPage::Split( const CString & string, TCHAR ch, CStringList & components )
1570{
1571	CString	temp;
1572	int		n;
1573
1574	temp = string;
1575
1576	while ( ( n = temp.Find( ch ) ) != -1 )
1577	{
1578		components.AddTail( temp.Left( n ) );
1579		temp = temp.Right( temp.GetLength() - ( n + 1 ) );
1580	}
1581
1582	components.AddTail( temp );
1583}
1584