AmendHub

Download

thecloud

/

SimpleSpooler

/

Spooler.c

 

(View History)

thecloud   Simple Spooler 0.4 release Latest amendment: 9 on 2026-08-07

1 /*
2 * Simple Spooler
3 * Copyright (C) 2026, Ken McLeod.
4 *
5 * This application registers itself as a virtual printer on the AppleTalk
6 * network and receives print jobs using PAP (Printer Access Protocol).
7 * It can optionally send the print job to a locally-connected ImageWriter
8 * printer which otherwise would not be visible on the network. It can also
9 * generate a PostScript file from the print job automatically, if the IWEm
10 * file is present in the spool directory.
11 *
12 * Written by: Ken McLeod, 2026-01-07
13 * Last update: 2026-08-05
14 *
15 * Some code was adapted from the Neighborhood Watch sample application
16 * by Ricardo Batista, found on Apple Developer CD Volume IX, and from the
17 * MoreFiles utility routines by Jim Luther, found on the March 1994 Dev CD.
18 *
19 * This software is provided as-is, with no warranties expressed or implied,
20 * under the terms of the BSD 2-Clause License. See separate "LICENSE" file.
21 */
22
23 #include <StdIO.h>
24 #include <String.h>
25 #include <Memory.h>
26 #include <Types.h>
27 #include <CursorCtl.h>
28 #include <AppleTalk.h>
29 #include <Packages.h>
30 #include <Events.h>
31 #include <SysEqu.h>
32 #include <GestaltEqu.h>
33 #include <Controls.h>
34 #include <Desk.h>
35 #include <Devices.h>
36 #include <Errors.h>
37 #include <Fonts.h>
38 #include <Menus.h>
39 #include <ToolUtils.h>
40 #include <Events.h>
41 #include <Quickdraw.h>
42 #include <Resources.h>
43 #include <OSEvents.h>
44 #include <Scrap.h>
45 #include "Logging.h"
46 #include "SpoolFiles.h"
47 #include "PrintQueue.h"
48 #include "PAP.h"
49
50 /* resource IDs */
51
52 #define appleMenu 128
53 #define fileMenu 129
54 #define editMenu 130
55 #define controlMenu 131
56
57 #define quitItem 1
58
59 #define undoItem 1
60 #define cutItem 3
61 #define copyItem 4
62 #define pasteItem 5
63 #define clearItem 6
64 #define selectAllItem 8
65
66 #define startSpoolerItem 1
67 #define stopSpoolerItem 2
68 #define settingsItem 4
69 #define statusItem 5
70
71 #define okItem 1
72 #define cancelItem 2
73 #define spoolerNameItem 3
74 #define iconLWItem 6
75 #define iconLQItem 7
76 #define iconIWItem 8
77 #define modemPortItem 10
78 #define printerPortItem 11
79 #define colorRibbonCBoxItem 13
80 #define sheetFeederCBoxItem 14
81 #define iw1ModeCBoxItem 15
82 #define noReverseCBoxItem 16
83 #define printingEnabledItem 17
84 #define scheduleUserItem 18
85 #define printerTypeUserItem 19
86 #define localPortUserItem 20
87 #define autoRunCBoxItem 21
88
89 #define logWindowID 128
90 #define spoolerDialogID 128
91 #define upDownArrowsPictID 128
92 #define typeStringsID 128
93 #define nameStringsID 129
94 #define portStringsID 130
95 #define buttonStringsID 131
96
97 #define okStrIndex 1
98 #define cancelStrIndex 2
99 #define startStrIndex 3
100 #define saveStrIndex 4
101
102 #define kIconFrameInset -5
103 #define kIconFrameSize 2
104 #define kButtonFrameInset -4
105 #define kButtonFrameSize 3
106 #define kButtonFrameRadius 18
107 #define kMinWindowHeight 100
108 #define kMinWindowWidth 416
109 #define kPrefsVersion 3
110
111
112 /* function prototypes */
113
114 void DoAbout(void);
115 void DoStatus(void);
116 OSErr ConfigureSpooler(Boolean startingServer);
117 OSErr StartSpooler(void);
118 OSErr StopSpooler(void);
119 void SetMenus(void);
120 Boolean Setup(void);
121 Boolean RunEventLoop(void);
122 Boolean DoCommand(long mResult);
123 Boolean HandleMouseDowns(void);
124 void LoadPreferences();
125 void SavePreferences();
126
127 char ourName[256];
128 char ourType[34];
129 char ourPort[34];
130 char ourPrefix[4];
131 int serverRef = 0;
132 Boolean inFront;
133 EventRecord myEvent;
134 MenuHandle myMenu[5];
135 WindowPtr textWindow = 0L;
136 TEHandle textTE = 0L;
137 Rect windowRect = { 40, 10, 320, 490 }; /* default, global coordinates */
138 ControlHandle textScrollBar = 0L;
139 PicHandle arrowsPictHdl = 0L;
140 short defaultPrinterItem = iconIWItem;
141 short defaultPortItem = modemPortItem;
142 Boolean defaultColorRibbon = false;
143 Boolean defaultSheetFeeder = false;
144 Boolean defaultPrintEnabled = true;
145 Boolean defaultIW1Mode = false;
146 Boolean defaultNoReverse = false;
147 Boolean defaultAutoRun = false;
148 short curSelectedIndex = 0; /* selected segment in date scheduler */
149 short schedStartHour = 0; /* default start to 12 AM */
150 short schedEndHour = 24; /* default end to next 12 AM */
151 char sAMStr[4]; /* pascal string with first 2 chars of AM string from itl0 */
152 char sPMStr[4]; /* pascal string with first 2 chars of PM string from itl0 */
153 char s12HrCycle; /* values: 0=24hr, 1=12hr */
154
155 char *sepStr = "\p----------------------------------------------------";
156 char *verStr = "\pVersion 0.4, Copyright © 2026 Ken McLeod.";
157
158
159 void main()
160 {
161 if (!Setup()) {
162 return;
163 }
164 /* either auto-run the server, or present the settings panel */
165 DoCommand((controlMenu << 16) + startSpoolerItem);
166
167 while (RunEventLoop()) ;
168
169 StopSpooler();
170 SavePreferences();
171 UnloadLogWindow();
172 }
173
174 Boolean Setup(void)
175 {
176 short count;
177 for (count = 0; count < 6; count++) {
178 MoreMasters();
179 }
180 InitGraf(&qd.thePort);
181 InitFonts();
182 InitWindows();
183 TEInit();
184 InitMenus();
185 InitDialogs(0L);
186 DrawMenuBar();
187 FlushEvents(everyEvent,0L);
188 InitCursor();
189 MaxApplZone();
190 SetCursor(*GetCursor(watchCursor));
191 InitAllPacks();
192 SetMenus();
193 if (!InitLogWindow(logWindowID, &textWindow, &textTE, &textScrollBar)) {
194 return(false);
195 }
196 if (InitSpoolFolder() != noErr) {
197 Message(LOG_ERR_X,"\pCould not find or create spool folder!");
198 }
199 LoadPreferences();
200 ShowLogWindow(&windowRect);
201 inFront = true;
202 return(true);
203 }
204
205 void SetMenus()
206 {
207 /* load menus from resource file */
208 int i;
209 for (i=appleMenu-appleMenu; i<=controlMenu-appleMenu; i++) {
210 myMenu[i] = GetMenu(appleMenu+i);
211 }
212 AddResMenu(myMenu[appleMenu-appleMenu],'DRVR');
213 /* install menus in menu bar */
214 for (i=appleMenu-appleMenu; i<=controlMenu-appleMenu; i++) {
215 InsertMenu(myMenu[i], 0);
216 }
217 EnableItem(myMenu[controlMenu-appleMenu],startSpoolerItem);
218 EnableItem(myMenu[editMenu-appleMenu],copyItem);
219 EnableItem(myMenu[editMenu-appleMenu],selectAllItem);
220 DrawMenuBar();
221 }
222
223 Boolean RunEventLoop()
224 {
225 register char theChar;
226 register Boolean event;
227 WindowPtr w;
228 GrafPtr savePort;
229 Rect box;
230 Point mouse;
231
232 CheckATP(); /* check incoming requests and responses */
233 CheckPrintQueue(defaultPrintEnabled, schedStartHour, schedEndHour);
234
235 event = GetNextEvent(everyEvent,&myEvent);
236 w = FrontWindow();
237 if ((w == textWindow) && inFront && textTE) {
238 GetPort(&savePort);
239 SetPort(textWindow);
240 /* %%% don't need to blink insertion point; text isn't editable */
241 /*TEIdle(textTE);*/
242 box = w->portRect;
243 GetMouse(&mouse);
244 box.right -= 16;
245 box.bottom -= 16;
246 if (PtInRect(mouse,&box)) {
247 SetCursor(*(GetCursor(iBeamCursor)));
248 } else {
249 InitCursor();
250 }
251 SetPort(savePort);
252 }
253 if (event) {
254 switch (myEvent.what) {
255 case app4Evt:
256 inFront = (myEvent.modifiers & 128) ? true : false;
257 break;
258 case activateEvt:
259 w = (WindowPtr) myEvent.message;
260 if (w == textWindow) {
261 ActivateLogWindow(myEvent.modifiers & activeFlag);
262 }
263 break;
264 case keyDown:
265 case autoKey:
266 theChar = myEvent.message & charCodeMask;
267 if (myEvent.modifiers & cmdKey) {
268 if (myEvent.modifiers & shiftKey) {
269 if (theChar == 'l') {
270 SetLogLevel(LogLevel()+1);
271 if (LogLevel() > LOG_DEBUG) {
272 SetLogLevel(LOG_ERR);
273 }
274 MessageWithIntValue(LOG_ERR,"\pSetting log level:", LogLevel());
275 } else if (theChar == 's') {
276 DoStatus();
277 }
278 }
279 if (!DoCommand(MenuKey(theChar))) {
280 return(false);
281 }
282 } else {
283 w = FrontWindow();
284 /* %%% don't accept keyboard input into log text */
285 #if 0
286 if (w == textWindow) {
287 short lines;
288 HLock((Handle)textTE);
289 lines = (**textTE).nLines;
290 TEKey(theChar,textTE);
291 if (lines != (**textTE).nLines)
292 AdjustTextScrollBar();
293 HUnlock((Handle)textTE);
294 }
295 #endif
296 }
297 break;
298 case updateEvt:
299 w = (WindowPtr) myEvent.message;
300 if (w == textWindow)
301 UpdateLogWindow();
302 break;
303 case mouseDown:
304 if (!HandleMouseDowns())
305 return(false);
306 break;
307 default:
308 break;
309 }
310 }
311 return(true);
312 }
313
314 Boolean DoCommand(long mResult)
315 {
316 OSErr err;
317 register short theItem;
318 char st[256];
319
320 theItem = LoWord(mResult);
321 switch (HiWord(mResult)) {
322 case appleMenu:
323 GetItem(myMenu[0],theItem,st);
324 if (theItem > 2)
325 OpenDeskAcc(st);
326 else
327 DoAbout();
328 break;
329 case fileMenu:
330 switch (theItem) {
331 case quitItem:
332 return(false);
333 break;
334 default:
335 break;
336 }
337 break;
338 case editMenu:
339 if (!SystemEdit(theItem-1)) {
340 switch (theItem) {
341 case undoItem:
342 break;
343 case cutItem:
344 if (textTE) {
345 TECut(textTE);
346 AdjustTextScrollBar();
347 }
348 ZeroScrap();
349 TEToScrap();
350 break;
351 case copyItem:
352 if (textTE) {
353 TECopy(textTE);
354 }
355 ZeroScrap();
356 TEToScrap();
357 break;
358 case pasteItem:
359 TEFromScrap();
360 if (textTE) {
361 TEPaste(textTE);
362 AdjustTextScrollBar();
363 }
364 break;
365 case clearItem:
366 if (textTE) {
367 TEDelete(textTE);
368 AdjustTextScrollBar();
369 }
370 break;
371 case selectAllItem:
372 if (textTE) {
373 int index;
374 HLock((Handle)textTE);
375 index = (**textTE).teLength;
376 HUnlock((Handle)textTE);
377 TESetSelect(0,index,textTE);
378 }
379 break;
380 default:
381 break;
382 }
383 }
384 break;
385 case controlMenu:
386 switch (theItem) {
387 case startSpoolerItem:
388 err = StartSpooler();
389 if (err == noErr) {
390 StartPrintQueue(ourPrefix,ourPort);
391 Message(LOG_ERR,"\pSpooler started");
392 EnableItem(myMenu[controlMenu-appleMenu],stopSpoolerItem);
393 DisableItem(myMenu[controlMenu-appleMenu],startSpoolerItem);
394 } else {
395 Message(LOG_ERR,"\pSelect Control > Start Spooler to start the server");
396 EnableItem(myMenu[controlMenu-appleMenu],startSpoolerItem);
397 DisableItem(myMenu[controlMenu-appleMenu],stopSpoolerItem);
398 }
399 break;
400 case stopSpoolerItem:
401 err = StopSpooler();
402 if (err == noErr) {
403 StopPrintQueue();
404 Message(LOG_ERR,"\pSpooler stopped");
405 } else {
406 MessageWithIntValue(LOG_ERR,"\pERROR: Could not stop spooler:",err);
407 }
408 EnableItem(myMenu[controlMenu-appleMenu],startSpoolerItem);
409 DisableItem(myMenu[controlMenu-appleMenu],stopSpoolerItem);
410 break;
411 case settingsItem:
412 err = ConfigureSpooler(false);
413 if (err == noErr && serverRef) {
414 /* spooler already running, recursively stop and restart it */
415 Boolean savedAutoRun = defaultAutoRun;
416 DoCommand((controlMenu << 16) + stopSpoolerItem);
417 defaultAutoRun = true; /* don't put up the dialog twice */
418 DoCommand((controlMenu << 16) + startSpoolerItem);
419 defaultAutoRun = savedAutoRun;
420 }
421 break;
422 case statusItem:
423 DoStatus();
424 break;
425 default:
426 break;
427 }
428 break;
429 default:
430 break;
431 }
432 HiliteMenu(0);
433 return(true);
434 }
435
436 Boolean HandleMouseDowns()
437 {
438 WindowPtr whichWindow;
439 Rect box;
440 long new;
441 short v, h;
442
443 switch (FindWindow(myEvent.where,&whichWindow)) {
444 case inSysWindow:
445 SystemClick(&myEvent,whichWindow);
446 break;
447 case inMenuBar:
448 return(DoCommand(MenuSelect(myEvent.where)));
449 break;
450 case inGrow:
451 box = qd.screenBits.bounds;
452 box.top = kMinWindowHeight;
453 box.left = kMinWindowWidth;
454 new = GrowWindow(whichWindow,myEvent.where,&box);
455 if (new) {
456 v = HiWord(new);
457 h = LoWord(new);
458 SetPort(whichWindow);
459 SizeWindow(whichWindow,h,v,true);
460 EraseRect(&(whichWindow->portRect));
461 InvalRect(&(whichWindow->portRect));
462 if (whichWindow == textWindow) {
463 GrowLogWindow(h,v);
464 }
465 }
466 break;
467 case inGoAway:
468 if (!TrackGoAway(whichWindow,myEvent.where))
469 break;
470 break;
471 case inDrag:
472 if ((myEvent.modifiers & cmdKey) || (FrontWindow() == whichWindow))
473 DragWindow(whichWindow,myEvent.where,&qd.screenBits.bounds);
474 else {
475 SelectWindow(whichWindow);
476 SetPort(whichWindow);
477 }
478 break;
479 case inContent:
480 if (whichWindow != FrontWindow()) {
481 SelectWindow(whichWindow);
482 SetPort(whichWindow);
483 }
484 else {
485 if (whichWindow == textWindow) {
486 HandleMouseInText(&myEvent);
487 }
488 }
489 break;
490 default:
491 break;
492 }
493 return(true);
494 }
495
496 void CenterRect(Rect *r)
497 {
498 /* centers the rectangle horizontally on main screen,
499 * one-third of the way down vertically from the top.
500 */
501 OffsetRect(r,
502 - r->left
503 + ((qd.screenBits.bounds.right - qd.screenBits.bounds.left)
504 - (r->right - r->left)) / 2,
505 - r->top
506 + (((qd.screenBits.bounds.bottom - qd.screenBits.bounds.top - GetMBarHeight())
507 - (r->bottom - r->top)) / 3) + GetMBarHeight());
508 }
509
510 DialogTHndl CenterDialog(short whichID)
511 {
512 DialogTHndl d = (DialogTHndl) GetResource('DLOG', whichID);
513 if ((d != 0L) && (ResError() == noErr)) {
514 HLock((Handle)d);
515 CenterRect(&(*d)->boundsRect);
516 HUnlock((Handle)d);
517 }
518 return(d);
519 }
520
521 void LoadDateFormatStrings(void)
522 {
523 /*Intl0Hndl h = (Intl0Hndl)GetResource('itl0',16383); /* explicit user overrides? */
524 Intl0Hndl h = (Intl0Hndl)IUGetIntl(0); /* get current date & time formats */
525 if (h) {
526 HLock((Handle)h);
527 s12HrCycle = ((**h).timeCycle == 0xFF) ? true:false; /* 0x00,0x01:24h, 0xFF:12hr */
528 if (s12HrCycle) {
529 /* these are C strings with a leading space character that we skip */
530 BlockMove(&(**h).mornStr[1],&sAMStr[1],2);
531 BlockMove(&(**h).eveStr[1],&sPMStr[1],2);
532 sAMStr[0] = sPMStr[0] = 2;
533 } else {
534 BlockMove("\p00",sAMStr,3);
535 BlockMove(sAMStr,sPMStr,3);
536 }
537 HUnlock((Handle)h);
538 ReleaseResource((Handle)h);
539 } else {
540 s12HrCycle = true;
541 BlockMove("\pAM",sAMStr,3);
542 BlockMove("\pPM",sPMStr,3);
543 }
544 }
545
546 void GetScheduleStrings(short scheduledHour, char *theHourStr, char *theAmPmStr)
547 {
548 /* fill in the provided Pascal string buffers, given scheduledHour as 0..24 */
549 /* note: each buffer must be able to hold at least 2 bytes + 1 length byte. */
550
551 short hour = scheduledHour;
552 BlockMove(sAMStr,theAmPmStr,3);
553 if (hour >= 12) {
554 hour = (hour == 12) ? 12 : hour - 12;
555 if (scheduledHour < 24) {
556 BlockMove(sPMStr,theAmPmStr,3);
557 }
558 }
559 theHourStr[0] = 2;
560 if (!s12HrCycle) {
561 theHourStr[1] = (scheduledHour < 10) ? '0':(scheduledHour < 20) ? '1':'2';
562 theHourStr[2] = '0' + (scheduledHour % 10);
563 } else if (hour > 9) {
564 theHourStr[1] = '1';
565 theHourStr[2] = '0' + (hour - 10);
566 } else if (hour < 1) {
567 theHourStr[1] = '1';
568 theHourStr[2] = '2';
569 } else {
570 theHourStr[1] = ' ';
571 theHourStr[2] = '1' + (hour - 1);
572 }
573 }
574
575 void HandleClickInScheduler(Point pt, Rect *r, Boolean redrawAll)
576 {
577 Boolean redraw = redrawAll;
578 short index;
579 Rect tmp = *r;
580 Rect parts[7] = {
581 { tmp.top, tmp.left+0, tmp.bottom, tmp.left+0 },
582 { tmp.top, tmp.left+2, tmp.bottom, tmp.left+21 },
583 { tmp.top, tmp.left+22, tmp.bottom, tmp.left+43 },
584 { tmp.top, tmp.left+44, tmp.bottom, tmp.left+54 },
585 { tmp.top, tmp.left+54, tmp.bottom, tmp.left+73 },
586 { tmp.top, tmp.left+74, tmp.bottom, tmp.left+95 },
587 { tmp.top, tmp.left+98, tmp.bottom, tmp.right },
588 };
589
590 for (index = 1; index < 6; index++) {
591 if (index == 3 || (!s12HrCycle && (index == 2 || index == 5))) {
592 continue; /* not selectable */
593 }
594 if (PtInRect(pt, &parts[index])) {
595 InvertRect(&parts[curSelectedIndex]); /* deselect the current selection first */
596 if (curSelectedIndex == index) {
597 curSelectedIndex = 0; /* we deselected this, so now there is no selection */
598 } else {
599 InvertRect(&parts[index]);
600 curSelectedIndex = index; /* this index is now the current selection */
601 }
602 break;
603 }
604 }
605 if (PtInRect(pt, &parts[6])) {
606 /* click in arrow control */
607 Boolean increase = false;
608 redraw = true;
609 if (pt.v < parts[6].top + ((parts[6].bottom-parts[6].top)/2)) {
610 /* down arrow, increase or toggle value */
611 increase = true;
612 }
613 switch (curSelectedIndex) {
614 case 0: /* nothing was selected, so select first item as a hint */
615 curSelectedIndex = 1;
616 InvertRect(&parts[curSelectedIndex]);
617 break;
618 case 1: /* start hour (0..24) */
619 if (increase) {
620 if (schedStartHour < 24) { schedStartHour++; }
621 } else {
622 if (schedStartHour > 0) { schedStartHour--; }
623 }
624 if (schedStartHour > schedEndHour) { schedEndHour++; /* push it forward */ }
625 break;
626 case 2: /* start am/pm */
627 if (schedStartHour == 24) {
628 /* toggle start from AM to PM (24 to 12) */
629 schedStartHour -= 12;
630 if (schedEndHour < schedStartHour) { schedEndHour = schedStartHour; }
631 } else if (schedStartHour < 12) {
632 /* toggle start from AM to PM (0..11 to 12..23) */
633 schedStartHour += 12;
634 if (schedEndHour < schedStartHour) { schedEndHour = schedStartHour; }
635 } else {
636 /* toggle from PM to AM (12..23 to 0..11) */
637 schedStartHour -= 12;
638 }
639 break;
640 case 4: /* end hour (0..24) */
641 if (increase) {
642 if (schedEndHour < 24) { schedEndHour++; }
643 } else {
644 if (schedEndHour > 0) { schedEndHour--; }
645 }
646 if (schedEndHour < schedStartHour) { schedStartHour = schedEndHour; }
647 break;
648 case 5: /* end am/pm */
649 if (schedEndHour == 24) {
650 /* toggle start from AM to PM (24 to 12) */
651 schedEndHour -= 12;
652 if (schedStartHour > schedEndHour) { schedStartHour = schedEndHour; }
653 } else if (schedEndHour < 12) {
654 /* toggle start from AM to PM (0..11 to 12..23) */
655 schedEndHour += 12;
656 if (schedEndHour < schedStartHour) { schedEndHour = schedStartHour; }
657 } else {
658 /* toggle from PM to AM (12..23 to 0..11) */
659 schedEndHour -= 12;
660 if (schedEndHour < schedStartHour) { schedStartHour = schedEndHour; }
661 }
662 break;
663 default:
664 break;
665 }
666 }
667
668 if (redraw) {
669 char startHourStr[4];
670 char startAmPmStr[4];
671 char endHourStr[4];
672 char endAmPmStr[4];
673 GetScheduleStrings(schedStartHour, startHourStr, startAmPmStr);
674 GetScheduleStrings(schedEndHour, endHourStr, endAmPmStr);
675 TextFont(1);
676 for (index = 1; index < 6; index++) {
677 EraseRect(&parts[index]);
678 MoveTo(parts[index].left,tmp.bottom-5);
679 switch (index) {
680 case 1: DrawString(startHourStr); break;
681 case 2: DrawString(startAmPmStr); break;
682 case 3: DrawString("\p-"); break;
683 case 4: DrawString(endHourStr); break;
684 case 5: DrawString(endAmPmStr); break;
685 default:
686 break;
687 }
688 if (index == curSelectedIndex) {
689 InvertRect(&parts[index]); /* re-select current selection */
690 }
691 }
692 TextFont(0);
693 }
694 }
695
696 void ShowButtonPress(DialogPtr theDialog, short theItem)
697 {
698 long finalCount;
699 short tmpInt;
700 Handle tmpHandle;
701 Rect tmpRect;
702
703 GetDItem(theDialog, theItem, &tmpInt, &tmpHandle, &tmpRect);
704 HiliteControl((ControlHandle)tmpHandle, true); /* "push" */
705 Delay(10L, &finalCount);
706 HiliteControl((ControlHandle)tmpHandle, false); /* "release" */
707 }
708
709 pascal Boolean MyDialogFilter(DialogPtr theDialog, EventRecord *theEvent, short *itemHit)
710 {
711 char tmpChar;
712 short tmpInt;
713 Handle tmpHandle;
714 Rect tmpRect, schedRect, picRect;
715 Point tmpPt = { 0,0 };
716
717 switch(theEvent->what) {
718 case keyDown:
719 case autoKey:
720 /* convert the key to char */
721 tmpChar = (unsigned char)(theEvent->message & charCodeMask);
722 if (tmpChar == '\r' || tmpChar == '\n' || tmpChar == '\3') {
723 /* return or enter pressed */
724 *itemHit = okItem;
725 ShowButtonPress(theDialog, okItem);
726 return true;
727 }
728 else if (((theEvent->modifiers & cmdKey) && (tmpChar == '.')) ||
729 (tmpChar == 0x1B)) {
730 /* command-period or escape pressed */
731 *itemHit = cancelItem;
732 ShowButtonPress(theDialog, cancelItem);
733 return true;
734 }
735 return false;
736 case mouseDown:
737 GetDItem(theDialog, scheduleUserItem, &tmpInt, &tmpHandle, &tmpRect);
738 tmpPt = theEvent->where;
739 GlobalToLocal(&tmpPt);
740 if (PtInRect(tmpPt, &tmpRect)) {
741 HandleClickInScheduler(tmpPt, &tmpRect, false);
742 }
743 return false;
744 case updateEvt:
745 /* is the update event for this window? */
746 if (theEvent->message == (long)theDialog) {
747 /* draw default button indicator */
748 GetDItem(theDialog, okItem, &tmpInt, &tmpHandle, &tmpRect);
749 PenSize(kButtonFrameSize,kButtonFrameSize);
750 InsetRect(&tmpRect,kButtonFrameInset,kButtonFrameInset);
751 FrameRoundRect(&tmpRect,kButtonFrameRadius,kButtonFrameRadius);
752 PenNormal();
753
754 /* draw group boxes around user items */
755 GetDItem(theDialog, printerTypeUserItem, &tmpInt, &tmpHandle, &tmpRect);
756 PenSize(1,1);
757 PenPat(&qd.gray);
758 FrameRect(&tmpRect);
759 PenNormal();
760 GetDItem(theDialog, localPortUserItem, &tmpInt, &tmpHandle, &tmpRect);
761 PenSize(1,1);
762 PenPat(&qd.gray);
763 FrameRect(&tmpRect);
764 PenNormal();
765
766 /* draw arrow control */
767 GetDItem(theDialog, scheduleUserItem, &tmpInt, &tmpHandle, &tmpRect);
768 HLock((Handle)arrowsPictHdl);
769 picRect = (**arrowsPictHdl).picFrame;
770 HUnlock((Handle)arrowsPictHdl);
771 schedRect = tmpRect;
772 tmpRect.left = schedRect.right - (picRect.right-picRect.left);
773 tmpRect.bottom = schedRect.top + (picRect.bottom-picRect.top);
774 DrawPicture(arrowsPictHdl,&tmpRect);
775 HandleClickInScheduler(tmpPt,&schedRect,true); /* draw current values */
776 }
777 return false;
778 default:
779 return false;
780 }
781 return false;
782 }
783
784 OSErr ConfigureSpooler(Boolean willStartServer)
785 {
786 char portStr[256];
787 char typeStr[256];
788 char nameStr[256];
789 GrafPtr savePort;
790 DialogTHndl dtHdl;
791 DialogPtr aDialog;
792 short item;
793 Handle H;
794 Rect box, printerBox, portBox;
795 DialogRecord dlgRec;
796
797 /* preload stuff */
798 LoadPreferences();
799 LoadDateFormatStrings();
800 arrowsPictHdl = (PicHandle)GetResource('PICT',upDownArrowsPictID);
801
802 GetPort(&savePort);
803 /* get handle to dialog template and center it before displaying */
804 dtHdl = CenterDialog(spoolerDialogID);
805 aDialog = GetNewDialog(spoolerDialogID, (Ptr)&dlgRec, (WindowPtr) -1L);
806 if (aDialog) {
807 /* set up default and alternate button titles */
808 GetIndString(nameStr,buttonStringsID,(willStartServer) ? startStrIndex:saveStrIndex);
809 GetDItem(aDialog, okItem, &item, &H, &box);
810 SetCTitle((ControlHandle)H, nameStr);
811 /* should we disable button if server is already running? */
812 /* (no, just stop and restart the server to pick up new settings) */
813 /*HiliteControl((ControlHandle)H,(serverRef!=0)?255:0);*/
814 GetIndString(nameStr,buttonStringsID,cancelStrIndex);
815 GetDItem(aDialog, cancelItem, &item, &H, &box);
816 SetCTitle((ControlHandle)H, nameStr);
817
818 /* set up default name, printer type, and printer options */
819 GetIndString(typeStr,typeStringsID,defaultPrinterItem-(iconLWItem-1));
820 GetIndString(nameStr,nameStringsID,defaultPrinterItem-(iconLWItem-1));
821 GetDItem(aDialog, spoolerNameItem, &item, &H, &box);
822 SetIText(H, (ourName[0] > 0) ? ourName : nameStr);
823 GetDItem(aDialog, defaultPrinterItem, &item, &H, &box);
824 printerBox = box;
825
826 GetDItem(aDialog, colorRibbonCBoxItem, &item, &H, &box);
827 SetCtlValue((ControlHandle)H,defaultColorRibbon);
828 HiliteControl((ControlHandle)H,(defaultPrinterItem==iconLWItem)?255:0);
829 GetDItem(aDialog, sheetFeederCBoxItem, &item, &H, &box);
830 SetCtlValue((ControlHandle)H,defaultSheetFeeder);
831 HiliteControl((ControlHandle)H,(defaultPrinterItem==iconLWItem)?255:0);
832 GetDItem(aDialog, iw1ModeCBoxItem, &item, &H, &box);
833 SetCtlValue((ControlHandle)H,defaultIW1Mode);
834 HiliteControl((ControlHandle)H,(defaultPrinterItem==iconLWItem)?255:0);
835 GetDItem(aDialog, noReverseCBoxItem, &item, &H, &box);
836 SetCtlValue((ControlHandle)H,defaultNoReverse);
837 HiliteControl((ControlHandle)H,(defaultPrinterItem==iconLWItem)?255:0);
838 GetDItem(aDialog, printingEnabledItem, &item, &H, &box);
839 SetCtlValue((ControlHandle)H,defaultPrintEnabled);
840 GetDItem(aDialog, autoRunCBoxItem, &item, &H, &box);
841 SetCtlValue((ControlHandle)H,defaultAutoRun);
842
843 /* set up default port */
844 GetIndString(portStr,portStringsID,defaultPortItem-(modemPortItem-1));
845 GetDItem(aDialog, defaultPortItem, &item, &H, &box);
846 portBox = box;
847
848 SetPort(aDialog);
849 ShowWindow(aDialog);
850 InitCursor();
851
852 /* draw frames for initial selections */
853 PenSize(kIconFrameSize,kIconFrameSize);
854 InsetRect(&printerBox,kIconFrameInset,kIconFrameInset);
855 FrameRect(&printerBox);
856 InsetRect(&portBox,kIconFrameInset,kIconFrameInset);
857 FrameRect(&portBox);
858 PenNormal();
859
860 item = 0;
861 while ((item != okItem) && (item != cancelItem)) {
862 if (item >= iconLWItem && item <= iconIWItem) {
863 short tmpItem, index;
864 for (index = iconLWItem; index <= iconIWItem; index++) {
865 GetDItem(aDialog, index, &tmpItem, &H, &box);
866 InsetRect(&box,kIconFrameInset,kIconFrameInset);
867 PenSize(kIconFrameSize,kIconFrameSize);
868 PenPat(&qd.white);
869 FrameRect(&box);
870 PenNormal();
871 }
872 GetDItem(aDialog, item, &tmpItem, &H, &box);
873 InsetRect(&box,kIconFrameInset,kIconFrameInset);
874 PenSize(kIconFrameSize,kIconFrameSize);
875 PenPat(&qd.black);
876 FrameRect(&box);
877 PenNormal();
878 GetIndString(typeStr, 128, item-(iconLWItem-1));
879 GetIndString(nameStr, 129, item-(iconLWItem-1));
880 GetDItem(aDialog, spoolerNameItem, &tmpItem, &H, &box);
881 SetIText(H, nameStr);
882 GetDItem(aDialog, colorRibbonCBoxItem, &tmpItem, &H, &box);
883 HiliteControl((ControlHandle)H,(item==iconLWItem)?255:0);
884 GetDItem(aDialog, sheetFeederCBoxItem, &tmpItem, &H, &box);
885 HiliteControl((ControlHandle)H,(item==iconLWItem)?255:0);
886 GetDItem(aDialog, iw1ModeCBoxItem, &tmpItem, &H, &box);
887 HiliteControl((ControlHandle)H,(item!=iconIWItem)?255:0);
888 if (item!=iconIWItem) { SetCtlValue((ControlHandle)H,0); }
889 GetDItem(aDialog, noReverseCBoxItem, &tmpItem, &H, &box);
890 HiliteControl((ControlHandle)H,(item==iconLWItem)?255:0);
891 if (item==iconLWItem) { SetCtlValue((ControlHandle)H,0); }
892 }
893 else if (item >= modemPortItem && item <= printerPortItem) {
894 short tmpItem, index;
895 for (index = modemPortItem; index <= printerPortItem; index++) {
896 GetDItem(aDialog, index, &tmpItem, &H, &box);
897 InsetRect(&box,kIconFrameInset,kIconFrameInset);
898 PenSize(kIconFrameSize,kIconFrameSize);
899 PenPat(&qd.white);
900 FrameRect(&box);
901 PenNormal();
902 }
903 GetDItem(aDialog, item, &tmpItem, &H, &box);
904 InsetRect(&box,kIconFrameInset,kIconFrameInset);
905 PenSize(kIconFrameSize,kIconFrameSize);
906 PenPat(&qd.black);
907 FrameRect(&box);
908 PenNormal();
909 GetIndString(portStr, 130, item-(modemPortItem-1));
910 }
911 else if ((item >= colorRibbonCBoxItem && item <= printingEnabledItem) ||
912 (item == autoRunCBoxItem)) {
913 short tmpItem, curValue;
914 GetDItem(aDialog, item, &tmpItem, &H, &box);
915 curValue = GetCtlValue((ControlHandle)H);
916 SetCtlValue((ControlHandle)H,!curValue);
917 }
918 ModalDialog((ModalFilterProcPtr)MyDialogFilter, &item);
919 }
920 if (item == okItem) {
921 short tmpItem;
922 Boolean hasColorRibbon, hasSheetFeeder;
923 Boolean iw1Mode, noReverse, printEnabled, autoRun;
924 GetDItem(aDialog, spoolerNameItem, &tmpItem, &H, &box);
925 GetIText(H, nameStr);
926 GetDItem(aDialog, colorRibbonCBoxItem, &tmpItem, &H, &box);
927 hasColorRibbon = (GetCtlValue((ControlHandle)H) != 0);
928 GetDItem(aDialog, sheetFeederCBoxItem, &tmpItem, &H, &box);
929 hasSheetFeeder = (GetCtlValue((ControlHandle)H) != 0);
930 GetDItem(aDialog, iw1ModeCBoxItem, &tmpItem, &H, &box);
931 iw1Mode = (GetCtlValue((ControlHandle)H) != 0);
932 GetDItem(aDialog, noReverseCBoxItem, &tmpItem, &H, &box);
933 noReverse = (GetCtlValue((ControlHandle)H) != 0);
934 GetDItem(aDialog, printingEnabledItem, &tmpItem, &H, &box);
935 printEnabled = (GetCtlValue((ControlHandle)H) != 0);
936 GetDItem(aDialog, autoRunCBoxItem, &tmpItem, &H, &box);
937 autoRun = (GetCtlValue((ControlHandle)H) != 0);
938 SetPrinterOptions(hasColorRibbon, hasSheetFeeder,iw1Mode,noReverse);
939 defaultColorRibbon = hasColorRibbon;
940 defaultSheetFeeder = hasSheetFeeder;
941 defaultPrintEnabled = printEnabled;
942 defaultIW1Mode = iw1Mode;
943 defaultNoReverse = noReverse;
944 defaultAutoRun = autoRun;
945 }
946 curSelectedIndex = 0;
947 ReleaseResource((Handle)arrowsPictHdl);
948 arrowsPictHdl = 0L;
949 CloseDialog(aDialog);
950 }
951 SetPort(savePort);
952 UpdateLogWindow();
953
954 if (item == okItem) {
955 /* save our name, type, and local printer port */
956 BlockMove(&nameStr[1],&ourName[1],nameStr[0]);
957 ourName[0] = nameStr[0];
958 BlockMove(&typeStr[1],&ourType[1],typeStr[0]);
959 ourType[0] = typeStr[0];
960 BlockMove(&portStr[1],&ourPort[1],portStr[0]);
961 ourPort[0] = portStr[0];
962 /* prefix is based on selected printer type */
963 ourPrefix[0] = 3;
964 ourPrefix[1] = ourType[1]; /* 'L' or 'I' */
965 ourPrefix[2] = (ourType[2] == 'Q') ? 'Q' : 'W';
966 ourPrefix[3] = '-';
967 /* save for next time (after spool folder is set up) */
968 SavePreferences();
969 }
970 return (item == okItem) ? noErr : userCanceledErr;
971 }
972
973 OSErr StartSpooler(void)
974 {
975 OSErr err = noErr;
976 if (!defaultAutoRun) {
977 err = ConfigureSpooler(true);
978 }
979 if (err == noErr) {
980 char nameStr[256];
981 EntityName name;
982 PAPStatusRec stRec;
983 name.zoneStr[0] = 1;
984 name.zoneStr[1] = '*';
985 BlockMove(ourType, name.typeStr, 33L);
986 BlockMove(ourName, name.objStr, 33L);
987 BlockMove("\pRegistering", stRec.statusStr, 12L);
988
989 err = SLInit(&name, kPAPMaxQuantum, &stRec, &serverRef);
990 if (err != noErr) {
991 MessageWithIntValue(LOG_ERR_X,"\pRegistering name:",err);
992 (void) SLClose(serverRef);
993 serverRef = 0;
994 } else {
995 BlockMove("Registered “", &nameStr[1],12L);
996 nameStr[0] = 12;
997 BlockMove(&ourName[1],&nameStr[nameStr[0]+1],(long)((short)ourName[0]));
998 nameStr[0] += ourName[0];
999 BlockMove("” in local zone", &nameStr[nameStr[0]+1],15L);
1000 nameStr[0] += 15;
1001 Message(LOG_ERR,nameStr);
1002 }
1003 }
1004 return err;
1005 }
1006
1007 OSErr StopSpooler(void)
1008 {
1009 OSErr err = SLClose(serverRef);
1010 serverRef = 0;
1011 return err;
1012 }
1013
1014 void DoAbout()
1015 {
1016 Message(0,sepStr);
1017 Message(0,"\pSimple Spooler is a basic print spooler application.");
1018 Message(0,"\pIt presents itself as an AppleTalk printer and will");
1019 Message(0,"\prespond to Printer Access Protocol (PAP) messages.");
1020 Message(0,verStr);
1021 Message(0,sepStr);
1022 }
1023
1024 void DoStatus()
1025 {
1026 Message(0,sepStr);
1027 MessageWithIntValue(0,"\pSpooler active:",serverRef);
1028 MessageWithQuotedString(0,"\pSpooler name:",ourName);
1029 MessageWithQuotedString(0,"\pPrinter type:",ourType);
1030 MessageWithIntValue(0,"\pColor ribbon:",(defaultColorRibbon)?1:0);
1031 MessageWithIntValue(0,"\pSheet feeder:",(defaultSheetFeeder)?1:0);
1032 MessageWithIntValue(0,"\pIW1 compatibility:",(defaultIW1Mode)?1:0);
1033 MessageWithIntValue(0,"\pSkip reverse feed:",(defaultNoReverse)?1:0);
1034 MessageWithQuotedString(0,"\pPrint to port:",ourPort);
1035 MessageWithIntValue(0,"\pPrinting enabled:",(defaultPrintEnabled)?1:0);
1036 MessageWithIntValue(0,"\pScheduled start hour:",schedStartHour);
1037 MessageWithIntValue(0,"\pScheduled end hour:",schedEndHour);
1038 MessageWithIntValue(0,"\pJobs spooled:",JobsSpooled());
1039 MessageWithIntValue(0,"\pJobs printed:",JobsPrinted());
1040 Message(0,sepStr);
1041 }
1042
1043 void LoadPreferences()
1044 {
1045 char *p;
1046 Ptr prefData = 0L;
1047 long count = 4L+2L+8+18+256+34+34;
1048 OSErr err = CopyDataFromPrefsFile(&count,&prefData);
1049 if (err != noErr || !prefData) { return; }
1050 if (*((long*)&prefData[0]) != 'pref' || *((short*)&prefData[4]) != kPrefsVersion) {
1051 DisposPtr(prefData);
1052 return;
1053 }
1054 defaultPrinterItem = *((char*)&prefData[6]);
1055 defaultPortItem = *((char*)&prefData[7]);
1056 defaultColorRibbon = *((char*)&prefData[8]);
1057 defaultSheetFeeder = *((char*)&prefData[9]);
1058 defaultPrintEnabled = *((char*)&prefData[10]);
1059 defaultIW1Mode = *((char*)&prefData[11]);
1060 schedStartHour = *((char*)&prefData[12]);
1061 schedEndHour = *((char*)&prefData[13]);
1062 /* window rect in global coordinates */
1063 windowRect = *((Rect*)&prefData[14]);
1064 defaultNoReverse = *((char*)&prefData[22]);
1065 defaultAutoRun = *((char*)&prefData[23]);
1066 /* reserved chars at prefsData[24..31] */
1067 p = (char*)&prefData[32];
1068 BlockMove(p,ourName,p[0]+1);
1069 p += p[0]+1;
1070 BlockMove(p,ourType,p[0]+1);
1071 p += p[0]+1;
1072 BlockMove(p,ourPort,p[0]+1);
1073 DisposPtr(prefData);
1074
1075 /* let PAP module know about the new options */
1076 SetPrinterOptions(defaultColorRibbon,defaultSheetFeeder,defaultIW1Mode,defaultNoReverse);
1077 }
1078
1079 void SavePreferences()
1080 {
1081 char *p, val;
1082 Boolean hasColorRibbon, hasSheetFeeder;
1083 Boolean iw1Mode, noReverse, printEnabled, autoRun;
1084 long length = 4L+2L+8+18+ourName[0]+1+ourType[0]+1+ourPort[0]+1;
1085 Ptr prefData = NewPtrClear(length);
1086 if (!prefData) { return; }
1087
1088 /* offset 0: magic 'pref' tag (4 bytes) */
1089 *((long*)&prefData[0]) = 'pref';
1090 /* offset 4: prefs version (2 bytes) */
1091 *((short*)&prefData[4]) = kPrefsVersion;
1092 /* offset 6: selected printer item */
1093 val = iconIWItem;
1094 if (ourType[1] == 'L') { val = (ourType[2] == 'Q') ? iconLQItem : iconLWItem; }
1095 *((char*)&prefData[6]) = val;
1096 /* offset 7: selected printer port item */
1097 val = (ourPort[1] == 'M') ? modemPortItem : printerPortItem;
1098 *((char*)&prefData[7]) = val;
1099 /* offset 8, 9: boolean values of Color Ribbon and Sheet Feeder */
1100 GetPrinterOptions(&hasColorRibbon,&hasSheetFeeder,&iw1Mode,&noReverse);
1101 *((char*)&prefData[8]) = (hasColorRibbon) ? -1 : 0;
1102 *((char*)&prefData[9]) = (hasSheetFeeder) ? -1 : 0;
1103 /* offset 10, 11: boolean values of Print Enabled and IW1 Mode */
1104 printEnabled = defaultPrintEnabled;
1105 *((char*)&prefData[10]) = (printEnabled) ? -1 : 0;
1106 iw1Mode = defaultIW1Mode;
1107 *((char*)&prefData[11]) = (iw1Mode) ? -1 : 0;
1108 /* offset 12: scheduled start hour for printing (1 byte) */
1109 *((char*)&prefData[12]) = (char)(schedStartHour & 0xFF);
1110 /* offset 13: scheduled end hour for printing (1 byte) */
1111 *((char*)&prefData[13]) = (char)(schedEndHour & 0xFF);
1112 /* offset 14-15:top, 16-17:left, 18-19:bottom, 20-21:right (window rect) */
1113 *((Rect*)&prefData[14]) = ((GrafPtr)textWindow)->portRect;
1114 LocalToGlobal((Point*)&prefData[14]);
1115 LocalToGlobal((Point*)&prefData[18]);
1116 /* offset 22, 23: boolean values of No Reverse and Auto Run */
1117 *((char*)&prefData[22]) = (noReverse) ? -1 : 0;
1118 autoRun = defaultAutoRun;
1119 *((char*)&prefData[23]) = (autoRun) ? -1 : 0;
1120 /* reserved chars from [24..31] */
1121 p = (char*)&prefData[32];
1122 BlockMove(ourName,p,ourName[0]+1);
1123 p += ourName[0]+1;
1124 BlockMove(ourType,p,ourType[0]+1);
1125 p += ourType[0]+1;
1126 BlockMove(ourPort,p,ourPort[0]+1);
1127
1128 (void)SaveDataToPrefsFile(length,prefData);
1129 DisposPtr(prefData);
1130 }