/* Print-Handler.c v1.1, 30 September 1989 * * This printer device handler implements the single sheet support * and data-spooling facility the usual PRT: device does not have. * * Each time a page has been successfully transferred to the printer * and single paper sheets are selected in Preferences, a requestor * pops up on the Workbench screen telling the user to insert the * next sheet of paper. If a process addresses this handler via * Open() and Write()s data to it, the incoming text is buffered * until the data stream is Close()d. After that the buffer sent to * the printer device and finally gets discarded. Note that there * will be a buffer for each calling process (up to twenty callers * are allowed), so you may get low on memory. This handler will * notice low memory situations and return a write error in critical * situations. This handler does no first-in-first-out or last-in- * first-out data stack handling, the first data to come out is from * the first buffer to be closed * * Don't be confused if the requestor pops up while the printer is * still working. Since most printers have a write buffer of their * own true synchronous printer I/O is almost impossible. * * Installation is as follows: * * * Add the following lines to your DEVS:Mountlist * * PRT: Handler = L:Print-Handler * Stacksize = 3000 * Priority = 5 * GlobVec = 1 * # * * * Add the following lines to your S:Startup-Sequence * * Assign PRT: Remove * Mount PRT: * * * Copy Print-Handler to your L: * * Skeleton handler code by Phillip Lindsay (C) 1986 Commodore * You may freely distribute this source and use it for Amiga Development, * as long as the Copyright notice is left intact. * * Print-Handler (C) Copyright 1989 by Olaf Barthel & ED Hannover * * Contact: Olaf Barthel, Electronic Design Hannover * Brabeckstrasse 35 * D-3000 Hannover 71 * * Federal Republic of Germany */ #include #include #include #include #include #include /* This version of BADDR() has no problems with castings. */ #undef BADDR #define BADDR(x) ((APTR)((long)x << 2)) /* Define the packet types which are not available * in the dos 1.1/1.2 include files. */ #define ACTION_FIND_INPUT 1005 #define ACTION_FIND_OUTPUT 1006 #define ACTION_END 1007 /* Some BCPL boolean definitions. */ #define DOS_FALSE 0 #define DOS_TRUE -1 /* Forward declarations. */ extern struct Library *OpenLibrary(); extern struct Process *FindTask(); extern struct MsgPort *CreatePort(); extern struct MsgPort *FindPort(); extern struct Message *GetMsg(); extern struct IOStdReq *CreateStdIO(); extern void *AllocMem(); /* This is a data segment, a part of a larger printer * buffer used for data-spooling. */ struct DataSeg { struct DataSeg *NextSeg; /* Next segment. */ APTR Buffer; /* Data array. */ long Length; /* Length of array. */ }; /* Some global data. */ struct IntuitionBase *IntuitionBase; struct Preferences *Preferences; struct DataSeg *PrintSlot[20]; struct IOStdReq *PrinterDevice; struct MsgPort *PrinterPort; /* Position counters to take care of the current page length. */ long LinesDone = 0; long ColumnsDone = 0; /* CreateSeg(Buffer,Length): * * Creates a new segment entry for a linked list of * buffers. */ struct DataSeg * CreateSeg(Buffer,Length) register APTR Buffer; register long Length; { register struct DataSeg *NewSeg; /* Allocate memory for the list element. */ if(NewSeg = (struct DataSeg *)AllocMem(sizeof(struct DataSeg),MEMF_PUBLIC | MEMF_CLEAR)) { /* Allocate memory for the data buffer. */ if(NewSeg -> Buffer = (APTR)AllocMem(Length,MEMF_PUBLIC | MEMF_CLEAR)) { /* Copy the data and set the buffer size. */ CopyMem(Buffer,NewSeg -> Buffer,Length); NewSeg -> Length = Length; /* Return the new segment. */ return(NewSeg); } /* Free the segment data. */ FreeMem(NewSeg,sizeof(struct DataSeg)); } /* We failed. */ return(NULL); } /* DeleteSeg(OldSeg): * * Deletes both the contents and the memory occupied * by a data segment and returns a pointer to the * next segment. */ struct DataSeg * DeleteSeg(OldSeg) register struct DataSeg *OldSeg; { register struct DataSeg *NextSeg = NULL; /* Valid pointer given? */ if(OldSeg) { /* Remember this. */ NextSeg = OldSeg -> NextSeg; /* Free the contents of the buffer. */ if(OldSeg -> Buffer && OldSeg -> Length) { FreeMem(OldSeg -> Buffer,OldSeg -> Length); /* Zero this out. */ OldSeg -> Buffer = NULL; OldSeg -> Length = 0; } /* Free the segment data. */ FreeMem(OldSeg,sizeof(struct DataSeg)); } /* Return pointer to next segment or null. */ return(NextSeg); } /* ReturnPacket(Packet,res1,res2): * * This one returns an AmigaDOS packet we have just * received. */ void ReturnPacket(Packet,res1,res2) register struct DosPacket *Packet; register ULONG res1,res2; { register struct Message *Message; register struct MsgPort *ReplyPort; register struct Process *ThisProg; Packet -> dp_Res1 = res1; Packet -> dp_Res2 = res2; ReplyPort = Packet -> dp_Port; Message = Packet -> dp_Link; ThisProg = (struct Process *)FindTask(NULL); Packet -> dp_Port = &ThisProg -> pr_MsgPort; Message -> mn_Node . ln_Name = (char *)Packet; Message -> mn_Node . ln_Succ = NULL; Message -> mn_Node . ln_Pred = NULL; PutMsg(ReplyPort,Message); } /* TaskWait(): * * This one waits for an AmigaDOS packet to arrive * and returns a pointer to it. */ struct DosPacket * TaskWait() { register struct Process *ThisProg; register struct MsgPort *MyPort; register struct Message *MyMessage; ThisProg = (struct Process *)FindTask(NULL); MyPort = &ThisProg -> pr_MsgPort; WaitPort(MyPort); MyMessage = (struct Message *)GetMsg(MyPort); return((struct DosPacket *)MyMessage -> mn_Node . ln_Name); } /* PrintIt(Buffer,Length): * * This function handles the printing. We could as well * replace the data <-> printer transfer by a short call * to Write(); probably the easiest way to implement * output redirection. */ long PrintIt(Buffer,Length) register APTR Buffer; register long Length; { /* Are buffer and size both valid? */ if(Buffer && Length) { /* Send the text to the printer. */ PrinterDevice -> io_Command = CMD_WRITE; PrinterDevice -> io_Data = Buffer; PrinterDevice -> io_Length = Length; return(DoIO(PrinterDevice)); } return(-1); } /* DoRequest(): * * Displays the requestor asking to insert the next * sheet of paper and returns the result. */ BOOL DoRequest() { static struct TextAttr DefaultFont = { (UBYTE *)"topaz.font",9,FS_NORMAL,FPF_ROMFONT }; static struct IntuiText RequestTxt[] = { {0,1,JAM1,18, 4,(struct TextAttr *)&DefaultFont,(UBYTE *)"Finished with current page,", &RequestTxt[1]}, {0,1,JAM1,18,14,(struct TextAttr *)&DefaultFont,(UBYTE *)"please insert next sheet.", NULL}, {0,1,JAM1, 5, 3,(struct TextAttr *)&DefaultFont,(UBYTE *)"Ready", NULL}, {0,1,JAM1, 5, 3,(struct TextAttr *)&DefaultFont,(UBYTE *)"Abort", NULL} }; /* Open the Workbench first, we don't want to have a dead * Workbench screen hanging around if a task preferred to * close it. */ OpenWorkBench(); return(AutoRequest(NULL,&RequestTxt[0],&RequestTxt[2],&RequestTxt[3],NULL,NULL,326,59)); } /* PrintData(Buffer,Length): * * This is the main interface processing the incoming * data. */ BOOL PrintData(Buffer,Length) register char *Buffer; register long Length; { register long i,j,InBuff = 0,PaperWidth = Preferences -> PrintRightMargin - Preferences -> PrintLeftMargin; char LineStack[1024]; /* Clear out the contents of the line buffer. */ for(i = 0 ; i < 1024 ; i++) LineStack[i] = 0; if(Buffer) { for(i = 0 ; i < Length ; i++) { /* Put the data into the line buffer. */ LineStack[InBuff++] = Buffer[i]; ColumnsDone++; /* Right margin reached/newline encountered? */ if(ColumnsDone == PaperWidth || Buffer[i] == '\n') { /* Overriding the paper width * does not necessarily include a * newline. */ if(ColumnsDone == PaperWidth || ColumnsDone == 1024) { LineStack[InBuff++] = '\n'; ColumnsDone++; } /* Send the line to the printer. */ PrintIt(LineStack,InBuff); /* Did we reach the bottom line? */ if((++LinesDone) == Preferences -> PaperLength) { LinesDone = 0; /* Inform the user about it. */ if(Preferences -> PaperType == SINGLE) if(!DoRequest()) return(FALSE); } /* Clear the rest. */ ColumnsDone = InBuff = 0; for(j = 0 ; j < 1024 ; j++) LineStack[j] = 0; } } /* Is there still something in the line buffer? */ if(InBuff) PrintIt(LineStack,InBuff); } /* We succeeded and nobody cancelled us. */ return(TRUE); } /* _main(): * * Handler main routine, bypasses all typical Aztec * startup code. */ _main() { struct Process *ThisProg = (struct Process *)FindTask(NULL); struct DosPacket *MyPacket; struct DeviceNode *MyNode; /* Our device node passed in parmpkt Arg3. */ long OpenCount = 0; /* Handler open flag. */ BOOL Running = TRUE; /* Handler main loop flag. */ register long NextSlot = 0; register long i; /* The list of available buffers. */ UBYTE Available[20]; /* Since we were started as a non-BCPL module we get sent the * parameter packet (i.e. parameter packet not in D1). */ MyPacket = TaskWait(); /* Wait for parameter packet. */ /* Mark all buffers as empty. */ for(i = 0 ; i < 20 ; i++) { Available[i] = TRUE; PrintSlot[i] = NULL; } /* Get a pointer to our device node. */ MyNode = (struct DeviceNode *)BADDR(MyPacket -> dp_Arg3); /* Do the main handler initialization. */ if(!(IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",33))) { ReturnPacket(MyPacket,DOS_FALSE,MyPacket -> dp_Res2); goto FallOff; } if(!(PrinterPort = (struct MsgPort *)CreatePort(NULL,0))) { ReturnPacket(MyPacket,DOS_FALSE,MyPacket -> dp_Res2); goto FallOff; } if(!(PrinterDevice = (struct IOStdReq *)CreateStdIO(PrinterPort))) { ReturnPacket(MyPacket,DOS_FALSE,MyPacket -> dp_Res2); goto FallOff; } if(OpenDevice("printer.device",0,PrinterDevice,0)) { ReturnPacket(MyPacket,DOS_FALSE,MyPacket -> dp_Res2); goto FallOff; } /* Initialize the Preferences pointer to * reflect current printer.device settings. */ Preferences = &((struct PrinterData *)PrinterDevice -> io_Device) -> pd_Preferences; /* If initialization was possible then we install our * taskid. If we don't...for every reference to our * handler a new process will be created. This is * fine for things like CON: (console handler) but * if you plan to be the only dude on block (like * the file-system handler or SER:) you should fill * the task field with your taskid (i.e. * &(pr_MsgPort)) Note: remember that shared code * has to be reentrant. (like CON: handler), keep * your variables on the stack [autos], and allocate * memory for larger data structures and "FLAG" * global data structures that need only be * intialized once. */ MyNode -> dn_Task = &ThisProg -> pr_MsgPort; ReturnPacket(MyPacket,DOS_TRUE,MyPacket -> dp_Res2); while(Running) /* Start of the real work. */ { MyPacket = TaskWait(); /* Wait for a packet. */ switch(MyPacket -> dp_Type) { /* Somebody Open()ed us. */ case ACTION_FIND_INPUT: case ACTION_FIND_OUTPUT: { struct FileHandle *FileHandle = (struct FileHandle *)BADDR(MyPacket -> dp_Arg1); /* Assume failure. */ FileHandle -> fh_Port = DOS_FALSE; for(i = 0 ; i < 20 ; i++) { /* Any buffer available? */ if(Available[i]) { /* We didn't fail. */ FileHandle -> fh_Port = DOS_TRUE; FileHandle -> fh_Arg1 = i; Available[i] = FALSE; /* Increment usercount. */ OpenCount++; break; } } /* Return the compliment. */ ReturnPacket(MyPacket,FileHandle -> fh_Port,MyPacket -> dp_Res2); break; } /* Someone Close()d the file. */ case ACTION_END: { long TheSlotIs = MyPacket -> dp_Arg1; BOOL GoOn = TRUE; /* We want to fall out of the loop if not OPEN. */ if((--OpenCount) <= 0) Running = FALSE; ReturnPacket(MyPacket,DOS_TRUE,MyPacket -> dp_Res2); /* Print the current buffer. */ while(PrintSlot[TheSlotIs]) { if(GoOn) if(!PrintData(PrintSlot[TheSlotIs] -> Buffer,PrintSlot[TheSlotIs] -> Length)) GoOn = FALSE; PrintSlot[TheSlotIs] = DeleteSeg(PrintSlot[TheSlotIs]); } Available[TheSlotIs] = TRUE; /* Reset the line counters. */ LinesDone = ColumnsDone = 0; break; } /* Someone tries to Read() us. */ case ACTION_READ: { /* We *always* read nothing. */ ReturnPacket(MyPacket,NULL,MyPacket -> dp_Res2); break; } /* Someone tries to Write() to us. */ case ACTION_WRITE: { long TheSlotIs = MyPacket -> dp_Arg1; char *Buffer = (char *)MyPacket -> dp_Arg2; struct DataSeg *NextSlot; MyPacket -> dp_Res1 = MyPacket -> dp_Arg3; /* Buffer not initialized yet? */ if(!PrintSlot[TheSlotIs]) { if(!(PrintSlot[TheSlotIs] = CreateSeg(Buffer,MyPacket -> dp_Res1))) MyPacket -> dp_Res1 = -1; } else { /* Add a new buffer to the current list. */ NextSlot = PrintSlot[TheSlotIs]; while(NextSlot -> NextSeg) NextSlot = NextSlot -> NextSeg; if(!(NextSlot -> NextSeg = CreateSeg(Buffer,MyPacket -> dp_Res1))) MyPacket -> dp_Res1 = -1; } /* We *always* write everything. */ ReturnPacket(MyPacket,MyPacket -> dp_Arg3,MyPacket -> dp_Res2); break; } /* Someone wants us the leave the town. */ case ACTION_DIE: { /* Empty all buffers. */ for(i = 0 ; i < 20 ; i++) while(PrintSlot[i]) PrintSlot[i] = DeleteSeg(PrintSlot[i]); /* Result will be ignored anyway. */ ReturnPacket(MyPacket,DOS_FALSE,ERROR_ACTION_NOT_KNOWN); goto FallOff; } /* For any other purpose: ignore the message. */ default: { /* Say what? */ ReturnPacket(MyPacket,DOS_FALSE,ERROR_ACTION_NOT_KNOWN); break; } } } /* Fall off the edge of the world. */ FallOff:MyNode -> dn_Task = FALSE; /* Zero the TaskID field of device node. */ /* Free all our data. */ if(PrinterDevice) { if(PrinterDevice -> io_Device) CloseDevice(PrinterDevice); DeleteStdIO(PrinterDevice); } if(PrinterPort) DeletePort(PrinterPort); if(IntuitionBase) CloseLibrary(IntuitionBase); /* This is truly the end. */ }