Browse Source

First commit with original version 0.9.9 retrieved from https://web-beta.archive.org/web/20120507043647/http://homepage.mac.com/monroe/styl/

Godzil 7 years ago
commit
b0c8b04bda
22 changed files with 3989 additions and 0 deletions
  1. 11 0
      Makefile.atalk
  2. 209 0
      README
  3. 64 0
      README.atalk
  4. 168 0
      README.protocol
  5. 45 0
      README.troubleshooting
  6. 862 0
      adsp.c
  7. 182 0
      adsp.h
  8. 2089 0
      lpstyl.c
  9. 17 0
      printcap
  10. 17 0
      printcap.a4
  11. 4 0
      scripts/direct_stylpbm
  12. 6 0
      scripts/direct_stylps
  13. 11 0
      scripts/stylascii
  14. 14 0
      scripts/stylascii-atalk
  15. 10 0
      scripts/stylascii.a4
  16. 6 0
      scripts/stylps
  17. 9 0
      scripts/stylps-atalk
  18. 7 0
      scripts/stylps-color
  19. 11 0
      scripts/stylps-color-atalk
  20. 7 0
      scripts/stylps-color.a4
  21. 6 0
      scripts/stylps.a4
  22. 234 0
      styl.ppd

+ 11 - 0
Makefile.atalk

@@ -0,0 +1,11 @@
+# Makefile for compiling in AppleTalk support in lpstyl.
+
+# Change /usr/local/include to the directory containing the 'atalk' includes
+# directory, and /usr/local/lib to the directory containing libatalk.a.
+
+CFLAGS= -g -Wall -DATALK=1 -I/usr/local/include
+LINKFLAGS= -L/usr/local/lib -latalk
+
+lpstyl: lpstyl.o adsp.o 
+	${CC} ${CFLAGS} lpstyl.o adsp.o ${LINKFLAGS} -o lpstyl
+

+ 209 - 0
README

@@ -0,0 +1,209 @@
+"lpstyl version 0.9.9"
+
+This is an Apple StyleWriter driver for un*x.  It was developed on
+NetBSD-mac68k, and has been reported to work on NetBSD-i386,
+linux-pmac, mklinux-ppc, and linux-x86.  
+
+Supported printers include:
+
+StyleWriter I
+StyleWriter II
+StyleWriter III (aka StyleWriter 1200)
+Color StyleWriter 1500
+Color StyleWriter 2200 
+Color StyleWriter 2400 
+Color StyleWriter 2500 
+
+If you have some breed of StyleWriter besides the ones mentioned
+here, feel free to try it.  The driver should realize that it
+doesn't know what sort of printer you have and try to treat it like
+a StyleWriter II or 2400, depending on values the printer returns.
+Try it and see, and please let me know what happens.
+
+Random notes about this version:
+
+Since the StyleWriter has no built-in fonts, this driver only prints
+a couple of types of raster image files at 360 dpi (the standard
+printer resolution of StyleWriters).  It is expected that gs will
+be used to create these files from PostScript files, but if you
+have some other way of creating them, feel free.  The printcap
+entry and shell scripts in this kit will allow you to "lpr" a
+postscript or ascii text file and have it printed directly (without
+ever creating the raster file on disk) by piping output from gs to
+the driver.
+
+If the printer runs out of paper, the driver will usually wait
+indefinitely for the problem to go away, retrying every 30 seconds.
+It will also send messges to the log file to this effect.  On the
+2400/2500, the retry will be every 5 seconds, and it won't reset
+the printer or resend any data.  I say "usually" because if the
+out-of-paper condition is detected at the wrong time, the driver
+doesn't quite know how to deal with it.  In these cases, it will
+exit with an non-zero status.
+
+If you want to turn the printer off during a long print job, the
+driver needs to be informed so that the printer's buffered data
+isn't lost.  Sending the driver process a SIGUSR1 will tell it to
+suspend printing after the current page finishes.  The driver will
+print a line to the lpd log file telling you when it is OK to turn
+off the printer.  While printing is suspended, the printer can be
+turned off, disconnected, etc.  (You can even connect a different
+type of StyleWriter -- when you restart printing, the driver will
+identify the printer again.)  Sending another SIGUSR1 will resume
+printing with the next page.
+
+The driver program itself is a pure filter -- it depends on lpd to
+connect its stdin and stdout to an image file and the tty the
+printer is attached to, respectively.  The printcap entry and sample
+scripts assume that the printer is attached to /dev/stylewriter,
+which whould be a symbolic link to whatever serial device the
+printer is connected to.  (In netbsd-mac68k, the printer port is
+/dev/tty01.)
+
+The driver program accepts the following flags:
+
+-f <device node>
+    Tells the driver to open a device directly instead of using
+	stdout to talk to the printer.
+
+-t <file format>
+	Tells the driver which file format to expect for input.  Currently
+	this can be pbmraw, bit, or bitcmyk.  Using "bitcmyk" implies that
+	the driver will attempt to print in color.
+
+-h <height>
+-w <width>
+	Tells the driver the height and width of the input file.  This will
+	be overridden by the embedded dimension data in pbmraw files.
+	These parameters are required for bit and bitcmyk files, since they
+	have no embedded height and width.
+
+-m
+    Disables the normal cropping that is done to take the top and left
+	margins into account.  This probably shouldn't be used for properly 
+	typeset pages (it will cause them to be shifted down and to the right 
+	instead of being properly placed on the page), but it can be useful
+	for files that don't take page margins into account, like some of the
+	samples that come with ghostscript.
+
+-v
+    Increases the amount of debugging information the driver sends
+	to stderr.  Multiple v's give more output.  Currently more than
+	two doesn't do anything different, but feel free to add as many
+	as you like if it makes you happy.  The stderr of the driver is
+	normally directed to /var/log/lpd-errs (or whatever file is
+	specified in the printcap entry) by lpd.  Watching this file 
+	with 'tail -f' can be informative.
+
+-p letter
+-p a4
+	Sets the paper size the driver expects.  Default is "letter".
+
+-H <height>
+-W <width>
+	Another way of setting the paper size.  Height and width are in
+	printer pixels.  One printer pixel is 1/360 of an inch.  No sanity
+	checking is done on the arguments, so be nice.
+
+-?
+	Prints a usage message.
+
+If the driver is compiled with AppleTalk support, the following options are
+also available:
+
+-a <nbpname>
+	Specify the name of the AppleTalk printer you want to use.  This is the
+	switch that tells the driver to use AppleTalk instead of serial
+	communication.  nbpname can be specified in standard AppleTalk wildcard
+	format (properly quoted to avoid shell expansion), but a name which 
+	matches multiple devices may produce undesired results.
+
+-u <username>
+	Specify the username that will be shown to other users while the printer
+	is busy.  This can be any string, but I recommend a proper 'user@host'
+	to avoid confusion.  The sample AppleTalk driver scripts show how to 
+	do this with the standard parameters that lpd passes to driver programs.
+
+MANIFEST
+
+This kit includes the following files:
+
+README:
+	this file
+README.atalk:
+	notes about using the driver with AppleTalk
+README.troubleshooting:
+	common questions and answers
+README.protocol:
+	information about the control protocol StyleWriters use
+lpstyl.c: 
+    the source for the driver program
+adsp.c:
+adsp.h:
+	an implementation of a subset of the adsp protocol
+printcap: 
+    a sample printcap file that uses the driver
+printcap.a4: 
+    a sample printcap file for use with A4 paper
+stylps:
+stylps-color:
+stylascii:
+    Shell scripts (used by the printcap example) that plug together 
+	GhostScript, enscript, and the printer driver in various ways.  
+	Henceforth known as "the driver scripts".
+stylps-atalk:
+stylps-color-atalk:
+stylascii-atalk:
+	Similar to the above, but using AppleTalk for communication.
+direct_stylpbm:
+direct_stylps:
+    Shell scripts that take pbmraw and postscript files as standard input 
+	and use the lpstyl driver (and ghostscript, for direct_stylps) to 
+	send them to a StyleWriter connected to /dev/stylewriter.  Note that 
+	you must have read/write access to the tty for this to work.  These 
+	are included mostly as examples and for testing purposes.  It's much
+	more pleasant to use lpd, once you get it set up.
+styl.ppd:
+	Experimental ppd file for use with systems that expect one
+	(such as netatalk/papd or the LaserWriter 8.5 driver using 'lpd' 
+	protocol).  This is the first time I've tried writing one of these 
+	things, so corrections are welcome.
+control_codes:
+	Information about the control protocol the driver uses, written after
+	the fact.  If this file and the source code conflict, believe the source.
+
+INSTALLATION
+
+To make this thing work, do the following:
+
+- compile lpstyl.c to create the executable lpstyl
+- make sure that the path to gs is correct in stylps (it assumes
+    /usr/local/gnu/bin/gs)
+- make lpstyl and the driver scripts world-executable and put them 
+    somewhere public (I suggest /usr/local/sbin)
+- make sure that the path to lpstyl is correct in the driver scripts (they 
+    assume /usr/local/sbin/lpstyl, etc.)
+- make /dev/stylewriter a symbolic link to the serial port your printer is 
+    connected to.  (On netbsd-mac68k, the printer port is /dev/tty01.)
+- install the printcap entries into /etc/printcap (making sure that all 
+    paths are correct there, too)
+- make sure the spool directory specified in the printcap entry exists
+- kick lpd so that it re-reads /etc/printcap
+
+That should do it.  Find a postscript file and 'lpr -Pstylps file.ps'  
+(Or, if you don't have a postscript file handy, print your favorite 
+man page with 
+'groff -mandoc /usr/local/share/man/man1/manpage.1 |lpr -Pstylps'.)
+
+CONTACTS
+
+I can be reached via email at monroe@pobox.com.  The latest version
+of this package will reside at <http://www.pobox.com/~monroe/styl/>.
+
+If you use this driver successfully, or want to use it but can't
+get it to work, feel free to send me email.  If you make improvements
+to or have questions about this package, please do send them my
+way.
+
+-- Monroe Williams  a.k.a.  monroe@pobox.com --
+Copyright 1996-2000 Monroe Williams, all rights reserved.

+ 64 - 0
README.atalk

@@ -0,0 +1,64 @@
+Using lpstyl over AppleTalk
+---------------------------
+
+Verison 0.9.9 of lpstyl contains preliminary support for StyleWriters
+connected to Ethernet with an "Apple StyleWriter Ethertalk Adapter".
+(Apple part number M4877.) This has only been tested with a
+StyleWriter 2400 and a 68k machine running NetBSD.  I'd be interested
+in the results people with other printer types and/or little-endian
+machines get with this code.
+
+I'd also like to find out whether this code will work with the
+"Farallon EtherMac iPrint Adapter SL", which is another device that
+performs a similar if not identical function and looks very similar
+to the Apple device (appears to be the same plastics), but is still
+in production.  (I'm pretty sure the Apple device has been
+discontinued.)  If you try this code out with one of these things,
+_please_ let me know what happens.
+
+You'll have to do a number of things to set this up:
+
+1)	Make sure netatalk is installed on your machine and compiled into the
+	kernel.  
+
+2)	Make sure your machine is set up to communicate over AppleTalk.  This
+	is a bit complex, and full instructions are included with the
+	netatalk package.  atalkd should be running, 'ifconfig -a' should
+	show an AppleTalk address bound to your Ethernet interface(s),
+	and 'netstat -r' should show some AppleTalk routes.
+
+3)	Find out where the atalk headers and libraries are installed.  
+	(The included makefile assumes they are in 
+	/usr/local/include/atalk/ and /usr/local/lib, respectively.)
+
+4)	Set up your printer and Ethernet adapter, and attach them to the
+	network.  You may want to use the MacOS driver to set the name
+	of the printer to something reasonable.  In the examples here,
+	the printer is a StyleWriter 2400 with the name "PaperShredder".
+
+5)	Find out your printer's nbp name.  This can be done with the
+	netatalk tool 'nbplkup'.  It will give you a number of lines of
+	output, one of which contains the name you gave your printer.
+	On my system, this line is:
+
+	PaperShredder:ColorStyleWriter2400AT             65280.1:129
+
+6)	Compile and install a version of lpstyl with AppleTalk support.
+	The easiest way to do this is to use the Makefile.atalk included
+	with the package.  Copy it to 'Makefile' and do 'make lpstyl'.
+
+	Note that a version compiled with AppleTalk support will still be 
+	able to print to serial connections.
+
+7)	Install lpstyl and the driver scripts as usual.  Modify the printcap
+	entry you want to use so that it points to one of the *-atalk scripts,
+	and change the name of the AppleTalk device in that script to
+	the name you retrieved in step 5.
+
+That should be it.  Try printing something.  If it doesn't work, add
+'-vvvvv' to the lpstyl invocation in the script you're using and examine
+the results in lpd-errs.  If they're not self-explanatory, send me mail
+and we'll try and figure it out.
+
+-- Monroe Williams  a.k.a.  monroe@pobox.com --
+Copyright 1996-2000 Monroe Williams, all rights reserved.

+ 168 - 0
README.protocol

