c2espcommon.c 14 KB
Newer Older
1 2 3 4 5
/* 
 *   Kodak ESP Cxxx (OPL?) Control Language filters for the  Common UNIX
 *   Printing System (CUPS).
 *  common functions for c2esp, c2espC filters
 *
6
 *  copyright Paul Newall May 2010 - Sept 2012. VERSION 4 (first used in c2esp26) 
7
 *
8 9 10 11 12 13 14 15 16 17
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
18 19
 */

20
#include "config.h"
21 22 23 24 25 26
#include <stdio.h>
#include <string.h>
#include <cups/sidechannel.h> //FlushBackChannel, and the side channel functions and constants
#include <fcntl.h> //files
#include <sys/stat.h> //for chmod
#include <time.h> //time functions used for debugging
27 28 29 30 31 32 33

#if HAVE_CUPSFILTERS == 1
#include <cupsfilters/driver.h> //has the dither functions
#else
#include <cups/driver.h> //has the dither functions
#endif

34 35 36 37 38
#include "c2espcommon.h" //the common library

/*
 * Constants...
 */
39
//unsigned char NL = 10;
40 41 42 43 44 45 46 47 48 49

/*
 * Globals...
 */
char		CallerName[50];  	/* String that identifies the calling program */
int		DoBack;			/* Enables the back channel comms */ 
char 		BackBuf[32000]; //for the back channel replies from the printer
int 		BackBufLen=sizeof(BackBuf)-1;
FILE 		*LogFile = NULL; //file descriptor for log file
time_t		StartTime;
50
int		BlackPercent, ColourPercent;
51

52 53 54 55 56 57 58 59 60 61 62 63 64 65
time_t KeepAwake(time_t Start, int Interval, FILE *PrintFile)
{
// Keeps the printer connection awake by sending DeviceStatus query not sooner than the specified interval in seconds
// Usage:   Start = KeepAwake(Start, Interval);
	if(time(NULL) - Start > Interval)
	{
		DoLog("Keeping printer awake by DeviceStatus?\n",0,0);
		GoodExchange(PrintFile, "DeviceStatus?", "0101,DeviceStatus.ImageDevice", DoBack,   1,  1.0);
		return (time(NULL));
	}
	else return (Start);
}


66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
void SetupLogging(char *ExtCallerName, int ExtDoBack, char *ExtLogFileName)
{
	strcpy(CallerName,ExtCallerName);
	DoBack=ExtDoBack;
	if(strlen(ExtLogFileName)>0)
	{
		remove(ExtLogFileName); //to be sure I only see the latest
		LogFile = fopen(ExtLogFileName, "w"); //open the log file
		sleep(3); //does this help chmod to work?
		chmod(ExtLogFileName, S_IRUSR | S_IWUSR | S_IROTH ); //let anyone read it
  		setbuf(LogFile, NULL);
		fprintf(LogFile, "KodakPrintLog %s\n",ExtCallerName);
	}
	StartTime = time(NULL);
}

void CloseLogging()
{
	if(LogFile) 
	{
		DoLog("Closing log\n",0,0);
		fclose(LogFile);
	}
}

void DoLog(char *PrintFormat, int I1, int I2)
{
	//prints a line with 2 integers to the log file and the cups error log
	char CupsFormat[200]; 
	strcpy(CupsFormat, "DEBUG: ");
	strcat(CupsFormat,CallerName);
	strcat(CupsFormat,":%d : ");
	strncat(CupsFormat,PrintFormat,150); //crop PrintFormat to avoid FAILING WITH BUFFER OVERFLOW
	// add \n if not \n at the end of cupsformat
	if(CupsFormat[strlen(CupsFormat)-1] != NL) strcat(CupsFormat,"\n");
	fprintf(stderr, CupsFormat, time(NULL)-StartTime, I1, I2);
	if (LogFile != NULL) fprintf(LogFile, CupsFormat, time(NULL)-StartTime, I1, I2);
}

void DoLogString(char *PrintFormat, char *String)
{
	//prints a line with a string to the log file and the cups error log
	char CupsFormat[200]; 
	strcpy(CupsFormat, "DEBUG: ");
	strcat(CupsFormat,CallerName);
	strcat(CupsFormat,":%d : ");
	strncat(CupsFormat,PrintFormat,150); //crop PrintFormat to avoid FAILING WITH BUFFER OVERFLOW
	fprintf(stderr, CupsFormat, time(NULL)-StartTime, String);
	if (LogFile != NULL) fprintf(LogFile, CupsFormat, time(NULL)-StartTime, String);
}

