+
+
+void DNSSD_API
+CPrinterSetupWizardSheet::OnBrowse(
+ DNSServiceRef inRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inErrorCode,
+ const char * inName,
+ const char * inType,
+ const char * inDomain,
+ void * inContext )
+{
+ DEBUG_UNUSED(inRef);
+
+ CPrinterSetupWizardSheet * self;
+ bool moreComing = (bool) (inFlags & kDNSServiceFlagsMoreComing);
+ CPropertyPage * active;
+ Printer * printer = NULL;
+ Service * service = NULL;
+ OSStatus err = kNoErr;
+
+ require_noerr( inErrorCode, exit );
+
+ self = reinterpret_cast <CPrinterSetupWizardSheet*>( inContext );
+ require_quiet( self, exit );
+
+ active = self->GetActivePage();
+ require_quiet( active, exit );
+
+ // Have we seen this printer before?
+
+ printer = self->Lookup( inName );
+
+ if ( printer )
+ {
+ service = printer->LookupService( inType );
+ }
+
+ if ( inFlags & kDNSServiceFlagsAdd )
+ {
+ BOOL newPrinter = FALSE;
+
+ if ( !printer )
+ {
+ printer = self->OnAddPrinter( inInterfaceIndex, inName, inType, inDomain, moreComing );
+ require_action( printer, exit, err = kUnknownErr );
+ newPrinter = TRUE;
+ }
+
+ // If we're looking at the browse list on page 2, then we need to call
+ // CPage2::OnAddPrinter() regardless of whether we've seen the printer
+ // or not because the moreComing flag might have changed from a previous
+ // call. If we only call CPage2::OnAddPrinter() when there's a new printer,
+ // we might not correctly update our UI, so if we've seen the printer before,
+ // call OnAddPrinter with a NULL parameter.
+
+ if ( self->GetActivePage() == &self->m_pgSecond )
+ {
+ self->m_pgSecond.OnAddPrinter( newPrinter ? printer : NULL, moreComing );
+ }
+
+ if ( !service )
+ {
+ err = self->OnAddService( printer, inInterfaceIndex, inName, inType, inDomain );
+ require_noerr( err, exit );
+ }
+ else
+ {
+ service->refs++;
+ }
+ }
+ else if ( printer )
+ {
+ check( service );
+
+ err = self->OnRemoveService( service );
+ require_noerr( err, exit );
+
+ if ( printer->services.size() == 0 )
+ {
+ err = self->OnRemovePrinter( printer, moreComing );
+ require_noerr( err, exit );
+ }
+ }
+
+exit:
+
+ return;
+}
+
+
+void DNSSD_API
+CPrinterSetupWizardSheet::OnResolve(
+ DNSServiceRef inRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inErrorCode,
+ const char * inFullName,
+ const char * inHostName,
+ uint16_t inPort,
+ uint16_t inTXTSize,
+ const char * inTXT,
+ void * inContext )
+{
+ DEBUG_UNUSED(inFullName);
+ DEBUG_UNUSED(inInterfaceIndex);
+ DEBUG_UNUSED(inFlags);
+ DEBUG_UNUSED(inRef);
+
+ CPrinterSetupWizardSheet * self;
+ Service * service;
+ Queue * q;
+ int idx;
+ OSStatus err;
+
+ require_noerr( inErrorCode, exit );
+
+ service = reinterpret_cast<Service*>( inContext );
+ require_quiet( service, exit);
+
+ check( service->refs != 0 );
+
+ self = service->printer->window;
+ require_quiet( self, exit );
+
+ err = self->StopOperation( service->serviceRef );
+ require_noerr( err, exit );
+
+ //
+ // hold on to the hostname...
+ //
+ err = UTF8StringToStringObject( inHostName, service->hostname );
+ require_noerr( err, exit );
+
+ //
+ // <rdar://problem/3739200> remove the trailing dot on hostname
+ //
+ idx = service->hostname.ReverseFind('.');
+
+ if ((idx > 1) && ((service->hostname.GetLength() - 1) == idx))
+ {
+ service->hostname.Delete(idx, 1);
+ }
+
+ //
+ // hold on to the port
+ //
+ service->portNumber = ntohs(inPort);
+
+ if ( service->qtotal == 1 )
+ {
+ //
+ // create a new queue
+ //
+ try
+ {
+ q = new Queue;
+ }
+ catch (...)
+ {
+ q = NULL;
+ }
+
+ require_action( q, exit, err = E_OUTOFMEMORY );
+
+ //
+ // parse the text record.
+ //
+
+ err = self->ParseTextRecord( service, q, inTXTSize, inTXT );
+ require_noerr( err, exit );
+
+ service->queues.push_back( q );
+
+ //
+ // we've completely resolved this service
+ //
+
+ self->OnResolveService( service );
+ }
+ else
+ {
+ //
+ // if qtotal is more than 1, then we need to get additional
+ // text records. if not, then this service is considered
+ // resolved
+ //
+
+ err = DNSServiceQueryRecord(&service->serviceRef, 0, inInterfaceIndex, inFullName, kDNSServiceType_TXT, kDNSServiceClass_IN, OnQuery, (void*) service );
+ require_noerr( err, exit );
+
+ err = self->StartOperation( service->serviceRef );
+ require_noerr( err, exit );
+ }
+
+exit:
+
+ return;
+}
+
+
+void DNSSD_API
+CPrinterSetupWizardSheet::OnQuery(
+ DNSServiceRef inRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inErrorCode,
+ const char * inFullName,
+ uint16_t inRRType,
+ uint16_t inRRClass,
+ uint16_t inRDLen,
+ const void * inRData,
+ uint32_t inTTL,
+ void * inContext)
+{
+ DEBUG_UNUSED( inTTL );
+ DEBUG_UNUSED( inRRClass );
+ DEBUG_UNUSED( inRRType );
+ DEBUG_UNUSED( inFullName );
+ DEBUG_UNUSED( inInterfaceIndex );
+ DEBUG_UNUSED( inRef );
+
+ Service * service = NULL;
+ Queue * q;
+ CPrinterSetupWizardSheet * self;
+ OSStatus err = kNoErr;
+
+ require_noerr( inErrorCode, exit );
+
+ service = reinterpret_cast<Service*>( inContext );
+ require_quiet( service, exit);
+
+ self = service->printer->window;
+ require_quiet( self, exit );
+
+ if ( ( inFlags & kDNSServiceFlagsAdd ) && ( inRDLen > 0 ) && ( inRData != NULL ) )
+ {
+ const char * inTXT = ( const char * ) inRData;
+
+ //
+ // create a new queue
+ //
+ try
+ {
+ q = new Queue;
+ }
+ catch (...)
+ {
+ q = NULL;
+ }
+
+ require_action( q, exit, err = E_OUTOFMEMORY );
+
+ err = service->printer->window->ParseTextRecord( service, q, inRDLen, inTXT );
+ require_noerr( err, exit );
+
+ //
+ // add this queue
+ //
+
+ service->queues.push_back( q );
+
+ if ( service->queues.size() == service->qtotal )
+ {
+ //
+ // else if moreComing is not set, then we're going
+ // to assume that we're done
+ //
+
+ self->StopOperation( service->serviceRef );
+
+ //
+ // sort the queues
+ //
+
+ service->queues.sort( OrderQueueFunc );
+
+ //
+ // we've completely resolved this service
+ //
+
+ self->OnResolveService( service );
+ }
+ }
+
+exit:
+
+ if ( err && service && ( service->serviceRef != NULL ) )
+ {
+ service->printer->window->StopOperation( service->serviceRef );
+ }
+
+ return;
+}
+
+
+Printer*
+CPrinterSetupWizardSheet::OnAddPrinter(
+ uint32_t inInterfaceIndex,
+ const char * inName,
+ const char * inType,
+ const char * inDomain,
+ bool moreComing)
+{
+ Printer * printer = NULL;
+ DWORD printerNameCount;
+ OSStatus err;
+
+ DEBUG_UNUSED( inInterfaceIndex );
+ DEBUG_UNUSED( inType );
+ DEBUG_UNUSED( inDomain );
+ DEBUG_UNUSED( moreComing );
+
+ try
+ {
+ printer = new Printer;
+ }
+ catch (...)
+ {
+ printer = NULL;
+ }
+
+ require_action( printer, exit, err = E_OUTOFMEMORY );
+
+ printer->window = this;
+ printer->name = inName;
+
+ err = UTF8StringToStringObject(inName, printer->displayName);
+ check_noerr( err );
+ printer->actualName = printer->displayName;
+ printer->installed = false;
+ printer->deflt = false;
+ printer->resolving = 0;
+
+ // Compare this name against printers that are already installed
+ // to avoid name clashes. Rename as necessary
+ // to come up with a unique name.
+
+ printerNameCount = 2;
+
+ for (;;)
+ {
+ CPrinterSetupWizardSheet::PrinterNames::iterator it;
+
+ // <rdar://problem/4141221> Don't use find to do comparisons because we need to
+ // do a case insensitive string comparison
+
+ for ( it = m_printerNames.begin(); it != m_printerNames.end(); it++ )
+ {
+ if ( (*it).CompareNoCase( printer->actualName ) == 0 )
+ {
+ break;
+ }
+ }
+
+ if (it != m_printerNames.end())
+ {
+ printer->actualName.Format(L"%s (%d)", printer->displayName, printerNameCount);
+ }
+ else
+ {
+ break;
+ }
+
+ printerNameCount++;
+ }
+
+ m_printers.push_back( printer );
+
+exit:
+
+ return printer;
+}
+
+
+OSStatus
+CPrinterSetupWizardSheet::OnAddService(
+ Printer * printer,
+ uint32_t inInterfaceIndex,
+ const char * inName,
+ const char * inType,
+ const char * inDomain)
+{
+ Service * service = NULL;
+ OSStatus err = kNoErr;
+
+ DEBUG_UNUSED( inName );
+ DEBUG_UNUSED( inDomain );
+
+ try
+ {
+ service = new Service;
+ }
+ catch (...)
+ {
+ service = NULL;
+ }
+
+ require_action( service, exit, err = E_OUTOFMEMORY );
+
+ service->printer = printer;
+ service->ifi = inInterfaceIndex;
+ service->type = inType;
+ service->domain = inDomain;
+ service->qtotal = 1;
+ service->refs = 1;
+ service->serviceRef = NULL;
+
+ printer->services.push_back( service );
+
+ //
+ // if the printer is selected, then we'll want to start a
+ // resolve on this guy
+ //
+
+ if ( printer == m_selectedPrinter )
+ {
+ StartResolve( service );
+ }
+
+exit:
+
+ return err;
+}
+
+
+OSStatus
+CPrinterSetupWizardSheet::OnRemovePrinter( Printer * printer, bool moreComing )
+{
+ CPropertyPage * active = GetActivePage();
+ OSStatus err = kNoErr;
+
+ if ( active == &m_pgSecond )
+ {
+ m_pgSecond.OnRemovePrinter( printer, moreComing );
+ }
+
+ m_printers.remove( printer );
+
+ if ( m_selectedPrinter == printer )
+ {
+ m_selectedPrinter = NULL;
+
+ if ( ( active == &m_pgThird ) || ( active == &m_pgFourth ) )
+ {
+ CString caption;
+ CString message;
+
+ caption.LoadString( IDS_ERROR_CAPTION );
+ message.LoadString( IDS_PRINTER_UNAVAILABLE );
+
+ MessageBox(message, caption, MB_OK|MB_ICONEXCLAMATION);
+
+ SetActivePage( &m_pgSecond );
+ }
+ }
+
+ delete printer;
+
+ return err;
+}
+
+
+OSStatus
+CPrinterSetupWizardSheet::OnRemoveService( Service * service )
+{
+ OSStatus err = kNoErr;
+
+ if ( service && ( --service->refs == 0 ) )
+ {
+ if ( service->serviceRef != NULL )
+ {
+ err = StopResolve( service );
+ require_noerr( err, exit );
+ }
+
+ service->printer->services.remove( service );
+
+ delete service;
+ }
+
+exit:
+
+ return err;
+}
+
+
+void
+CPrinterSetupWizardSheet::OnResolveService( Service * service )
+{
+ // Make sure that the active page is page 2
+
+ require_quiet( GetActivePage() == &m_pgSecond, exit );
+
+ if ( !--service->printer->resolving )
+ {
+ // sort the services now. we want the service that
+ // has the highest priority queue to be first in
+ // the list.
+
+ service->printer->services.sort( OrderServiceFunc );
+
+ // Now we can hit next
+
+ SetWizardButtons( PSWIZB_BACK|PSWIZB_NEXT );
+
+ // Reset the cursor
+
+ m_active = m_arrow;
+
+ // And tell page 2 about it
+
+ m_pgSecond.OnResolveService( service );
+ }
+
+exit:
+
+ return;
+}
+
+
+OSStatus
+CPrinterSetupWizardSheet::StartBrowse()
+{
+ OSStatus err;
+
+ //
+ // setup the DNS-SD browsing
+ //
+ err = DNSServiceBrowse( &m_pdlBrowser, 0, 0, kPDLServiceType, NULL, OnBrowse, this );
+ require_noerr( err, exit );
+
+ err = StartOperation( m_pdlBrowser );
+ require_noerr( err, exit );
+
+ err = DNSServiceBrowse( &m_lprBrowser, 0, 0, kLPRServiceType, NULL, OnBrowse, this );
+ require_noerr( err, exit );
+
+ err = StartOperation( m_lprBrowser );
+ require_noerr( err, exit );
+
+ err = DNSServiceBrowse( &m_ippBrowser, 0, 0, kIPPServiceType, NULL, OnBrowse, this );
+ require_noerr( err, exit );
+
+ err = StartOperation( m_ippBrowser );
+ require_noerr( err, exit );
+
+exit:
+
+ return err;
+}
+
+
+OSStatus
+CPrinterSetupWizardSheet::StopBrowse()
+{
+ OSStatus err;
+
+ err = StopOperation( m_pdlBrowser );
+ require_noerr( err, exit );
+
+ err = StopOperation( m_lprBrowser );
+ require_noerr( err, exit );
+
+ err = StopOperation( m_ippBrowser );
+ require_noerr( err, exit );
+
+ while ( m_printers.size() > 0 )
+ {
+ Printer * printer = m_printers.front();
+
+ m_printers.pop_front();
+
+ if ( printer->resolving )
+ {
+ StopResolve( printer );
+ }
+
+ delete printer;
+ }
+
+exit:
+
+ return err;
+}
+
+
+OSStatus
+CPrinterSetupWizardSheet::StartResolve( Printer * printer )
+{
+ OSStatus err = kNoErr;
+ Services::iterator it;
+
+ check( printer );
+
+ for ( it = printer->services.begin(); it != printer->services.end(); it++ )
+ {
+ if ( (*it)->serviceRef == NULL )
+ {
+ err = StartResolve( *it );
+ require_noerr( err, exit );
+ }
+ }
+
+ m_selectedPrinter = printer;
+
+exit:
+
+ return err;
+}
+
+
+OSStatus
+CPrinterSetupWizardSheet::StartResolve( Service * service )
+{
+ OSStatus err = kNoErr;
+
+ check( service->serviceRef == NULL );
+
+ //
+ // clean out any queues that were collected during a previous
+ // resolve
+ //
+
+ service->EmptyQueues();
+
+ //
+ // now start the new resolve
+ //
+
+ err = DNSServiceResolve( &service->serviceRef, 0, 0, service->printer->name.c_str(), service->type.c_str(), service->domain.c_str(), (DNSServiceResolveReply) OnResolve, service );
+ require_noerr( err, exit );
+
+ err = StartOperation( service->serviceRef );
+ require_noerr( err, exit );
+
+ //
+ // If we're not currently resolving, then disable the next button
+ // and set the cursor to hourglass
+ //
+
+ if ( !service->printer->resolving )
+ {
+ SetWizardButtons( PSWIZB_BACK );
+
+ m_active = m_wait;
+ SetCursor(m_active);
+ }
+
+ service->printer->resolving++;
+
+exit:
+
+ return err;
+}
+
+
+OSStatus
+CPrinterSetupWizardSheet::StopResolve(Printer * printer)
+{
+ OSStatus err = kNoErr;
+
+ check( printer );
+
+ Services::iterator it;
+
+ for ( it = printer->services.begin(); it != printer->services.end(); it++ )
+ {
+ if ( (*it)->serviceRef )
+ {
+ err = StopResolve( *it );
+ require_noerr( err, exit );
+ }
+ }
+
+exit:
+
+ return err;
+}
+
+
+OSStatus
+CPrinterSetupWizardSheet::StopResolve( Service * service )
+{
+ OSStatus err;
+
+ check( service->serviceRef );
+
+ err = StopOperation( service->serviceRef );
+ require_noerr( err, exit );
+
+ service->printer->resolving--;
+
+exit:
+
+ return err;
+}
+
+
+OSStatus
+CPrinterSetupWizardSheet::StartOperation( DNSServiceRef ref )
+{
+ OSStatus err;
+
+ err = WSAAsyncSelect((SOCKET) DNSServiceRefSockFD(ref), m_hWnd, WM_SOCKET_EVENT, FD_READ|FD_CLOSE);
+ require_noerr( err, exit );
+
+ m_serviceRefList.push_back( ref );
+
+exit:
+
+ return err;
+}
+
+
+OSStatus
+CPrinterSetupWizardSheet::StopOperation( DNSServiceRef & ref )
+{
+ OSStatus err = kNoErr;
+
+ if ( ref )
+ {
+ m_serviceRefList.remove( ref );
+
+ if ( IsWindow( m_hWnd ) )
+ {
+ err = WSAAsyncSelect((SOCKET) DNSServiceRefSockFD( ref ), m_hWnd, 0, 0 );
+ require_noerr( err, exit );
+ }
+
+ DNSServiceRefDeallocate( ref );
+ ref = NULL;
+ }
+
+exit:
+
+ return err;
+}
+
+
+OSStatus
+CPrinterSetupWizardSheet::ParseTextRecord( Service * service, Queue * q, uint16_t inTXTSize, const char * inTXT )
+{
+ check( service );
+ check( q );
+
+ // <rdar://problem/3946587> Use TXTRecord APIs declared in dns_sd.h
+
+ bool qtotalDefined = false;
+ const void * val;
+ char buf[256];
+ uint8_t len;
+ OSStatus err = kNoErr;
+
+ // <rdar://problem/3987680> Default to queue "lp"
+
+ q->name = L"lp";
+
+ // <rdar://problem/4003710> Default pdl key to be "application/postscript"
+
+ q->pdl = L"application/postscript";
+
+ if ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "rp", &len ) ) != NULL )
+ {
+ // Stringize val ( doesn't have trailing '\0' yet )
+
+ memcpy( buf, val, len );
+ buf[len] = '\0';
+
+ err = UTF8StringToStringObject( buf, q->name );
+ require_noerr( err, exit );
+ }
+
+ if ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "pdl", &len ) ) != NULL )
+ {
+ // Stringize val ( doesn't have trailing '\0' yet )
+
+ memcpy( buf, val, len );
+ buf[len] = '\0';
+
+ err = UTF8StringToStringObject( buf, q->pdl );
+ require_noerr( err, exit );
+ }
+
+ if ( ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "usb_mfg", &len ) ) != NULL ) ||
+ ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "usb_manufacturer", &len ) ) != NULL ) )
+ {
+ // Stringize val ( doesn't have trailing '\0' yet )
+
+ memcpy( buf, val, len );
+ buf[len] = '\0';
+
+ err = UTF8StringToStringObject( buf, q->usb_MFG );
+ require_noerr( err, exit );
+ }
+
+ if ( ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "usb_mdl", &len ) ) != NULL ) ||
+ ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "usb_model", &len ) ) != NULL ) )
+ {
+ // Stringize val ( doesn't have trailing '\0' yet )
+
+ memcpy( buf, val, len );
+ buf[len] = '\0';
+
+ err = UTF8StringToStringObject( buf, q->usb_MDL );
+ require_noerr( err, exit );
+ }
+
+ if ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "ty", &len ) ) != NULL )
+ {
+ // Stringize val ( doesn't have trailing '\0' yet )
+
+ memcpy( buf, val, len );
+ buf[len] = '\0';
+
+ err = UTF8StringToStringObject( buf, q->description );
+ require_noerr( err, exit );
+ }
+
+ if ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "product", &len ) ) != NULL )
+ {
+ // Stringize val ( doesn't have trailing '\0' yet )
+
+ memcpy( buf, val, len );
+ buf[len] = '\0';
+
+ err = UTF8StringToStringObject( buf, q->product );
+ require_noerr( err, exit );
+ }
+
+ if ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "note", &len ) ) != NULL )
+ {
+ // Stringize val ( doesn't have trailing '\0' yet )
+
+ memcpy( buf, val, len );
+ buf[len] = '\0';
+
+ err = UTF8StringToStringObject( buf, q->location );
+ require_noerr( err, exit );
+ }
+
+ if ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "qtotal", &len ) ) != NULL )
+ {
+ // Stringize val ( doesn't have trailing '\0' yet )
+
+ memcpy( buf, val, len );
+ buf[len] = '\0';
+
+ service->qtotal = (unsigned short) atoi( buf );
+ qtotalDefined = true;
+ }
+
+ if ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "priority", &len ) ) != NULL )
+ {
+ // Stringize val ( doesn't have trailing '\0' yet )
+
+ memcpy( buf, val, len );
+ buf[len] = '\0';
+
+ q->priority = atoi( buf );
+ }
+
+ // <rdar://problem/4124524> Was this printer discovered via OS X Printer Sharing?
+
+ if ( TXTRecordContainsKey( inTXTSize, inTXT, "printer-state" ) || TXTRecordContainsKey( inTXTSize, inTXT, "printer-type" ) )
+ {
+ service->printer->isCUPSPrinter = true;
+ }
+
+exit:
+
+ // The following code is to fix a problem with older HP
+ // printers that don't include "qtotal" in their text
+ // record. We'll check to see if the q->name is "TEXT"
+ // and if so, we're going to modify it to be "lp" so
+ // that we don't use the wrong queue
+
+ if ( !err && !qtotalDefined && ( q->name == L"TEXT" ) )
+ {
+ q->name = "lp";
+ }
+
+ return err;
+}
+
+
+Printer*
+CPrinterSetupWizardSheet::Lookup(const char * inName)
+{
+ check( inName );
+
+ Printer * printer = NULL;
+ Printers::iterator it;
+
+ for ( it = m_printers.begin(); it != m_printers.end(); it++ )
+ {
+ if ( (*it)->name == inName )
+ {
+ printer = *it;
+ break;
+ }
+ }
+
+ return printer;
+}
+
+
+bool
+CPrinterSetupWizardSheet::OrderServiceFunc( const Service * a, const Service * b )
+{
+ Queue * q1, * q2;
+
+ q1 = (a->queues.size() > 0) ? a->queues.front() : NULL;
+
+ q2 = (b->queues.size() > 0) ? b->queues.front() : NULL;
+
+ if ( !q1 && !q2 )
+ {
+ return true;
+ }
+ else if ( q1 && !q2 )
+ {
+ return true;
+ }
+ else if ( !q1 && q2 )
+ {
+ return false;
+ }
+ else if ( q1->priority < q2->priority )
+ {
+ return true;
+ }
+ else if ( q1->priority > q2->priority )
+ {
+ return false;
+ }
+ else if ( ( a->type == kPDLServiceType ) || ( ( a->type == kLPRServiceType ) && ( b->type == kIPPServiceType ) ) )
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+
+bool
+CPrinterSetupWizardSheet::OrderQueueFunc( const Queue * q1, const Queue * q2 )
+{
+ return ( q1->priority <= q2->priority ) ? true : false;
+}
+
+
+