@@ -0,0 +1,168 @@
+This file is an explanation of what I've figured out about control codes
+for StyleWriter printers.  If you just want to use the driver, you
+don't need to read any further -- this is here purely to satisfy
+curiosity about how the driver works (or fails to work, as the
+case may be).
+
+If you have specific questions you would like to see answered here in
+future versions, send them to monroe@pobox.com.
+
+
+All StyleWriters use a serial interface.  They have a standard RS422
+DIN8 plug, which is electrically compatible with RS232 given the
+right plug adapter.  At power-on, they default to communicating at
+57600 baud, and later models can be put into a mode where they
+externally clock the serial port at a rate close to 1 megabit.
+
+The communication protocol with the printer uses _no_ flow control
+in the traditional sense.  There is no hardware handshaking and
+no XON/XOFF flow control.  The driver must be aware of the size of
+the printer's buffer, and not send too much data at one time.  One
+of the codes the driver can send causes the printer to return a
+byte which seems to indicate how full its buffer is.  The driver
+could potentially use this information to keep feeding data as
+quickly as possible, but it currently just waits for the buffer to
+drain completely before sending more data.
+
+This protocol is the main reason that the driver is a standalone
+program, and not simply a driver module in ghostscript.  When I
+started work on the driver, I looked into making a gs driver module,
+but the architecture didn't seem to support two-way communication
+with the printer.  It's possible that later versions of ghostscript
+have removed this limitation, but I haven't looked at it lately.
+
+StyleWriters are rather unforgiving to experimentation.  Sending
+a control code that the printer doesn't understand or a buffer of
+image data that has been encoded incorrectly causes the printer to
+eject the page and reset itself.  
+
+
+Sending the question mark character '?' causes the printer to return
+a string identifying which model it is.  The string is terminated
+with a carriage return (character 0x0D).
+
+All other control codes seem to be of one of two types.  One type
+consists of four characters; three 0xFF bytes followed by a one-byte
+code.  The other type consists of one- or two-byte codes that are
+not preceeded by 0xFF bytes.  The first type causes the printer to
+send back one byte of data, usually some sort of error code or
+configuration information.  The second type causes no reply.  I
+speculate that the first type could be sent in the middle of a
+block of image data -- because of the data encoding used, there
+should never be three 0xFF bytes in a row in the image data.
+
+All of the definitions of control codes given here were arrived at
+by deduction or simple trial and error.  This should not be taken
+as definitive information.  If you find errors or omissions in this
+list, or figure out anything interesting that belongs here, please
+send your information to monroe@pobox.com.
+
+
+
+Known ident strings:
+
+"IJ10\r" = StyleWriter I
+"SW\r"   = StyleWriter II
+"SW3\r"  = StyleWriter 1200
+"CS\r"   = Color Stylewriter 1500, 2200, 2400 or 2500
+
+Type 1 codes (prefix these with "\xFF\xFF\xFF"):
+
+"I" - Ejects the page and resets the printer.
+"1" - Returns something about the status of the printer.
+"2" - Returns error status of the printer.  The return value may actually 
+	consist of bitfields.  Known return values:
+	0x00 -- nothing wrong
+	0x80 -- nothing wrong (possibly "bad command?)
+	0x04 -- printer is out of paper
+
+"B" - Returns the status of the printer's buffer.  The meaning of the
+	return value is different on different printer models.  On the 2400,
+	0xF8 means the buffer is empty, and lower values indicate more data
+	in the buffer.  On the 2500, 0x80 means the buffer is empty, and lower
+	values indicate more data in the buffer.  I don't know what values
+	indicate a completely full buffer on either model.
+
+"p" - Distinguishes between the printers that return 'CS'.  Known return 
+	values:
+	0x01 -- StyleWriter 2400
+	0x02 -- StyleWriter 2200
+	0x04 -- StyleWriter 1500
+	0x05 -- StyleWriter 2500
+	Honorable mention goes to the person who finds a printer with 0x03 as 
+	its 'p' code.
+
+"S" - Tells the printer to try again after it has reported being out of
+	paper.  This code is only known to work on the 2400/2500.
+
+"H" - Tells something about the configuration of the printer.  The return
+	value may actually consist of bitfields.  Known return values:
+	0x01 -- Black ink cartridge installed (in 2400/2500)
+	0x81 -- Color ink cartridge installed (in 2400/2500)
+
+"R" - Not sure, but it seems to have something to do with turning on
+	high-quality printing on the 2400/2500.
+
+
+Type 2 codes (sent by themselves):
+
+"?" - Tells the printer to return its ID string.
+
+"\x0C" - Marks the end of the data for a page.
+
+"nuA" - Start a new page on a StyleWriter I.
+
+"L" - Start a new page on all other StyleWriters.
+
+The following were gleaned from the MacOS printer drivers, and are used while
+setting up the printer.  I don't know exactly what some of them do.
+
+"D" - I don't remember.
+
+"N" - I don't remember.
+
+"m0nZAH" - Sets up for normal-quality printing.
+
+"m0sAB" - Sets up for high-quality printing.
+
+
+Image Data
+----------
+
+Bitmap data for the printer to print is sent in encoded blocks.
+Each block consists of a rectangle in printer coordinates, the
+two-byte length of the data, and the encoded scanlines in top-to-bottom
+order.  Printer coordinates have (0,0) at the top left of the
+_printable_ area of the page.  This means that the top and left
+margins must be dealt with by the driver.  Very similar formats
+are used for monochrome and color data.
+
+The overall format of a data block is as follows:
+
+'R' for monochrome or 'c' for color
+<LSB of rect.left>
+<MSB of rect.left>
+<LSB of rect.top>
+<MSB of rect.top>
+<LSB of rect.right>
+<MSB of rect.right>
+<LSB of rect.bottom>
+<MSB of rect.bottom>
+'G'
+<LSB of size>
+<MSB of size>
+<'size' bytes of encoded data>
+'\0'
+
+Bitmap data is encoded using a modified RLE scheme with differencing
+from previous lines.  The differencing algorithm is reset for each
+new block of data that is sent.  For color data, the bitmap for
+each plane (C,M,Y,K) is encoded separately and the encoded data
+for the planes in each scanline is interleaved in CMYK order.
+
+The encoding scheme is best described by the code in the function
+encodescanline().  The code was written while figuring out the
+encoding scheme by trial-and-error.  During the course of this it
+was redesigned and rewritten several times, and it's not pretty,
+but it works.
+

+ 45 - 0
README.troubleshooting

@@ -0,0 +1,45 @@
+Troubleshooting problems with lpstyl
+------------------------------------
+
+Before you send me email with questions about this driver, please do two
+things:
+
+- Read through this file and make sure your question isn't answered here
+
+- Add the flag '-vvvvv' to the invocation of 'lpstyl' in whichever driver
+	script you're using, and look at the output in the lpd-errs file.
+	Without this information, it's very unlikely that I'll be able to
+	make any useful suggestions.
+
+
+Q:	"lpr gives me the message 'jobs queued, but cannot start daemon.'"
+
+A:	lpd needs to be running for printing to work.  What's more, it needs
+	to run as root to work correctly.  lpd is normally run from the system
+	startup scripts, and most systems have a config file that determines
+	whether it is run at boot.  
+
+
+Q:	"Does lpstyl work with the StyleWriter 4100?"
+
+A:	No, but the 4100 is supported directly by GhostScript.  I believe
+	the option for the HP 550, 560, or 6xxC is what you want.
+
+
+Q:	"Where can I get the Apple drivers for the StyleWriter XXX?"
+
+A:	You'd be surprised how often I get this one.   The following page
+	will tell you which driver software supports your StyleWriter:
+	
+	http://til.info.apple.com/techinfo.nsf/artnum/n30153
+	
+	All of the printers supported by lpstyl are listed as working with 
+	either the "Color StyleWriter 1500" driver:
+	
+	http://asu.info.apple.com/swupdates.nsf/artnum/n10264
+	
+	or the "Color StyleWriter 2500" driver:
+	
+	http://asu.info.apple.com/swupdates.nsf/artnum/n10265
+
+

+ 862 - 0
adsp.c

@@ -0,0 +1,862 @@
+/*****************************************************************************
+	adsp.c
+
+	This file is part of the lpstyl package.
+
+	Copyright (C) 1996-2000 Monroe Williams (monroe@pobox.com)
+	All rights reserved.
+
+	Redistribution and use in source and binary forms, with or without
+	modification, are permitted provided that the following conditions
+	are met:
+	1. Redistributions of source code must retain the above copyright
+		notice, this list of conditions and the following disclaimer.
+	2. Redistributions in binary form must reproduce the above copyright
+		notice, this list of conditions and the following disclaimer in the
+		documentation and/or other materials provided with the distribution.
+
+	THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+	IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+	OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+	IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+	INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+	NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+	DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+	THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+	(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+	OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*****************************************************************************/
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netatalk/at.h>
+#include <atalk/nbp.h>
+#include <string.h>
+#include <termios.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "adsp.h"
+
+/* XXX -- For debugging only? */
+#include <stdio.h>
+#include <signal.h>
+
+/* local prototypes */
+int adsp_send_control(struct adsp_endp *endp, int flags);
+
+int adsp_send_data(struct adsp_endp *endp,
+						int flags,
+						char *data,
+						int len);
+
+int adsp_send_attn(struct adsp_endp *endp,
+						int flags,
+						u_int16_t code,
+						char *data,
+						int len);
+
+int adsp_recv_packet(struct adsp_endp *endp, int block);
+
+int adsp_process_packet(char *pkt, 
+					int pktsize, 
+					struct adsp_endp *endp);
+
+int adsp_setup_endp(struct adsp_socket *s,
+					struct adsp_endp *endp,
+					struct sockaddr_at *addr);
+
+/* This is in libatalk (in nbp_util.c), but it doesn't look like the
+	prototype is in any header files.
+*/
+int nbp_name(char *name, char **objp, char **typep, char **zonep);
+
+int adsp_open_socket(struct adsp_socket *s)
+{
+	struct sockaddr_at addr;
+
+	/* In case we fail */
+	if((s->socket = socket(AF_APPLETALK, SOCK_DGRAM, 0)) < 0)
+	{
+		return(s->socket);
+	}
+
+	bzero(&addr, sizeof(struct sockaddr_at));
+	addr.sat_len = sizeof(struct sockaddr_at);
+	addr.sat_family = AF_APPLETALK;
+	addr.sat_addr.s_net = ATADDR_ANYNET;
+	addr.sat_addr.s_node = ATADDR_ANYNODE;
+	addr.sat_port = ATADDR_ANYPORT;
+
+	if(bind(s->socket, (struct sockaddr*)&addr, addr.sat_len) < 0)
+	{
+		s->socket = -1;
+		return(-1);
+	}
+
+	{
+		int len = sizeof(struct sockaddr_at);
+		if(getsockname(s->socket, (struct sockaddr*)&addr, &len) < 0)
+		{
+			s->socket = -1;
+			return(-1);
+		}
+	}
+
+	bcopy(&addr, &(s->local_addr), sizeof(struct sockaddr_at));
+
+	/* XXX -- This could be done a lot better. */
+	s->lastConnID = getpid();
+	return(0);
+}
+
+int adsp_close_socket(struct adsp_socket *s)
+{
+	close(s->socket);
+	return(0);
+}
+
+int adsp_recv_packet(struct adsp_endp *endp, int block)
+{
+	char pkt[1024];
+	size_t len;
+	struct sockaddr_at from;
+	int fromlen;
+	int result = 0;
+	int retries = 0;
+	int rcv_result;
+
+
+	/* Set a the receive timeout on the socket. */
+	{
+		struct timeval tv;
+		if(block == 1)
+		{
+			/* Normal timeout will be 30 seconds */
+			tv.tv_sec = 30;
+			tv.tv_usec = 0;
+		}
+		else if(block == 2)
+		{
+			/* Special case for attn retries -- 5 seconds, don't transmit
+				keepalives.
+			*/
+			tv.tv_sec = 5;
+			tv.tv_usec = 0;
+		}
+		else
+		{
+			/* A timeout of 0.1 second is close enough to non-blocking
+				for our purposes. 
+			*/
+			tv.tv_sec = 0;
+			tv.tv_usec = 100000;
+		}
+		if(setsockopt(endp->local_socket->socket, 
+				SOL_SOCKET, 
+				SO_RCVTIMEO, 
+				&tv,
+				sizeof(struct timeval)) < 0)
+		{
+			perror("setsockopt");
+		}
+	}
+
+	while(1)
+	{
+		errno = 0;
+		fromlen = sizeof(struct sockaddr_at);
+		len = 1024;
+		bcopy(&(endp->remote_addr), &from, fromlen);
+		rcv_result = recvfrom(endp->local_socket->socket,
+				pkt,
+				len,
+				0,
+				(struct sockaddr*)&(from),
+				&fromlen);
+
+		if(rcv_result == -1)
+		{
+			if(errno == EWOULDBLOCK)
+			{
+				/* The timer expired. */
+				if(block == 1)
+				{
+					/* Blocking.  Send a keepalive and retry. */
+					switch(endp->state)
+					{
+						case ADSP_STATE_CLOSED:
+							/* There are times when we don't want to retry. */
+							return(-1);
+						break;
+					}
+
+					if(retries < 4)
+					{
+						/* Send an echo request packet and try again. */
+						adsp_send_control(endp, 
+								ADSPOP_PROBE_ACK | ADSPFLAG_ACK);
+						retries++;
+						continue;
+					}
+					else
+					{
+						/* That's all the retries.  The connection is dead. */
+						adsp_send_control(endp, ADSPOP_CLOSE);
+						endp->state = ADSP_STATE_CLOSED;
+							return(-1);
+					}
+				}
+				else
+				{
+					/* Non-blocking, no packet pending. */
+					return(0);
+				}
+			}
+			else
+			{
+				/* Unknown error.  Bail. */
+				perror("recvfrom");
+				return(-1);
+			}
+		}
+		else
+		{
+			len = rcv_result;
+			break;
+		}
+	}
+
+	if(pkt[0] != DDPTYPE_ADSP)
+		result = -1;
+
+	if(result == 0)
+	{
+		bcopy(&from, &(endp->remote_addr), sizeof(struct sockaddr_at));
+		result = adsp_process_packet(pkt+1, len-1, endp);
+	}
+
+	return(result);
+}
+
+int adsp_process_packet(char *pkt, 
+					int pktsize, 
+					struct adsp_endp *endp)
+{
+	int result = 1;
+	struct adsphdr *hdr = (struct adsphdr*)pkt;
+	char *data = pkt + SZ_ADSPHDR;
+	int len = pktsize - SZ_ADSPHDR;
+	int needsAck = 0;
+	int op = hdr->flags & ADSPOP_MASK;
+	int32_t temp;
+
+	/* convert the header from network to host byte order. */
+	hdr->src_conn_id = ntohs(hdr->src_conn_id);
+	hdr->first_byte_seq = ntohl(hdr->first_byte_seq);
+	hdr->next_rcv_seq = ntohl(hdr->next_rcv_seq);
+	hdr->rcv_window = ntohs(hdr->rcv_window);
+
+	if(hdr->flags & ADSPFLAG_ACK)
+		needsAck = 1;
+
+	if(hdr->flags & ADSPFLAG_ATTN)
+	{
+		struct adsp_attn_data *attn = (struct adsp_attn_data *)data;
+		attn->code = ntohs(attn->code);
+
+		/* attention packet */
+		if(endp->attn_send_seq + 1 == hdr->next_rcv_seq)
+		{
+			/* Update our state. */
+			endp->attn_send_seq += 1;
+		}
+
+		if(hdr->flags & ADSPFLAG_CONTROL)
+		{
+			/* This is an attention ack. */
+		}
+		else
+		{
+			/* This is an attention data packet. */
+			if((hdr->first_byte_seq == endp->attn_recv_seq) && 
+					!endp->attn_valid)
+			{
+				/* This is the right sequence number, and we have room to
+					store it.
+				*/
+				endp->attn_valid = 1;
+				endp->attn_code = attn->code;
+				endp->attn_size = len - 2;
+				bcopy(&(attn->data), endp->attn_buffer, endp->attn_size);
+
+				/* For next time... */
+				endp->attn_recv_seq++;
+
+				/* Send an ack. */
+				adsp_send_attn(endp, ADSPFLAG_CONTROL, attn->code, NULL, 0);
+			}
+			else if(hdr->first_byte_seq + 1 == endp->attn_recv_seq)
+			{
+				/* The ack got lost and they retransmitted.  
+					Send another ack. 
+				*/
+				adsp_send_attn(endp, ADSPFLAG_CONTROL, attn->code, NULL, 0);
+			}
+			else
+			{
+				/* Bad sequence number or no room for attention msg.
+					This packet will drop.
+				*/
+			}
+		}
+	}
+	else if(hdr->flags & ADSPFLAG_CONTROL)
+	{
+		struct adsp_cntl_data *cntl = (struct adsp_cntl_data *)data;
+		cntl->version = ntohs(cntl->version);
+		cntl->dst_conn_id = ntohs(cntl->dst_conn_id);
+		cntl->attn_rcv_seq = ntohl(cntl->attn_rcv_seq);
+
+		/* control packet */
+		endp->oldest_seq = hdr->next_rcv_seq;
+		endp->rmt_window_seq = hdr->next_rcv_seq + hdr->rcv_window - 1;
+
+		switch(op)
+		{
+			case ADSPOP_OPEN_REQ:
+			case ADSPOP_OPEN_REQ_ACK:
+			case ADSPOP_OPEN_ACK:
+				/* Handshaking is going well. */
+				endp->remote_connID = hdr->src_conn_id;
+				endp->send_seq = hdr->next_rcv_seq;
+				endp->attn_recv_seq = cntl->attn_rcv_seq;
+
+				/* This changes the connection state. */
+				if(op == ADSPOP_OPEN_REQ)
+					endp->state = ADSP_STATE_OPEN_RCVD;
+				else
+					endp->state = ADSP_STATE_OPEN;
+
+				if(op == ADSPOP_OPEN_REQ_ACK)
+				{
+					adsp_send_control(endp, ADSPOP_OPEN_ACK);
+					needsAck = 0;
+				}
+#if 0
+				/* This is handled from inside adsp_accept(). */
+				else if(op == ADSPOP_OPEN_REQ)
+				{
+					adsp_send_control(endp, ADSPOP_OPEN_REQ_ACK);
+					needsAck = 0;
+				}
+#endif
+			break;
+
+			case ADSPOP_PROBE_ACK:
+				endp->oldest_seq = hdr->next_rcv_seq;
+				endp->rmt_window_seq = hdr->next_rcv_seq + hdr->rcv_window - 1;
+			break;
+
+			case ADSPOP_RESET_REQ:
+				temp = hdr->first_byte_seq - endp->recv_seq;
+				if((temp >= 0) && (temp <= endp->recv_window))
+				{
+					/* This is a legal forward reset.  Do it. */
+					endp->recv_seq = hdr->first_byte_seq;
+					endp->recv_next_input = endp->recv_next_output;
+					endp->recv_window = ADSP_RECV_BUFFER_SIZE;
+				}
+
+				/* Ack regardless. */
+				adsp_send_control(endp, ADSPOP_RESET_ACK);
+				needsAck = 0;
+			break;
+
+			case ADSPOP_RESET_ACK:
+				/* XXX -- something should happen here. */
+			break;
+
+			case ADSPOP_RETRANS:
+				/* Slow down, we lost someone... */
+				endp->send_seq = hdr->next_rcv_seq;
+				endp->rmt_window_seq = hdr->next_rcv_seq + hdr->rcv_window - 1;
+			break;
+
+			/* XXX -- handle all the cases here... */
+			default:
+				endp->state = ADSP_STATE_CLOSED;
+			break;
+
+		}
+	}
+	else
+	{
+		/* data packet */
+		endp->oldest_seq = hdr->next_rcv_seq;
+		endp->rmt_window_seq = hdr->next_rcv_seq + hdr->rcv_window - 1;
+
+		if(endp->recv_seq == hdr->first_byte_seq)
+		{
+			if(len <= endp->recv_window)
+			{
+				if(len > 0)
+				{
+					int i;
+					/* This packet contains data.  
+						pump it into the ring buffer.  
+					*/
+					for(i=0; i < len; i++)
+					{
+						endp->recv_buffer[endp->recv_next_input++] = data[i];
+						endp->recv_next_input %= ADSP_RECV_BUFFER_SIZE;
+					}
+					endp->recv_window -= len;
+					endp->recv_seq += len;
+
+					needsAck = 1;
+				}
+
+				if(hdr->flags & ADSPFLAG_EOM)
+				{
+					/* The EOM consumes a sequence number. */
+					endp->recv_seq++;
+
+					/* It also means we should push the 
+						results to the client. 
+					*/
+					result = 2;
+
+					needsAck = 1;
+				}
+
+			}
+			else
+			{
+				/* The input packet exceeded our window.  Drop it. */
+			}
+		}
+		else
+		{
+			/* out-of-sequence packet.  Drop it on the floor. */
+			/* adsp_send_control(endp, ADSPOP_RETRANS | ADSPFLAG_ACK); */
+		}
+	}
+
+
+	if(needsAck)
+	{
+		/* Sender requested an ack, and one hasn't been sent yet. */
+		adsp_send_control(endp, ADSPOP_PROBE_ACK);
+	}
+
+	return(result);
+}
+
+
+int adsp_send_data(struct adsp_endp *endp,
+						int flags,
+						char *data,
+						int len)
+{
+	struct
+	{
+		char ddptype;
+		struct adsp_packet pkt;
+	} __attribute__((packed)) p;
+ 
+	p.ddptype = DDPTYPE_ADSP;
+	p.pkt.hdr.src_conn_id = htons(endp->connID);
+	p.pkt.hdr.first_byte_seq = htonl(endp->send_seq);
+	p.pkt.hdr.next_rcv_seq = htonl(endp->recv_seq);
+	p.pkt.hdr.rcv_window = htons(endp->recv_window);
+	p.pkt.hdr.flags = flags;
+
+	if(data != NULL && len != 0)
+		bcopy(data, &(p.pkt.data), len);
+
+	if(sendto(endp->local_socket->socket, 
+			  &p, 
+			  1 + sizeof(struct adsphdr) + len,
+			  0,
+			  (struct sockaddr*)&(endp->remote_addr),
+			  sizeof(struct sockaddr_at)) < 0)
+	{
+		return(-1);
+	}
+
+	endp->send_seq += len;
+	if(flags & ADSPFLAG_EOM)
+		endp->send_seq += 1;
+
+	return(0);
+}
+
+int adsp_send_attn(struct adsp_endp *endp,
+						int flags,
+						u_int16_t code,
+						char *data,
+						int len)
+{
+	struct
+	{
+		char ddptype;
+		struct adsp_attn_packet pkt;
+	} p;
+ 
+	p.ddptype = DDPTYPE_ADSP;
+	p.pkt.hdr.src_conn_id = htons(endp->connID);
+	p.pkt.hdr.first_byte_seq = htonl(endp->attn_send_seq);
+	p.pkt.hdr.next_rcv_seq = htonl(endp->attn_recv_seq);
+	p.pkt.hdr.rcv_window = htons(0);
+	p.pkt.hdr.flags = flags | ADSPFLAG_ATTN;
+	p.pkt.data.code = htons(code);
+
+	if(data != NULL && len > 0)
+		bcopy(data, &(p.pkt.data.data), len);
+
+	if(sendto(endp->local_socket->socket, 
+			  &p, 
+			  1 + sizeof(struct adsphdr) + 2 + len,
+			  0,
+			  (struct sockaddr*)&(endp->remote_addr),
+			  sizeof(struct sockaddr_at)) < 0)
+	{
+		return(-1);
+	}
+ 
+	return(0);
+}
+
+int adsp_send_control(struct adsp_endp *endp,
+						int flags)
+{
+	int sendSize;
+	struct
+	{
+		char ddptype;
+		struct adsp_cntl_packet pkt;
+	} p;
+ 
+	p.ddptype = DDPTYPE_ADSP;
+	p.pkt.hdr.src_conn_id = htons(endp->connID);
+	p.pkt.hdr.first_byte_seq = htonl(endp->send_seq);
+	p.pkt.hdr.next_rcv_seq = htonl(endp->recv_seq);
+	p.pkt.hdr.rcv_window = htons(endp->recv_window);
+	p.pkt.hdr.flags = ADSPFLAG_CONTROL | flags;
+
+	switch(flags & ADSPOP_MASK)
+	{
+		default:
+			sendSize = SZ_ADSPHDR + 1;
+		break;
+
+		case ADSPOP_OPEN_REQ:
+		case ADSPOP_OPEN_ACK:
+		case ADSPOP_OPEN_REQ_ACK:
+		case ADSPOP_OPEN_NAK:
+			sendSize = sizeof(p);
+			p.pkt.data.version = htons(ADSP_VERSION);
+			p.pkt.data.dst_conn_id = htons(endp->remote_connID);
+			p.pkt.data.attn_rcv_seq = htonl(endp->attn_recv_seq);
+		break;
+	}
+ 
+	if(sendto(endp->local_socket->socket, 
+			  &p, 
+			  sendSize,
+			  0,
+			  (struct sockaddr*)&(endp->remote_addr),
+			  sizeof(struct sockaddr_at)) < 0)
+	{
+		return(-1);
+	}
+ 
+	return(0);
+}
+
+int adsp_setup_endp(struct adsp_socket *s,
+					struct adsp_endp *endp,
+					struct sockaddr_at *addr)
+{
+	endp->local_socket = s;
+	if(addr != NULL)
+		bcopy(addr, &(endp->remote_addr), sizeof(struct sockaddr_at));
+	endp->connID = ++(s->lastConnID);
+	endp->remote_connID = 0;
+	endp->state = ADSP_STATE_CLOSED;
+
+	endp->send_seq = 0;
+	endp->oldest_seq = 0;
+	endp->rmt_window_seq = 0;
+	endp->recv_seq = 0;
+	endp->recv_window = ADSP_RECV_BUFFER_SIZE;
+	endp->recv_next_input = 0;
+	endp->recv_next_output = 0;
+
+	endp->attn_send_seq = 0;
+	endp->attn_recv_seq = 0;
+	endp->attn_valid = 0;
+
+	return(0);
+}
+
+
+int adsp_connect(	struct adsp_socket *s, 
+					struct adsp_endp *endp,
+					char *name)
+{
+	int count;
+	struct nbpnve nn;
+	char *Obj = "=";
+	char *Type = "=";
+	char *Zone = "*";
+
+	nbp_name(name, &Obj, &Type, &Zone);
+	count = nbp_lookup(Obj, Type, Zone, &nn, 1);
+	if(count < 1)
+		return(-1);
+	
+	/* Looking up the name worked.  Set up the connection state. */
+	adsp_setup_endp(s, endp, &(nn.nn_sat));
+
+	/* Start the handshaking process. */
+	if(adsp_send_control(endp, ADSPOP_OPEN_REQ) < 0)
+		return(-1);
+
+	endp->state = ADSP_STATE_OPEN_SENT;
+
+	while(endp->state == ADSP_STATE_OPEN_SENT)
+	{
+		if(adsp_recv_packet(endp, 1) < 0)
+			return(-1);
+	}
+
+	if(endp->state != ADSP_STATE_OPEN)
+		return(-1);
+
+	return(0);
+}
+
+int adsp_disconnect(struct adsp_endp *endp)
+{
+	if(endp->state == ADSP_STATE_OPEN)
+		adsp_send_control(endp, ADSPOP_CLOSE);
+
+	endp->state = ADSP_STATE_CLOSED;
+
+	return(0);
+}
+
+int adsp_listen(struct adsp_socket *s,
+				struct adsp_endp *endp)
+{
+	adsp_setup_endp(s, endp, NULL);
+	endp->state = ADSP_STATE_CLOSED;
+
+	/* We'll need to set up enough of the remote address to
+		be able to receive packets.
+	*/
+	bzero(&(endp->remote_addr), sizeof(struct sockaddr_at));
+	endp->remote_addr.sat_len = sizeof(struct sockaddr_at);
+	endp->remote_addr.sat_family = AF_APPLETALK;
+	endp->remote_addr.sat_addr.s_net = ATADDR_ANYNET;
+	endp->remote_addr.sat_addr.s_node = ATADDR_ANYNODE;
+	endp->remote_addr.sat_port = ATADDR_ANYPORT;
+
+	while(endp->state == ADSP_STATE_CLOSED)
+	{
+		adsp_recv_packet(endp, 1);
+	}
+
+	if(endp->state == ADSP_STATE_OPEN_RCVD)
+		return(1);
+	
+	return(0);
+}
+
+int adsp_accept(struct adsp_endp *endp)
+{
+	if(endp->state != ADSP_STATE_OPEN_RCVD)
+		return(-1);
+	
+	if(adsp_send_control(endp, ADSPOP_OPEN_REQ_ACK) < 0)
+		return(-1);
+	
+	endp->state = ADSP_STATE_OPEN_SENT;
+
+	while(endp->state == ADSP_STATE_OPEN_SENT)
+	{
+		if(adsp_recv_packet(endp, 1) < 0)
+			return(-1);
+	}
+
+	if(endp->state != ADSP_STATE_OPEN)
+		return(-1);
+
+	return(0);
+}
+
+int adsp_write(struct adsp_endp *endp, char *data, int len)
+{
+	u_int32_t bufferStart = endp->send_seq;
+	u_int32_t bufferEnd = bufferStart + len;
+	int32_t bytesNow, bytesToAck;
+	int flags;
+	int waitResult;
+
+	/* We'll stay here until the other end has ack'ed the entire buffer.
+		Technically this is inefficient, but at least it's easy.
+	*/
+	bytesToAck = bufferEnd - endp->oldest_seq;
+	while(bytesToAck > 0)
+	{
+		flags = ADSPFLAG_ACK;
+		/* Figure out how many bytes we want to try and transmit. */
+		bytesNow = bufferEnd - endp->send_seq;
+		if(bytesNow < 0)
+			bytesNow = 0;
+
+		if(bytesNow > ADSP_MAX_DATA_SIZE)
+			bytesNow = ADSP_MAX_DATA_SIZE;
+		if(bytesNow > (int32_t)(endp->rmt_window_seq - endp->send_seq))
+			bytesNow = endp->rmt_window_seq - endp->send_seq;
+		
+		if(bytesNow > 0)
+		{
+			/* The last chunk of this write will be marked EOM. */
+			if(bytesNow == bufferEnd - endp->send_seq)
+				flags |= ADSPFLAG_EOM;
+
+			if(adsp_send_data(endp, flags, 
+					data + (endp->send_seq - bufferStart), 
+					bytesNow) < 0)
+			{
+				return(-1);
+			}
+		}
+
+		waitResult = adsp_recv_packet(endp, 1);
+		if(waitResult < 0)
+			return(-1);
+
+		bytesToAck = bufferEnd - endp->oldest_seq;
+	}
+
+	return(len);
+}
+
+int adsp_idle(struct adsp_endp *endp)
+{
+	/* Deal with all pending incoming packets. */
+	while(adsp_recv_packet(endp, 0) > 0)
+		;
+
+	return(0);
+}
+
+int adsp_read(struct adsp_endp *endp, char *data, int len)
+{
+	int bytesRead = 0;
+	int rcv_result;
+
+	do
+	{
+
+		rcv_result = adsp_recv_packet(endp, 1);
+		if(rcv_result < 0)
+		{
+			return(-1);
+		}
+		
+		while((bytesRead < len) && 
+				(endp->recv_next_output != endp->recv_next_input))
+		{
+			data[bytesRead++] = endp->recv_buffer[endp->recv_next_output++];
+			endp->recv_next_output %= ADSP_RECV_BUFFER_SIZE;
+			endp->recv_window++;
+		}
+	} while((bytesRead < len) && (rcv_result != 0));
+
+	return(bytesRead);
+}
+
+int adsp_read_nonblock(struct adsp_endp *endp, char *data, int len)
+{
+	int rcv_result;
+	int bytesRead = 0;
+
+	do
+	{
+		rcv_result = adsp_recv_packet(endp, 0);
+		if(rcv_result < 0)
+		{
+			if(bytesRead == 0)
+				bytesRead = -1;
+
+			return(bytesRead);
+		}
+
+		while((bytesRead < len) && 
+				(endp->recv_next_output != endp->recv_next_input))
+		{
+			data[bytesRead++] = endp->recv_buffer[endp->recv_next_output++];
+			endp->recv_next_output %= ADSP_RECV_BUFFER_SIZE;
+			endp->recv_window++;
+		}
+
+
+	} while((bytesRead < len) && (rcv_result > 0));
+
+	return(bytesRead);
+}
+
+int adsp_read_attn(struct adsp_endp *endp, u_int16_t *code, char *data)
+{
+	int rcv_result;
+	
+	rcv_result = adsp_recv_packet(endp, 2);
+	if(rcv_result < 0)
+		return(-1);
+
+	if(endp->attn_valid)
+	{
+		/* An attention message has been received. */
+		endp->attn_valid = 0;
+		*code = endp->attn_code;
+		bcopy(endp->attn_buffer, data, endp->attn_size);
+		return(endp->attn_size);
+	}
+
+	return(-1);
+}
+
+int adsp_write_attn(struct adsp_endp *endp, u_int16_t code, char *data, int len)
+{
+	int result = 0;
+	u_int32_t initial_seq = endp->attn_send_seq;
+
+	adsp_send_attn(endp, 0, code, data, len);
+	do
+	{
+		result = adsp_recv_packet(endp, 1);
+		if(result == -1)
+			break;
+
+		if(endp->attn_send_seq != initial_seq)
+		{
+			/* The packet has been acknowledged.  We're done. */
+			return(0);
+		}
+	} while(1);
+
+	return(result);
+}
+int adsp_fwd_reset(struct adsp_endp *endp)
+{
+	if(adsp_send_control(endp, ADSPOP_RESET_REQ) < 0)
+		return(-1);
+
+	/* XXX -- this should use timers and retries. */
+	return(0);
+}

+ 182 - 0
adsp.h

@@ -0,0 +1,182 @@
+/******************************************************************************
+	adsp.h
+
+	This file is part of the lpstyl package.
+
+	Copyright (C) 1996-2000 Monroe Williams (monroe@pobox.com)
+	All rights reserved.
+
+	Redistribution and use in source and binary forms, with or without
+	modification, are permitted provided that the following conditions
+	are met:
+	1. Redistributions of source code must retain the above copyright
+		notice, this list of conditions and the following disclaimer.
+	2. Redistributions in binary form must reproduce the above copyright
+		notice, this list of conditions and the following disclaimer in the
+		documentation and/or other materials provided with the distribution.
+
+	THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+	IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+	OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+	IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+	INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+	NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+	DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+	THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+	(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+	OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+******************************************************************************/
+#include <machine/endian.h>
+
+struct adsphdr {
+    u_int16_t	src_conn_id;
+	u_int32_t	first_byte_seq;
+	u_int32_t	next_rcv_seq;
+    u_int16_t	rcv_window;
+	u_int8_t	flags;
+} __attribute__((packed));
+
+struct adsp_data
+{
+	char		data[572];
+} __attribute__((packed));
+
+struct adsp_attn_data
+{
+	u_int16_t	code;
+	char		data[570];
+} __attribute__((packed));
+
+struct adsp_cntl_data
+{
+	u_int16_t	version;
+	u_int16_t	dst_conn_id;
+	u_int32_t	attn_rcv_seq;
+} __attribute__((packed));
+
+
+struct adsp_cntl_packet
+{
+	struct adsphdr hdr;
+	struct adsp_cntl_data data;
+} __attribute__((packed));
+
+struct adsp_attn_packet
+{
+	struct adsphdr hdr;
+	struct adsp_attn_data data;
+} __attribute__((packed));
+
+struct adsp_packet
+{
+	struct adsphdr hdr;
+	char data[572];
+} __attribute__((packed));
+
+#ifndef DDPTYPE_ADSP
+	#define DDPTYPE_ADSP 7
+#endif
+
+enum
+{
+	ADSP_VERSION = 0x0100,
+	SZ_ADSPHDR = 13,
+
+	ADSPOP_MASK			= 0x0F,
+	ADSPOP_PROBE_ACK	= 0x00,
+	ADSPOP_OPEN_REQ		= 0x01,
+	ADSPOP_OPEN_ACK		= 0x02,
+	ADSPOP_OPEN_REQ_ACK	= 0x03,
+	ADSPOP_OPEN_NAK		= 0x04,
+	ADSPOP_CLOSE		= 0x05,
+	ADSPOP_RESET_REQ	= 0x06,
+	ADSPOP_RESET_ACK	= 0x07,
+	ADSPOP_RETRANS		= 0x08,
+
+	ADSPFLAG_CONTROL	= 0x80,
+	ADSPFLAG_ACK		= 0x40,
+	ADSPFLAG_EOM		= 0x20,
+	ADSPFLAG_ATTN		= 0x10,
+
+	ADSP_RECV_BUFFER_SIZE	= 2048,
+	ADSP_ATTN_BUFFER_SIZE	= 570,
+	ADSP_MAX_DATA_SIZE		= 572,
+
+	ADSP_STATE_CLOSED		= 0,
+	ADSP_STATE_OPEN_SENT,
+	ADSP_STATE_OPEN_RCVD,
+	ADSP_STATE_OPEN,
+	ADSP_STATE_HALF_CLOSED
+
+};
+
+/* State for an ADSP socket. */
+struct adsp_socket
+{
+	int					socket;
+	struct sockaddr_at	local_addr;
+	u_int16_t			lastConnID;
+};
+
+/* Endpoint state for an ADSP connection. */
+struct adsp_endp
+{
+	/* connection state */
+	struct adsp_socket	*local_socket;
+	struct sockaddr_at	remote_addr;
+	u_int16_t			connID;
+	u_int16_t			remote_connID;
+	u_int16_t			state;
+	
+	/* sequencing variables */
+	u_int32_t	send_seq;		/* next byte to transmit */
+	u_int32_t	oldest_seq;		/* oldest byte in local send queue */
+	u_int32_t	rmt_window_seq;	/* last byte of remote window */
+	u_int32_t	recv_seq;		/* next byte to receive */
+	u_int32_t	recv_window;	/* local window size */
+
+	/* receive ring buffer */
+	char	recv_buffer[ADSP_RECV_BUFFER_SIZE];
+	int		recv_next_input;
+	int		recv_next_output;
+
+	/* attention message handling */
+	u_int32_t	attn_send_seq;	/* next outgoing sequence number */
+	u_int32_t	attn_recv_seq;	/* next incoming sequence number */
+	int			attn_valid;		/* true if an attention message is pending. */
+	char		attn_buffer[ADSP_ATTN_BUFFER_SIZE];
+	int			attn_size;		/* length of the message in the buffer */
+	u_int16_t	attn_code;		/* code on this message */
+};
+
+
+int adsp_open_socket(struct adsp_socket *s);
+int adsp_close_socket(struct adsp_socket *s);
+
+int adsp_connect(	struct adsp_socket *s, 
+					struct adsp_endp *endp,
+					char *name);
+
+int adsp_disconnect(struct adsp_endp *endp);
+
+int adsp_listen(struct adsp_socket *s,
+				struct adsp_endp *endp);
+int adsp_accept(struct adsp_endp *endp);
+
+int adsp_idle(struct adsp_endp *endp);
+
+int adsp_read(struct adsp_endp *endp, char *data, int len);
+int adsp_read_nonblock(struct adsp_endp *endp, char *data, int len);
+int adsp_write(struct adsp_endp *endp, char *data, int len);
+
+/* There must be at least ADSP_RECV_BUFFER_SIZE bytes at *data to receive
+	the message.
+*/
+int adsp_read_attn(struct adsp_endp *endp, u_int16_t *code, char *data);
+
+int adsp_write_attn(struct adsp_endp *endp, 
+					u_int16_t code, 
+					char *data, 
+					int len);
+
+int adsp_fwd_reset(struct adsp_endp *endp);

+ 2089 - 0
lpstyl.c

@@ -0,0 +1,2089 @@
+/* 
+	lpstyl.c
+	version 0.9.9
+
+	Copyright (C) 1996-2000 Monroe Williams (monroe@pobox.com)
+	All rights reserved.
+
+	Redistribution and use in source and binary forms, with or without
+	modification, are permitted provided that the following conditions
+	are met:
+	1. Redistributions of source code must retain the above copyright
+		notice, this list of conditions and the following disclaimer.
+	2. Redistributions in binary form must reproduce the above copyright
+		notice, this list of conditions and the following disclaimer in the
+		documentation and/or other materials provided with the distribution.
+
+	THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+	IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+	OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+	IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+	INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+	NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+	DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+	THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+	(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+	OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+	-----
+	
+	This is a printer driver for Apple StyleWriter printers.  Here is the 
+	known status of printers it supports:
+	
+	- Color Stylewriter 2500:
+		Fully supported in 360x360dpi, B/W and Color.
+	- Color Stylewriter 2400:
+		Fully supported in 360x360dpi, B/W and Color.
+	- Color Stylewriter 2200:
+		Supposed to work as of 0.9.9 (I haven't seen it, though.)
+	- Color Stylewriter 1500:
+		Works in B/W and Color.
+	- StyleWriter III, StyleWriter 1200:
+		"Works peachy." -- Charles Broderick, bbroder@mit.edu
+	- StyleWriter II:
+		Reported to work quite well.
+	- StyleWriter I:
+		Working again as of version 0.9.7.
+	- Other stylewriters?:
+		Give it a try.  The code will treat any printer it doesn't
+		recognize like a SWII, so it just might work.  (That's how
+		the SW2400 first worked, anyway.)
+
+	Future releases of this driver will have more documentation, including 
+	some explanation of the control codes I have figured out.  Stay tuned.
+	
+	Any feedback should be directed to monroe@pobox.com.
+*/
+
+/* Version history:
+	0.9.9:
+		- Fixed a problem with identifying the StyleWriter 2400 and 2500.
+		- Added changes from Paulo Abreu <paulotex@geocities.com>
+			to support the StyleWriter 2200.
+		- Initial support for the Apple StyleWriter Ethertalk Adapter
+			(Apple part number M4877).  This is a box which connects a
+			number of different models of StyleWriter directly to an
+			Ethernet network.  This requires netatalk support on the
+			machine you're compiling on.  It also required an 
+			implementation of a subset of the ADSP protocol for the 
+			Appletalk stack, which is in the file adsp.c.  This device
+			looks very similar to the "Farallon EtherMac iPrint Adapter SL",
+			(it performs the same function and appears to use the same plastics),
+			and I'd like to find out if the Farallon device works the same.
+	0.9.8:
+		- Added changes from Takashi Oe <toe@unlinfo.unl.edu> to
+			support the StyleWriter 1500.
+		- Made the code's entire concept of paper sizes and margins much
+			less confused.  
+		- Added A4 paper support.  (Finally.)
+		- Added a mechanism for specifying arbitrary paper sizes on 
+			the command line.  -W and -H (yes, capital letters) specify
+			the width and height of the paper in _pixels_.  One pixel is 
+			1/360 of an inch.  In the normal case, these numbers should 
+			match the dimensions of the input file.  No validity checking 
+			is done on the paper sizes thus received, and I'm sure one could
+			make things go rather badly by entering wildly inaccurate numbers.
+			Be nice.
+		- Improved out-of-paper handling.  The code for all printer
+			types should now handle out-of-paper conditions by retrying.
+	0.9.7:
+		- Finally got hold of a StyleWriter I.  
+			It didn't work.  
+			It does now.
+	0.9.6:
+		- Incorporated new information about control codes from 
+			Paul Mackerras <paulus@cs.anu.edu.au> to make the 
+			StyleWriter 2500 work.
+		- Improved out-of-paper handling on 2400/2500.  (Instead of
+			retransmitting data, we now use another control code to make
+			the printer try again.)  Thanks to Paul Mackerras for coming
+			up with the right control code.
+		- The driver is now aware of whether a color ink cartridge is
+			installed in the 2400/2500.
+	0.9.5:
+		- Finally, really fixed the finish-page code to wait for the
+			right thing.  (Hooray for trial and error...)
+		- Figured out how long a microsecond was (duh...) and fixed the
+			various retry-loops to sleep for 0.1 second like I originally
+			intended.
+		- Now deals with the printer running out of paper by retrying
+			every 30 seconds until the problem goes away (or someone
+			puts a stop to it).
+		- No longer reset before every page.  It's unnecessary.  Now we
+			reset before the first page of the job, and after printing
+			is unpaused with SIGUSR1.
+	0.9.4:
+		- Now recognizes the SW1200/SW3, courtesy of Charles Broderick 
+			<bbroder@mit.edu>.
+		- Uses non-blocking I/O to read from the printer.
+	0.9.3:
+		- Less confusing comments about the default margins.
+		- Now checks for what I think are error codes from the printer
+			and terminates on errors.
+		- Code that gets printer status is now less likely to hang.
+		- Disabled the expanded vertical print area on the SW2400 until
+			I can figure out why it's causing errors on some pages.
+	0.9.2:
+		- Generalized the printable area and margins constants so that they can
+			be set to different values after the printer is identified.  The 
+			SW2400 case in printerSetup now sets larger print area and smaller 
+			margins to match what the 2400 can do.  More printers will be added
+			as I get their specs.
+		- Added a flag -m to disable cropping for top and left margins.
+		    Use this flag if you want to see the entire image file.
+		    (Files rendered for a full 8.5"x11" page will be offset down and to
+		    the right from where they should be on the page.)
+	0.9.1:
+		- Fixed a problem with waiting for the last part of a page to
+			print.  The problem would cause the next page's reset to
+			happen too soon, chopping off the last part of the page.
+	0.9:
+		- Fully functional COLOR on a Color Stylewriter 2400!!!
+		- Added signal handlers for common kill signals that eject the
+			page and reset the printer.
+		- Two new input formats: 'bit' and 'bitcmyk'.
+		- Flags, flags, flags.  Type 'lpstyl -?' to see usage.
+		- Now sets up the serial port properly (57600 baud, raw mode)
+			using termios.
+		- Uses a larger buffer size on Color StyleWriter 2400.
+	0.2.0d2:
+		- Fully functional in B&W on a Color StyleWriter 2400 and a
+			SWII
+	0.2.0d1:
+		- Changed a couple of things to make it work better (but still not
+			quite right) on a SWII.
+		- First try at using the printer's buffer more efficiently.
+	0.2.0d0:
+		- First attempt at direct StyleWriter II support, with help from
+			Jack Howarth <howarth@bromo.med.uc.edu>.  This includes the
+			first attempt at compensating for what looks like a "scanline
+			differencing" algorithm the SWII uses, conditional on the 
+			returned printer type.
+		- Included a modification suggested by Stefan Schmiedl
+			<101321.3101@CompuServe.COM> -- reducing the maximum
+			amount of data that will be sent at once from 0x10000
+			to 0x4000.  (Apparently, some more complex files caused
+			the printer's buffer to overflow.)
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifdef unix
+	#include <sys/types.h>
+	#include <termios.h>
+	#include <unistd.h>
+	#include <errno.h>
+	#include <signal.h>
+	#include <fcntl.h>
+#else
+	/* I sometimes compile this under MacOS for various reasons.  This
+		source file gets #included in another one which includes the right
+		header files and defines dummy versions of various unix-specific functions.  
+	*/
+#endif
+
+#ifdef ATALK
+	#include <netatalk/at.h> 
+	#include <atalk/nbp.h>
+
+	#include "adsp.h"
+#endif
+
+/* Just a few prototypes...  Yeah, I was raised on ANSI C.  So sue me. */
+void fixPageSizes(void);
+int main(int argc, char **argv);
+int printStdin(void);
+size_t encodescanline(unsigned char *src, size_t srcLen, unsigned char *dst);
+void sendEncodedData(unsigned char *buffer, size_t size);
+void sendrect(long top, long left, long bottom, long right);
+
+size_t inputRead(void *buffer, size_t size);
+int inputGetc(void);
+void inputPutback(int c);
+
+void printerSetup(void);
+size_t comm_printer_read(void *buffer, size_t size);
+size_t comm_printer_write(void *buffer, size_t size);
+size_t comm_printer_writestring(char *buffer);
+void comm_printer_writeFFFx(char x);
+int comm_printer_getc(void);
+int comm_printer_getc_block(void);
+void ejectAndReset(void);
+void finishPage(void);
+void waitNonBusy(int canHandlePaperOut);
+void printerFlushInput(void);
+
+void print_error(char *s);
+void print_info(char *s);
+void waitStatus(int stat, int canHandlePaperOut);
+int getStatus(int which);
+
+
+/***** Some relevant constants *****/
+/* NOTE: To change these values on a per-printer basis, add a case
+	to the "Printer-specific setup" in printerSetup().  Copying
+	the SW2400 case is a good place to start.
+*/
+
+/* size of the printer's buffer */
+long MAX_BUFFER = 0x00004000;
+
+/* Default to letter-size paper, which is 8.5 x 11 inches @ 360dpi */
+long PAGE_WIDTH = 3060;
+long PAGE_HEIGHT = 3960;
+
+long PRINT_WIDTH;
+long PRINT_HEIGHT;
+long TOP_MARGIN;	
+long BOTTOM_MARGIN;
+long LEFT_MARGIN;
+
+/* number of BYTES of each scanline to leave off the left.  (pixels / 8) */
+long LEFT_MARGIN_BYTES;
+long PRINT_ROWBYTES;
+
+#ifndef nil
+	#define nil  ((void*)0)
+#endif
+
+/* Do we really care? */
+int verbose = 0;
+
+volatile int paused = 0;
+
+char *ProcName;
+
+void handler();
+void cleanup();
+void usage(void);
+int readFileHeader(void);
+size_t readFileScanline(char *bufK, char *bufC, char *bufM, char *bufY);
+size_t appendEncode(size_t length, char *in, char *last, char *out);
+
+/* Variables printStdin() needs which can also be set by arguments */
+long height = -1, width = -1;
+enum
+{
+	FILE_PBMRAW,
+	FILE_BIT,
+	FILE_BITCMYK
+};
+int fileType = FILE_PBMRAW;
+int fileIsColor = 0;		/* Set to true in readFileHeader if the input
+								file contains color information. */
+int canPrintColor = 0;		/* set to true in printerSetup() if a color ink
+								cartridge is installed. */
+
+int printQuality = 1;		/* 0 = draft (not implemented yet), 1 = normal,
+								2 = high. */
+size_t rowbytes;
+
+enum
+{
+	KIND_SW1,
+	KIND_SW2,
+	KIND_SW3,
+	KIND_SW1500,
+	KIND_SW2200,
+	KIND_SW2400,
+	KIND_SW2500,
+	KIND_SWUNKNOWN
+};
+int printerType;
+int noMargins = 0;
+int doReset = 1;
+int bufferHelp = 0;
+int atalk_connection = 0;
+char *atalk_username = NULL;
+
+#ifdef ATALK
+	struct adsp_socket s1, s2;
+	struct adsp_endp end1, end2;
+
+	int at_printer_open(char *name);
+	void at_printer_kill(void);
+	void at_printer_setstatus(char *status);
+#endif
+
+long USLEEP_TIME = 100000;	/* 0.1 seconds for usleep() */
+
+void fixPageSizes(void)
+{
+	/* This routine assumes that the following are set up before it is called:
+		(these depend only on the paper size)
+			PAGE_WIDTH		- width of the paper
+			PAGE_HEIGHT		- height of the paper
+		(these depend only on the capabilities of the printer's hardware)
+			PRINT_WIDTH 	- width of the printer's imageable area
+			LEFT_MARGIN		- printer's left margin
+			TOP_MARGIN		- printer's top margin
+			BOTTOM_MARGIN	- printer's bottom margin
+		
+		All values are in pixels.  There are 360 pixels per inch. 
+	*/
+
+	/* the height of the printer's imageable area */
+	PRINT_HEIGHT = PAGE_HEIGHT - (TOP_MARGIN + BOTTOM_MARGIN + 1);
+
+	/* The number of imageable bytes in each scanline */
+	PRINT_ROWBYTES = (PRINT_WIDTH + 7) / 8;
+
+	/* The number of bytes to chop off the left of each scanline to make a left margin */
+	LEFT_MARGIN_BYTES = (LEFT_MARGIN + 7) / 8;
+}
+
+int main(int argc, char **argv)
+{	
+	int ch;
+	
+	ProcName = argv[0];
+	
+	atalk_username = getlogin();
+
+	/* Enable print pause */
+	signal(SIGUSR1, handler);
+	
+	/* Make sure we eject the page if the driver is killed off. */
+	signal(SIGTERM, cleanup);
+	signal(SIGHUP, cleanup);
+	signal(SIGINT, cleanup);
+	
+	/* Figure out the options */
+	while((ch = getopt(argc, argv, "a:t:f:h:w:H:W:q:p:u:v?mb")) != -1)
+	{
+		switch(ch)
+		{
+			case 'f':		/* file to open as printer device */
+			{
+				int fd;
+				
+				fd = open(optarg, O_RDWR, 0);
+				if(fd == -1)
+				{
+					perror("open");
+					exit(1);
+				}
+				else
+				{
+					dup2(fd, 1);
+				}
+			}
+			break;
+
+			case 'a':	/* Printer is on an AppleTalk box. */
+			{
+#ifdef ATALK
+				at_printer_open(optarg);
+				atalk_connection = 1;
+#else
+				fprintf(stderr, "%s: AppleTalk support not compiled in.\n", 
+						ProcName);
+				exit(1);
+#endif
+			}
+			break;
+
+			case 'u':	/* username for setting status of appletalk printer */
+				atalk_username = optarg;
+			break;
+			
+			case 't':		/* file format */
+				if(strcmp(optarg, "pbmraw") == 0)
+					fileType = FILE_PBMRAW;
+				else if(strcmp(optarg, "bit") == 0)
+					fileType = FILE_BIT;
+				else if(strcmp(optarg, "bitcmyk") == 0)
+					fileType = FILE_BITCMYK;
+				else
+					usage();
+			break;
+			
+			case 'p':
+				if(strcmp(optarg, "letter") == 0)
+				{
+					/* Print on U.S. Letter-size paper */
+					PAGE_WIDTH = 3060;    /* 8.5" * 360dpi */
+					PAGE_HEIGHT = 3960;   /* 11"  * 360dpi */
+				}
+				else if(strcmp(optarg, "a4") == 0)
+				{
+					/* Print on A4 paper */
+					PAGE_WIDTH = 2975;    /* about 8.27"  * 360dpi */
+					PAGE_HEIGHT = 4210;   /* about 11.69" * 360dpi */
+				}
+				else
+				{
+					usage();
+				}
+			break;
+			
+			case 'H':		/* paper height (in pixels) */
+				PAGE_HEIGHT = atoi(optarg);
+			break;
+
+			case 'W':		/* paper width (in pixels) */
+				PAGE_WIDTH = atoi(optarg);
+			break;
+			
+			case 'h':		/* input file height (in pixels) */
+				height = atoi(optarg);
+			break;
+
+			case 'w':		/* input file width (in pixels) */
+				width = atoi(optarg);
+			break;
+			
+			case 'm':
+				/* don't crop margins */
+				noMargins = 1;
+			break;
+
+			case 'q':		/* print quality (0, 1, or 2) */
+				printQuality = atoi(optarg);
+			break;
+
+			case 'v':
+				verbose++;
+			break;
+			
+			case 'b':
+				bufferHelp = 1;
+			break;
+
+			case '?':
+			default:
+				usage();
+			break;
+		}
+	}
+	
+	/* Set up some necessary terminal parameters */
+	if(!atalk_connection)
+	{
+		struct termios t;
+		
+		tcgetattr(1, &t);
+		cfmakeraw(&t);
+		cfsetispeed(&t, B57600);
+		cfsetospeed(&t, B57600);
+		if(tcsetattr(1, TCSAFLUSH, &t) == -1)
+		{
+			perror("tcsetattr");
+			exit(1);
+		}
+		sleep(1);
+	}
+	
+	fprintf(stderr, "%s: printing started, pid = %d.\n", ProcName, getpid());
+	
+	/* I'd like to do something here so that the driver can be sending data
+		to the printer and working on the next chunk at the same time.
+		(On my SE/30, it seems to spend about half of its time doing each.)
+		Sooner or later, I'll figure out something with pipes or shared
+		memory or something.  Not yet.
+	*/
+	if(bufferHelp)
+	{
+#if 0
+		pipe();
+		dup2(fd, 1);
+#endif
+	}
+
+	/* print some jobs */
+	while(printStdin() == 0)
+	{
+		doReset = 0;
+		if(paused)
+		{
+			fprintf(stderr, "%s: printing paused, you may mess with the printer now.\n", ProcName);
+			while(paused)
+				pause();
+			fprintf(stderr, "%s: printing resumed.\n", ProcName);
+
+			/* Reset the printer before the next page.  Who knows what
+				they might have done to the poor thing?
+			*/
+			doReset = 1;
+		}
+	}
+
+	fprintf(stderr, "%s: printing finished, exiting.\n", ProcName);
+
+#ifdef ATALK
+	if(atalk_connection)
+	{
+		at_printer_kill();
+	}
+#endif
+
+	return(0);
+}
+
+void usage(void)
+{
+	fprintf(stderr, "usage: lpstyl [-v] [-m] [-q printQuality] [-t pbmraw | bit | bitcmyk]\n");
+	fprintf(stderr, "              [-f printer_device] [-p letter | a4]\n");
+	fprintf(stderr, "              [-h inputHeight] [-w inputWidth]\n");
+	fprintf(stderr, "              [-H paperHeight] [-W paperWidth]\n");
+	exit(1);
+}
+
+void handler()
+{
+	paused = !paused;
+	
+	if(paused)
+		fprintf(stderr, "%s: got SIGUSR1, printing will pause after the next page.\n", ProcName);
+	else
+		fprintf(stderr, "%s: got SIGUSR1, printing will resume at the next opportunity.\n", ProcName);
+}
+
+void cleanup()
+{
+	fprintf(stderr, "%s: Caught a signal, resetting printer.\n", ProcName);
+
+#if ATALK
+	if(atalk_connection)
+	{
+		at_printer_kill();
+	}
+	else
+#endif
+	{
+		/* Reset the printer and eject the page. */
+		ejectAndReset();
+	}
+
+	fprintf(stderr, "%s: printing stopped, exiting.\n", ProcName);
+
+	exit(0);
+}
+
+int printStdin(void)
+{
+	long lastRow, curRow;
+	long printwidth, printheight;
+	unsigned char *bufK, *lastK;
+	unsigned char *lastMark, *dataBuf, *curMark, *tempMark;
+	unsigned char *bufC = nil, *bufM = nil, *bufY = nil, *lastC = nil, *lastM = nil, *lastY = nil;
+	long result;
+	int retry;
+	
+	result = readFileHeader();
+	if(result != 0)
+		return(result);
+	
+	if(verbose)
+	{
+		fprintf(stderr, "%s: Got a valid header, input image is %dx%d.\n", 
+				ProcName, (int)width, (int)height);
+	}
+	
+	/* This may modify margins, etc. */
+	printerSetup();
+
+	/* start on the data */
+	rowbytes = (width + 7) >> 3;
+	printwidth = rowbytes - LEFT_MARGIN_BYTES;
+	if(printwidth > PRINT_ROWBYTES)
+		printwidth = PRINT_ROWBYTES;
+	
+	printheight = height;
+	if(printheight > PRINT_HEIGHT + TOP_MARGIN)
+		printheight = PRINT_HEIGHT + TOP_MARGIN;
+	
+	/* this buffer is intentionally too big. */
+	dataBuf = malloc(MAX_BUFFER * 2);
+	bufK = malloc(rowbytes + 8);
+	lastK = malloc(rowbytes + 8);
+	
+	/* Check for malloc errors  */
+	if((!bufK) || (!lastK) || (!dataBuf))
+	{
+		print_error("Couldn't allocate buffers");
+		return(-1);
+	}
+	else
+	{
+		print_info("Buffers have been allocated.");
+	}
+	
+	if(fileIsColor && canPrintColor)
+	{
+		bufC = malloc(rowbytes + 8);
+		bufM = malloc(rowbytes + 8);
+		bufY = malloc(rowbytes + 8);
+		lastC = malloc(rowbytes + 8);
+		lastM = malloc(rowbytes + 8);
+		lastY = malloc(rowbytes + 8);
+		if((!bufC) || (!bufM) || (!bufY) || (!lastC) || (!lastM) || (!lastY))
+		{
+			print_error("Couldn't allocate color buffers");
+			return(-1);
+		}
+		else
+		{
+			print_info("Color buffers have been allocated.");
+		}
+	}
+
+	for(curRow = 0;curRow < TOP_MARGIN;curRow++)
+	{
+		/* read in a scanline and ignore it */
+		readFileScanline(nil, nil, nil, nil);
+	}
+	print_info("Skipped top margin.");
+	
+	curMark = lastMark = dataBuf;
+	lastRow = curRow;
+	
+	/* Make sure the "previous" line looks blank. */
+	memset(lastK, 0, rowbytes + 8);
+	if(lastC)
+		memset(lastC, 0, rowbytes + 8);
+	if(lastM)
+		memset(lastM, 0, rowbytes + 8);
+	if(lastY)
+		memset(lastY, 0, rowbytes + 8);
+	
+	print_info("Encoding data...");
+	for(;;)
+	{
+		if(curRow < height)
+		{
+			/* read a scanline */
+			result = readFileScanline(bufK, bufC, bufM, bufY);
+	
+			/* check for input problems */
+			if(result != rowbytes)
+			{
+				print_error("Error reading input file or pipe");
+				/* eject the page and reset the printer */
+				ejectAndReset();
+				return(-1);
+			}
+		}
+		else
+		{
+			/* fake it */
+			memset(bufK, 0, rowbytes);
+		}
+		curRow++;
+		
+		/* encode the scanline */
+		switch(printerType)
+		{
+		case KIND_SW1:
+			/* encode the scanline into the buffer */
+			curMark += appendEncode(printwidth, bufK + LEFT_MARGIN_BYTES, nil, curMark);
+		break;
+
+		case KIND_SW1500:
+		case KIND_SW2200:
+		case KIND_SW2400:
+		case KIND_SW2500:
+			if(fileIsColor && canPrintColor)
+			{
+				curMark += appendEncode(printwidth, bufC + LEFT_MARGIN_BYTES, lastC + LEFT_MARGIN_BYTES, curMark);
+				curMark += appendEncode(printwidth, bufM + LEFT_MARGIN_BYTES, lastM + LEFT_MARGIN_BYTES, curMark);
+				curMark += appendEncode(printwidth, bufY + LEFT_MARGIN_BYTES, lastY + LEFT_MARGIN_BYTES, curMark);
+			}
+		/* FALLTHROUGH */
+		default: 
+			curMark += appendEncode(printwidth, bufK + LEFT_MARGIN_BYTES, lastK + LEFT_MARGIN_BYTES, curMark);
+		break;
+		}
+
+		/* swap the buffers */
+		tempMark = lastK;		lastK = bufK;		bufK = tempMark;
+		if(fileIsColor && canPrintColor)
+		{
+			tempMark = lastC;		lastC = bufC;		bufC = tempMark;
+			tempMark = lastM;		lastM = bufM;		bufM = tempMark;
+			tempMark = lastY;		lastY = bufY;		bufY = tempMark;
+		}
+		
+		/* if this chunk put us over the buffer limit... */
+		if((curMark - dataBuf >= MAX_BUFFER) || (curRow >= printheight))
+		{
+			if(curMark - dataBuf < MAX_BUFFER)
+			{
+				/* This is the last bit. */
+				lastMark = curMark;
+				curRow++;
+			}
+			
+			/* wait for a not-busy status */
+			waitNonBusy(0);
+			
+			do
+			{
+				retry = 0;
+				/* send the last batch of scanlines */
+				print_info("Ready to send a block of image data...");
+
+				sendrect(lastRow - TOP_MARGIN, 0, 
+						(curRow-2) - TOP_MARGIN, PRINT_WIDTH);
+				sendEncodedData(dataBuf, (lastMark - dataBuf));
+				print_info("data sent.");
+
+				if(lastRow - TOP_MARGIN == 0)
+				{
+					/* This is the first part of the page. */
+					
+					/* Wait long enough to make sure the printer knows if it's out of paper... */
+					waitNonBusy(1);
+					
+					if(getStatus('2') == 4)
+					{
+						switch(printerType)
+						{
+
+						default:
+							/* the printer is out of paper. */
+							print_error("The printer is out of paper -- trying again in 30 seconds...");
+							ejectAndReset();
+							retry = 1;
+							sleep(30);
+						break;
+
+						case KIND_SW1500:
+						case KIND_SW2200:
+						case KIND_SW2400:
+						case KIND_SW2500:
+							do
+							{
+								print_error("The printer is out of paper -- trying to continue in 5 seconds...");
+								sleep(5);
+								comm_printer_writeFFFx('S');
+							}
+							while(getStatus('2') == 4);
+						break;
+
+						}
+					}
+				}
+			} while(retry);
+			
+			if(lastMark != curMark)
+			{
+				/* Re-encode the last scanline */
+				print_info("Encoding data...");
+				curMark = dataBuf;
+				/* (no differencing) */
+				switch(printerType)
+				{
+				case KIND_SW1500:
+				case KIND_SW2200:
+				case KIND_SW2400:
+				case KIND_SW2500:
+					if(fileIsColor && canPrintColor)
+					{
+						curMark += appendEncode(printwidth, lastC + LEFT_MARGIN_BYTES, nil, curMark);
+						curMark += appendEncode(printwidth, lastM + LEFT_MARGIN_BYTES, nil, curMark);
+						curMark += appendEncode(printwidth, lastY + LEFT_MARGIN_BYTES, nil, curMark);
+					}	
+				/* FALLTHROUGH */
+				default:
+					curMark += appendEncode(printwidth, lastK + LEFT_MARGIN_BYTES, nil, curMark);
+					lastRow = curRow-1;
+				break;
+				}
+			}
+			else
+			{
+				/* we're done with the page */
+				print_info("Done with page.");
+				break;
+			}
+		}
+		
+		lastMark = curMark;
+	}
+	
+	/* This was adjusted to do the last bit of the page -- adjust it back. */
+	curRow--;
+	
+	print_info("Skipping bottom margin.");
+	/* skip any extra lines in the input file */
+	for(;curRow < height;curRow++)
+	{
+		/* read in a scanline and ignore it */
+		readFileScanline(nil, nil, nil, nil);
+	}
+	print_info("Skipped bottom margin.");
+
+	/* wait for a not-busy status */
+	waitNonBusy(0);
+
+	/* finish printing the page */
+	finishPage();
+	
+	/* clean up buffers */
+	free(bufK);
+	free(dataBuf);
+	if(bufC) free(bufC);
+	if(bufM) free(bufM);
+	if(bufY) free(bufY);
+	if(lastC) free(lastC);
+	if(lastM) free(lastM);
+	if(lastY) free(lastY);
+	
+	return(0);
+}
+
+int readFileHeader(void)
+{
+	int c, i;
+	char smallBuf[32];
+
+	fileIsColor = 0;
+
+	switch(fileType)
+	{
+		case FILE_PBMRAW:
+			/* read the file header (including height and width) */
+			height = -1;
+			do
+			{
+				c = inputGetc();
+				switch(c)
+				{
+					case -1:
+						/* end of file -- we're done here.  Note that this is not an error */
+						return(1);
+						break;
+					case '#':
+						/* comment line -- skip it */
+						while((c = inputGetc()) != 0x0A)
+							;
+					break;
+					case ' ': case '\n': case '\t':
+						/* random whitespace... just ignore it. */
+					break;
+					case 'P':
+						/* magic number */
+						if((c = inputGetc()) != '4')
+						{
+							/* bad magic number */
+							print_error("Bad magic number in input file");
+							return(-1);
+						}
+						/* there should be one whitespace character */
+					break;
+					case '0': case '1': case '2': case '3': case '4':
+					case '5': case '6': case '7': case '8': case '9':
+						/* read width */
+						smallBuf[0] = c;
+						for(i=1;isdigit(c = inputGetc()) && (i < sizeof(smallBuf));i++)
+							smallBuf[i] = c;
+						if(!isspace(c))
+						{
+							print_error("Bad input file format");
+						}
+						smallBuf[i] = 0;
+						width = atoi(smallBuf);
+						/* read height */
+						for(i=0;isdigit(c = inputGetc()) && (i < sizeof(smallBuf));i++)
+							smallBuf[i] = c;
+						if(!isspace(c))
+						{
+							print_error("Bad input file format");
+						}
+						smallBuf[i] = 0;
+						height = atoi(smallBuf);
+					break;
+					default:
+						print_error("Bad header format in input file");
+						return(-1);
+					break;
+				}
+			}while(height == -1);
+			
+			/* the header has been taken care of.  The rest of the file is just image data. */	
+		break;
+		
+		case FILE_BITCMYK:
+			fileIsColor = 1;
+			print_info("Input file is in color.");
+		/* FALLTHROUGH */
+		
+		case FILE_BIT:
+			/* These files have no header, but we can make sure we were given
+				a height and width...
+			*/
+			
+			if((height == -1) || (width == -1))
+			{
+				fprintf(stderr, "%s: Width and height must be specified for 'bit' and 'bitcmyk' files.\n", 
+						ProcName);
+				return(-1);
+			}
+			
+			c = inputGetc();
+			if(c == -1)
+			{
+				/* We're done.  Note that this is not an error. */
+				return(1);
+			}
+			else
+			{
+				inputPutback(c);
+			}
+		break;
+	}
+	return(0);
+}
+
+
+size_t readFileScanline(char *bufK, char *bufC, char *bufM, char *bufY)
+{
+	static char *inputBuf = nil;
+	static size_t inputSize = 0;
+	size_t result = 0;
+	
+	switch(fileType)
+	{
+		case FILE_PBMRAW:
+		case FILE_BIT:
+			/* These two are the same, after the header. */
+			if(bufK)
+			{
+				result = inputRead(bufK, rowbytes);
+			}
+			else
+			{
+				/* the black buffer is nil -- read into a dummy buffer. */
+				if(inputSize < rowbytes)
+				{
+					/* reallocate the buffer */
+					if(inputBuf)
+						free(inputBuf);
+					inputBuf = malloc(rowbytes);
+					inputSize = rowbytes;
+				}
+				result = inputRead(inputBuf, rowbytes);
+			}
+		break;
+		
+		case FILE_BITCMYK:
+		{
+			size_t filerowbytes = ((width * 4) + 7) >> 3;
+			
+			if(inputSize < filerowbytes)
+			{
+				/* reallocate the buffer */
+				if(inputBuf)
+					free(inputBuf);
+				inputBuf = malloc(filerowbytes);
+				inputSize = filerowbytes;
+			}
+			
+			if(inputRead(inputBuf, filerowbytes) != filerowbytes)
+			{
+				result = 0;
+				return(result);
+			}
+			else
+			{
+				result = rowbytes;
+			}
+			
+			if(!bufK)
+			{
+				/* skip the hard part. */
+				return(result);
+			}
+			
+			/* ASSUMPTION: any extra bits in the file (due to rowbytes) are 0. */
+			/* NOTE: I haven't thought through whether this code will work on a
+				little-endian system.  Caveat emptor.
+			*/
+
+			/* Convert the bits from chunky to planar */
+			/* NOTE: This is probably the worst way to do this, but I'm not feeling
+				inspired at the moment.  It should work, and I can replace it later
+				with something more efficient.
+			*/
+			if(bufC && bufM && bufY)
+			{
+				register unsigned long byte, i;
+				register unsigned long cmyk = 0;
+				register unsigned char *input = (unsigned char*)inputBuf;
+				
+				/* full color */
+				for(i = 0; i < filerowbytes; i++)
+				{
+					static unsigned long bitsTable[] = {
+						0x00000000, 0x00000001, 0x00000100, 0x00000101,
+						0x00010000, 0x00010001, 0x00010100, 0x00010101,
+						0x01000000, 0x01000001, 0x01000100, 0x01000101,
+						0x01010000, 0x01010001, 0x01010100, 0x01010101 
+					};
+					
+					/* get the input */
+					byte = (*input++) & 0x000000FF;
+					
+					/* shift everything */
+					cmyk <<= 2;
+					
+					/* extract the bits from this nibble */
+					cmyk |= (bitsTable[(byte >> 4) & 0x0000000F]) << 1;
+					cmyk |= bitsTable[byte & 0x0000000F];
+
+					if((i & 0x03) == 0)
+					{
+						/* write the output */
+						*((unsigned char *)bufK)++ = cmyk & 0x000000FF;		cmyk >>= 8;
+						*((unsigned char *)bufY)++ = cmyk & 0x000000FF;		cmyk >>= 8;
+						*((unsigned char *)bufM)++ = cmyk & 0x000000FF;		cmyk >>= 8;
+						*((unsigned char *)bufC)++ = cmyk & 0x000000FF;
+						cmyk = 0;
+					}
+				}
+
+				/* Even up the bytes */
+				for(;(i & 0x03) != 0;i++)
+				{
+					/* shift everything */
+					cmyk <<= 2;
+				}
+				
+				/* write the final part */
+				*((unsigned char *)bufK)++ = cmyk & 0x000000FF;		cmyk >>= 8;
+				*((unsigned char *)bufY)++ = cmyk & 0x000000FF;		cmyk >>= 8;
+				*((unsigned char *)bufM)++ = cmyk & 0x000000FF;		cmyk >>= 8;
+				*((unsigned char *)bufC)++ = cmyk & 0x000000FF;
+			}
+			else
+			{
+				register int byte, i;
+				register char *input = inputBuf;
+				register int k = 0;
+				
+				/* Black channel only */
+				for(i = 0; i < filerowbytes; i++)
+				{
+					/* get the input */
+					byte = *input++;
+					
+					/* shift everything */
+					k <<= 2;
+					
+					if(byte & 0x10){k |= 2;}
+					if(byte & 0x01){k |= 1;}
+
+					if((i & 0x03) == 0)
+					{
+						/* write the output */
+						*bufK++ = k;
+					}
+				}
+
+				/* Even up the bytes */
+				for(;(i & 0x03) != 0;i++)
+				{
+					/* shift everything */
+					k <<= 2;
+				}
+				
+				/* write the final byte */
+				*bufK++ = k;
+			}
+		}
+		break;
+	}
+	
+	return(result);
+}
+
+size_t appendEncode(size_t length, char *in, char *last, char *out)
+{
+	static char delta[1024];	/* bigger than we'll ever need */
+	size_t result = 0;
+	
+	if(last)
+	{
+		/* XOR the input with the last scanline */
+		unsigned char *src1 = in, *src2 = last, *dst = delta;
+		size_t size = length;
+
+		for(;size > 0; size--)
+			*dst++ = (*src1++) ^ (*src2++);
+
+		/* encode the difference into the buffer */
+		result = encodescanline(delta, length, out);
+	}
+	else	
+	{
+		/* encode the scanline into the buffer */
+		result = encodescanline(in, length, out);
+	}
+	
+	return(result);
+}
+
+
+void ejectAndReset(void)
+{
+	/* Eject the page and reset the printer. */
+	comm_printer_writeFFFx('I');
+}
+
+void finishPage(void)
+{
+	int c;
+
+	/* Print the last part of the page. */
+	switch(printerType)
+	{
+		default:
+			print_info("finishpage: sending \"0x0C\"");
+			comm_printer_writestring("\x0C");
+			
+			do
+			{
+				c = getStatus('1');
+				if(verbose > 2)
+				{
+					fprintf(stderr, "%s: status(1) = 0x%X. \n", 
+						ProcName, c);
+				}
+				usleep(USLEEP_TIME);
+			}while(c == 1);
+			
+			waitNonBusy(0);
+			/* XXX -- This may not be necessary */
+			/*
+			print_info("finishpage: sending \"El\"");
+			comm_printer_writestring("El");
+			*/
+		break;
+
+		case KIND_SW1:
+			print_info("finishpage: sending \"0x0C\"");
+			comm_printer_writestring("\x0C");
+			waitNonBusy(0);
+		break;
+	}
+}
+
+void waitNonBusy(int canHandlePaperOut)
+{
+	print_info("About to wait for a non-busy status...");
+	switch(printerType)
+	{
+		case KIND_SW1500:
+			if(canPrintColor)
+			{
+				/* It seems 1500 sends different "wait" status code when color ink
+				   cartridge is installed.  <toe@unlinfo.unl.edu> */
+				waitStatus(0x87, canHandlePaperOut);
+			}
+			else
+			{
+				waitStatus(0x9F, canHandlePaperOut);
+			}
+		break;
+
+		case KIND_SW2500:
+			waitStatus(0x80, canHandlePaperOut);
+		break;
+
+		case KIND_SW2200:
+			waitStatus(0xA3, canHandlePaperOut);
+		break;
+
+		case KIND_SW1:
+			waitStatus(0xA0, canHandlePaperOut);
+		break;
+
+		default:
+			waitStatus(0xF8, canHandlePaperOut);
+		break;
+	}
+#ifdef ATALK
+	if(atalk_connection)
+	{
+		char buf[2];
+
+		buf[0] = 0;
+		adsp_write_attn(&end2, 0x0006, buf, 1);
+		adsp_read(&end2, buf, 2);
+
+		buf[0] = 0;
+		adsp_write_attn(&end2, 0x0006, buf, 1);
+		adsp_read(&end2, buf, 2);
+	}
+#endif
+
+}
+
+#define MAX_RUN 0x3E
+#define MAX_BLOCK 0x3E
+#define RUN_THRESH 0x01
+#define DATA_WHITE 0x00
+#define DATA_BLACK 0xFF
+#define DATA_OTHER 0x0A
+#define MASK_RUNWHT 0x80
+#define MASK_RUNBLK 0xC0
+#define MASK_RUNDATA 0x00
+
+size_t encodescanline(unsigned char *src, size_t srcLen, unsigned char *dst)
+{
+	register signed long s, d, i, runStart, runLen;
+	unsigned char runChar;
+	
+	/* SPECIAL CASE: check for a blank line */
+	for(i=0;i < srcLen;i++)
+		if(src[i] != DATA_WHITE)
+			break;
+	
+	if(i == srcLen)
+	{
+		*dst = MASK_RUNWHT;
+		return(1);
+	}
+	
+	s = d = 0;
+	while(s < srcLen)
+	{
+		runStart = runLen = 0;
+		runChar = DATA_OTHER;	/* Just so long as it's not black or white... */
+		
+		/* Find the first run */
+		for(i = s; (i < srcLen); i++)
+		{
+			if((runChar == DATA_WHITE) || (runChar == DATA_BLACK))
+			{
+				if(src[i] != runChar)
+				{	/* This run is over */
+					if(i - runStart >= RUN_THRESH)
+					{	/* The run was long enough to count.  Break out. */
+						break;
+					}
+					else
+					{	/* The run was too short to count. */
+						runChar = DATA_OTHER;
+					}
+				}
+				else	
+				{	
+					if(i - runStart >= MAX_RUN)
+					{	/* This is enough of a run to encode. */
+						break;
+					}
+				}
+			}
+			else	
+			{	/* runChar == DATA_OTHER */
+				if((src[i] == DATA_WHITE) || (src[i] == DATA_BLACK))
+				{	/* Start a run */
+					runChar = src[i];
+					runStart = i;
+				}
+				else if(i - s >= MAX_BLOCK)
+				{	/* This block is the maximum length. */
+					break;
+				}
+			}
+		}	/* end for */
+
+		if(runChar != DATA_OTHER)
+		{
+			/* We were in a run when we broke out. */
+			runLen = i - runStart;
+		}
+		else
+		{
+			runStart = i;
+			runLen = 0;
+		}
+		
+		if(runStart != s)
+		{
+			/* Encode a run of random data */
+			dst[d++] = runStart - s;
+			for(;s < runStart;)
+				dst[d++] = src[s++];
+		}
+		
+		if(runLen > 0)
+		{
+			/* Encode a run of black or white */
+			if(runChar == DATA_BLACK)
+				dst[d++] = MASK_RUNBLK + runLen;
+			else
+			{
+				if(s + runLen < srcLen)
+					dst[d++] = MASK_RUNWHT + runLen;
+				else
+					break;	/* Let this be taken up with the padding. */
+			}
+			s += runLen;
+		}
+		
+		
+	}	/* end while */
+	
+	/* pad out to the width of the page with white */
+	for(;s < PRINT_ROWBYTES;)
+	{
+		runLen = PRINT_ROWBYTES - s;
+		if(runLen > MAX_RUN)
+			runLen = MAX_RUN;
+		dst[d++] = MASK_RUNWHT + runLen;
+		s += runLen;
+	}
+	return(d);
+}
+
+
+void sendEncodedData(unsigned char *buffer, size_t size)
+{
+	unsigned char begin[4];
+	unsigned char save;
+
+	if(verbose)
+	{
+		fprintf(stderr, "%s: Sending encoded data (0x%x bytes).\n", 
+				ProcName, size);
+	}	
+	begin[0] = 'G';
+	begin[1] = size & 0x000000FF;
+	begin[2] = size >> 8;
+	
+	comm_printer_write(begin, 3);
+
+	save = buffer[size];
+	buffer[size] = 0;
+	comm_printer_write(buffer, size+1);
+	buffer[size] = save;
+}
+
+void sendrect(long top, long left, long bottom, long right)
+{
+	unsigned char buffer[9];
+	
+	if(verbose)
+	{
+		fprintf(stderr, "%s: Sending rect %ld,%ld,%ld,%ld.\n", 
+				ProcName, top, left, bottom, right);
+	}	
+	/* top = 0, bottom = 0xb5 */
+	/* left = 0;
+	right = PRINT_WIDTH; */
+	
+	if(fileIsColor && canPrintColor)
+		buffer[0] = 'c';
+	else
+		buffer[0] = 'R';
+
+	buffer[1] = left & 0x000000FF;
+	buffer[2] = left >> 8;
+	buffer[3] = top & 0x000000FF;
+	buffer[4] = top >> 8;
+	buffer[5] = right & 0x000000FF;
+	buffer[6] = right >> 8;
+	buffer[7] = bottom & 0x000000FF;
+	buffer[8] = bottom >> 8;
+	
+	comm_printer_write(buffer, 9);
+}
+
+int inputGetc(void)
+{
+	unsigned char c;
+	size_t length;
+	unsigned long result = 0;
+	
+	length = inputRead(&c, 1);
+	result = c;
+	if(length == 1)
+		return(result);
+	else
+		return(-1);
+}
+
+void printerSetup(void)
+{
+	char smallBuf[32];
+	int c, c1, c2, i;
+
+	if(doReset)
+	{
+		print_info("Resetting printer...");
+		ejectAndReset();
+		sleep(2);
+
+#if 1
+		do
+		{
+			usleep(USLEEP_TIME);
+			c1 = getStatus('1');
+			c2 = getStatus('2');
+			c = getStatus('B');
+			if(verbose > 2)
+			{
+				fprintf(stderr, "%s: printer state 1 = 0x%X, 2 = 0x%X, B = 0x%X, \n", 
+					ProcName, c1, c2, c);
+			}
+			
+			/* The SW1 shows readiness this way. */
+			if((c1 == 1) && (c2 == 0) && (c == 0xA0))
+				break;
+				
+		}while((c1 == 1) || (c1 == -1));
+#endif
+	
+		print_info("Identifying printer...");
+		comm_printer_writestring("?");
+
+		usleep(USLEEP_TIME);	/* wait long enough for the printer to respond. */
+		for(i=0;((c = comm_printer_getc_block()) != -1) && (i < sizeof(smallBuf)-1); i++)
+		{
+			if(c == 0x0D)
+				break;
+			smallBuf[i] = c;
+			usleep(USLEEP_TIME);
+		}
+		smallBuf[i] = 0;
+		
+		if((c == -1) && (errno != EAGAIN))
+		{
+			print_error("Something went wrong reading from the printer...");
+			fprintf(stderr, "%s: fatal error, exiting.\n", ProcName);
+			exit(1);
+		}
+		
+		/* What kind of printer is it? */
+		if(i == 0)
+		{
+			print_error("Error reading from printer.  Is it connected and turned on?");
+			fprintf(stderr, "%s: fatal error, exiting.\n", ProcName);
+			exit(1);
+		}
+		else if(!strcmp(smallBuf, "IJ10"))
+		{
+			printerType = KIND_SW1;
+			print_info("Printer is a StyleWriter I.");
+		}
+		else if(!strcmp(smallBuf, "SW"))
+		{
+			printerType = KIND_SW2;
+			print_info("Printer is a StyleWriter II.");
+		}
+		 else if(!strcmp(smallBuf, "SW3"))
+		 {
+			 printerType = KIND_SW3;
+			 print_info("Printer is a StyleWriter III (1200).");
+		 }
+		else if(!strcmp(smallBuf, "CS"))
+		{
+			printerType = KIND_SW2400;
+			print_info("Printer is a Color StyleWriter 1500, 2200, 2400 or 2500.");
+		}
+		else
+		{
+			fprintf(stderr, "%s: Unknown printer ID string \"%s\".  Treating it like a StyleWriter II.  (Be amazed if this works.)\n", 
+				ProcName, smallBuf);
+			printerType = KIND_SW2;
+		}
+
+		/* Printer-specific setup */
+		canPrintColor = 0;
+		switch(printerType)
+		{
+			case KIND_SW1:
+				/* The buffer on the SW1 is smaller. */
+				MAX_BUFFER = 0x00002000;
+				
+				/* Otherwise, it's just like the others. */
+			/* FALLTHROUGH */
+			default:
+				PRINT_WIDTH = 2879;			/* (360 dpi * 8 inches) - 1 */
+				LEFT_MARGIN = 90;			/* 0.25 inches * 360 dpi */
+				TOP_MARGIN = 90;			/* 0.25 inches * 360 dpi */
+				BOTTOM_MARGIN = 90;			/* 0.25 inches * 360 dpi */
+			break;
+			
+			case KIND_SW1500:
+			case KIND_SW2200:
+			case KIND_SW2400:
+			case KIND_SW2500:
+				i = getStatus('p');
+				if (verbose)
+					fprintf(stderr, "%s: status(p) = 0x%x\n", ProcName, i);
+
+				switch(i)
+				{
+					case 0x01:
+						print_info("Looks like the printer is really a 2400.");
+						printerType = KIND_SW2400;
+					break;
+					
+					case 0x02:
+						print_info("Looks like the printer is really a 2200.");
+						printerType = KIND_SW2200;
+					break;
+					
+					case 0x04:
+						print_info("Looks like the printer is really a 1500.");
+						printerType = KIND_SW1500;
+					break;
+					
+					case 0x05:
+						print_info("Looks like the printer is really a 2500.");
+						printerType = KIND_SW2500;
+					break;
+					
+					default:
+						print_info("Unknown printer subtype!  Treating it like a 2400.");
+						printerType = KIND_SW2400;
+					break;
+				}
+
+
+				comm_printer_writestring("D");
+				i = getStatus('H');
+				if (verbose)
+					fprintf(stderr, "%s: status(H) = 0x%x\n", ProcName, i);
+				if(i & 0x0080)
+				{
+					print_info("A color ink cartridge is installed.");
+					canPrintColor = 1;
+				}
+				else
+				{
+					print_info("A black ink cartridge is installed.");
+				}
+
+				switch(printQuality)
+				{
+					default:
+						comm_printer_writestring("m0nZAH");
+					break;
+
+					case 2:
+						comm_printer_writestring("m0sAB");
+						i = getStatus('R');
+						if (verbose)
+							fprintf(stderr, "%s: status(R) = 0x%x\n", ProcName, i);
+						break;
+						comm_printer_writestring("N");
+					break;
+				}
+				/* This thing has a larger buffer,... */
+				MAX_BUFFER = 0x00008000;
+				/* ...larger print area... */
+				PRINT_WIDTH = 2919;			/* = (360 dpi * 8.1111....") - 1 */
+				LEFT_MARGIN = 72;			/* about 0.2" * 360 dpi */
+				TOP_MARGIN = 90;			/* 0.25 inches * 360 dpi */
+				BOTTOM_MARGIN = 90;			/* 0.25 inches * 360 dpi */
+
+				/* I think this should work, but it seems to be causing 
+					errors printing certain files.  I'll disable it for now... 
+				*/
+				/* TOP_MARGIN = 45;*/  /* about 0.12" * 360 */
+				/* BOTTOM_MARGIN = 45;*/  /* about 0.12" * 360 */
+
+			break;
+		}
+
+		if(noMargins)
+		{
+			BOTTOM_MARGIN += TOP_MARGIN;
+			TOP_MARGIN = 0;
+			LEFT_MARGIN = 0;
+		}
+		fixPageSizes();
+
+		print_info("The printer is now set up.");
+	}
+
+	/* Do as little as needed to start a new page. */
+	switch(printerType)
+	{
+		case KIND_SW1:
+		default:
+			comm_printer_writestring("nuA");
+		break;
+
+		case KIND_SW1500:
+		case KIND_SW2200:
+		case KIND_SW2400:
+		case KIND_SW2500:
+			comm_printer_writestring("L");
+		break;
+	}
+}
+
+size_t comm_printer_read(void *buffer, size_t size)
+{
+	size_t result;
+#ifdef ATALK
+	if(atalk_connection)
+	{
+		result = adsp_read_nonblock(&end2, (char*)buffer, size);
+	}
+	else
+#endif
+	{
+		int flags;
+
+		flags = fcntl(1, F_GETFL, 0);
+		fcntl(1, F_SETFL, flags | O_NONBLOCK);
+
+		result = read(1, buffer, size);
+
+		fcntl(1, F_SETFL, flags);
+	}
+
+	return(result);
+}
+size_t comm_printer_write(void *buffer, size_t size)
+{
+	size_t result;
+#ifdef ATALK
+	if(atalk_connection)
+	{
+		result = adsp_write(&end2, (char*)buffer, size);
+	}
+	else
+#endif
+	{
+		result = write(1, buffer, size);
+	}
+
+	return(result);
+}
+
+size_t comm_printer_writestring(char *buffer)
+{
+	return(comm_printer_write(buffer, strlen(buffer)));
+}
+
+int comm_printer_getc(void)
+{
+	unsigned char c[1];
+	size_t length;
+	unsigned long result = 0;
+	
+	length = comm_printer_read(c, 1);
+	result = c[0];
+	if(length == 1)
+		return(result);
+	else
+		return(-1);
+}
+
+void printerFlushInput(void)
+{
+	int flags;
+	char dummy[2];
+	
+	flags = fcntl(1, F_GETFL, 0);
+	fcntl(1, F_SETFL, flags | O_NONBLOCK);
+	
+	while(read(1, dummy, 1) != EAGAIN)
+	{
+		/* discard these characters */
+	}
+
+	fcntl(1, F_SETFL, flags);
+}
+
+void comm_printer_writeFFFx(char x)
+{
+	unsigned char string[4];
+	string[0] = string[1] = string[2] = 0xFF;
+	string[3] = x;
+	comm_printer_write(string, 4);
+}
+
+void print_error(char *s)
+{
+	/* Do something appropriate with error messages... */
+	fprintf(stderr, "%s: %s (%d)\n", 
+			ProcName, s, errno);
+}
+
+void print_info(char *s)
+{
+	/* Print out a status message */
+	if(verbose)
+		fprintf(stderr, "%s: %s\n", ProcName, s);
+}
+
+int comm_printer_getc_block(void)
+{
+	int result;
+	int i;
+
+	/* wait up to about ten seconds... */
+	for(i = 100; i > 0; i--)
+	{
+		result = comm_printer_getc();
+		if(result != -1)
+			break;
+		else if(errno != EAGAIN)
+		{
+			/* Something more drastic than no pending input */
+			/* MBW -- XXX -- What should happen here? */
+			perror("read");
+			break;
+		}
+		usleep(USLEEP_TIME);
+	}
+
+	if(result == -1)
+	{
+		/* Timeout waiting for input from the printer */
+		/* MBW -- XXX -- What should happen here? */
+	}
+
+	return(result);
+}
+
+int getStatus(int which)
+{
+	comm_printer_writeFFFx(which);
+	return(comm_printer_getc_block());
+}
+
+void waitStatus(int stat, int canHandlePaperOut)
+{
+	int c, last = stat;
+	int c1 = 0, c2 = 0, last1 = 0, last2 = 0;
+	
+	last1 = c1 = getStatus('1');
+	last2 = c2 = getStatus('2');
+	last = c = getStatus('B');
+	
+	while(c != stat)
+	{
+		usleep(USLEEP_TIME);
+		if(verbose)
+		{
+			if((c != last) || (c1 != last1) || (c2 != last2) || (verbose > 2))
+			{
+				fprintf(stderr, "%s: printer state 1 = 0x%X, 2 = 0x%X, B = 0x%X, \n", 
+						ProcName, c1, c2, c);
+			}
+		}
+
+		switch(c2)
+		{
+			case 4:		/* The printer is out of paper... */
+
+				if(canHandlePaperOut)
+				{
+					/* The caller will detect and deal with out-of-paper conditions. */
+					return;
+				}
+					
+				switch(printerType)
+				{
+					default:
+						/* MBW -- XXX -- Figure out how to deal with this... */
+						fprintf(stderr, 
+						"%s: The printer is out of paper at a bad time, exiting.\n" 
+						, ProcName);
+						ejectAndReset();
+						exit(0);
+					break;
+
+					case KIND_SW1500:
+					case KIND_SW2200:
+					case KIND_SW2400:
+					case KIND_SW2500:
+						do
+						{
+							print_error("The printer is out of paper -- trying to continue in 5 seconds...");
+							sleep(5);
+							comm_printer_writeFFFx('S');
+						}
+						while(getStatus('2') == 4);
+					break;
+				}
+			break;
+
+			default:
+				/* something went wrong... */
+				fprintf(stderr, 
+						"%s: Error code from the printer (0x%X), exiting.\n", 
+						ProcName, c2);
+				ejectAndReset();
+				/* Exit code 1 causes lpd to restart and try again.
+					I need to think about whether this is the right behavior
+					if something is wrong with the printer...
+				*/
+				exit(0);
+			break;
+
+			case 0x80:
+			case 0:
+				/* everything's fine... */
+			break;
+		}
+	
+		last = c;
+		last1 = c1;
+		last2 = c2;
+
+		c1 = getStatus('1');
+		c2 = getStatus('2');
+		c = getStatus('B');
+	}
+
+	if(verbose)
+	{
+		fprintf(stderr, "%s: printer state 1 = 0x%X, 2 = 0x%X, B = 0x%X, \n", 
+				ProcName, c1, c2, c);
+	}
+}
+
+static int inputBack = -1;
+size_t inputRead(void *buffer, size_t size)
+{
+	size_t result = 0, len = 0;
+	
+	if((inputBack != -1) && (size > result))
+	{
+		((char*)buffer)[result] = inputBack & 0x000000FF;
+		result++;
+		inputBack = -1;
+	}
+	
+	while(size - result > 0)
+	{
+		len = read(0, (void*)(((char *)buffer) + result), size - result);
+		if(len < 0)
+		{
+			/* check the error code */
+			if(errno != EINTR)
+				return(-1);
+			else
+			{
+				print_error("Interrupted system call in input read.");
+				usleep(USLEEP_TIME);
+			}
+		}
+		else if(len == 0)
+		{
+			/* That's all she wrote... */
+			return(result);
+		}
+		else
+			result += len;
+	}
+	return(result);
+}
+
+void inputPutback(int c)
+{
+	inputBack = c;
+}
+
+#ifdef ATALK
+
+int at_printer_open(char *name)
+{
+	struct printRequest
+	{
+		u_int32_t	port;
+		char 		string[66];
+	} __attribute__((packed));
+
+	int result;
+
+	if(verbose > 0)
+	{
+		fprintf(stderr, "%s:opening appletalk connnection to %s\n",
+				ProcName, name);
+	}
+
+	if(adsp_open_socket(&s1) < 0)
+	{
+		fprintf(stderr, "%s:failed to open the socket.\n", ProcName);
+		exit(1);
+	}
+
+	if(adsp_open_socket(&s2) < 0)
+	{
+		fprintf(stderr, "%s:failed to open the socket.\n", ProcName);
+		exit(1);
+	}
+
+	/* Get the status of the printer */
+	while(1)
+	{
+		u_int16_t results, results2;
+		char scratch[4];
+
+		if(adsp_connect(&s1, &end1, name) < 0)
+		{
+			fprintf(stderr, "%s:failed to connect.\n", ProcName);
+			exit(1);
+		}
+
+		{
+			struct printRequest req;
+			req.port = htons(s2.local_addr.sat_port);
+			strcpy(req.string + 1, atalk_username);
+			req.string[0] = strlen(req.string + 1);
+
+			adsp_write_attn(&end1, 0x000b, (char*)&req, sizeof(req));
+		}
+
+		if(adsp_read(&end1, (char*)&results, 2) != 2)
+		{
+			fprintf(stderr, "%s:failed to read result code from printer.\n", 
+				ProcName);
+			exit(1);
+		}
+
+		results = ntohs(results);
+		if(results == 0)
+		{
+			break;
+		}
+		else if(results == 0xFFFF)
+		{
+			fprintf(stderr, "%s:error opening data connection (%d).\n", 
+				ProcName, results);
+			exit(1);
+		}
+		else
+		{
+			u_int16_t jobcount;
+			char username[72], status[259];
+
+			/* The printer is busy. */
+			scratch[0] = 0;
+			adsp_write_attn(&end1, 0x0011, scratch, 1);
+
+			if(adsp_read(&end1, (char*)&jobcount, 2) != 2)
+			{
+				fprintf(stderr, "%s:failed to read job count from printer.\n", 
+					ProcName);
+				exit(1);
+			}
+
+			jobcount = ntohs(jobcount);
+
+			scratch[0] = 0;
+			adsp_write_attn(&end1, 0x000e, scratch, 1);
+
+			if(adsp_read(&end1, username, 72) != 72)
+			{
+				fprintf(stderr, "%s:failed to read user name from printer.\n", 
+					ProcName);
+				exit(1);
+			}
+
+			scratch[0] = scratch[1] = 0;
+			adsp_write_attn(&end1, 0x0010, scratch, 1);
+
+			if(adsp_read(&end1, status, 259) != 259)
+			{
+				fprintf(stderr, "%s:failed to read user name from printer.\n", 
+					ProcName);
+				exit(1);
+			}
+
+			username[username[6] + 7] = 0;
+			status[status[2] + 3] = 0;
+			fprintf(stderr, 
+				"%s:printer busy, %d jobs in queue, user '%s', status '%s'\n", 
+				ProcName, jobcount, username+7, status+3);
+			fprintf(stderr, 
+				"%s:will retry in 15 seconds.\n", 
+				ProcName);
+			
+			sleep(15);
+		}
+
+		scratch[0] = 0;
+		adsp_write_attn(&end1, 0x0012, scratch, 1);
+		if(adsp_read(&end1, (char*)&results2, 2) != 2)
+		{
+			fprintf(stderr, "%s:failed to read result code from printer.\n", 
+				ProcName);
+			exit(1);
+		}
+
+		adsp_disconnect(&end1);
+
+		if(results == 0 && results2 == 0)
+			break;
+	}
+
+	do
+	{
+		result = adsp_listen(&s2, &end2);
+
+		if(result < 0)
+		{
+			fprintf(stderr, "%s:listen failed.\n", ProcName);
+			exit(1);
+		}
+	} while(result == 0);
+
+	if(adsp_accept(&end2) < 0)
+	{
+		fprintf(stderr, "%s:accept failed.\n", ProcName);
+		exit(1);
+	}
+
+	adsp_close_socket(&s1);
+
+	at_printer_setstatus("Printing");
+
+	return(0);
+}
+
+void at_printer_kill(void)
+{
+	char scratch[32];
+
+	/* This whole sequence was determined by examining packet dumps.
+		If you don't get it just right, after the disconnect the printer 
+		tries to open a new connection to the original control socket.
+	*/
+
+	adsp_fwd_reset(&end2);
+
+	/* Make sure there isn't pending input. */
+	adsp_read_nonblock(&end2, scratch, 32);
+
+	scratch[0] = 0;
+	adsp_write(&end2, scratch, 1);
+
+	comm_printer_writeFFFx('I');
+
+	scratch[0] = 0x00;
+	adsp_write_attn(&end2, 0x0012, scratch, 1);
+	adsp_read(&end2, scratch, 2);
+
+	adsp_disconnect(&end2);
+	adsp_close_socket(&s2);
+}
+
+void at_printer_setstatus(char *status)
+{
+	char buf[257];
+
+	buf[0] = 0x06;
+	buf[1] = 0x47;
+	buf[2] = strlen(status);
+	strncpy(buf + 3, status, buf[2]);
+
+	adsp_write_attn(&end2, 0x000a, buf, 257);
+	adsp_read(&end2, buf, 2);
+}
+
+#endif

+ 17 - 0
printcap

@@ -0,0 +1,17 @@
+lp|Apple Stylewriter for ASCII, using gs for rasterization:\
+	:lp=/dev/stylewriter:sd=/var/spool/stylps:\
+	:px#3060:py#3960:sh:sf:rw:\
+	:if=/usr/local/sbin/stylascii:\
+	:lf=/var/log/lpd-errs:
+
+stylps|Apple Stylewriter for PostScript, using gs for rasterization:\
+	:lp=/dev/stylewriter:sd=/var/spool/stylps:\
+	:px#3060:py#3960:sh:sf:rw:\
+	:if=/usr/local/sbin/stylps:\
+	:lf=/var/log/lpd-errs:
+
+cstylps|Apple Stylewriter for color postscript, using gs for rasterization:\
+	:lp=/dev/stylewriter:sd=/var/spool/stylps:\
+	:px#3060:py#3960:sh:sf:rw:\
+	:if=/usr/local/sbin/stylps-color:\
+	:lf=/var/log/lpd-errs:

+ 17 - 0
printcap.a4

@@ -0,0 +1,17 @@
+lp|Apple Stylewriter for ASCII, using gs for rasterization:\
+	:lp=/dev/stylewriter:sd=/var/spool/stylps:\
+	:px#2975:py#4210:sh:sf:rw:\
+	:if=/usr/local/sbin/stylascii.a4:\
+	:lf=/var/log/lpd-errs:
+
+stylps|Apple Stylewriter for PostScript, using gs for rasterization:\
+	:lp=/dev/stylewriter:sd=/var/spool/stylps:\
+	:px#2975:py#4210:sh:sf:rw:\
+	:if=/usr/local/sbin/stylps.a4:\
+	:lf=/var/log/lpd-errs:
+
+cstylps|Apple Stylewriter for color postscript, using gs for rasterization:\
+	:lp=/dev/stylewriter:sd=/var/spool/stylps:\
+	:px#2975:py#4210:sh:sf:rw:\
+	:if=/usr/local/sbin/stylps-color.a4:\
+	:lf=/var/log/lpd-errs:

+ 4 - 0
scripts/direct_stylpbm

@@ -0,0 +1,4 @@
+#!/bin/sh
+
+cat $* |\
+/usr/local/sbin/lpstyl -t pbmraw -f /dev/stylewriter

+ 6 - 0
scripts/direct_stylps

@@ -0,0 +1,6 @@
+#!/bin/sh
+
+cat $* |\
+/usr/local/gnu/bin/gs -q -dNOPAUSE -sDEVICE=pbmraw -r360x360 \
+	-sOutputFile=- - \
+|/usr/local/sbin/lpstyl -t pbmraw -f /dev/stylewriter

+ 11 - 0
scripts/stylascii

@@ -0,0 +1,11 @@
+#!/bin/sh
+
+# Substitute your favorite ascii-to-postscript filter here...
+# note: with gnu enscript, the '--pass-through' flag makes the filter
+# transparent to postscript files.  Handy.
+
+/usr/local/bin/enscript -M Letterdj --pass-through --lineprinter -o - |\
+/usr/local/gnu/bin/gs -q -dNOPAUSE -sDEVICE=pbmraw -r360x360 \
+	-sOutputFile=- - \
+|/usr/local/sbin/lpstyl
+

+ 14 - 0
scripts/stylascii-atalk

@@ -0,0 +1,14 @@
+#!/bin/sh
+
+# This script assumes that lpd passes the username and hostname of
+# the user that owns the print job in arguments 5 and 7, respectively.
+
+# Substitute your favorite ascii-to-postscript filter here...
+# note: with gnu enscript, the '--pass-through' flag makes the filter
+# transparent to postscript files.  Handy.
+
+/usr/local/bin/enscript -M Letterdj --pass-through --lineprinter -o - |\
+/usr/local/gnu/bin/gs -q -dNOPAUSE -sDEVICE=pbmraw -r360x360 \
+	-sOutputFile=- - \
+|/usr/local/sbin/lpstyl -a 'PaperShredder:ColorStyleWriter2400AT' -u ${5}@${7}
+

+ 10 - 0
scripts/stylascii.a4

@@ -0,0 +1,10 @@
+#!/bin/sh
+
+# Substitute your favorite ascii-to-postscript filter here...
+# note: with gnu enscript, the '--pass-through' flag makes the filter
+# transparent to postscript files.  Handy.
+/usr/local/bin/enscript -M Letterdj --pass-through --lineprinter -o - |\
+/usr/local/gnu/bin/gs -q -dNOPAUSE -sDEVICE=pbmraw -r360x360 \
+	-sOutputFile=- - \
+|/usr/local/sbin/lpstyl -p a4
+

+ 6 - 0
scripts/stylps

@@ -0,0 +1,6 @@
+#!/bin/sh
+
+/usr/local/gnu/bin/gs -q -dNOPAUSE -dSAFER -sDEVICE=pbmraw -r360x360 \
+	-sOutputFile=- - \
+|/usr/local/sbin/lpstyl
+

+ 9 - 0
scripts/stylps-atalk

@@ -0,0 +1,9 @@
+#!/bin/sh
+
+# This script assumes that lpd passes the username and hostname of
+# the user that owns the print job in arguments 5 and 7, respectively.
+
+/usr/local/gnu/bin/gs -q -dNOPAUSE -dSAFER -sDEVICE=pbmraw -r360x360 \
+	-sOutputFile=- - \
+|/usr/local/sbin/lpstyl -a 'PaperShredder:ColorStyleWriter2400AT' -u ${5}@${7}
+

+ 7 - 0
scripts/stylps-color

@@ -0,0 +1,7 @@
+#!/bin/sh
+
+/usr/local/gnu/bin/gs -q -dNOPAUSE -sDEVICE=bitcmyk \
+	-r360x360 -g3060x3960 \
+	-sOutputFile=- - \
+|/usr/local/sbin/lpstyl -t bitcmyk -w 3060 -h 3960
+

+ 11 - 0
scripts/stylps-color-atalk

@@ -0,0 +1,11 @@
+#!/bin/sh
+
+# This script assumes that lpd passes the username and hostname of
+# the user that owns the print job in arguments 5 and 7, respectively.
+
+/usr/local/gnu/bin/gs -q -dNOPAUSE -sDEVICE=bitcmyk \
+	-r360x360 -g3060x3960 \
+	-sOutputFile=- - \
+|/usr/local/sbin/lpstyl -t bitcmyk -w 3060 -h 3960 \
+		-a 'PaperShredder:ColorStyleWriter2400AT' -u ${5}@${7}
+

+ 7 - 0
scripts/stylps-color.a4

@@ -0,0 +1,7 @@
+#!/bin/sh
+
+/usr/local/gnu/bin/gs -q -dNOPAUSE -sDEVICE=bitcmyk \
+	-r360x360 -g2975x4210 \
+	-sOutputFile=- - \
+|/usr/local/sbin/lpstyl -t bitcmyk -w 2975 -h 4210 -p a4
+

+ 6 - 0
scripts/stylps.a4

@@ -0,0 +1,6 @@
+#!/bin/sh
+
+/usr/local/gnu/bin/gs -q -dNOPAUSE -sDEVICE=pbmraw -r360x360 \
+	-sOutputFile=- - \
+|/usr/local/sbin/lpstyl -p a4
+

+ 234 - 0
styl.ppd

@@ -0,0 +1,234 @@
+*PPD-Adobe: "4.3"
+*FormatVersion: "4.3"
+*FileVersion: "1.0"
+*LanguageVersion: English
+*LanguageEncoding: ISOLatin1
+*PCFileName: "STYLPSGS.PPD"
+
+*ShortNickName: "stylps color"
+*NickName: "StyleWriter 2400"
+*ModelName: "StyleWriter 2400/GhostScript"
+*Product: "(Aladdin Ghostscript)"
+*PSVersion: "(2010) 401"
+*PSVersion: "(2010) 510"
+*Manufacturer: None
+
+*% Written by Monroe Williams <monroe@pobox.com> for use with the
+*% 'lpstyl' StyleWriter driver and GhostScript.  For more information,
+*% see <http://www.pobox.com/~monroe/styl/>.
+
+*% ==============Constraints =================
+
+*UIConstraints: *ColorModel DeviceCMYK *BitsPerPixel None
+
+*% =========== Device Capabilities ===============
+
+*LanguageLevel: "2"
+*FreeVM: "4076994"
+*ColorDevice:  True
+*DefaultColorSpace: CMYK
+*Throughput: "2"
+*TTRasterizer: None
+
+*Password: "()" 
+
+*Resolution 360dpi: ""
+*DefaultResolution: 360dpi
+
+*% ============Paper Size Information ==============
+
+*DefaultPageSize: Letter
+*PageSize Letter/US Letter: "1 dict dup /PageSize [612 792] put setpagedevice"
+
+*DefaultPageRegion: Letter
+*PageRegion Letter/US Letter: "1 dict dup /PageSize [612 792] put setpagedevice"
+
+*DefaultImageableArea: Letter
+*ImageableArea Letter/US Letter: "18 18 576 756"
+
+*DefaultPaperDimension: Letter
+*PaperDimension Letter: "612 792"
+
+*HWMargins: "18 18 18 18"
+
+*DefaultFont: Courier
+
+*% Actual fonts
+
+*Font URWBookmanL-DemiBold: Standard "(001.000)" Standard ROM
+*Font URWBookmanL-DemiBoldItal: Standard "(001.000)" Standard ROM
+*Font URWBookmanL-Ligh: Standard "(001.000)" Standard ROM
+*Font URWBookmanL-LighItal: Standard "(001.000)" Standard ROM
+
+*Font NimbusMonL-Regu: Standard "(001.000)" Standard ROM
+*Font NimbusMonL-ReguObli: Standard "(001.000)" Standard ROM
+*Font NimbusMonL-Bold: Standard "(001.000)" Standard ROM
+*Font NimbusMonL-BoldObli: Standard "(001.000)" Standard ROM
+
+*Font URWGothicL-Book: Standard "(001.000)" Standard ROM
+*Font URWGothicL-BookObli: Standard "(001.000)" Standard ROM
+*Font URWGothicL-Demi: Standard "(001.000)" Standard ROM
+*Font URWGothicL-DemiObli: Standard "(001.000)" Standard ROM
+
+*Font NimbusSanL-Regu: Standard "(001.000)" Standard ROM
+*Font NimbusSanL-ReguItal: Standard "(001.000)" Standard ROM
+*Font NimbusSanL-Bold: Standard "(001.000)" Standard ROM
+*Font NimbusSanL-BoldItal: Standard "(001.000)" Standard ROM
+
+*Font NimbusSanL-ReguCond: Standard "(001.000)" Standard ROM
+*Font NimbusSanL-ReguCondItal: Standard "(001.000)" Standard ROM
+*Font NimbusSanL-BoldCond: Standard "(001.000)" Standard ROM
+*Font NimbusSanL-BoldCondItal: Standard "(001.000)" Standard ROM
+
+*Font URWPalladioL-Roma: Standard "(001.000)" Standard ROM
+*Font URWPalladioL-Ital: Standard "(001.000)" Standard ROM
+*Font URWPalladioL-Bold: Standard "(001.000)" Standard ROM
+*Font URWPalladioL-BoldItal: Standard "(001.000)" Standard ROM
+
+*Font CenturySchL-Roma: Standard "(001.000)" Standard ROM
+*Font CenturySchL-Ital: Standard "(001.000)" Standard ROM
+*Font CenturySchL-Bold: Standard "(001.000)" Standard ROM
+*Font CenturySchL-BoldItal: Standard "(001.000)" Standard ROM
+
+*Font NimbusRomNo9L-Regu: Standard "(001.000)" Standard ROM
+*Font NimbusRomNo9L-ReguItal: Standard "(001.000)" Standard ROM
+*Font NimbusRomNo9L-Medi: Standard "(001.000)" Standard ROM
+*Font NimbusRomNo9L-MediItal: Standard "(001.000)" Standard ROM
+
+*Font StandardSymL: Standard "(001.000)" Standard ROM
+
+*Font URWChanceryL-MediItal: Standard "(001.000)" Standard ROM
+
+*% --- Aliases ---
+
+*Font Dingbats: Standard "(001.000)" Standard ROM
+*Font Bookman-Demi: Standard "(001.000)" Standard ROM
+*Font Bookman-DemiItalic: Standard "(001.000)" Standard ROM
+*Font Bookman-Light: Standard "(001.000)" Standard ROM
+*Font Bookman-LightItalic: Standard "(001.000)" Standard ROM
+
+*Font Courier: Standard "(001.000)" Standard ROM
+*Font Courier-Oblique: Standard "(001.000)" Standard ROM
+*Font Courier-Bold: Standard "(001.000)" Standard ROM
+*Font Courier-BoldOblique: Standard "(001.000)" Standard ROM
+
+*Font AvantGarde-Book: Standard "(001.000)" Standard ROM
+*Font AvantGarde-BookOblique: Standard "(001.000)" Standard ROM
+*Font AvantGarde-Demi: Standard "(001.000)" Standard ROM
+*Font AvantGarde-DemiOblique: Standard "(001.000)" Standard ROM
+
+*Font Helvetica: Standard "(001.000)" Standard ROM
+*Font Helvetica-Oblique: Standard "(001.000)" Standard ROM
+*Font Helvetica-Bold: Standard "(001.000)" Standard ROM
+*Font Helvetica-BoldOblique: Standard "(001.000)" Standard ROM
+
+*Font Helvetica-Narrow: Standard "(001.000)" Standard ROM
+*Font Helvetica-Narrow-Oblique: Standard "(001.000)" Standard ROM
+*Font Helvetica-Narrow-Bold: Standard "(001.000)" Standard ROM
+*Font Helvetica-Narrow-BoldOblique: Standard "(001.000)" Standard ROM
+
+*Font Palatino-Roman: Standard "(001.000)" Standard ROM
+*Font Palatino-Italic: Standard "(001.000)" Standard ROM
+*Font Palatino-Bold: Standard "(001.000)" Standard ROM
+*Font Palatino-BoldItalic: Standard "(001.000)" Standard ROM
+
+*Font NewCenturySchlbk-Roman: Standard "(001.000)" Standard ROM
+*Font NewCenturySchlbk-Italic: Standard "(001.000)" Standard ROM
+*Font NewCenturySchlbk-Bold: Standard "(001.000)" Standard ROM
+*Font NewCenturySchlbk-BoldItalic: Standard "(001.000)" Standard ROM
+
+*Font Times-Roman: Standard "(001.000)" Standard ROM
+*Font Times-Italic: Standard "(001.000)" Standard ROM
+*Font Times-Bold: Standard "(001.000)" Standard ROM
+*Font Times-BoldItalic: Standard "(001.000)" Standard ROM
+
+*Font Symbol: Standard "(001.000)" Standard ROM
+
+*Font ZapfChancery-MediumItalic: Standard "(001.000)" Standard ROM
+
+*Font ZapfDingbats: Standard "(001.000)" Standard ROM
+
+*% --- Contributed fonts ---
+
+*Font Charter-Roman: Standard "(001.000)" Standard ROM
+*Font Charter-Italic: Standard "(001.000)" Standard ROM
+*Font Charter-Bold: Standard "(001.000)" Standard ROM
+*Font Charter-BoldItalic: Standard "(001.000)" Standard ROM
+
+
+*Font Utopia-Regular: Standard "(001.000)" Standard ROM
+*Font Utopia-Italic: Standard "(001.000)" Standard ROM
+*Font Utopia-Bold: Standard "(001.000)" Standard ROM
+*Font Utopia-BoldItalic: Standard "(001.000)" Standard ROM
+
+
+*Font URWAntiquaT-RegularCondensed: Standard "(001.000)" Standard ROM
+*Font URWGroteskT-Bold: Standard "(001.000)" Standard ROM
+
+
+*Font Calligraphic-Hiragana: Standard "(001.000)" Standard ROM
+*Font Calligraphic-Katakana: Standard "(001.000)" Standard ROM
+
+
+*Font Shareware-Cyrillic-Regular: Standard "(001.000)" Standard ROM
+*Font Shareware-Cyrillic-Italic: Standard "(001.000)" Standard ROM
+
+*% Aliases
+*Font Cyrillic: Standard "(001.000)" Standard ROM
+*Font Cyrillic-Regular: Standard "(001.000)" Standard ROM
+*Font Cyrillic-Italic: Standard "(001.000)" Standard ROM
+
+*Font Hershey-Gothic-English: Standard "(001.000)" Standard ROM
+*Font Hershey-Gothic-German: Standard "(001.000)" Standard ROM
+*Font Hershey-Gothic-Italian: Standard "(001.000)" Standard ROM
+
+*Font Hershey-Plain-Duplex: Standard "(001.000)" Standard ROM
+*Font Hershey-Plain-Duplex-Italic: Standard "(001.000)" Standard ROM
+*Font Hershey-Plain-Triplex: Standard "(001.000)" Standard ROM
+*Font Hershey-Plain-Triplex-Italic: Standard "(001.000)" Standard ROM
+
+*Font Hershey-Script-Complex: Standard "(001.000)" Standard ROM
+*Font Hershey-Script-Simplex: Standard "(001.000)" Standard ROM
+
+*% Fonts created algorithmically from the above.
+
+*Font Hershey-Gothic-English-Bold: Standard "(001.000)" Standard ROM
+*Font Hershey-Gothic-English-Oblique: Standard "(001.000)" Standard ROM
+*Font Hershey-Gothic-English-SemiBold: Standard "(001.000)" Standard ROM
+*Font Hershey-Gothic-German-Bold: Standard "(001.000)" Standard ROM
+*Font Hershey-Gothic-German-Oblique: Standard "(001.000)" Standard ROM
+*Font Hershey-Gothic-Italian-Bold: Standard "(001.000)" Standard ROM
+*Font Hershey-Gothic-Italian-Oblique: Standard "(001.000)" Standard ROM
+
+*Font Hershey-Plain-Duplex-Bold: Standard "(001.000)" Standard ROM
+*Font Hershey-Plain-Duplex-Bold-Italic: Standard "(001.000)" Standard ROM
+*Font Hershey-Plain-Triplex-Bold: Standard "(001.000)" Standard ROM
+*Font Hershey-Plain-Triplex-Bold-Italic: Standard "(001.000)" Standard ROM
+
+*Font Hershey-Script-Complex-Bold: Standard "(001.000)" Standard ROM
+*Font Hershey-Script-Complex-Oblique: Standard "(001.000)" Standard ROM
+*Font Hershey-Script-Simplex-Bold: Standard "(001.000)" Standard ROM
+*Font Hershey-Script-Simplex-Oblique: Standard "(001.000)" Standard ROM
+
+*% Fonts consisting only of characters from the original Hershey
+*% distribution.  These are Type 3 fonts.
+
+*Font Hershey-Greek-Complex: Standard "(001.000)" Standard ROM
+*Font Hershey-Greek-Simplex: Standard "(001.000)" Standard ROM
+
+*Font Hershey-Plain: Standard "(001.000)" Standard ROM
+*Font Hershey-Plain-Simplex: Standard "(001.000)" Standard ROM
+
+*% Fonts created algorithmically from the above.
+
+*Font Hershey-Plain-Bold: Standard "(001.000)" Standard ROM
+*Font Hershey-Plain-Bold-Oblique: Standard "(001.000)" Standard ROM
+*Font Hershey-Plain-Oblique: Standard "(001.000)" Standard ROM
+*Font Hershey-Plain-Simplex-Bold: Standard "(001.000)" Standard ROM
+*Font Hershey-Plain-Simplex-Bold-Oblique: Standard "(001.000)" Standard ROM
+*Font Hershey-Plain-Simplex-Oblique: Standard "(001.000)" Standard ROM
+
+*% This font, and only this font among the Hershey fonts, uses
+*% the SymbolEncoding.
+
+*Font Hershey-Symbol: Standard "(001.000)" Standard ROM