/* DoOutJob used to enable one call to send to the specified job file and to stdout (if not testing)
And log the result */
void DoOutJob(FILE *OutFile, char *PrintFormat, int I1, int I2)
{
	int BytesRead = 0; //int because cupsBackChannel can return -1
	char Display[80];
	char LogFormat[200];
	int i;

	if (OutFile) fprintf(OutFile, PrintFormat, I1, I2); //to the specified file
127
#if TESTING == 0
128
	strcpy(LogFormat, "-> ");
129 130 131
#else
	strcpy(LogFormat, "-block- ");
#endif
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
	strcat(LogFormat,PrintFormat);
	DoLog(LogFormat, I1, I2); //and the log
#if TESTING == 0
	fprintf(stdout, PrintFormat, I1, I2); //and to the output
	fflush(stdout);

	if(DoBack)
	{
		BytesRead = cupsBackChannelRead(BackBuf, BackBufLen, 0.5); //read the reply from printer
		if(BytesRead >= 1) 
		{
			if(BytesRead<BackBufLen) BackBuf[BytesRead]=0; //add null terminator NB BytesRead==-1 if nothing read
			for(i=0;i<79;++i) Display[i] = BackBuf[i]; //copy the first 79 chars to Display
			Display[79] = 0; //add null terminator
			DoLogString("Reply = %s\n", Display);
		}
		else DoLog("No reply\n", 0,0);
	}

#endif
//	KeepAwakeStart = time(NULL); // reset timer
}


/* FlushBackChannel gets rid of any previous reply that could cause confusion */
int FlushBackChannel(char *IdString, float DrainTime)
{
//returns 1 if sucessful
	cups_sc_status_t status;
	char BackBuf[2]; //useless buffer to satisfy cupsSideChannelDoRequest
	int BackBufLen=sizeof(BackBuf)-1;
	if(DoBack)
	{
		status = cupsSideChannelDoRequest(CUPS_SC_CMD_DRAIN_OUTPUT, BackBuf, &BackBufLen, DrainTime);
		if(status == CUPS_SC_STATUS_OK) 
		{
			DoLogString("<did DRAIN_OUTPUT %s>\n", IdString);
			return(1);
		}
		else
		{
			if(status == CUPS_SC_STATUS_TIMEOUT) DoLogString("<Failed DRAIN_OUTPUT %s = Timeout>\n", IdString);
			else if(status == CUPS_SC_STATUS_IO_ERROR)  DoLogString("<Failed DRAIN_OUTPUT %s = IO error>\n", IdString);
			else if(status == CUPS_SC_STATUS_NOT_IMPLEMENTED) DoLogString("<Failed DRAIN_OUTPUT %s = not implemented>\n", IdString);
			else  DoLogString("<Failed DRAIN_OUTPUT %s = unknown reason>\n", IdString);
			return(0);
		}
	}
	else return(0);
}

183 184 185 186 187 188 189 190

/* GoodExchange now matches against substrings in the reply so we can cope with queued messages better. Thanks to Gordon for this improvement to GoodExchange 4/11/11
	The UnexpectedLogLimit added 11/11/11
	Note that strtok() replaces the delimiters by null bytes, so you can't search the buffer easily afterwards.
	It returns the nubmer of bytes read if the reply includes the one expected,
	otherwise -(the number of bytes read) if the reply did not include Expect, or 0 if there was no reply */


