AmendHub

Download

thecloud

/

SimpleSpooler

/

Spooler.c

 

(View History)

thecloud   refactor StartSpooler code to allow auto-running server Latest amendment: 3 on 2026-07-29

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