#include <CoreFoundation/CoreFoundation.h>
#include <ApplicationServices/ApplicationServices.h>
#include <IOKit/usb/IOUSBLib.h>
#include <IOKit/IOCFPlugIn.h>
#include <mach/mach.h>
#include <mach/mach_error.h>
#include <mach/mach_time.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <pthread.h> // Used for writegReadMutex
#ifndef kPMPrinterURI
#define kPMPrinterURI CFSTR("Printer URI")
#endif
#define USB_INTERFACE_KIND CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID190)
#define kUSBLanguageEnglish 0x409
#define kUSBPrintingSubclass 1
#define kUSBPrintingProtocolNoOpen 0
#define kUSBPrintingProtocolUnidirectional 1
#define kUSBPrintingProtocolBidirectional 2
#define kUSBPrintClassGetDeviceID 0
#define kUSBPrintClassGetCentronicsStatus 1
#define kUSBPrintClassSoftReset 2
#define kUSBPrinterClassTypeID (CFUUIDGetConstantUUIDWithBytes(NULL, 0x06, 0x04, 0x7D, 0x16, 0x53, 0xA2, 0x11, 0xD6, 0x92, 0x06, 0x00, 0x30, 0x65, 0x52, 0x45, 0x92))
#define kUSBPrinterClassInterfaceID (CFUUIDGetConstantUUIDWithBytes(NULL, 0x03, 0x34, 0x6D, 0x74, 0x53, 0xA3, 0x11, 0xD6, 0x9E, 0xA1, 0x76, 0x30, 0x65, 0x52, 0x45, 0x92))
#define kUSBGenericPrinterClassDriver CFSTR( "/System/Library/Printers/Libraries/USBGenericPrintingClass.plugin" )
#define kUSBGenericTOPrinterClassDriver CFSTR( "/System/Library/Printers/Libraries/USBGenericTOPrintingClass.plugin" )
#define kUSBClassDriverProperty CFSTR( "USB Printing Class" )
#define kUSBPrinterClassDeviceNotOpen -9664
typedef union {
char b;
struct {
unsigned reserved0:2;
unsigned paperError:1;
unsigned select:1;
unsigned notError:1;
unsigned reserved1:3;
} status;
} CentronicsStatusByte;
typedef struct
{
CFStringRef manufacturer; CFStringRef product; CFStringRef compatible; CFStringRef serial; CFStringRef command; CFStringRef ppdURL; } USBPrinterAddress;
typedef IOUSBInterfaceInterface190 **USBPrinterInterface;
typedef struct
{
UInt8 requestType;
UInt8 request;
UInt16 value;
UInt16 index;
UInt16 length;
void *buffer;
} USBIODeviceRequest;
typedef struct classDriverContext
{
IUNKNOWN_C_GUTS;
CFPlugInRef plugin; IUnknownVTbl **factory;
void *vendorReference; UInt32 location; UInt8 interfaceNumber;
UInt16 vendorID;
UInt16 productID;
USBPrinterInterface interface; UInt8 outpipe; UInt8 inpipe;
kern_return_t (*DeviceRequest)( struct classDriverContext **printer, USBIODeviceRequest *iorequest, UInt16 timeout );
kern_return_t (*GetString)( struct classDriverContext **printer, UInt8 whichString, UInt16 language, UInt16 timeout, CFStringRef *result );
kern_return_t (*SoftReset)( struct classDriverContext **printer, UInt16 timeout );
kern_return_t (*GetCentronicsStatus)( struct classDriverContext **printer, CentronicsStatusByte *result, UInt16 timeout );
kern_return_t (*GetDeviceID)( struct classDriverContext **printer, CFStringRef *devid, UInt16 timeout );
kern_return_t (*ReadPipe)( struct classDriverContext **printer, UInt8 *buffer, UInt32 *count );
kern_return_t (*WritePipe)( struct classDriverContext **printer, UInt8 *buffer, UInt32 *count, Boolean eoj );
kern_return_t (*Open)( struct classDriverContext **printer, UInt32 location, UInt8 protocol );
kern_return_t (*Abort)( struct classDriverContext **printer );
kern_return_t (*Close)( struct classDriverContext **printer );
kern_return_t (*Initialize)( struct classDriverContext **printer, struct classDriverContext **baseclass );
kern_return_t (*Terminate)( struct classDriverContext **printer );
} USBPrinterClassContext;
typedef struct usbPrinterClassType
{
USBPrinterClassContext *classdriver;
CFUUIDRef factoryID;
UInt32 refCount;
} USBPrinterClassType;
#if DEBUG==2
#define DEBUG_ERR(c, x) showint(x, c)
#define DEBUG_DUMP( text, buf, len ) dump( text, buf, len )
#define DEBUG_CFString( text, a ) showcfstring( text, a )
#define DEBUG_CFCompareString( text, a, b ) cmpcfs( text, a, b )
#elif DEBUG==1
#define DEBUG_ERR(c, x) if (c) fprintf(stderr, x, c)
#define DEBUG_DUMP( text, buf, len )
#define DEBUG_CFString( text, a )
#define DEBUG_CFCompareString( text, a, b )
#else
#define DEBUG_ERR(c, x)
#define DEBUG_DUMP( text, buf, len )
#define DEBUG_CFString( text, a )
#define DEBUG_CFCompareString( text, a, b )
#endif
typedef struct
{
Boolean tbcpQuoteReads; Boolean escapeNextRead; UInt8 *tbcpReadData; UInt32 readLength; int match_endoffset, match_startoffset; UInt8 *tbcpWriteData; UInt32 tbcpBufferLength, tbcpBufferRemaining;
Boolean sendStatusNextWrite;
} PostScriptData;
typedef struct
{
CFPlugInRef plugin; USBPrinterClassContext **classdriver; CFStringRef bundle; UInt32 location; USBPrinterAddress address; CFURLRef reference; } USBPrinterInfo;
USBPrinterInfo *UsbCopyPrinter( USBPrinterInfo *aPrinter );
CFMutableArrayRef UsbGetAllPrinters( void );
void UsbReleasePrinter( USBPrinterInfo *aPrinter );
void UsbReleaseAllPrinters( CFMutableArrayRef printers );
kern_return_t UsbRegistryOpen( USBPrinterAddress *usbAddress, USBPrinterInfo **result );
kern_return_t UsbUnloadClassDriver( USBPrinterInfo *printer );
kern_return_t UsbLoadClassDriver( USBPrinterInfo *printer, CFUUIDRef interfaceID, CFStringRef classDriverBundle );
CFStringRef UsbMakeFullUriAddress( USBPrinterInfo *aPrinter );
int UsbSamePrinter( const USBPrinterAddress *lastTime, const USBPrinterAddress *thisTime );
OSStatus UsbGetPrinterAddress( USBPrinterInfo *thePrinter, USBPrinterAddress *address, UInt16 timeout );
static void setupCFLanguage(void);
#define kDeviceIDKeyCommand CFSTR("COMMAND SET:")
#define kDeviceIDKeyCommandAbbrev CFSTR( "CMD:" )
#define kDeviceIDKeyManufacturer CFSTR("MANUFACTURER:")
#define kDeviceIDKeyManufacturerAbbrev CFSTR( "MFG:" )
#define kDeviceIDKeyModel CFSTR("MODEL:")
#define kDeviceIDKeyModelAbbrev CFSTR( "MDL:" )
#define kDeviceIDKeySerial CFSTR("SN:")
#define kDeviceIDKeySerialAbbrev CFSTR("SERN:")
#define kDeviceIDKeyCompatible CFSTR("COMPATIBLITY ID:")
#define kDeviceIDKeyCompatibleAbbrev CFSTR("CID:")
#define kDeviceIDKeyValuePairDelimiter CFSTR(";")
static CFStringRef CreateEncodedCFString(CFStringRef string);
static CFRange DelimitSubstring( CFStringRef stringToSearch, CFStringRef delim, CFRange bounds, CFStringCompareFlags options );
static void parseOptions(const char *options, char *serial, UInt32 *location);
CFStringRef
DeviceIDCreateValueList( const CFStringRef deviceID,
const CFStringRef abbrevKey,
const CFStringRef key );
static int addPercentEscapes(const unsigned char* src, char* dst, int dstMax);
static int removePercentEscapes(const char* src, unsigned char* dst, int dstMax);
#if defined(TCP_NODELAY)
#undef TCP_NODELAY
#endif
#if defined(TCP_MAXSEG)
#undef TCP_MAXSEG
#endif
#include <cups/cups.h>
#define PRINTER_POLLING_INTERVAL 5
#define INITIAL_LOG_INTERVAL (PRINTER_POLLING_INTERVAL)
#define SUBSEQUENT_LOG_INTERVAL (3*INITIAL_LOG_INTERVAL)
#define WAITEOF_DELAY 7
#define USB_MAX_STR_SIZE 1024
static volatile int done = 0;
static int gWaitEOF = false;
static pthread_cond_t *gReadCompleteConditionPtr = NULL;
static pthread_mutex_t *gReadMutexPtr = NULL;
#if DEBUG==2
static char
hexdigit( char c )
{
return ( c < 0 || c > 15 )? '?': (c < 10)? c + '0': c - 10 + 'A';
}
static char
asciidigit( char c )
{
return (c< 20 || c > 0x7E)? '.': c;
}
void
dump( char *text, void *s, int len )
{
int i;
char *p = (char *) s;
char m[1+2*16+1+16+1];
fprintf(stderr, "%s pointer %x len %d\n", text, (unsigned int) p, len );
for ( ; len > 0; len -= 16 )
{
char *q = p;
char *out = m;
*out++ = '\t';
for ( i = 0; i < 16 && i < len; ++i, ++p )
{
*out++ = hexdigit( (*p >> 4) & 0x0F );
*out++ = hexdigit( *p & 0x0F );
}
for ( ;i < 16; ++i )
{
*out++ = ' ';
*out++ = ' ';
}
*out++ = '\t';
for ( i = 0; i < 16 && i < len; ++i, ++q )
*out++ = asciidigit( *q );
*out = 0;
m[ strlen( m ) ] = '\0';
fprintf(stderr, "%s\n", m );
}
}
void
printcfs( char *text, CFStringRef s )
{
char dest[1024];
if ( s != NULL )
{
if ( CFStringGetCString(s, dest, sizeof(dest), kCFStringEncodingUTF8) )
sprintf( dest, "%s <%s>\n", text, dest );
else
sprintf( dest, "%s [Unknown string]\n", text );
} else {
sprintf( dest, "%s [NULL]\n", text );
}
perror( dest );
}
void
cmpcfs( char *text, CFStringRef a, CFStringRef b )
{
CFRange found = {0, 0};
printcfs( text, a );
printcfs( " ", b );
if (a != NULL && b != NULL) {
found = CFStringFind( a, b, kCFCompareCaseInsensitive );
} else if (a == NULL && b == NULL) {
found.length = 1; found.location = 0;
} else {
found.length = 0; }
if ( found.length > 0 )
fprintf(stderr, "matched @%d:%d\n", (int) found.location, (int) found.length);
else
fprintf(stderr, "not matched\n" );
}
#endif //DEBUG==2
#ifdef PARSE_PS_ERRORS
static const char *nextLine (const char *buffer);
static void parsePSError (char *sockBuffer, int len);
static const char *nextLine (const char *buffer)
{
const char *cptr, *lptr = NULL;
for (cptr = buffer; *cptr && lptr == NULL; cptr++)
if (*cptr == '\n' || *cptr == '\r')
lptr = cptr;
return lptr;
}
static void parsePSError (char *sockBuffer, int len)
{
static char gErrorBuffer[1024] = "";
static char *gErrorBufferPtr = gErrorBuffer;
static char *gErrorBufferEndPtr = gErrorBuffer + sizeof(gErrorBuffer);
char *pCommentBegin, *pCommentEnd, *pLineEnd;
char *logLevel;
char logstr[1024];
int logstrlen;
if (gErrorBufferPtr + len > gErrorBufferEndPtr - 1)
gErrorBufferPtr = gErrorBuffer;
if (len > sizeof(gErrorBuffer) - 1)
len = sizeof(gErrorBuffer) - 1;
memcpy(gErrorBufferPtr, (const void *)sockBuffer, len);
gErrorBufferPtr += len;
*(gErrorBufferPtr + 1) = '\0';
pLineEnd = (char *)nextLine((const char *)gErrorBuffer);
while (pLineEnd != NULL)
{
*pLineEnd++ = '\0';
pCommentBegin = strstr(gErrorBuffer,"%%[");
pCommentEnd = strstr(gErrorBuffer, "]%%");
if (pCommentBegin != gErrorBuffer && pCommentEnd != NULL)
{
pCommentEnd += 3;
*pCommentEnd = '\0';
if (strncasecmp(pCommentBegin, "%%[ Error:", 10) == 0)
logLevel = "DEBUG";
else if (strncasecmp(pCommentBegin, "%%[ Flushing", 12) == 0)
logLevel = "DEBUG";
else
logLevel = "INFO";
if ((logstrlen = snprintf(logstr, sizeof(logstr), "%s: %s\n", logLevel, pCommentBegin)) >= sizeof(logstr))
{
logstrlen = sizeof(logstr) - 1;
logstr[logstrlen - 1] = '\n';
}
write(STDERR_FILENO, logstr, logstrlen);
}
strcpy(gErrorBuffer, pLineEnd);
gErrorBufferPtr = gErrorBuffer;
pLineEnd = (char *)nextLine((const char *)gErrorBuffer);
}
}
#endif // PARSE_PS_ERRORS
void *
readthread( void *reference )
{
UInt8 readbuffer[512];
UInt32 rbytes;
kern_return_t readstatus;
USBPrinterClassContext **classdriver = (USBPrinterClassContext **) reference;
struct mach_timebase_info timeBaseInfo;
uint64_t start,
delay;
mach_timebase_info(&timeBaseInfo);
delay = ((uint64_t)250000000 * (uint64_t)timeBaseInfo.denom) / (uint64_t)timeBaseInfo.numer;
do
{
start = mach_absolute_time();
rbytes = sizeof(readbuffer) - 1;
readstatus = (*classdriver)->ReadPipe( classdriver, readbuffer, &rbytes );
if ( kIOReturnSuccess == readstatus && rbytes > 0 )
{
write( STDOUT_FILENO, readbuffer, rbytes );
if (gWaitEOF && readbuffer[rbytes-1] == 0x4)
break;
#ifdef PARSE_PS_ERRORS
parsePSError(readbuffer, rbytes);
#endif
}
if ((readstatus != kIOReturnSuccess || rbytes == 0) && (gWaitEOF || !done))
mach_wait_until(start + delay);
} while ( gWaitEOF || !done );
pthread_mutex_lock(gReadMutexPtr);
pthread_cond_signal(gReadCompleteConditionPtr);
pthread_mutex_unlock(gReadMutexPtr);
return NULL;
}
int print_device(const char *uri, const char *hostname, const char *resource, const char *options, int fd, int copies)
{
UInt32 wbytes,
buffersize = 2048;
ssize_t nbytes;
off_t tbytes;
char *buffer,
*bufptr;
pthread_cond_t readCompleteCondition;
pthread_mutex_t readMutex;
pthread_t thr;
int thread_created = 0;
USBPrinterInfo *targetPrinter = NULL;
USBPrinterInfo *potentialPrinter = NULL;
CFMutableArrayRef usbPrinters;
char manufacturer_buf[USB_MAX_STR_SIZE],
product_buf[USB_MAX_STR_SIZE],
serial_buf[USB_MAX_STR_SIZE];
CFStringRef manufacturer;
CFStringRef product;
CFStringRef serial;
UInt32 location = 0;
OSStatus status = noErr;
#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
struct sigaction action;
#endif
setupCFLanguage();
fprintf(stderr, "INFO: Opening connection\n");
parseOptions(options, serial_buf, &location);
if (resource[0] == '/')
resource++;
removePercentEscapes(hostname, manufacturer_buf, sizeof(manufacturer_buf));
removePercentEscapes(resource, product_buf, sizeof(product_buf));
manufacturer = CFStringCreateWithCString(NULL, manufacturer_buf, kCFStringEncodingUTF8);
product = CFStringCreateWithCString(NULL, product_buf, kCFStringEncodingUTF8);
serial = CFStringCreateWithCString(NULL, serial_buf, kCFStringEncodingUTF8);
USBPrinterInfo *activePrinter = NULL;
USBPrinterClassContext **classdriver;
int countdown = INITIAL_LOG_INTERVAL;
do
{
usbPrinters = UsbGetAllPrinters();
if ( NULL != usbPrinters && (manufacturer || product ) )
{
int i,
numPrinters = CFArrayGetCount(usbPrinters);
for ( i = 0; i < numPrinters; ++i )
{
int match = FALSE;
USBPrinterInfo *printer = (USBPrinterInfo *) CFArrayGetValueAtIndex( usbPrinters, i );
if ( printer )
{
match = printer->address.manufacturer && manufacturer? CFEqual(printer->address.manufacturer, manufacturer ): FALSE;
if ( match )
{
match = printer->address.product && product? CFEqual(printer->address.product, product ): FALSE;
}
if ( match )
{
if (serial != NULL && CFStringGetLength(serial))
{
match = printer->address.serial? CFEqual(printer->address.serial, serial ): FALSE;
}
else if (location != 0 && printer->location != 0)
{
match = (printer->location == location);
if (!match && potentialPrinter == NULL)
potentialPrinter = UsbCopyPrinter( printer );
}
}
if ( match )
{
targetPrinter = UsbCopyPrinter( printer );
break; }
}
}
if (potentialPrinter != nil)
{
if (targetPrinter == nil)
targetPrinter = potentialPrinter;
else
UsbReleasePrinter(potentialPrinter);
potentialPrinter = nil;
}
}
UsbReleaseAllPrinters( usbPrinters );
if ( NULL != targetPrinter )
status = UsbRegistryOpen( &targetPrinter->address, &activePrinter );
if ( NULL == activePrinter )
{
sleep( PRINTER_POLLING_INTERVAL );
countdown -= PRINTER_POLLING_INTERVAL;
if ( !countdown )
{
if (NULL == targetPrinter)
fprintf(stderr, "WARNING: Printer not responding\n" );
else
fprintf(stderr, "INFO: Printer busy\n" );
countdown = SUBSEQUENT_LOG_INTERVAL; }
}
} while ( NULL == activePrinter );
classdriver = activePrinter->classdriver;
if ( NULL == classdriver )
{
perror("ERROR: Unable to open USB Printing Class port");
return (status);
}
if (!fd)
{
#ifdef HAVE_SIGSET
sigset(SIGTERM, SIG_IGN);
#elif defined(HAVE_SIGACTION)
memset(&action, 0, sizeof(action));
sigemptyset(&action.sa_mask);
action.sa_handler = SIG_IGN;
sigaction(SIGTERM, &action, NULL);
#else
signal(SIGTERM, SIG_IGN);
#endif
}
buffer = malloc( buffersize );
if ( !buffer ) {
fprintf(stderr, "ERROR: Couldn't allocate internal buffer\n" );
status = -1;
}
else
{
if (pthread_cond_init(&readCompleteCondition, NULL) == 0)
{
gReadCompleteConditionPtr = &readCompleteCondition;
if (pthread_mutex_init(&readMutex, NULL) == 0)
{
gReadMutexPtr = &readMutex;
if (pthread_create(&thr, NULL, readthread, classdriver ) > 0)
fprintf(stderr, "WARNING: Couldn't create read channel\n");
else
thread_created = 1;
}
}
}
fprintf(stderr, "INFO: Sending data\n");
while (noErr == status && copies > 0)
{
copies --;
if (STDIN_FILENO != fd)
{
fputs("PAGE: 1 1", stderr);
lseek( fd, 0, SEEK_SET ); }
tbytes = 0;
while (noErr == status && (nbytes = read(fd, buffer, buffersize)) > 0)
{
tbytes += nbytes;
bufptr = buffer;
while (nbytes > 0 && noErr == status )
{
wbytes = nbytes;
status = (*classdriver)->WritePipe( classdriver, (UInt8*)bufptr, &wbytes, 0 );
if (wbytes < 0 || noErr != status)
{
OSStatus err;
err = (*classdriver)->Abort( classdriver );
fprintf(stderr, "ERROR: %ld: Unable to send print file to printer (canceled %ld)\n", status, err );
break;
}
nbytes -= wbytes;
bufptr += wbytes;
}
if (fd != 0 && noErr == status)
fprintf(stderr, "DEBUG: Sending print file, %qd bytes...\n", (off_t)tbytes);
}
}
done = 1;
if ( thread_created )
{
struct timespec sleepUntil = { time(NULL) + WAITEOF_DELAY, 0 };
pthread_mutex_lock(&readMutex);
if (pthread_cond_timedwait(&readCompleteCondition, &readMutex, (const struct timespec *)&sleepUntil) != 0)
gWaitEOF = false;
pthread_mutex_unlock(&readMutex);
pthread_join( thr,NULL);
}
(*classdriver)->Close( classdriver ); UsbUnloadClassDriver( activePrinter );
free( buffer );
if (STDIN_FILENO != fd)
close(fd);
if (gReadCompleteConditionPtr != NULL)
pthread_cond_destroy(gReadCompleteConditionPtr);
if (gReadMutexPtr != NULL)
pthread_mutex_destroy(gReadMutexPtr);
return status == kIOReturnSuccess? 0: status;
}
static Boolean
encodecfstr( CFStringRef cfsrc, char *dst, long len )
{
return CFStringGetCString(cfsrc? cfsrc:CFSTR("Unknown"), dst, len, kCFStringEncodingUTF8 );
}
void list_devices(void)
{
char encodedManufacturer[1024];
char encodedProduct[1024];
char uri[1024];
CFMutableArrayRef usbBusPrinters = UsbGetAllPrinters();
CFIndex i, numPrinters = NULL != usbBusPrinters? CFArrayGetCount( usbBusPrinters ): 0;
puts("direct usb \"Unknown\" \"USB Printer (usb)\"");
for ( i = 0; i < numPrinters; ++i )
{
USBPrinterInfo *printer = (USBPrinterInfo *) CFArrayGetValueAtIndex( usbBusPrinters, i );
if ( printer )
{
CFStringRef addressRef = UsbMakeFullUriAddress( printer );
if ( addressRef )
{
if ( CFStringGetCString(addressRef, uri, sizeof(uri), kCFStringEncodingUTF8) ) {
encodecfstr( printer->address.manufacturer, encodedManufacturer, sizeof(encodedManufacturer) );
encodecfstr( printer->address.product, encodedProduct, sizeof(encodedProduct) );
printf("direct %s \"%s %s\" \"%s\"\n", uri, encodedManufacturer, encodedProduct, encodedProduct);
}
}
}
}
UsbReleaseAllPrinters( usbBusPrinters );
fflush(NULL);
}
static void parseOptions(const char *options, char *serial, UInt32 *location)
{
char *serialnumber;
char optionName[255],
value[255],
*ptr;
if (serial)
*serial = '\0';
if (location)
*location = 0;
if (!options)
return;
serialnumber = NULL;
while (*options != '\0')
{
for (ptr = optionName; *options && *options != '=' && *options != '+'; )
*ptr++ = *options++;
*ptr = '\0';
value[0] = '\0';
if (*options == '=')
{
options ++;
for (ptr = value; *options && *options != '+';)
*ptr++ = *options++;
*ptr = '\0';
if (*options == '+')
options ++;
}
else if (*options == '+')
{
options ++;
}
if (strcasecmp(optionName, "waiteof") == 0)
{
if (strcasecmp(value, "on") == 0 ||
strcasecmp(value, "yes") == 0 ||
strcasecmp(value, "true") == 0)
{
gWaitEOF = true;
}
else if (strcasecmp(value, "off") == 0 ||
strcasecmp(value, "no") == 0 ||
strcasecmp(value, "false") == 0)
{
gWaitEOF = false;
}
else
{
fprintf(stderr, "WARNING: Boolean expected for waiteof option \"%s\"\n", value);
}
}
else if (strcasecmp(optionName, "serial") == 0)
{
strcpy(serial, value);
serialnumber = serial;
}
else if (strcasecmp(optionName, "location") == 0 && location)
{
*location = strtol(value, NULL, 16);
}
}
return;
}
static int addPercentEscapes(const unsigned char* src, char* dst, int dstMax)
{
unsigned char c;
char *dstEnd = dst + dstMax - 1;
while (*src)
{
c = *src++;
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') || (c == '.' || c == '-' || c == '*' || c == '_'))
{
if (dst >= dstEnd)
return -1;
*dst++ = c;
}
else
{
if (dst >= dstEnd - 2)
return -1;
snprintf(dst, dstEnd - dst, "%%%02x", c);
dst += 3;
}
}
*dst = '\0';
return 0;
}
static void setupCFLanguage(void)
{
CFStringRef lang[1] = {NULL};
CFArrayRef langArray = NULL;
const char *requestedLang = NULL;
requestedLang = getenv("LANG");
if (requestedLang != NULL) {
lang[0] = CFStringCreateWithCString(kCFAllocatorDefault, requestedLang, kCFStringEncodingUTF8);
langArray = CFArrayCreate(kCFAllocatorDefault, (const void **)lang, sizeof(lang) / sizeof(lang[0]), &kCFTypeArrayCallBacks);
CFPreferencesSetAppValue(CFSTR("AppleLanguages"), langArray, kCFPreferencesCurrentApplication);
fprintf(stderr, "DEBUG: usb: AppleLanguages = \"%s\"\n", requestedLang);
CFRelease(lang[0]);
CFRelease(langArray);
} else {
fprintf(stderr, "DEBUG: usb: LANG environment variable missing.\n");
}
}
static int removePercentEscapes(const char* src, unsigned char* dst, int dstMax)
{
int c;
const unsigned char *dstEnd = dst + dstMax;
while (*src && dst < dstEnd)
{
c = *src++;
if (c == '%')
{
sscanf(src, "%02x", &c);
src += 2;
}
*dst++ = (char)c;
}
if (dst >= dstEnd)
return -1;
*dst = '\0';
return 0;
}
static CFRange
DelimitSubstring( CFStringRef stringToSearch, CFStringRef delim, CFRange bounds, CFStringCompareFlags options )
{
CFRange where_delim, value;
while ( bounds.length > 0 && CFStringFindWithOptions( stringToSearch, CFSTR(" "), bounds, kCFCompareAnchored, &where_delim ) )
{
++bounds.location; --bounds.length;
}
value = bounds; if ( bounds.length > 0 && CFStringFindWithOptions( stringToSearch, delim, bounds, options, &where_delim ) )
{
value.length = where_delim.location - bounds.location ;
}
DEBUG_CFString( "\tFind target", stringToSearch );
DEBUG_CFString( "\tFind pattern", delim );
DEBUG_ERR( (int) value.location, "\t\tFound %d\n" );
DEBUG_ERR( (int) value.length, " length %d" );
return value;
}
CFStringRef
DeviceIDCreateValueList( const CFStringRef deviceID, const CFStringRef abbrevKey, const CFStringRef key )
{
CFRange found = CFRangeMake( -1,0); CFStringRef valueList = NULL;
DEBUG_CFString( "---------DeviceIDCreateValueList DeviceID:", deviceID );
DEBUG_CFString( "---------DeviceIDCreateValueList key:", key );
DEBUG_CFString( "---------DeviceIDCreateValueList abbrevkey:", abbrevKey );
if ( NULL != deviceID && NULL != abbrevKey )
found = CFStringFind( deviceID, abbrevKey, kCFCompareCaseInsensitive );
if ( NULL != deviceID && NULL != key && found.length <= 0 )
found = CFStringFind( deviceID, key, kCFCompareCaseInsensitive );
if ( found.length > 0 )
{
CFRange search = CFRangeMake( found.location + found.length,
CFStringGetLength( deviceID ) - (found.location + found.length) );
valueList = CFStringCreateWithSubstring ( kCFAllocatorDefault, deviceID,
DelimitSubstring( deviceID, kDeviceIDKeyValuePairDelimiter, search, kCFCompareCaseInsensitive ) );
DEBUG_CFString( "---------DeviceIDCreateValueList:", valueList );
}
return valueList;
}
static int
CompareSameString( const CFStringRef a, const CFStringRef b )
{
if ( NULL == a && NULL == b )
return 0;
else if ( NULL != a && NULL != b )
return CFStringCompare( a, b, kCFCompareAnchored );
else
return 1; }
kern_return_t
UsbLoadClassDriver( USBPrinterInfo *printer, CFUUIDRef interfaceID, CFStringRef classDriverBundle )
{
kern_return_t kr = kUSBPrinterClassDeviceNotOpen;
if ( NULL != classDriverBundle )
printer->bundle = classDriverBundle; else
classDriverBundle = kUSBGenericTOPrinterClassDriver;
DEBUG_CFString( "UsbLoadClassDriver classDriverBundle", classDriverBundle );
if ( NULL != classDriverBundle )
{
USBPrinterClassContext **classdriver = NULL;
CFURLRef classDriverURL = CFURLCreateWithFileSystemPath( NULL, classDriverBundle, kCFURLPOSIXPathStyle, TRUE );
CFPlugInRef plugin = NULL == classDriverURL? NULL: CFPlugInCreate( NULL, classDriverURL );
if ( NULL != plugin)
{
CFArrayRef factories = CFPlugInFindFactoriesForPlugInTypeInPlugIn( kUSBPrinterClassTypeID, plugin );
DEBUG_ERR( 0, "UsbLoadClassDriver plugin %x\n" );
if (NULL != factories && CFArrayGetCount(factories) > 0)
{
CFUUIDRef factoryID = CFArrayGetValueAtIndex( factories, 0 );
IUnknownVTbl **iunknown = CFPlugInInstanceCreate( NULL, factoryID, kUSBPrinterClassTypeID );
DEBUG_ERR( 0, "UsbLoadClassDriver factories %x\n" );
if (NULL != iunknown)
{
DEBUG_ERR( 0, "UsbLoadClassDriver CFPlugInInstanceCreate %x\n" );
kr = (*iunknown)->QueryInterface( iunknown, CFUUIDGetUUIDBytes(interfaceID), (LPVOID *) &classdriver );
(*iunknown)->Release( iunknown );
if ( S_OK == kr && NULL != classdriver )
{
DEBUG_ERR( kr, "UsbLoadClassDriver QueryInterface %x\n" );
printer->plugin = plugin;
kr = (*classdriver)->Initialize( classdriver, printer->classdriver );
kr = kIOReturnSuccess;
printer->classdriver = classdriver;
}
else
{
DEBUG_ERR( kr, "UsbLoadClassDriver QueryInterface FAILED %x\n" );
}
}
else
{
DEBUG_ERR( kr, "UsbLoadClassDriver CFPlugInInstanceCreate FAILED %x\n" );
}
}
else
{
DEBUG_ERR( kr, "UsbLoadClassDriver factories FAILED %x\n" );
}
}
else
{
DEBUG_ERR( kr, "UsbLoadClassDriver plugin FAILED %x\n" );
}
if ( kr != kIOReturnSuccess || NULL == plugin || NULL == classdriver )
{
UsbUnloadClassDriver( printer );
}
}
return kr;
}
kern_return_t
UsbUnloadClassDriver( USBPrinterInfo *printer )
{
DEBUG_ERR( kIOReturnSuccess, "UsbUnloadClassDriver %x\n" );
if ( NULL != printer->classdriver )
(*printer->classdriver)->Release( printer->classdriver );
printer->classdriver = NULL;
if ( NULL != printer->plugin )
CFRelease( printer->plugin );
printer->plugin = NULL;
return kIOReturnSuccess;
}
void
UsbAddressDispose( USBPrinterAddress *address )
{
if ( address->product != NULL ) CFRelease( address->product );
if ( address->manufacturer != NULL ) CFRelease( address->manufacturer );
if ( address->serial != NULL ) CFRelease( address->serial );
if ( address->command != NULL ) CFRelease( address->command );
address->product =
address->manufacturer =
address->serial =
address->command = NULL;
}
OSStatus
UsbGetPrinterAddress( USBPrinterInfo *thePrinter, USBPrinterAddress *address, UInt16 timeout )
{
OSStatus err;
CFStringRef deviceId = NULL;
USBPrinterClassContext **printer = NULL == thePrinter? NULL: thePrinter->classdriver;
address->manufacturer =
address->product =
address->compatible =
address->serial =
address->command = NULL;
DEBUG_DUMP( "UsbGetPrinterAddress thePrinter", thePrinter, sizeof(USBPrinterInfo) );
err = (*printer)->GetDeviceID( printer, &deviceId, timeout );
if ( noErr == err && NULL != deviceId )
{
address->command = DeviceIDCreateValueList( deviceId, kDeviceIDKeyCommandAbbrev, kDeviceIDKeyCommand );
address->product = DeviceIDCreateValueList( deviceId, kDeviceIDKeyModelAbbrev, kDeviceIDKeyModel );
address->compatible = DeviceIDCreateValueList( deviceId, kDeviceIDKeyCompatibleAbbrev, kDeviceIDKeyCompatible );
address->manufacturer = DeviceIDCreateValueList( deviceId, kDeviceIDKeyManufacturerAbbrev, kDeviceIDKeyManufacturer );
address->serial = DeviceIDCreateValueList( deviceId, kDeviceIDKeySerialAbbrev, kDeviceIDKeySerial );
CFRelease( deviceId );
}
DEBUG_CFString( "UsbGetPrinterAddress DeviceID address->product", address->product );
DEBUG_CFString( "UsbGetPrinterAddress DeviceID address->compatible", address->compatible );
DEBUG_CFString( "UsbGetPrinterAddress DeviceID address->manufacturer", address->manufacturer );
DEBUG_CFString( "UsbGetPrinterAddress DeviceID address->serial", address->serial );
if ( NULL == address->product || NULL == address->manufacturer || NULL == address->serial )
{
IOUSBDeviceDescriptor desc;
USBIODeviceRequest request;
request.requestType = USBmakebmRequestType( kUSBIn, kUSBStandard, kUSBDevice );
request.request = kUSBRqGetDescriptor;
request.value = (kUSBDeviceDesc << 8) | 0;
request.index = 0;
request.length = sizeof(desc);
request.buffer = &desc;
err = (*printer)->DeviceRequest( printer, &request, timeout );
DEBUG_ERR( (kern_return_t) err, "UsbGetPrinterAddress: GetDescriptor %x" );
if ( kIOReturnSuccess == err )
{
if ( NULL == address->product)
{
err = (*printer)->GetString( printer, desc.iProduct, kUSBLanguageEnglish, timeout, &address->product );
if ( kIOReturnSuccess != err || address->product == NULL) {
address->product = CFSTR("Unknown");
}
}
DEBUG_CFString( "UsbGetPrinterAddress: UsbGetString address->product\n", address->product );
if ( NULL == address->manufacturer )
{
err = (*printer)->GetString( printer, desc.iManufacturer, kUSBLanguageEnglish, timeout, &address->manufacturer );
if (kIOReturnSuccess != err || address->manufacturer == NULL) {
address->manufacturer = CFSTR("Unknown");
}
}
DEBUG_CFString( "UsbGetPrinterAddress: UsbGetString address->manufacturer\n", address->manufacturer );
if ( NULL == address->serial )
{
if ( 0 == desc.iSerialNumber )
{
address->serial = CFStringCreateWithFormat( NULL, NULL, CFSTR("%lx"), (*printer)->location );
}
else
{
err = (*printer)->GetString( printer, desc.iSerialNumber, kUSBLanguageEnglish, timeout, &address->serial );
if ( address->serial )
{
UniChar nulbyte = { 0 };
CFStringRef trim = CFStringCreateWithCharacters(NULL, &nulbyte, 1);
CFMutableStringRef newserial = CFStringCreateMutableCopy(NULL, 0, address->serial);
CFStringTrim( newserial, trim );
CFRelease(trim);
CFRelease( address->serial );
address->serial = newserial;
}
}
}
DEBUG_CFString( "UsbGetPrinterAddress: UsbGetString address->serial\n", address->serial );
}
}
if ( NULL != address->product)
CFRetain(address->product); if ( NULL != address->manufacturer )
CFRetain( address->manufacturer );
if ( NULL != address->serial )
CFRetain( address->serial );
return err;
}
int
UsbSamePrinter( const USBPrinterAddress *a, const USBPrinterAddress *b )
{
int result = 0;
DEBUG_CFCompareString( "UsbSamePrinter serial", a->serial, b->serial );
DEBUG_CFCompareString( "UsbSamePrinter product", a->product, b->product );
DEBUG_CFCompareString( "UsbSamePrinter manufacturer", a->manufacturer, b->manufacturer );
result = !CompareSameString( a->serial, b->serial );
if ( result ) result = !CompareSameString( a->product, b->product );
if ( result ) result = !CompareSameString( a->manufacturer, b->manufacturer );
return result;
}
CFStringRef
UsbMakeFullUriAddress( USBPrinterInfo *printer )
{
CFMutableStringRef printerUri = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, CFSTR("usb://") );
if ( NULL != printerUri )
{
CFStringRef serial = printer->address.serial;
CFStringAppend(printerUri, printer->address.manufacturer? CreateEncodedCFString( printer->address.manufacturer ): CFSTR("Unknown") );
CFStringAppend(printerUri, CFSTR("/") );
CFStringAppend(printerUri, printer->address.product? CreateEncodedCFString( printer->address.product ): CFSTR("Unknown") );
CFStringAppend(printerUri, serial == NULL? CFSTR("?location="): CFSTR("?serial=") );
if ( serial == NULL)
serial = CFStringCreateWithFormat( NULL, NULL, CFSTR("%lx"), printer->location );
CFStringAppend(printerUri, serial? CreateEncodedCFString( serial ): CFSTR("Unknown") );
}
return printerUri;
}
CFMutableArrayRef
UsbGetAllPrinters( void )
{
kern_return_t kr; mach_port_t master_device_port = 0;
io_service_t usbInterface = 0;
io_iterator_t iter = 0;
CFMutableArrayRef printers = CFArrayCreateMutable( NULL, 0, NULL );
do
{
kr = IOMasterPort( bootstrap_port, &master_device_port );
DEBUG_ERR( kr, "UsbGetAllPrinters IOMasterPort %x\n" );
if(kIOReturnSuccess != kr) break;
{
CFDictionaryRef usbMatch = NULL;
usbMatch = IOServiceMatching(kIOUSBInterfaceClassName);
if ( !usbMatch ) break;
DEBUG_ERR( kr, "UsbGetAllPrinters IOServiceMatching %x\n" );
kr = IOServiceGetMatchingServices(master_device_port, usbMatch, &iter);
usbMatch = NULL;
DEBUG_ERR( kr, "UsbGetAllPrinters IOServiceGetMatchingServices %x\n" );
if(kIOReturnSuccess != kr || iter == 0) break;
}
while ( 0 != (usbInterface = IOIteratorNext(iter)) )
{
IOCFPlugInInterface **iodev;
USBPrinterInterface intf;
HRESULT res;
SInt32 score;
CFMutableDictionaryRef properties;
CFStringRef classDriver = NULL;
kr = IORegistryEntryCreateCFProperties( usbInterface, &properties, kCFAllocatorDefault, kNilOptions);
if ( kIOReturnSuccess == kr && NULL != properties)
{
classDriver = (CFStringRef) CFDictionaryGetValue( properties, kUSBClassDriverProperty );
if ( NULL != classDriver )
CFRetain( classDriver );
CFRelease( properties );
}
kr = IOCreatePlugInInterfaceForService( usbInterface,
kIOUSBInterfaceUserClientTypeID,
kIOCFPlugInInterfaceID,
&iodev,
&score);
DEBUG_ERR( kr, "UsbGetAllPrinters IOCreatePlugInInterfaceForService %x\n" );
if ( kIOReturnSuccess == kr )
{
UInt8 intfClass = 0;
UInt8 intfSubClass = 0;
res = (*iodev)->QueryInterface( iodev, USB_INTERFACE_KIND, (LPVOID *) &intf);
DEBUG_ERR( (kern_return_t) res, "UsbGetAllPrinters QueryInterface %x\n" );
(*iodev)->Release(iodev);
if ( noErr != res ) break;
kr = (*intf)->GetInterfaceClass(intf, &intfClass);
DEBUG_ERR(kr, "UsbGetAllPrinters GetInterfaceClass %x\n");
if ( kIOReturnSuccess == kr )
kr = (*intf)->GetInterfaceSubClass(intf, &intfSubClass);
DEBUG_ERR(kr, "UsbGetAllPrinters GetInterfaceSubClass %x\n");
if ( kIOReturnSuccess == kr &&
kUSBPrintingClass == intfClass &&
kUSBPrintingSubclass == intfSubClass )
{
USBPrinterInfo printer,
*printerInfo;
memset( &printer, 0, sizeof(USBPrinterInfo) );
kr = (*intf)->GetLocationID(intf, &printer.location);
DEBUG_ERR(kr, "UsbGetAllPrinters GetLocationID %x\n");
if ( kIOReturnSuccess == kr )
{
kr = UsbLoadClassDriver( &printer, kUSBPrinterClassInterfaceID, classDriver );
DEBUG_ERR(kr, "UsbGetAllPrinters UsbLoadClassDriver %x\n");
if ( kIOReturnSuccess == kr && printer.classdriver )
{
(*(printer.classdriver))->interface = intf;
kr = UsbGetPrinterAddress( &printer, &printer.address, 60000L );
{
kern_return_t unload_err = UsbUnloadClassDriver( &printer );
if ( kIOReturnSuccess == kr )
kr = unload_err;
}
}
}
printerInfo = UsbCopyPrinter( &printer );
if ( NULL != printerInfo )
CFArrayAppendValue( printers, (const void *) printerInfo );
} kr = (*intf)->Release(intf);
}
IOObjectRelease(usbInterface);
usbInterface = 0;
} } while ( 0 );
if (iter)
{
IOObjectRelease(iter);
iter = 0;
}
if (master_device_port)
{
mach_port_deallocate(mach_task_self(), master_device_port);
master_device_port = 0;
}
return printers;
}
void
UsbReleasePrinter( USBPrinterInfo *printer )
{
if ( printer )
{
UsbUnloadClassDriver( printer );
if ( NULL != printer->address.manufacturer )
CFRelease( printer->address.manufacturer );
if ( NULL != printer->address.product )
CFRelease( printer->address.product );
if ( NULL != printer->address.serial )
CFRelease( printer->address.serial );
if ( NULL != printer->address.command )
CFRelease( printer->address.command );
if ( NULL != printer->bundle )
CFRelease( printer->bundle );
free( printer );
}
}
void
UsbReleaseAllPrinters( CFMutableArrayRef printers )
{
if ( NULL != printers )
{
CFIndex i,
numPrinters = CFArrayGetCount(printers);
for ( i = 0; i < numPrinters; ++i )
UsbReleasePrinter( (USBPrinterInfo *) CFArrayGetValueAtIndex( printers, i ) );
CFRelease( printers );
}
}
USBPrinterInfo *
UsbCopyPrinter( USBPrinterInfo *aPrinter )
{
USBPrinterInfo *printerInfo = (USBPrinterInfo *) calloc( 1, sizeof(USBPrinterInfo));
if ( NULL != printerInfo && NULL != aPrinter )
{
printerInfo->location = aPrinter->location;
if ( NULL != (printerInfo->address.manufacturer = aPrinter->address.manufacturer) )
CFRetain( printerInfo->address.manufacturer );
if ( NULL != (printerInfo->address.product = aPrinter->address.product) )
CFRetain( printerInfo->address.product );
if ( NULL != (printerInfo->address.serial = aPrinter->address.serial) )
CFRetain( printerInfo->address.serial );
if ( NULL != (printerInfo->address.command = aPrinter->address.command) )
CFRetain( printerInfo->address.command );
if ( NULL != (printerInfo->bundle = aPrinter->bundle) )
CFRetain( printerInfo->bundle );
}
return printerInfo;
}
kern_return_t
UsbRegistryOpen( USBPrinterAddress *usbAddress, USBPrinterInfo **result )
{
kern_return_t kr = -1; CFMutableArrayRef printers = UsbGetAllPrinters();
CFIndex numPrinters = NULL != printers? CFArrayGetCount( printers): 0;
CFIndex i;
*result = NULL; for ( i = 0; i < numPrinters; ++i )
{
USBPrinterInfo *thisPrinter = (USBPrinterInfo *) CFArrayGetValueAtIndex( printers, i );
if ( NULL != thisPrinter && UsbSamePrinter( usbAddress, &thisPrinter->address ) )
{
*result = UsbCopyPrinter( thisPrinter ); if ( NULL != *result )
{
USBPrinterClassContext **printer = NULL;
kr = UsbLoadClassDriver( *result, kUSBPrinterClassInterfaceID, NULL );
if ( kIOReturnSuccess == kr && (*result)->bundle )
kr = UsbLoadClassDriver( *result, kUSBPrinterClassInterfaceID, (*result)->bundle );
if ( kIOReturnSuccess == kr && NULL != (*result)->classdriver )
{
printer = (*result)->classdriver;
kr = (*printer)->Open( printer, (*result)->location, kUSBPrintingProtocolBidirectional );
if ( kIOReturnSuccess != kr || NULL == (*printer)->interface )
kr = (*printer)->Open( printer, (*result)->location, kUSBPrintingProtocolUnidirectional );
if ( kIOReturnSuccess == kr )
{
if ( NULL == (*printer)->interface )
{
(*printer)->Close( printer );
UsbReleasePrinter( *result );
*result = NULL;
}
}
}
}
break;
}
}
UsbReleaseAllPrinters( printers ); DEBUG_ERR( kr, "UsbRegistryOpen return %x\n" );
return kr;
}
static CFStringRef CreateEncodedCFString(CFStringRef string)
{
CFStringRef result = NULL;
char *bufferUTF8 = NULL;
char *bufferEncoded = NULL;
if (string != NULL)
{
CFIndex bufferSizeUTF8 = (3 * CFStringGetLength(string));
if ((bufferUTF8 = (char*)malloc(bufferSizeUTF8)) != NULL)
{
CFStringGetCString(string, bufferUTF8, bufferSizeUTF8, kCFStringEncodingUTF8);
{
UInt16 bufferSizeEncoded = (3 * strlen(bufferUTF8)) + 1;
if ((bufferEncoded = (char*)malloc(bufferSizeEncoded)) != NULL)
{
addPercentEscapes(bufferUTF8, bufferEncoded, bufferSizeEncoded);
result = CFStringCreateWithCString(kCFAllocatorDefault, bufferEncoded, kCFStringEncodingUTF8);
}
}
}
}
if (bufferUTF8) free(bufferUTF8);
if (bufferEncoded) free(bufferEncoded);
return result;
}