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