191 192 193 194 195
int GoodExchange(FILE *PrintFile, char *Command, char *Expect, int DoBack,  unsigned int SleepTime, float ReplyTime)
{
	int BytesRead = 0; //int because cupsBackChannel can return -1
	char Display[80];
	int i;
196 197 198 199 200 201 202 203
	int UnexpectedCount = 0;
	const int UnexpectedLogLimit = 5; //stops the log file being filled with Status replies due to keep awake.
	char * Token1;
	char * TokenList;
	const char * Delimiters = ";&";         // ; for normal replies & for device.status? requests....
                                                // don't actually need the info but nice to be able to read it all.....
	int ReturnSign = -1;                    //assume we won't find the string we want 
	int BlackPercentFound, ColourPercentFound;
204
#if TESTING == 0
205
	DoLogString("-> %s\n", Command); //now also sends to stderr
206 207 208
#else
	DoLogString("-block- %s\n", Command); //now also sends to stderr
#endif
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
	if(PrintFile) fprintf(PrintFile, "%s", Command); //to the global print file
#if TESTING == 0
	fprintf(stdout, "%s", Command); //printer command
	fflush(stdout); //force a packet to the printer so it can reply
	sleep(SleepTime); //give it a chance to reply before trying to read the reply (may not be needed)

	if(DoBack)
	{
		BytesRead = cupsBackChannelRead(BackBuf, BackBufLen, ReplyTime); //read the reply from printer
		if(BytesRead < 1) 
		{
			DoLog("No reply\n",0,0);
			return 0;
		}
		else
		{
			BackBuf[BytesRead]=0; //add null terminator NB BytesRead==-1 if nothing read
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
			//fprintf(stderr,"thisline->%s\n",BackBuf);

			// search for any special replies here before strtok changes the buffer
			BlackPercentFound=MarkerPercent(BackBuf,0);
			ColourPercentFound=MarkerPercent(BackBuf,1);
			if(BlackPercentFound >= 0) BlackPercent=BlackPercentFound;
			if(ColourPercentFound >= 0) ColourPercent=ColourPercentFound;

			TokenList = BackBuf;
			Token1 = strtok(TokenList , Delimiters);		
			while (Token1 != NULL)
				{
				for(i=0;i<79;++i) Display[i] = Token1 [i]; //copy the first 79 chars to Display
		                if (strncmp(Token1 , Expect, strlen(Expect) -1) == 0) //reduce string length by 1 as ; removed 
					{
					DoLogString("Expected reply = %s\n", Display);
					ReturnSign = 1;
			 		}
				else
					{
					// limit the number of unexpected replies that are logged
					if(UnexpectedCount <= UnexpectedLogLimit) DoLogString("Unexpected reply = %s\n", Display);
					++UnexpectedCount;
					//ReturnSign defaults to unexpected unless changed by a single occurance of expected...
					//so don't alter it here!
                                        }
				Token1 = strtok(NULL , Delimiters);	
				} 
			return (ReturnSign * BytesRead);
255 256
		}
	}
257
	return 0;
258
#endif
259
}
260

261 262 263
int
MarkerPercent(char *Buf, int GetColour) /* GetColour = 1 for "Color" or 0 for "Black" */
{
264
	/* search for the ink data in the buffer. Returns -1 if not found */
265 266 267 268 269 270
	char *MarkerLevelString;
	
		if(GetColour) MarkerLevelString = strstr(Buf, "DeviceStatus.Printer.InkLevelPercent.Color=");
		else MarkerLevelString = strstr(Buf, "DeviceStatus.Printer.InkLevelPercent.Black=");
		if (MarkerLevelString)
		{
271 272
			//DoLog("Found marker level",0,0);
			MarkerLevelString = strstr(MarkerLevelString, "=");
273 274
			if (MarkerLevelString)
			{
275 276 277 278 279 280 281 282 283 284
				if(strncmp(MarkerLevelString + 1,"F",1)==0) 
				{
					DoLog("Found marker %d level Full = %d",GetColour,100);
					return (100);
				}
				else 
				{
					DoLog("Found marker %d level %d",GetColour, atoi(MarkerLevelString + 1));
					return (atoi(MarkerLevelString + 1));
				}
285 286
			}
		}
287 288 289 290 291 292 293 294 295 296 297 298 299 300
		else 
		{
			//DoLog("Failed to find marker %d level",GetColour,0);
			return -1;
		}
	return -1;
}


void
MarkerSetup()
{
   	fprintf(stderr, "ATTR: marker-colors=black,magenta\n"); //displays ink drops in printer manager
   	fprintf(stderr, "ATTR: marker-names=black,colour\n");
301 302 303 304 305 306 307 308 309 310 311 312
}



void SetPaperSize(char Size[], int PaperPoints)
{
    //converts length of page in cups header (in points) into a string that the printer recognises

	strcpy(Size, "MediaSize=na_letter_8.5x11in;"); //default

    switch (PaperPoints)
    {
313 314 315
      case 421 : // A6 
		strcpy(Size, "MediaSize=iso_a6_105x148mm;");
	  break;
316 317 318
      case 432 : // Photo 4x6" 
		strcpy(Size, "MediaSize=na_index4x6_4x6in;");
	  break;
319 320 321
      case 504 : // Photo 5x7" 
		strcpy(Size, "MediaSize=na_5x7_5x7in;");
	  break;
322 323 324 325 326
      case 540 : // Monarch Envelope 
	  break;
      case 595 : // A5 
		strcpy(Size, "MediaSize=iso_a5_148x210mm;");
	  break;
327 328
      case 624 : // DL Envelope 
		strcpy(Size, "MediaSize=iso_dl_110x220mm;");
329
	  break;
330 331
      case 649 : // EnvC5 Envelope 
		strcpy(Size, "MediaSize=iso_c5_162x229mm;");
332
	  break;
333 334
      case 684 : // Env10 Envelope 
		strcpy(Size, "MediaSize=na_number10_4.125x9.5in;");
335 336
	  break;
      case 709 : // B5 Envelope 
337 338 339 340
		strcpy(Size, "MediaSize=iso_b5_176x250mm;");
	  break;
      case 720 : // Photo 8x10" 
		strcpy(Size, "MediaSize=na_govtletter_8x10in;");
341 342
	  break;
      case 756 : // Executive
343
		strcpy(Size, "MediaSize=na_executive_7.25x10.5in;");
344 345 346 347 348 349 350 351
	  break;
      case 792 : // Letter 
		strcpy(Size, "MediaSize=na_letter_8.5x11in;");
 	  break;
      case 842 : // A4 
		strcpy(Size, "MediaSize=iso_a4_210x297mm;");
	  break;
      case 1008 : // Legal
352
		strcpy(Size, "MediaSize=na_legal_8.5x14in;");
353 354 355 356 357 358 359 360 361
	  break;
      case 1191 : // A3 
	  break;
      case 1224 : // Tabloid 
	  break;
    }
}

