1 /* -*- Mode: C; tab-width: 4 -*-
3 * Copyright (c) 1997-2004 Apple Computer, Inc. All rights reserved.
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 Change History (most recent first):
19 $Log: PrinterSetupWizardSheet.cpp,v $
20 Revision 1.40 2009/06/18 18:05:50 herscher
21 <rdar://problem/4694554> Eliminate the first screen of Printer Wizard and maybe combine others ("I'm Feeling Lucky")
23 Revision 1.39 2009/06/11 22:27:16 herscher
24 <rdar://problem/4458913> Add comprehensive logging during printer installation process.
26 Revision 1.38 2009/05/27 04:49:02 herscher
27 <rdar://problem/4417884> Consider setting DoubleSpool for LPR queues to improve compatibility
29 Revision 1.37 2009/03/30 19:17:37 herscher
30 <rdar://problem/5925472> Current Bonjour code does not compile on Windows
31 <rdar://problem/6141389> Printer Wizard crashes on launch when Bonjour Service isn't running
32 <rdar://problem/5258789> Buffer overflow in PrinterWizard when printer dns hostname is too long
33 <rdar://problem/5187308> Move build train to Visual Studio 2005
35 Revision 1.36 2008/10/23 22:33:23 cheshire
36 Changed "NOTE:" to "Note:" so that BBEdit 9 stops putting those comment lines into the funtion popup menu
38 Revision 1.35 2006/08/14 23:24:09 cheshire
39 Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0
41 Revision 1.34 2005/10/05 17:32:51 herscher
42 <rdar://problem/4141221> Use a case insensitive compare operation to check whether a printer with the same name has already been installed.
44 Revision 1.33 2005/07/11 20:17:15 shersche
45 <rdar://problem/4124524> UI fixes associated with CUPS printer workaround fix.
47 Revision 1.32 2005/07/07 17:53:20 shersche
48 Fix problems associated with the CUPS printer workaround fix.
50 Revision 1.31 2005/06/30 18:02:54 shersche
51 <rdar://problem/4124524> Workaround for Mac OS X Printer Sharing bug
53 Revision 1.30 2005/04/13 17:46:22 shersche
54 <rdar://problem/4082122> Generic PCL not selected when printers advertise multiple text records
56 Revision 1.29 2005/02/14 20:48:37 shersche
57 <rdar://problem/4003710> Default pdl key to "application/postscript"
59 Revision 1.28 2005/02/14 20:37:53 shersche
60 <rdar://problem/4003944> Populate comment field with the model name that users see in the wizard UI.
62 Revision 1.27 2005/02/09 05:04:03 shersche
63 <rdar://problem/3946587> Use TXTRecordGetValuePtr() API in ParseTextRecord
65 Revision 1.26 2005/02/08 21:45:06 shersche
66 <rdar://problem/3947490> Default to Generic PostScript or PCL if unable to match driver
68 Revision 1.25 2005/02/08 18:54:17 shersche
69 <rdar://problem/3987680> Default queue name is "lp" when rp key is not specified.
71 Revision 1.24 2005/02/01 02:15:55 shersche
72 <rdar://problem/3946587> Use TXTRecord parsing APIs in ParseTextRecord
74 Revision 1.23 2005/01/31 23:54:30 shersche
75 <rdar://problem/3947508> Start browsing when printer wizard starts. Move browsing logic from CSecondPage object to CPrinterSetupWizardSheet object.
77 Revision 1.22 2005/01/25 18:49:43 shersche
78 Get icon resources from resource DLL
80 Revision 1.21 2005/01/10 01:09:32 shersche
81 Use the "note" key to populate pLocation field when setting up printer
83 Revision 1.20 2005/01/03 19:05:01 shersche
84 Store pointer to instance of wizard sheet so that print driver install thread sends a window message to the correct window
86 Revision 1.19 2004/12/31 07:23:53 shersche
87 Don't modify the button setting in SetSelectedPrinter()
89 Revision 1.18 2004/12/29 18:53:38 shersche
90 <rdar://problem/3725106>
91 <rdar://problem/3737413> Added support for LPR and IPP protocols as well as support for obtaining multiple text records. Reorganized and simplified codebase.
92 Bug #: 3725106, 3737413
94 Revision 1.17 2004/10/12 18:02:53 shersche
95 <rdar://problem/3764873> Escape '/', '@', '"' characters in printui command.
98 Revision 1.16 2004/09/13 21:27:22 shersche
99 <rdar://problem/3796483> Pass the moreComing flag to OnAddPrinter and OnRemovePrinter callbacks
102 Revision 1.15 2004/09/11 05:59:06 shersche
103 <rdar://problem/3785766> Fix code that generates unique printer names based on currently installed printers
106 Revision 1.14 2004/09/02 01:57:58 cheshire
107 <rdar://problem/3783611> Fix incorrect testing of MoreComing flag
109 Revision 1.13 2004/07/26 21:06:29 shersche
110 <rdar://problem/3739200> Removing trailing '.' in hostname
113 Revision 1.12 2004/07/13 21:24:23 rpantos
114 Fix for <rdar://problem/3701120>.
116 Revision 1.11 2004/06/28 00:51:47 shersche
117 Move call to EnumPrinters out of browse callback into standalone function
119 Revision 1.10 2004/06/27 23:06:47 shersche
120 code cleanup, make sure EnumPrinters returns non-zero value
122 Revision 1.9 2004/06/27 15:49:31 shersche
123 clean up some cruft in the printer browsing code
125 Revision 1.8 2004/06/27 08:04:51 shersche
126 copy selected printer to prevent printer being deleted out from under
128 Revision 1.7 2004/06/26 23:27:12 shersche
129 support for installing multiple printers of the same name
131 Revision 1.6 2004/06/26 21:22:39 shersche
132 handle spaces in file names
134 Revision 1.5 2004/06/26 03:19:57 shersche
135 clean up warning messages
137 Submitted by: herscher
139 Revision 1.4 2004/06/25 02:26:52 shersche
140 Normalize key fields in text record entries
141 Submitted by: herscher
143 Revision 1.3 2004/06/24 20:12:07 shersche
145 Submitted by: herscher
147 Revision 1.2 2004/06/23 17:58:21 shersche
148 <rdar://problem/3701837> eliminated memory leaks on exit
149 <rdar://problem/3701926> installation of a printer that is already installed results in a no-op
150 Bug #: 3701837, 3701926
151 Submitted by: herscher
153 Revision 1.1 2004/06/18 04:36:57 rpantos
160 #include "PrinterSetupWizardApp.h"
161 #include "PrinterSetupWizardSheet.h"
162 #include "CommonServices.h"
163 #include "DebugServices.h"
164 #include "WinServices.h"
166 #include <winspool.h>
171 #pragma warning(disable:4702)
174 #if( !TARGET_OS_WINDOWS_CE )
175 # include <mswsock.h>
176 # include <process.h>
181 #define WM_SOCKET_EVENT ( WM_USER + 0x100 )
182 #define WM_PROCESS_EVENT ( WM_USER + 0x101 )
185 // CPrinterSetupWizardSheet
186 CPrinterSetupWizardSheet
* CPrinterSetupWizardSheet::m_self
;
188 IMPLEMENT_DYNAMIC(CPrinterSetupWizardSheet
, CPropertySheet
)
189 CPrinterSetupWizardSheet::CPrinterSetupWizardSheet(UINT nIDCaption
, CWnd
* pParentWnd
, UINT iSelectPage
)
190 :CPropertySheet(nIDCaption
, pParentWnd
, iSelectPage
),
191 m_selectedPrinter(NULL
),
192 m_driverThreadExitCode( 0 ),
193 m_driverThreadFinished( false ),
194 m_pdlBrowser( NULL
),
195 m_ippBrowser( NULL
),
196 m_lprBrowser( NULL
),
199 m_arrow
= LoadCursor(0, IDC_ARROW
);
200 m_wait
= LoadCursor(0, IDC_APPSTARTING
);
210 CPrinterSetupWizardSheet::~CPrinterSetupWizardSheet()
214 while ( m_printers
.size() > 0 )
216 printer
= m_printers
.front();
217 m_printers
.pop_front();
226 // ------------------------------------------------------
227 // SetSelectedPrinter
229 // Manages setting a printer as the printer to install. Stops
230 // any pending resolves.
233 CPrinterSetupWizardSheet::SetSelectedPrinter(Printer
* printer
)
235 check( !printer
|| ( printer
!= m_selectedPrinter
) );
237 m_selectedPrinter
= printer
;
242 CPrinterSetupWizardSheet::LoadPrinterNames()
248 // rdar://problem/3701926 - Printer can't be installed twice
250 // First thing we want to do is make sure the printer isn't already installed.
251 // If the printer name is found, we'll try and rename it until we
252 // find a unique name
254 DWORD dwNeeded
= 0, dwNumPrinters
= 0;
256 BOOL ok
= EnumPrinters(PRINTER_ENUM_LOCAL
, NULL
, 4, NULL
, 0, &dwNeeded
, &dwNumPrinters
);
257 err
= translate_errno( ok
, errno_compat(), kUnknownErr
);
259 if ((err
== ERROR_INSUFFICIENT_BUFFER
) && (dwNeeded
> 0))
263 buffer
= new unsigned char[dwNeeded
];
270 require_action( buffer
, exit
, kNoMemoryErr
);
271 ok
= EnumPrinters(PRINTER_ENUM_LOCAL
, NULL
, 4, buffer
, dwNeeded
, &dwNeeded
, &dwNumPrinters
);
272 err
= translate_errno( ok
, errno_compat(), kUnknownErr
);
273 require_noerr( err
, exit
);
275 for (DWORD index
= 0; index
< dwNumPrinters
; index
++)
277 PRINTER_INFO_4
* lppi4
= (PRINTER_INFO_4
*) (buffer
+ index
* sizeof(PRINTER_INFO_4
));
279 m_printerNames
.push_back( lppi4
->pPrinterName
);
295 // ------------------------------------------------------
298 // Installs a printer with Windows.
300 // Note: this works one of two ways, depending on whether
301 // there are drivers already installed for this printer.
302 // If there are, then we can just create a port with XcvData,
303 // and then call AddPrinter. If not, we use the printui.dll
304 // to install the printer. Actually installing drivers that
305 // are not currently installed is painful, and it's much
306 // easier and less error prone to just let printui.dll do
307 // the hard work for us.
311 CPrinterSetupWizardSheet::InstallPrinter(Printer
* printer
)
318 service
= printer
->services
.front();
322 // if the driver isn't installed, then install it
325 if ( !printer
->driverInstalled
)
331 m_driverThreadFinished
= false;
336 hThread
= (HANDLE
) _beginthreadex_compat( NULL
, 0, InstallDriverThread
, printer
, 0, &threadID
);
337 err
= translate_errno( hThread
, (OSStatus
) GetLastError(), kUnknownErr
);
338 require_noerr_with_log( log
, "_beginthreadex_compat()", err
, exit
);
343 while (!m_driverThreadFinished
)
347 GetMessage( &msg
, m_hWnd
, 0, 0 );
348 TranslateMessage(&msg
);
349 DispatchMessage(&msg
);
353 // Wait until child process exits.
355 dwResult
= WaitForSingleObject( hThread
, INFINITE
);
356 err
= translate_errno( dwResult
== WAIT_OBJECT_0
, errno_compat(), err
= kUnknownErr
);
357 require_noerr_with_log( log
, "WaitForSingleObject()", err
, exit
);
360 // check the return value of thread
362 require_noerr_with_log( log
, "thread exit code", m_driverThreadExitCode
, exit
);
365 // now we know that the driver was successfully installed
367 printer
->driverInstalled
= true;
370 if ( service
->type
== kPDLServiceType
)
372 err
= InstallPrinterPDLAndLPR( printer
, service
, PROTOCOL_RAWTCP_TYPE
, log
);
373 require_noerr_with_log( log
, "InstallPrinterPDLAndLPR()", err
, exit
);
375 else if ( service
->type
== kLPRServiceType
)
377 err
= InstallPrinterPDLAndLPR( printer
, service
, PROTOCOL_LPR_TYPE
, log
);
378 require_noerr_with_log( log
, "InstallPrinterPDLAndLPR()", err
, exit
);
380 else if ( service
->type
== kIPPServiceType
)
382 err
= InstallPrinterIPP( printer
, service
, log
);
383 require_noerr_with_log( log
, "InstallPrinterIPP()", err
, exit
);
387 require_action_with_log( log
, ( service
->type
== kPDLServiceType
) || ( service
->type
== kLPRServiceType
) || ( service
->type
== kIPPServiceType
), exit
, err
= kUnknownErr
);
390 printer
->installed
= true;
393 // if the user specified a default printer, set it
397 ok
= SetDefaultPrinter( printer
->actualName
);
398 err
= translate_errno( ok
, errno_compat(), err
= kUnknownErr
);
399 require_noerr_with_log( log
, "SetDefaultPrinter()", err
, exit
);
409 CPrinterSetupWizardSheet::InstallPrinterPDLAndLPR(Printer
* printer
, Service
* service
, DWORD protocol
, Logger
& log
)
411 PRINTER_DEFAULTS printerDefaults
= { NULL
, NULL
, SERVER_ACCESS_ADMINISTER
};
413 DWORD cbInputData
= 100;
414 PBYTE pOutputData
= NULL
;
415 DWORD cbOutputNeeded
= 0;
416 PORT_DATA_1 portData
;
417 PRINTER_INFO_2 pInfo
;
419 HANDLE hPrinter
= NULL
;
424 check(printer
!= NULL
);
425 check(printer
->installed
== false);
427 q
= service
->queues
.front();
430 ok
= OpenPrinter(L
",XcvMonitor Standard TCP/IP Port", &hXcv
, &printerDefaults
);
431 err
= translate_errno( ok
, errno_compat(), kUnknownErr
);
432 require_noerr_with_log( log
, "OpenPrinter()", err
, exit
);
435 // BUGBUG: MSDN said this is not required, but my experience shows it is required
439 pOutputData
= new BYTE
[cbInputData
];
446 require_action_with_log( log
, pOutputData
, exit
, err
= kNoMemoryErr
);
451 ZeroMemory(&portData
, sizeof(PORT_DATA_1
));
453 require_action_with_log( log
, wcslen(printer
->portName
) < sizeof_array(portData
.sztPortName
), exit
, err
= kSizeErr
);
454 wcscpy_s(portData
.sztPortName
, printer
->portName
);
456 portData
.dwPortNumber
= service
->portNumber
;
457 portData
.dwVersion
= 1;
458 portData
.dwDoubleSpool
= 1;
460 portData
.dwProtocol
= protocol
;
461 portData
.cbSize
= sizeof PORT_DATA_1
;
462 portData
.dwReserved
= 0L;
464 require_action_with_log( log
, wcslen(q
->name
) < sizeof_array(portData
.sztQueue
), exit
, err
= kSizeErr
);
465 wcscpy_s(portData
.sztQueue
, q
->name
);
467 require_action_with_log( log
, wcslen( service
->hostname
) < sizeof_array(portData
.sztHostAddress
), exit
, err
= kSizeErr
);
468 wcscpy_s( portData
.sztHostAddress
, service
->hostname
);
470 ok
= XcvData(hXcv
, L
"AddPort", (PBYTE
) &portData
, sizeof(PORT_DATA_1
), pOutputData
, cbInputData
, &cbOutputNeeded
, &dwStatus
);
471 err
= translate_errno( ok
, errno_compat(), kUnknownErr
);
472 require_noerr_with_log( log
, "XcvData()", err
, exit
);
477 ZeroMemory(&pInfo
, sizeof(pInfo
));
479 pInfo
.pPrinterName
= printer
->actualName
.GetBuffer();
480 pInfo
.pServerName
= NULL
;
481 pInfo
.pShareName
= NULL
;
482 pInfo
.pPortName
= printer
->portName
.GetBuffer();
483 pInfo
.pDriverName
= printer
->modelName
.GetBuffer();
484 pInfo
.pComment
= printer
->displayModelName
.GetBuffer();
485 pInfo
.pLocation
= q
->location
.GetBuffer();
486 pInfo
.pDevMode
= NULL
;
487 pInfo
.pDevMode
= NULL
;
488 pInfo
.pSepFile
= L
"";
489 pInfo
.pPrintProcessor
= L
"winprint";
490 pInfo
.pDatatype
= L
"RAW";
491 pInfo
.pParameters
= L
"";
492 pInfo
.pSecurityDescriptor
= NULL
;
493 pInfo
.Attributes
= PRINTER_ATTRIBUTE_QUEUED
;
495 pInfo
.DefaultPriority
= 0;
499 hPrinter
= AddPrinter(NULL
, 2, (LPBYTE
) &pInfo
);
500 err
= translate_errno( hPrinter
, errno_compat(), kUnknownErr
);
501 require_noerr_with_log( log
, "AddPrinter()", err
, exit
);
505 if (hPrinter
!= NULL
)
507 ClosePrinter(hPrinter
);
515 if (pOutputData
!= NULL
)
517 delete [] pOutputData
;
525 CPrinterSetupWizardSheet::InstallPrinterIPP(Printer
* printer
, Service
* service
, Logger
& log
)
527 DEBUG_UNUSED( service
);
529 Queue
* q
= service
->SelectedQueue();
530 HANDLE hPrinter
= NULL
;
531 PRINTER_INFO_2 pInfo
;
539 ZeroMemory(&pInfo
, sizeof(PRINTER_INFO_2
));
541 pInfo
.pPrinterName
= printer
->actualName
.GetBuffer();
542 pInfo
.pPortName
= printer
->portName
.GetBuffer();
543 pInfo
.pDriverName
= printer
->modelName
.GetBuffer();
544 pInfo
.pPrintProcessor
= L
"winprint";
545 pInfo
.pLocation
= q
->location
.GetBuffer();
546 pInfo
.pComment
= printer
->displayModelName
.GetBuffer();
547 pInfo
.Attributes
= PRINTER_ATTRIBUTE_NETWORK
| PRINTER_ATTRIBUTE_LOCAL
;
549 hPrinter
= AddPrinter(NULL
, 2, (LPBYTE
)&pInfo
);
550 err
= translate_errno( hPrinter
, errno_compat(), kUnknownErr
);
551 require_noerr_with_log( log
, "AddPrinter()", err
, exit
);
555 if ( hPrinter
!= NULL
)
557 ClosePrinter(hPrinter
);
564 BEGIN_MESSAGE_MAP(CPrinterSetupWizardSheet
, CPropertySheet
)
565 ON_MESSAGE( WM_SOCKET_EVENT
, OnSocketEvent
)
566 ON_MESSAGE( WM_PROCESS_EVENT
, OnProcessEvent
)
572 // ------------------------------------------------------
575 // Traps when the user hits Finish
577 BOOL
CPrinterSetupWizardSheet::OnCommand(WPARAM wParam
, LPARAM lParam
)
580 // Check if this is OK
582 if (wParam
== ID_WIZFINISH
) // If OK is hit...
587 return CPropertySheet::OnCommand(wParam
, lParam
);
591 // ------------------------------------------------------
594 // Initializes this Dialog object.
596 BOOL
CPrinterSetupWizardSheet::OnInitDialog()
600 CPropertySheet::OnInitDialog();
603 require_noerr( err
, exit
);
611 if ( err
== kDNSServiceErr_Firewall
)
613 CString text
, caption
;
615 text
.LoadString( IDS_FIREWALL
);
616 caption
.LoadString( IDS_FIREWALL_CAPTION
);
618 MessageBox(text
, caption
, MB_OK
|MB_ICONEXCLAMATION
);
622 CString text
, caption
;
624 text
.LoadString( IDS_NO_MDNSRESPONDER_SERVICE_TEXT
);
625 caption
.LoadString( IDS_ERROR_CAPTION
);
627 MessageBox(text
, caption
, MB_OK
|MB_ICONEXCLAMATION
);
637 // ------------------------------------------------------
640 // This is called when Windows wants to know what cursor
641 // to display. So we tell it.
644 CPrinterSetupWizardSheet::OnSetCursor(CWnd
* pWnd
, UINT nHitTest
, UINT message
)
647 DEBUG_UNUSED(nHitTest
);
648 DEBUG_UNUSED(message
);
655 // ------------------------------------------------------
658 // This is not fully implemented yet.
662 CPrinterSetupWizardSheet::OnContextMenu(CWnd
* pWnd
, CPoint pos
)
673 // ------------------------------------------------------
676 // This is called when the user hits the "Finish" button
679 CPrinterSetupWizardSheet::OnOK()
681 check ( m_selectedPrinter
!= NULL
);
683 SetWizardButtons( PSWIZB_DISABLEDFINISH
);
685 if ( InstallPrinter( m_selectedPrinter
) != kNoErr
)
690 caption
.LoadString(IDS_INSTALL_ERROR_CAPTION
);
691 message
.LoadString(IDS_INSTALL_ERROR_MESSAGE
);
693 MessageBox(message
, caption
, MB_OK
|MB_ICONEXCLAMATION
);
700 // CPrinterSetupWizardSheet message handlers
702 void CPrinterSetupWizardSheet::Init(void)
704 AddPage(&m_pgSecond
);
706 AddPage(&m_pgFourth
);
708 m_psh
.dwFlags
&= (~PSH_HASHELP
);
710 m_psh
.dwFlags
|= PSH_WIZARD97
|PSH_WATERMARK
|PSH_HEADER
;
711 m_psh
.pszbmWatermark
= MAKEINTRESOURCE(IDB_WATERMARK
);
712 m_psh
.pszbmHeader
= MAKEINTRESOURCE(IDB_BANNER_ICON
);
714 m_psh
.hInstance
= GetNonLocalizedResources();
721 CPrinterSetupWizardSheet::OnSocketEvent(WPARAM inWParam
, LPARAM inLParam
)
723 if (WSAGETSELECTERROR(inLParam
) && !(HIWORD(inLParam
)))
725 dlog( kDebugLevelError
, "OnServiceEvent: window error\n" );
729 SOCKET sock
= (SOCKET
) inWParam
;
732 ServiceRefList::iterator begin
= m_serviceRefList
.begin();
733 ServiceRefList::iterator end
= m_serviceRefList
.end();
737 DNSServiceRef ref
= *begin
++;
741 if ((SOCKET
) DNSServiceRefSockFD(ref
) == sock
)
743 DNSServiceProcessResult(ref
);
754 CPrinterSetupWizardSheet::OnProcessEvent(WPARAM inWParam
, LPARAM inLParam
)
756 DEBUG_UNUSED(inLParam
);
758 m_driverThreadExitCode
= (DWORD
) inWParam
;
759 m_driverThreadFinished
= true;
766 CPrinterSetupWizardSheet::InstallDriverThread( LPVOID inParam
)
768 Printer
* printer
= (Printer
*) inParam
;
773 PROCESS_INFORMATION pi
;
780 // because we're calling endthreadex(), C++ objects won't be cleaned up
781 // correctly. we'll nest the CString 'command' inside a block so
782 // that it's destructor will be invoked.
787 ZeroMemory( &si
, sizeof(si
) );
789 ZeroMemory( &pi
, sizeof(pi
) );
791 command
.Format(L
"rundll32.exe printui.dll,PrintUIEntry /ia /m \"%s\" /f \"%s\"", (LPCTSTR
) printer
->modelName
, (LPCTSTR
) printer
->infFileName
);
793 ok
= CreateProcess(NULL
, command
.GetBuffer(), NULL
, NULL
, FALSE
, 0, NULL
, NULL
, &si
, &pi
);
794 err
= translate_errno( ok
, errno_compat(), kUnknownErr
);
795 require_noerr( err
, exit
);
797 dwResult
= WaitForSingleObject( pi
.hProcess
, INFINITE
);
798 translate_errno( dwResult
== WAIT_OBJECT_0
, errno_compat(), err
= kUnknownErr
);
799 require_noerr( err
, exit
);
801 ok
= GetExitCodeProcess( pi
.hProcess
, &exitCode
);
802 err
= translate_errno( ok
, errno_compat(), kUnknownErr
);
803 require_noerr( err
, exit
);
809 // Close process and thread handles.
813 CloseHandle( pi
.hProcess
);
818 CloseHandle( pi
.hThread
);
822 // alert the main thread
824 m_self
->PostMessage( WM_PROCESS_EVENT
, err
, exitCode
);
826 _endthreadex_compat( 0 );
833 CPrinterSetupWizardSheet::OnBrowse(
835 DNSServiceFlags inFlags
,
836 uint32_t inInterfaceIndex
,
837 DNSServiceErrorType inErrorCode
,
840 const char * inDomain
,
845 CPrinterSetupWizardSheet
* self
;
846 bool moreComing
= (bool) (inFlags
& kDNSServiceFlagsMoreComing
);
847 CPropertyPage
* active
;
848 Printer
* printer
= NULL
;
849 Service
* service
= NULL
;
850 OSStatus err
= kNoErr
;
852 require_noerr( inErrorCode
, exit
);
854 self
= reinterpret_cast <CPrinterSetupWizardSheet
*>( inContext
);
855 require_quiet( self
, exit
);
857 active
= self
->GetActivePage();
858 require_quiet( active
, exit
);
860 // Have we seen this printer before?
862 printer
= self
->Lookup( inName
);
866 service
= printer
->LookupService( inType
);
869 if ( inFlags
& kDNSServiceFlagsAdd
)
873 // If not, then create a new one
875 printer
= self
->OnAddPrinter( inInterfaceIndex
, inName
, inType
, inDomain
, moreComing
);
876 require_action( printer
, exit
, err
= kUnknownErr
);
881 err
= self
->OnAddService( printer
, inInterfaceIndex
, inName
, inType
, inDomain
);
882 require_noerr( err
, exit
);
893 err
= self
->OnRemoveService( service
);
894 require_noerr( err
, exit
);
896 if ( printer
->services
.size() == 0 )
898 err
= self
->OnRemovePrinter( printer
, moreComing
);
899 require_noerr( err
, exit
);
910 CPrinterSetupWizardSheet::OnResolve(
912 DNSServiceFlags inFlags
,
913 uint32_t inInterfaceIndex
,
914 DNSServiceErrorType inErrorCode
,
915 const char * inFullName
,
916 const char * inHostName
,
922 DEBUG_UNUSED(inFullName
);
923 DEBUG_UNUSED(inInterfaceIndex
);
924 DEBUG_UNUSED(inFlags
);
927 CPrinterSetupWizardSheet
* self
;
933 require_noerr( inErrorCode
, exit
);
935 service
= reinterpret_cast<Service
*>( inContext
);
936 require_quiet( service
, exit
);
938 check( service
->refs
!= 0 );
940 self
= service
->printer
->window
;
941 require_quiet( self
, exit
);
943 err
= self
->StopOperation( service
->serviceRef
);
944 require_noerr( err
, exit
);
947 // hold on to the hostname...
949 err
= UTF8StringToStringObject( inHostName
, service
->hostname
);
950 require_noerr( err
, exit
);
953 // <rdar://problem/3739200> remove the trailing dot on hostname
955 idx
= service
->hostname
.ReverseFind('.');
957 if ((idx
> 1) && ((service
->hostname
.GetLength() - 1) == idx
))
959 service
->hostname
.Delete(idx
, 1);
963 // hold on to the port
965 service
->portNumber
= ntohs(inPort
);
967 if ( service
->qtotal
== 1 )
970 // create a new queue
981 require_action( q
, exit
, err
= E_OUTOFMEMORY
);
984 // parse the text record.
987 err
= self
->ParseTextRecord( service
, q
, inTXTSize
, inTXT
);
988 require_noerr( err
, exit
);
990 service
->queues
.push_back( q
);
993 // we've completely resolved this service
996 self
->OnResolveService( service
);
1001 // if qtotal is more than 1, then we need to get additional
1002 // text records. if not, then this service is considered
1006 err
= DNSServiceQueryRecord(&service
->serviceRef
, 0, inInterfaceIndex
, inFullName
, kDNSServiceType_TXT
, kDNSServiceClass_IN
, OnQuery
, (void*) service
);
1007 require_noerr( err
, exit
);
1009 err
= self
->StartOperation( service
->serviceRef
);
1010 require_noerr( err
, exit
);
1020 CPrinterSetupWizardSheet::OnQuery(
1021 DNSServiceRef inRef
,
1022 DNSServiceFlags inFlags
,
1023 uint32_t inInterfaceIndex
,
1024 DNSServiceErrorType inErrorCode
,
1025 const char * inFullName
,
1029 const void * inRData
,
1033 DEBUG_UNUSED( inTTL
);
1034 DEBUG_UNUSED( inRRClass
);
1035 DEBUG_UNUSED( inRRType
);
1036 DEBUG_UNUSED( inFullName
);
1037 DEBUG_UNUSED( inInterfaceIndex
);
1038 DEBUG_UNUSED( inRef
);
1040 Service
* service
= NULL
;
1042 CPrinterSetupWizardSheet
* self
;
1043 OSStatus err
= kNoErr
;
1045 require_noerr( inErrorCode
, exit
);
1047 service
= reinterpret_cast<Service
*>( inContext
);
1048 require_quiet( service
, exit
);
1050 self
= service
->printer
->window
;
1051 require_quiet( self
, exit
);
1053 if ( ( inFlags
& kDNSServiceFlagsAdd
) && ( inRDLen
> 0 ) && ( inRData
!= NULL
) )
1055 const char * inTXT
= ( const char * ) inRData
;
1058 // create a new queue
1069 require_action( q
, exit
, err
= E_OUTOFMEMORY
);
1071 err
= service
->printer
->window
->ParseTextRecord( service
, q
, inRDLen
, inTXT
);
1072 require_noerr( err
, exit
);
1078 service
->queues
.push_back( q
);
1080 if ( service
->queues
.size() == service
->qtotal
)
1083 // else if moreComing is not set, then we're going
1084 // to assume that we're done
1087 self
->StopOperation( service
->serviceRef
);
1093 service
->queues
.sort( OrderQueueFunc
);
1096 // we've completely resolved this service
1099 self
->OnResolveService( service
);
1105 if ( err
&& service
&& ( service
->serviceRef
!= NULL
) )
1107 service
->printer
->window
->StopOperation( service
->serviceRef
);
1115 CPrinterSetupWizardSheet::OnAddPrinter(
1116 uint32_t inInterfaceIndex
,
1117 const char * inName
,
1118 const char * inType
,
1119 const char * inDomain
,
1122 Printer
* printer
= NULL
;
1123 DWORD printerNameCount
;
1126 DEBUG_UNUSED( inInterfaceIndex
);
1127 DEBUG_UNUSED( inType
);
1128 DEBUG_UNUSED( inDomain
);
1132 printer
= new Printer
;
1139 require_action( printer
, exit
, err
= E_OUTOFMEMORY
);
1141 printer
->window
= this;
1142 printer
->name
= inName
;
1144 err
= UTF8StringToStringObject(inName
, printer
->displayName
);
1146 printer
->actualName
= printer
->displayName
;
1147 printer
->installed
= false;
1148 printer
->deflt
= false;
1149 printer
->resolving
= 0;
1151 // Compare this name against printers that are already installed
1152 // to avoid name clashes. Rename as necessary
1153 // to come up with a unique name.
1155 printerNameCount
= 2;
1159 CPrinterSetupWizardSheet::PrinterNames::iterator it
;
1161 // <rdar://problem/4141221> Don't use find to do comparisons because we need to
1162 // do a case insensitive string comparison
1164 for ( it
= m_printerNames
.begin(); it
!= m_printerNames
.end(); it
++ )
1166 if ( (*it
).CompareNoCase( printer
->actualName
) == 0 )
1172 if (it
!= m_printerNames
.end())
1174 printer
->actualName
.Format(L
"%s (%d)", printer
->displayName
, printerNameCount
);
1184 m_printers
.push_back( printer
);
1186 if ( GetActivePage() == &m_pgSecond
)
1188 m_pgSecond
.OnAddPrinter( printer
, moreComing
);
1198 CPrinterSetupWizardSheet::OnAddService(
1200 uint32_t inInterfaceIndex
,
1201 const char * inName
,
1202 const char * inType
,
1203 const char * inDomain
)
1205 Service
* service
= NULL
;
1206 OSStatus err
= kNoErr
;
1208 DEBUG_UNUSED( inName
);
1209 DEBUG_UNUSED( inDomain
);
1213 service
= new Service
;
1220 require_action( service
, exit
, err
= E_OUTOFMEMORY
);
1222 service
->printer
= printer
;
1223 service
->ifi
= inInterfaceIndex
;
1224 service
->type
= inType
;
1225 service
->domain
= inDomain
;
1226 service
->qtotal
= 1;
1228 service
->serviceRef
= NULL
;
1230 printer
->services
.push_back( service
);
1233 // if the printer is selected, then we'll want to start a
1234 // resolve on this guy
1237 if ( printer
== m_selectedPrinter
)
1239 StartResolve( service
);
1249 CPrinterSetupWizardSheet::OnRemovePrinter( Printer
* printer
, bool moreComing
)
1251 CPropertyPage
* active
= GetActivePage();
1252 OSStatus err
= kNoErr
;
1254 if ( active
== &m_pgSecond
)
1256 m_pgSecond
.OnRemovePrinter( printer
, moreComing
);
1259 m_printers
.remove( printer
);
1261 if ( m_selectedPrinter
== printer
)
1263 m_selectedPrinter
= NULL
;
1265 if ( ( active
== &m_pgThird
) || ( active
== &m_pgFourth
) )
1270 caption
.LoadString( IDS_ERROR_CAPTION
);
1271 message
.LoadString( IDS_PRINTER_UNAVAILABLE
);
1273 MessageBox(message
, caption
, MB_OK
|MB_ICONEXCLAMATION
);
1275 SetActivePage( &m_pgSecond
);
1286 CPrinterSetupWizardSheet::OnRemoveService( Service
* service
)
1288 OSStatus err
= kNoErr
;
1290 if ( service
&& ( --service
->refs
== 0 ) )
1292 if ( service
->serviceRef
!= NULL
)
1294 err
= StopResolve( service
);
1295 require_noerr( err
, exit
);
1298 service
->printer
->services
.remove( service
);
1310 CPrinterSetupWizardSheet::OnResolveService( Service
* service
)
1312 // Make sure that the active page is page 2
1314 require_quiet( GetActivePage() == &m_pgSecond
, exit
);
1316 if ( !--service
->printer
->resolving
)
1318 // sort the services now. we want the service that
1319 // has the highest priority queue to be first in
1322 service
->printer
->services
.sort( OrderServiceFunc
);
1324 // Now we can hit next
1326 SetWizardButtons( PSWIZB_BACK
|PSWIZB_NEXT
);
1332 // And tell page 2 about it
1334 m_pgSecond
.OnResolveService( service
);
1344 CPrinterSetupWizardSheet::StartBrowse()
1349 // setup the DNS-SD browsing
1351 err
= DNSServiceBrowse( &m_pdlBrowser
, 0, 0, kPDLServiceType
, NULL
, OnBrowse
, this );
1352 require_noerr( err
, exit
);
1354 err
= StartOperation( m_pdlBrowser
);
1355 require_noerr( err
, exit
);
1357 err
= DNSServiceBrowse( &m_lprBrowser
, 0, 0, kLPRServiceType
, NULL
, OnBrowse
, this );
1358 require_noerr( err
, exit
);
1360 err
= StartOperation( m_lprBrowser
);
1361 require_noerr( err
, exit
);
1363 err
= DNSServiceBrowse( &m_ippBrowser
, 0, 0, kIPPServiceType
, NULL
, OnBrowse
, this );
1364 require_noerr( err
, exit
);
1366 err
= StartOperation( m_ippBrowser
);
1367 require_noerr( err
, exit
);
1376 CPrinterSetupWizardSheet::StopBrowse()
1380 err
= StopOperation( m_pdlBrowser
);
1381 require_noerr( err
, exit
);
1383 err
= StopOperation( m_lprBrowser
);
1384 require_noerr( err
, exit
);
1386 err
= StopOperation( m_ippBrowser
);
1387 require_noerr( err
, exit
);
1389 while ( m_printers
.size() > 0 )
1391 Printer
* printer
= m_printers
.front();
1393 m_printers
.pop_front();
1395 if ( printer
->resolving
)
1397 StopResolve( printer
);
1410 CPrinterSetupWizardSheet::StartResolve( Printer
* printer
)
1412 OSStatus err
= kNoErr
;
1413 Services::iterator it
;
1417 for ( it
= printer
->services
.begin(); it
!= printer
->services
.end(); it
++ )
1419 if ( (*it
)->serviceRef
== NULL
)
1421 err
= StartResolve( *it
);
1422 require_noerr( err
, exit
);
1426 m_selectedPrinter
= printer
;
1435 CPrinterSetupWizardSheet::StartResolve( Service
* service
)
1437 OSStatus err
= kNoErr
;
1439 check( service
->serviceRef
== NULL
);
1442 // clean out any queues that were collected during a previous
1446 service
->EmptyQueues();
1449 // now start the new resolve
1452 err
= DNSServiceResolve( &service
->serviceRef
, 0, 0, service
->printer
->name
.c_str(), service
->type
.c_str(), service
->domain
.c_str(), (DNSServiceResolveReply
) OnResolve
, service
);
1453 require_noerr( err
, exit
);
1455 err
= StartOperation( service
->serviceRef
);
1456 require_noerr( err
, exit
);
1459 // If we're not currently resolving, then disable the next button
1460 // and set the cursor to hourglass
1463 if ( !service
->printer
->resolving
)
1465 SetWizardButtons( PSWIZB_BACK
);
1468 SetCursor(m_active
);
1471 service
->printer
->resolving
++;
1480 CPrinterSetupWizardSheet::StopResolve(Printer
* printer
)
1482 OSStatus err
= kNoErr
;
1486 Services::iterator it
;
1488 for ( it
= printer
->services
.begin(); it
!= printer
->services
.end(); it
++ )
1490 if ( (*it
)->serviceRef
)
1492 err
= StopResolve( *it
);
1493 require_noerr( err
, exit
);
1504 CPrinterSetupWizardSheet::StopResolve( Service
* service
)
1508 check( service
->serviceRef
);
1510 err
= StopOperation( service
->serviceRef
);
1511 require_noerr( err
, exit
);
1513 service
->printer
->resolving
--;
1522 CPrinterSetupWizardSheet::StartOperation( DNSServiceRef ref
)
1526 err
= WSAAsyncSelect((SOCKET
) DNSServiceRefSockFD(ref
), m_hWnd
, WM_SOCKET_EVENT
, FD_READ
|FD_CLOSE
);
1527 require_noerr( err
, exit
);
1529 m_serviceRefList
.push_back( ref
);
1538 CPrinterSetupWizardSheet::StopOperation( DNSServiceRef
& ref
)
1540 OSStatus err
= kNoErr
;
1544 m_serviceRefList
.remove( ref
);
1546 if ( IsWindow( m_hWnd
) )
1548 err
= WSAAsyncSelect((SOCKET
) DNSServiceRefSockFD( ref
), m_hWnd
, 0, 0 );
1549 require_noerr( err
, exit
);
1552 DNSServiceRefDeallocate( ref
);
1563 CPrinterSetupWizardSheet::ParseTextRecord( Service
* service
, Queue
* q
, uint16_t inTXTSize
, const char * inTXT
)
1568 // <rdar://problem/3946587> Use TXTRecord APIs declared in dns_sd.h
1570 bool qtotalDefined
= false;
1574 OSStatus err
= kNoErr
;
1576 // <rdar://problem/3987680> Default to queue "lp"
1580 // <rdar://problem/4003710> Default pdl key to be "application/postscript"
1582 q
->pdl
= L
"application/postscript";
1584 if ( ( val
= TXTRecordGetValuePtr( inTXTSize
, inTXT
, "rp", &len
) ) != NULL
)
1586 // Stringize val ( doesn't have trailing '\0' yet )
1588 memcpy( buf
, val
, len
);
1591 err
= UTF8StringToStringObject( buf
, q
->name
);
1592 require_noerr( err
, exit
);
1595 if ( ( val
= TXTRecordGetValuePtr( inTXTSize
, inTXT
, "pdl", &len
) ) != NULL
)
1597 // Stringize val ( doesn't have trailing '\0' yet )
1599 memcpy( buf
, val
, len
);
1602 err
= UTF8StringToStringObject( buf
, q
->pdl
);
1603 require_noerr( err
, exit
);
1606 if ( ( ( val
= TXTRecordGetValuePtr( inTXTSize
, inTXT
, "usb_mfg", &len
) ) != NULL
) ||
1607 ( ( val
= TXTRecordGetValuePtr( inTXTSize
, inTXT
, "usb_manufacturer", &len
) ) != NULL
) )
1609 // Stringize val ( doesn't have trailing '\0' yet )
1611 memcpy( buf
, val
, len
);
1614 err
= UTF8StringToStringObject( buf
, q
->usb_MFG
);
1615 require_noerr( err
, exit
);
1618 if ( ( ( val
= TXTRecordGetValuePtr( inTXTSize
, inTXT
, "usb_mdl", &len
) ) != NULL
) ||
1619 ( ( val
= TXTRecordGetValuePtr( inTXTSize
, inTXT
, "usb_model", &len
) ) != NULL
) )
1621 // Stringize val ( doesn't have trailing '\0' yet )
1623 memcpy( buf
, val
, len
);
1626 err
= UTF8StringToStringObject( buf
, q
->usb_MDL
);
1627 require_noerr( err
, exit
);
1630 if ( ( val
= TXTRecordGetValuePtr( inTXTSize
, inTXT
, "ty", &len
) ) != NULL
)
1632 // Stringize val ( doesn't have trailing '\0' yet )
1634 memcpy( buf
, val
, len
);
1637 err
= UTF8StringToStringObject( buf
, q
->description
);
1638 require_noerr( err
, exit
);
1641 if ( ( val
= TXTRecordGetValuePtr( inTXTSize
, inTXT
, "product", &len
) ) != NULL
)
1643 // Stringize val ( doesn't have trailing '\0' yet )
1645 memcpy( buf
, val
, len
);
1648 err
= UTF8StringToStringObject( buf
, q
->product
);
1649 require_noerr( err
, exit
);
1652 if ( ( val
= TXTRecordGetValuePtr( inTXTSize
, inTXT
, "note", &len
) ) != NULL
)
1654 // Stringize val ( doesn't have trailing '\0' yet )
1656 memcpy( buf
, val
, len
);
1659 err
= UTF8StringToStringObject( buf
, q
->location
);
1660 require_noerr( err
, exit
);
1663 if ( ( val
= TXTRecordGetValuePtr( inTXTSize
, inTXT
, "qtotal", &len
) ) != NULL
)
1665 // Stringize val ( doesn't have trailing '\0' yet )
1667 memcpy( buf
, val
, len
);
1670 service
->qtotal
= (unsigned short) atoi( buf
);
1671 qtotalDefined
= true;
1674 if ( ( val
= TXTRecordGetValuePtr( inTXTSize
, inTXT
, "priority", &len
) ) != NULL
)
1676 // Stringize val ( doesn't have trailing '\0' yet )
1678 memcpy( buf
, val
, len
);
1681 q
->priority
= atoi( buf
);
1684 // <rdar://problem/4124524> Was this printer discovered via OS X Printer Sharing?
1686 if ( TXTRecordContainsKey( inTXTSize
, inTXT
, "printer-state" ) || TXTRecordContainsKey( inTXTSize
, inTXT
, "printer-type" ) )
1688 service
->printer
->isSharedFromOSX
= true;
1693 // The following code is to fix a problem with older HP
1694 // printers that don't include "qtotal" in their text
1695 // record. We'll check to see if the q->name is "TEXT"
1696 // and if so, we're going to modify it to be "lp" so
1697 // that we don't use the wrong queue
1699 if ( !err
&& !qtotalDefined
&& ( q
->name
== L
"TEXT" ) )
1709 CPrinterSetupWizardSheet::Lookup(const char * inName
)
1713 Printer
* printer
= NULL
;
1714 Printers::iterator it
;
1716 for ( it
= m_printers
.begin(); it
!= m_printers
.end(); it
++ )
1718 if ( (*it
)->name
== inName
)
1730 CPrinterSetupWizardSheet::OrderServiceFunc( const Service
* a
, const Service
* b
)
1734 q1
= (a
->queues
.size() > 0) ? a
->queues
.front() : NULL
;
1736 q2
= (b
->queues
.size() > 0) ? b
->queues
.front() : NULL
;
1742 else if ( q1
&& !q2
)
1746 else if ( !q1
&& q2
)
1750 else if ( q1
->priority
< q2
->priority
)
1754 else if ( q1
->priority
> q2
->priority
)
1758 else if ( ( a
->type
== kPDLServiceType
) || ( ( a
->type
== kLPRServiceType
) && ( b
->type
== kIPPServiceType
) ) )
1770 CPrinterSetupWizardSheet::OrderQueueFunc( const Queue
* q1
, const Queue
* q2
)
1772 return ( q1
->priority
<= q2
->priority
) ? true : false;