void
362
DisplayHeader(cups_page_header2_t *header)
363 364 365 366 367
{
 /*
  * Show page device dictionary...
  */

368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
  DoLog("StartPage...\n",0,0);
  DoLogString("MediaClass = \"%s\"\n", header->MediaClass);
  DoLogString("MediaColor = \"%s\"\n", header->MediaColor);
  DoLogString("MediaType = \"%s\"\n", header->MediaType);
  DoLogString("OutputType = \"%s\"\n", header->OutputType);
  DoLog("AdvanceDistance = %d\n", header->AdvanceDistance,0);
  DoLog("AdvanceMedia = %d\n", header->AdvanceMedia,0);
  DoLog("Collate = %d\n", header->Collate,0);
  DoLog("CutMedia = %d\n", header->CutMedia,0);
  DoLog("Duplex = %d\n", header->Duplex,0);
  DoLog("HWResolution = [ %d %d ]\n", header->HWResolution[0], header->HWResolution[1]);
  DoLog("ImagingBoundingBox = [ %d %d", header->ImagingBoundingBox[0], header->ImagingBoundingBox[1]);
  DoLog(" %d %d ]\n", header->ImagingBoundingBox[2], header->ImagingBoundingBox[3]);
  DoLog("InsertSheet = %d\n", header->InsertSheet,0);
  DoLog("Jog = %d\n", header->Jog,0);
  DoLog("LeadingEdge = %d\n", header->LeadingEdge,0);
  DoLog("Margins = [ %d %d ]\n", header->Margins[0], header->Margins[1]);
  DoLog("ManualFeed = %d\n", header->ManualFeed,0);
  DoLog("MediaPosition = %d\n", header->MediaPosition,0);
  DoLog("MediaWeight = %d\n", header->MediaWeight,0);
  DoLog("MirrorPrint = %d\n", header->MirrorPrint,0);
  DoLog("NegativePrint = %d\n", header->NegativePrint,0);
  DoLog("NumCopies = %d\n", header->NumCopies,0);
  DoLog("Orientation = %d\n", header->Orientation,0);
  DoLog("OutputFaceUp = %d\n", header->OutputFaceUp,0);
  DoLog("PageSize = [ %d %d ]\n", header->PageSize[0], header->PageSize[1]);
  DoLog("Separations = %d\n", header->Separations,0);
  DoLog("TraySwitch = %d\n", header->TraySwitch,0);
  DoLog("Tumble = %d\n", header->Tumble,0);
  DoLog("cupsWidth = %d\n", header->cupsWidth,0);
  DoLog("cupsHeight = %d\n", header->cupsHeight,0);
  DoLog("cupsMediaType = %d\n", header->cupsMediaType,0);
  DoLog("cupsBitsPerColor = %d\n", header->cupsBitsPerColor,0);
  DoLog("cupsBitsPerPixel = %d\n", header->cupsBitsPerPixel,0);
  DoLog("cupsBytesPerLine = %d\n", header->cupsBytesPerLine,0);
  DoLog("cupsColorOrder = %d\n", header->cupsColorOrder,0);
  DoLog("cupsColorSpace = %d\n", header->cupsColorSpace,0);
  DoLog("cupsCompression = %d\n", header->cupsCompression,0);
406 407
}

408 409 410 411 412 413 414 415 416 417 418 419
int 
HeaderInvalid(cups_page_header2_t *header)
{
/* checks the header has sensible values and returns 1 if they are not sensible */

	if(header->HWResolution[0] != 300 && header->HWResolution[0] != 600) 
	{
		DoLog("Header error:  x resolution %d\n",header->HWResolution[0],0);
		return 1;
	}
	return 0;
}
420