AmendHub

jcs

/

wikipedia

/

amendments

/

1

*: Import skeleton code


jcs made amendment 1 about 1 year ago
--- browser.c Thu Aug 18 22:03:09 2022 +++ browser.c Thu Aug 18 22:03:09 2022 @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +#include "wikipedia.h" +#include "browser.h" +#include "focusable.h" +#include "util.h" + +#define PADDING 10 + +bool browser_close(struct focusable *focusable); +void browser_idle(struct focusable *focusable, EventRecord *event); +void browser_update_menu(struct browser *browser); +void browser_update(struct focusable *focusable, EventRecord *event); +void browser_key_down(struct focusable *focusable, EventRecord *event); +void browser_mouse_down(struct focusable *focusable, EventRecord *event); +bool browser_handle_menu(struct focusable *focusable, short menu, + short item); + +Pattern fill_pattern; + +void +browser_idle(struct focusable *focusable, EventRecord *event) +{ + struct browser *browser = (struct browser *)focusable->cookie; +#if 0 + switch (browser->state) { + case BROWSER_STATE_IDLE: + if (browser->need_refresh) { + browser->need_refresh = false; + browser->state = BROWSER_STATE_UPDATE_AMENDMENT_LIST; + } + break; + case BROWSER_STATE_ADD_FILE: + if (repo_add_file(browser->repo) == NULL) + browser->state = BROWSER_STATE_IDLE; + else + browser->state = BROWSER_STATE_UPDATE_FILE_LIST; + break; + case BROWSER_STATE_UPDATE_FILE_LIST: + browser_add_files(browser); + browser->state = BROWSER_STATE_IDLE; + break; + case BROWSER_STATE_UPDATE_AMENDMENT_LIST: + browser_filter_amendments(browser); + browser->state = BROWSER_STATE_IDLE; + break; + case BROWSER_STATE_OPEN_COMMITTER: + committer_init(browser); + browser->state = BROWSER_STATE_WAITING_FOR_COMMITTER; + break; + case BROWSER_STATE_WAITING_FOR_COMMITTER: + break; + case BROWSER_STATE_DISCARD_CHANGES: + browser_discard_changes(browser); + browser->state = BROWSER_STATE_IDLE; + break; + case BROWSER_STATE_EXPORT_PATCH: + browser_export_patch(browser); + browser->state = BROWSER_STATE_IDLE; + break; + case BROWSER_STATE_APPLY_PATCH: + browser_apply_patch(browser); + browser->state = BROWSER_STATE_IDLE; + break; + case BROWSER_STATE_EDIT_AMENDMENT: + browser_edit_amendment(browser); + browser->state = BROWSER_STATE_IDLE; + break; + } +#endif +} + +struct browser * +browser_init(void) +{ + char title[256]; + struct browser *browser; + struct focusable *focusable; + Rect bounds = { 0 }, te_bounds = { 0 }; + Rect data_bounds = { 0, 0, 0, 1 }; /* tlbr */ + Point cell_size = { 0, 0 }; + Cell cell = { 0 }; + short n; + + browser = xmalloczero(sizeof(struct browser), "browser"); + browser->state = BROWSER_STATE_IDLE; + + GetIndPattern(&fill_pattern, sysPatListID, 22); + + /* main window */ + bounds.left = (PADDING / 2); + bounds.top = screenBits.bounds.top + (GetMBarHeight() * 2) - 1 + + (PADDING / 2); + bounds.right = screenBits.bounds.right - 1 - (PADDING / 2); + bounds.bottom = screenBits.bounds.bottom - 1 - (PADDING / 2); + + snprintf(title, sizeof(title), "%s", PROGRAM_NAME); + CtoPstr(title); + browser->win = NewWindow(0L, &bounds, title, false, noGrowDocProc, + (WindowPtr)-1L, true, 0); + if (!browser->win) + err(1, "Can't create window"); + SetPort(browser->win); + + bounds.top = PADDING; + bounds.left = PADDING; + bounds.right = browser->win->portRect.right - SCROLLBAR_WIDTH - PADDING; + bounds.bottom = browser->win->portRect.bottom - PADDING; + te_bounds = bounds; + InsetRect(&te_bounds, 2, 2); + browser->te = TEStylNew(&te_bounds, &bounds); + TEAutoView(true, browser->te); + (*(browser->te))->caretHook = NullCaretHook; + TEActivate(browser->te); + + /* scrollbar for diff text */ + bounds.right = browser->win->portRect.right - PADDING; + bounds.left = bounds.right - SCROLLBAR_WIDTH; + bounds.bottom++; + bounds.top--; + browser->te_scroller = NewControl(browser->win, &bounds, "\p", true, + 1, 1, 1, scrollBarProc, 0L); + + browser_update_menu(browser); + UpdateScrollbarForTE(browser->te_scroller, browser->te, true); + + focusable = xmalloczero(sizeof(struct focusable), "focusable"); + focusable->cookie = browser; + focusable->win = browser->win; + focusable->idle = browser_idle; + focusable->update = browser_update; + focusable->mouse_down = browser_mouse_down; + focusable->menu = browser_handle_menu; + focusable->close = browser_close; + focusable_add(focusable); + + return browser; +} + +bool +browser_close(struct focusable *focusable) +{ + struct browser *browser = (struct browser *)focusable->cookie; + + TEDispose(browser->te); + DisposeWindow(browser->win); + + free(browser); + + return true; +} + +void +browser_update_menu(struct browser *browser) +{ + size_t vlines; + TERec *te; + Cell cell = { 0, 0 }; + + TextFont(systemFont); + TextSize(12); + + te = *(browser->te); + + DisableItem(edit_menu, EDIT_MENU_CUT_ID); + + if (te->selStart == te->selEnd) + DisableItem(edit_menu, EDIT_MENU_COPY_ID); + else + EnableItem(edit_menu, EDIT_MENU_COPY_ID); + + DisableItem(edit_menu, EDIT_MENU_PASTE_ID); + + if (te->nLines == 0) + DisableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID); + else + EnableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID); +} + +void +browser_update(struct focusable *focusable, EventRecord *event) +{ + struct browser *browser = (struct browser *)focusable->cookie; + Str255 buf; + Rect r; + short what = -1; + + if (event != NULL) + what = event->what; + + switch (what) { + case -1: + case updateEvt: + FillRect(&browser->win->portRect, fill_pattern); + + r = (*(browser->te))->viewRect; + FillRect(&r, white); + TEUpdate(&r, browser->te); + InsetRect(&r, -1, -1); + FrameRect(&r); + + UpdtControl(browser->win, browser->win->visRgn); + + browser_update_menu(browser); + + break; + } +} + +void +browser_mouse_down(struct focusable *focusable, EventRecord *event) +{ + Cell selected = { 0 }, now = { 0 }; + Point p; + ControlHandle control; + Rect r; + struct browser *browser = (struct browser *)focusable->cookie; + short val, adj, page, was_selected, part, i, data_len; + char *path; + + p = event->where; + GlobalToLocal(&p); + + /* is it in TE? */ + r = (*(browser->te))->viewRect; + if (PtInRect(p, &r)) { + TEClick(p, ((event->modifiers & shiftKey) != 0), browser->te); + browser_update_menu(browser); + return; + } + + switch (part = FindControl(p, browser->win, &control)) { + case inButton: + break; + case inUpButton: + case inDownButton: + case inPageUp: + case inPageDown: + if (control == browser->te_scroller) + SetTrackControlTE(browser->te); + else + break; + TrackControl(control, p, TrackMouseDownInControl); + break; + case inThumb: + val = GetCtlValue(control); + if (TrackControl(control, p, 0L) == 0) + break; + adj = val - GetCtlValue(control); + if (adj != 0) { + val -= adj; + if (control == browser->te_scroller) + TEScroll(0, adj * TEGetHeight(0, 0, browser->te), + browser->te); + SetCtlValue(control, val); + } + break; + } +} + +bool +browser_handle_menu(struct focusable *focusable, short menu, short item) +{ + struct browser *browser = (struct browser *)focusable->cookie; + + switch (menu) { + case EDIT_MENU_ID: + switch (item) { + case EDIT_MENU_COPY_ID: + TECopy(browser->te); + return true; + case EDIT_MENU_SELECT_ALL_ID: + TESetSelect(0, 1024 * 32, browser->te); + return true; + } + break; + } + + return false; +} --- browser.h Thu Aug 18 22:01:49 2022 +++ browser.h Thu Aug 18 22:01:49 2022 @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __BROWSER_H__ +#define __BROWSER_H__ + +#include <stdlib.h> + +enum { + BROWSER_STATE_IDLE +}; + +struct browser { + short state; + WindowPtr win; + TEHandle te; + ControlHandle te_scroller; +}; + +struct browser *browser_init(void); + +#endif --- dnr.c Thu Aug 18 21:15:47 2022 +++ dnr.c Thu Aug 18 21:15:47 2022 @@ -0,0 +1,347 @@ +/* + * DNR.c - DNR library for MPW + * + * (c) Copyright 1988 by Apple Computer. All rights reserved + * + * Modifications by Jim Matthews, Dartmouth College, 5/91 + */ + +#include <OSUtils.h> +#include <Files.h> +#include <Folders.h> +#include <GestaltEqu.h> +#include <Traps.h> + +#include "dnr.h" + +#define OPENRESOLVER 1 +#define CLOSERESOLVER 2 +#define STRTOADDR 3 +#define ADDRTOSTR 4 +#define ENUMCACHE 5 +#define ADDRTONAME 6 +#define HINFO 7 +#define MXINFO 8 + +TrapType GetTrapType(unsigned long theTrap); +Boolean TrapAvailable(unsigned long trap); +void GetSystemFolder(short *vRefNumP, long *dirIDP); +void GetCPanelFolder(short *vRefNumP, long *dirIDP); +short SearchFolderForDNRP(long targetType, long targetCreator, + short vRefNum, long dirID); +short OpenOurRF(void); +pascal void StrToAddrMarkDone(struct hostInfo *hi, char *data); + +Handle dnr = nil; + +TrapType +GetTrapType(unsigned long theTrap) +{ + if (BitAnd(theTrap, 0x0800) > 0) + return (ToolTrap); + + return (OSTrap); +} + +Boolean +TrapAvailable(unsigned long trap) +{ + TrapType trapType = ToolTrap; + unsigned long numToolBoxTraps; + + if (NGetTrapAddress(_InitGraf, ToolTrap) == + NGetTrapAddress(0xAA6E, ToolTrap)) + numToolBoxTraps = 0x200; + else + numToolBoxTraps = 0x400; + + trapType = GetTrapType(trap); + if (trapType == ToolTrap) { + trap = BitAnd(trap, 0x07FF); + if (trap >= numToolBoxTraps) + trap = _Unimplemented; + } + + return (NGetTrapAddress(trap, trapType) != + NGetTrapAddress(_Unimplemented, ToolTrap)); +} + +void +GetSystemFolder(short *vRefNumP, long *dirIDP) +{ + SysEnvRec info; + long wdProcID; + + SysEnvirons(1, &info); + if (GetWDInfo(info.sysVRefNum, vRefNumP, dirIDP, &wdProcID) != noErr) { + *vRefNumP = 0; + *dirIDP = 0; + } +} + +void +GetCPanelFolder(short *vRefNumP, long *dirIDP) +{ + Boolean hasFolderMgr = false; + long feature; + + if (TrapAvailable(_GestaltDispatch) && + Gestalt(gestaltFindFolderAttr, &feature) == noErr) + hasFolderMgr = true; + + if (!hasFolderMgr) { + GetSystemFolder(vRefNumP, dirIDP); + return; + } + + if (FindFolder(kOnSystemDisk, kControlPanelFolderType, + kDontCreateFolder, vRefNumP, dirIDP) != noErr) { + *vRefNumP = 0; + *dirIDP = 0; + } +} + +/* + * SearchFolderForDNRP is called to search a folder for files that might + * contain the 'dnrp' resource + */ +short +SearchFolderForDNRP(long targetType, long targetCreator, short vRefNum, + long dirID) +{ + HParamBlockRec fi; + Str255 filename; + short refnum; + + fi.fileParam.ioCompletion = nil; + fi.fileParam.ioNamePtr = filename; + fi.fileParam.ioVRefNum = vRefNum; + fi.fileParam.ioDirID = dirID; + fi.fileParam.ioFDirIndex = 1; + + while (PBHGetFInfo(&fi, false) == noErr) { + /* scan system folder for driver resource files of specific type & creator */ + if (fi.fileParam.ioFlFndrInfo.fdType == targetType && + fi.fileParam.ioFlFndrInfo.fdCreator == targetCreator) { + /* found the MacTCP driver file? */ + refnum = HOpenResFile(vRefNum, dirID, filename, fsRdPerm); + if (Get1IndResource('dnrp', 1)) + return refnum; + CloseResFile(refnum); + } + + /* check next file in system folder */ + fi.fileParam.ioFDirIndex++; + fi.fileParam.ioDirID = dirID; /* PBHGetFInfo() clobbers ioDirID */ + } + return -1; +} + +/* OpenOurRF is called to open the MacTCP driver resources */ +short +OpenOurRF(void) +{ + short refnum; + short vRefNum; + long dirID; + + /* first search Control Panels for MacTCP 1.1 */ + GetCPanelFolder(&vRefNum, &dirID); + refnum = SearchFolderForDNRP('cdev', 'ztcp', vRefNum, dirID); + if (refnum != -1) + return (refnum); + + /* next search System Folder for MacTCP 1.0.x */ + GetSystemFolder(&vRefNum, &dirID); + refnum = SearchFolderForDNRP('cdev', 'mtcp', vRefNum, dirID); + if (refnum != -1) + return (refnum); + + /* finally, search Control Panels for MacTCP 1.0.x */ + GetCPanelFolder(&vRefNum, &dirID); + refnum = SearchFolderForDNRP('cdev', 'mtcp', vRefNum, dirID); + if (refnum != -1) + return (refnum); + + return -1; +} + +OSErr +OpenResolver(char *fileName) +{ + short refnum; + OSErr rc; + + if (dnr != nil) + /* resolver already loaded in */ + return (noErr); + + /* + * Open the MacTCP driver to get DNR resources. Search for it based on + * creator & type rather than simply file name. + */ + refnum = OpenOurRF(); + + /* + * Ignore failures since the resource may have been installed in the + * System file if running on a Mac 512Ke. + */ + + /* load in the DNR resource package */ + ResrvMem(16000L); /* s-dorner@uiuc.edu, 9/5/91 */ + dnr = Get1IndResource('dnrp', 1); + if (dnr == nil) { + /* can't open DNR */ + return (ResError()); + } + + DetachResource(dnr); + if (refnum != -1) { + CloseWD(refnum); + CloseResFile(refnum); + } + + /* lock the DNR resource since it cannot be relocated while opened */ + MoveHHi(dnr); + HLock(dnr); + + /* call open resolver */ + rc = ((OpenResolverProcPtr)*dnr)(OPENRESOLVER, fileName); + if (rc != noErr) { + /* problem with open resolver, flush it */ + HUnlock(dnr); + DisposHandle(dnr); + dnr = nil; + } + + return (rc); +} + +OSErr +CloseResolver(void) +{ + if (dnr == nil) + /* resolver not loaded error */ + return (notOpenErr); + + /* call close resolver */ + (void)((CloseResolverProcPtr)*dnr)(CLOSERESOLVER); + + /* release the DNR resource package */ + HUnlock(dnr); + DisposHandle(dnr); + dnr = nil; + + return (noErr); +} + +OSErr +StrToAddr(char *hostName, struct hostInfo *rtnStruct, + ResultProcPtr resultproc, char *userDataPtr) +{ + OSErr err; + + if (dnr == nil) + /* resolver not loaded error */ + return (notOpenErr); + + err = ((StrToAddrProcPtr)*dnr)(STRTOADDR, hostName, rtnStruct, + resultproc, userDataPtr); + return err; +} + +OSErr +AddrToStr(unsigned long addr, char *addrStr) +{ + if (dnr == nil) + /* resolver not loaded error */ + return (notOpenErr); + + (void)((AddrToStrProcPtr)*dnr)(ADDRTOSTR, addr, addrStr); + return (noErr); +} + +OSErr +EnumCache(EnumResultProcPtr enumResultProc, char *userDataPtr) +{ + if (dnr == nil) + /* resolver not loaded error */ + return (notOpenErr); + + return ((EnumCacheProcPtr)*dnr)(ENUMCACHE, enumResultProc, + userDataPtr); +} + +OSErr +AddrToName(ip_addr addr, struct hostInfo *hostInfoPtr, + ResultProcPtr resultProc, char *userDataPtr) +{ + if (dnr == nil) + /* resolver not loaded error */ + return (notOpenErr); + + return ((AddrToNameProcPtr)*dnr)(ADDRTONAME, addr, hostInfoPtr, + resultProc, userDataPtr); +} + +#if 0 +OSErr +HInfo(char *hostName, struct returnRec *returnRecPtr, long resultProc, + char *userDataPtr) +{ + if (dnr == nil) + /* resolver not loaded error */ + return (notOpenErr); + + return ((*dnr)(HINFO, hostName, returnRecPtr, resultProc, userDataPtr)); +} + +OSErr +MXInfo(char *hostName, struct returnRec *returnRecPtr, long resultProc, + char *userDataPtr) +{ + if (dnr == nil) + /* resolver not loaded error */ + return (notOpenErr); + + return ((*dnr)(MXINFO, hostName, returnRecPtr, resultProc, userDataPtr)); +} +#endif + +pascal void +StrToAddrMarkDone(struct hostInfo *hi, char *data) +{ + volatile int *done = (int *)data; + *done = 1; +} + +OSErr +ResolveName(char **name, unsigned long *ipAddress) +{ + OSErr osErr; + struct hostInfo aHostInfo; + volatile int done = 0; + + osErr = OpenResolver(nil); + if (osErr) + return osErr; + + osErr = StrToAddr(*name, &aHostInfo, (ResultProcPtr)StrToAddrMarkDone, + (char *)&done); + + if (osErr == cacheFault) { + /* StrToAddrMarkDone will set done when DNS resolution finishes */ + while (!done) + ; + } + + if ((aHostInfo.rtnCode == noErr) || (aHostInfo.rtnCode == cacheFault)) { + /* use the first IP address for this host */ + *ipAddress = aHostInfo.addr[0]; + osErr = noErr; + } else + osErr = aHostInfo.rtnCode; + + CloseResolver(); + return osErr; +} --- dnr.h Thu Aug 18 21:13:21 2022 +++ dnr.h Thu Aug 18 21:13:21 2022 @@ -0,0 +1,76 @@ +/* + dnr.h (formerly AddressXlation.h) + MacTCP name to address translation routines. + + Copyright Apple Computer, Inc. 1988 + All rights reserved + +*/ + +#ifndef __ADDRESSXLATION__ +#define __ADDRESSXLATION__ + +#include "MacTCP.h" + +#define NUM_ALT_ADDRS 4 + +struct hostInfo { + short _pad; /* XXX: i don't know why this is needed, but without it, + * StrToAddrProcPtr() returns everything shifted 2 bytes */ + short rtnCode; + char cname[255]; + unsigned long addr[NUM_ALT_ADDRS]; +}; + +enum AddrClasses { + A = 1, + NS, + CNAME = 5, + lastClass = 65535 +}; + +struct cacheEntryRecord { + char *cname; + unsigned short type; + enum AddrClasses class; + unsigned long ttl; + union { + char *name; + ip_addr addr; + } rdata; +}; + +typedef pascal void (*EnumResultProcPtr)(struct cacheEntryRecord *cacheEntryRecordPtr, + char *userDataPtr); +typedef pascal void (*ResultProcPtr)(struct hostInfo *hostInfoPtr, + char *userDataPtr); + +extern OSErr OpenResolver(char *fileName); +extern OSErr StrToAddr(char *hostName, struct hostInfo *hostInfoPtr, + ResultProcPtr resultProc, char *userDataPtr); +extern OSErr AddrToStr(unsigned long addr, char *addrStr); +extern OSErr EnumCache(EnumResultProcPtr enumResultProc, char *userDataPtr); +extern OSErr AddrToName(ip_addr addr, struct hostInfo *hostInfoPtr, + ResultProcPtr resultProc, char *userDataPtr); +extern OSErr CloseResolver(void); + +/* + * The above functions call into the dnr resource and pass the function id + * and function-specific arguments, so the compiler needs to know the types + */ +typedef OSErr (*OpenResolverProcPtr)(UInt32, char *); +typedef OSErr (*CloseResolverProcPtr)(UInt32); +typedef OSErr (*StrToAddrProcPtr)(UInt32, char *hostName, + struct hostInfo *hostInfoPtr, ResultProcPtr resultProc, + char *userDataPtr); +typedef OSErr (*AddrToStrProcPtr)(UInt32, unsigned long addr, + char *addrStr); +typedef OSErr (*EnumCacheProcPtr)(UInt32, EnumResultProcPtr enumResultProc, + char *userDataPtr); +typedef OSErr (*AddrToNameProcPtr)(UInt32, ip_addr addr, + struct hostInfo *hostInfoPtr, ResultProcPtr resultProc, + char *userDataPtr); + +OSErr ResolveName(char **name, unsigned long *ipAddress); + +#endif --- focusable.c Thu Aug 18 21:14:38 2022 +++ focusable.c Thu Aug 18 21:14:38 2022 @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <string.h> + +#include "focusable.h" +#include "util.h" + +struct focusable **focusables = NULL; +short nfocusables = 0; + +struct focusable * +focusable_find(GrafPtr win) +{ + short n; + + for (n = 0; n < nfocusables; n++) { + if (focusables[n]->win == win) + return focusables[n]; + } + + return NULL; +} + +struct focusable * +focusable_focused(void) +{ + if (nfocusables && focusables[0]->visible) + return focusables[0]; + + return NULL; +} + +void +focusable_add(struct focusable *focusable) +{ + short n; + + nfocusables++; + focusables = xreallocarray(focusables, sizeof(struct focusable *), + nfocusables); + + if (nfocusables > 1) + for (n = nfocusables - 1; n > 0; n--) + focusables[n] = focusables[n - 1]; + + focusables[0] = focusable; + + focusable_show(focusable); +} + +bool +focusable_show(struct focusable *focusable) +{ + struct focusable *last, *tmp; + short n; + + if (nfocusables > 1 && focusables[0] != focusable) { + last = focusables[0]; + + if (last->modal) + /* other focusables cannot steal focus from modals */ + return false; + + focusables[0] = focusable; + for (n = 1; n < nfocusables; n++) { + tmp = focusables[n]; + focusables[n] = last; + last = tmp; + if (last == focusable) + break; + } + } + + if (!focusable->visible) { + focusable->visible = true; + ShowWindow(focusable->win); + } + + SelectWindow(focusable->win); + SetPort(focusable->win); + + return true; +} + +bool +focusable_close(struct focusable *focusable) +{ + short n; + + if (focusable->close) { + if (!focusable->close(focusable)) + return false; + } else + DisposeWindow(focusable->win); + + for (n = 0; n < nfocusables; n++) { + if (focusables[n] == focusable) { + for (; n < nfocusables - 1; n++) + focusables[n] = focusables[n + 1]; + break; + } + } + + nfocusables--; + if (nfocusables) + focusables = xreallocarray(focusables, sizeof(Ptr), nfocusables); + else { + free(focusables); + focusables = NULL; + } + + if (nfocusables && focusables[0]->visible) + focusable_show(focusables[0]); + + return true; +} + +void +focusable_hide(struct focusable *focusable) +{ + short n; + + HideWindow(focusable->win); + focusable->visible = false; + + for (n = 0; n < nfocusables; n++) { + if (focusables[n] == focusable) { + for (; n < nfocusables - 1; n++) + focusables[n] = focusables[n + 1]; + break; + } + } + focusables[nfocusables - 1] = focusable; +} + +bool +focusables_quit(void) +{ + short tnfocusables = nfocusables; + short n; + struct focusable **tfocusables; + bool quit = true; + + if (nfocusables) { + /* + * nfocusables and focusables array will probably be + * modified as each focusable quits + */ + tfocusables = xcalloc(sizeof(Ptr), tnfocusables, "tfocusables"); + memcpy(tfocusables, focusables, sizeof(Ptr) * tnfocusables); + + for (n = 0; n < tnfocusables; n++) { + if (tfocusables[n] && !focusable_close(tfocusables[n])) { + quit = false; + break; + } + } + + free(tfocusables); + } + + return quit; +} --- focusable.h Tue Aug 16 13:33:08 2022 +++ focusable.h Tue Aug 16 13:33:08 2022 @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __FOCUSABLE_H__ +#define __FOCUSABLE_H__ + +#include "util.h" + +struct focusable { + WindowPtr win; + bool visible; + void *cookie; + bool modal; + short (*wait_type)(struct focusable *focusable); + void (*idle)(struct focusable *focusable, EventRecord *event); + void (*update)(struct focusable *focusable, EventRecord *event); + void (*key_down)(struct focusable *focusable, EventRecord *event); + void (*mouse_down)(struct focusable *focusable, EventRecord *event); + bool (*menu)(struct focusable *focusable, short menu, short item); + void (*suspend)(struct focusable *focusable, EventRecord *event); + void (*resume)(struct focusable *focusable, EventRecord *event); + bool (*close)(struct focusable *focusable); + void (*atexit)(struct focusable *focusable); +}; +extern struct focusable **focusables; +extern short nfocusables; + +struct focusable * focusable_find(GrafPtr win); +struct focusable * focusable_focused(void); +void focusable_add(struct focusable *focusable); +bool focusable_show(struct focusable *focusable); +bool focusable_close(struct focusable *focusable); +void focusable_hide(struct focusable *focusable); +bool focusables_quit(void); + +#endif --- MacTCP.h Wed Jul 13 17:14:33 2022 +++ MacTCP.h Wed Jul 13 17:14:33 2022 @@ -0,0 +1,877 @@ +/* + File: MacTCP.h + + Contains: TCP Manager Interfaces. + + Version: Technology: MacTCP 2.0.6 + Package: Universal Interfaces 2.1ß1 in “MPW Prerelease” on ETO #17 + + Copyright: © 1984-1995 by Apple Computer, Inc. + All rights reserved. + + Bugs?: If you find a problem with this file, use the Apple Bug Reporter + stack. Include the file and version information (from above) + in the problem description and send to: + Internet: apple.bugs@applelink.apple.com + AppleLink: APPLE.BUGS + +*/ + +#ifndef __MACTCP__ +#define __MACTCP__ + + +#ifndef __TYPES__ +#include <Types.h> +#endif + +#if 0 +#ifndef __APPLETALK__ +#include <AppleTalk.h> +#endif +#endif + +enum { + kPascalStackBased = 0, + kCStackBased = 0, + kThinkCStackBased = 0 +}; +#define SIZE_CODE(size) (0) +#define RESULT_SIZE(sizeCode) (0) +#define STACK_ROUTINE_PARAMETER(whichParam, sizeCode) (0) + +#ifdef __cplusplus +extern "C" { +#endif + +#if PRAGMA_ALIGN_SUPPORTED +#pragma options align=mac68k +#endif + +#if PRAGMA_IMPORT_SUPPORTED +#pragma import on +#endif + +/* +Developer Notes: + 0. This MacTCP header replaces what used to be defined in the following header files + MacTCPCommonTypes.h + GetMyIPAddr.h + MiscIPPB.h + TCPPB.h + UDPPB.h + + When the various control calls are made to the ip driver, you must set up a + NewRoutineDescriptor for every non-nil completion routine and/or notifyProc parameter. + Otherwise, the 68K driver code, will not correctly call your routine. + 1. For ipctlGetAddr Control calls, use NewGetIPIOCompletionProc + to set up a GetIPIOCompletionUPP universal procptr to pass as + the ioCompletion parameter. + 2. For the ipctlEchoICMP and ipctlLAPStats Control calls, use + NewIPIOCompletion to set up a IPIOCompletionUPP universal procptr + to pass in the ioCompletion field of the parameter block. + 3. For TCPCreatePB Control calls, use NewTCPNotifyProc to set up a + TCPNotifyUPP universal procptr to pass in the notifyProc field + of the parameter block + 4. For all of the TCP Control calls using the TCPiopb parameter block, + use NewTCPIOCompletionProc to set up a TCPIOCompletionUPP + universal procptr to pass in the ioCompletion field of the paramter + block. + 5. For UDBCreatePB Control calls, use NewUDPNotifyProc to set up a + UDPNotifyUPP universal procptr to pass in the notifyProc field + of the parameter block + 6. For all of the UDP Control calls using the UDPiopb parameter block, + use NewUDPIOCompletionProc to set up a UDPIOCompletionUPP + universal procptr to pass in the ioCompletion field of the paramter + block. + 7. For all calls implementing a notifyProc or ioCompletion routine + which was set up using a NewTCPRoutineProc call, do not call + DisposeRoutineSDescriptor on the universal procptr until + after the completion or notify proc has completed. +*/ +/* MacTCP return Codes in the range -23000 through -23049 */ + +enum { + inProgress = 1, /* I/O in progress */ + ipBadLapErr = -23000, /* bad network configuration */ + ipBadCnfgErr = -23001, /* bad IP configuration error */ + ipNoCnfgErr = -23002, /* missing IP or LAP configuration error */ + ipLoadErr = -23003, /* error in MacTCP load */ + ipBadAddr = -23004, /* error in getting address */ + connectionClosing = -23005, /* connection is closing */ + invalidLength = -23006, + connectionExists = -23007, /* request conflicts with existing connection */ + connectionDoesntExist = -23008, /* connection does not exist */ + insufficientResources = -23009, /* insufficient resources to perform request */ + invalidStreamPtr = -23010, + streamAlreadyOpen = -23011, + connectionTerminated = -23012, + invalidBufPtr = -23013, + invalidRDS = -23014, + invalidWDS = -23014, + openFailed = -23015, + commandTimeout = -23016, + duplicateSocket = -23017 +}; + +/* Error codes from internal IP functions */ +enum { + ipDontFragErr = -23032, /* Packet too large to send w/o fragmenting */ + ipDestDeadErr = -23033, /* destination not responding */ + icmpEchoTimeoutErr = -23035, /* ICMP echo timed-out */ + ipNoFragMemErr = -23036, /* no memory to send fragmented pkt */ + ipRouteErr = -23037, /* can't route packet off-net */ + nameSyntaxErr = -23041, + cacheFault = -23042, + noResultProc = -23043, + noNameServer = -23044, + authNameErr = -23045, + noAnsErr = -23046, + dnrErr = -23047, + outOfMemory = -23048 +}; + +enum { + BYTES_16WORD = 2, /* bytes per = 16, bit ip word */ + BYTES_32WORD = 4, /* bytes per = 32, bit ip word */ + BYTES_64WORD = 8 /* bytes per = 64, bit ip word */ +}; + +/* 8-bit quantity */ +typedef unsigned char UInt8; +typedef signed char SInt8; +typedef UInt8 b_8; + +/* 16-bit quantity */ +typedef unsigned short UInt16; +typedef signed short SInt16; +typedef UInt16 b_16; + +/* 32-bit quantity */ +typedef unsigned long UInt32; +typedef signed long SInt32; +typedef UInt32 b_32; + +/* IP address is 32-bits */ +typedef b_32 ip_addr; + +struct ip_addrbytes { + union { + b_32 addr; + UInt8 byte[4]; + } a; +}; +typedef struct ip_addrbytes ip_addrbytes; + +struct wdsEntry { + unsigned short length; /* length of buffer */ + Ptr ptr; /* pointer to buffer */ +}; +typedef struct wdsEntry wdsEntry; + +struct rdsEntry { + unsigned short length; /* length of buffer */ + Ptr ptr; /* pointer to buffer */ +}; +typedef struct rdsEntry rdsEntry; + +typedef unsigned long BufferPtr; + +typedef unsigned long StreamPtr; + + +enum { + netUnreach = 0, + hostUnreach = 1, + protocolUnreach = 2, + portUnreach = 3, + fragReqd = 4, + sourceRouteFailed = 5, + timeExceeded = 6, + parmProblem = 7, + missingOption = 8, + lastICMPMsgType = 32767 +}; + +typedef unsigned short ICMPMsgType; + +typedef b_16 ip_port; + +struct ICMPReport { + StreamPtr streamPtr; + ip_addr localHost; + ip_port localPort; + ip_addr remoteHost; + ip_port remotePort; + short reportType; + unsigned short optionalAddlInfo; + unsigned long optionalAddlInfoPtr; +}; +typedef struct ICMPReport ICMPReport; + +/* csCode to get our IP address */ + +enum { + ipctlGetAddr = 15 +}; + +typedef void (*GetIPIOCompletionProcPtr)(struct GetAddrParamBlock *iopb); + +#if GENERATINGCFM +typedef UniversalProcPtr GetIPIOCompletionUPP; +#else +typedef GetIPIOCompletionProcPtr GetIPIOCompletionUPP; +#endif + +#define GetIPParamBlockHeader \ + struct QElem* qLink; \ + short qType; \ + short ioTrap; \ + Ptr ioCmdAddr; \ + GetIPIOCompletionUPP ioCompletion; \ + OSErr ioResult; \ + StringPtr ioNamePtr; \ + short ioVRefNum; \ + short ioCRefNum; \ + short csCode +struct GetAddrParamBlock { + struct QElem *qLink; + short qType; + short ioTrap; + Ptr ioCmdAddr; + GetIPIOCompletionUPP ioCompletion; + OSErr ioResult; + StringPtr ioNamePtr; + short ioVRefNum; + short ioCRefNum; + short csCode; /* standard I/O header */ + ip_addr ourAddress; /* our IP address */ + long ourNetMask; /* our IP net mask */ +}; +typedef struct GetAddrParamBlock GetAddrParamBlock; + +/* control codes */ + +enum { + ipctlEchoICMP = 17, /* send icmp echo */ + ipctlLAPStats = 19 /* get lap stats */ +}; + +typedef void (*IPIOCompletionProcPtr)(struct ICMPParamBlock *iopb); + +#if GENERATINGCFM +typedef UniversalProcPtr IPIOCompletionUPP; +#else +typedef IPIOCompletionProcPtr IPIOCompletionUPP; +#endif + +#define IPParamBlockHeader \ + struct QElem* qLink; \ + short qType; \ + short ioTrap; \ + Ptr ioCmdAddr; \ + IPIOCompletionUPP ioCompletion; \ + OSErr ioResult; \ + StringPtr ioNamePtr; \ + short ioVRefNum; \ + short ioCRefNum; \ + short csCode +struct ICMPParamBlock { + struct QElem *qLink; + short qType; + short ioTrap; + Ptr ioCmdAddr; + IPIOCompletionUPP ioCompletion; + OSErr ioResult; + StringPtr ioNamePtr; + short ioVRefNum; + short ioCRefNum; + short csCode; /* standard I/O header */ + short params[11]; + struct { + unsigned long echoRequestOut; /* time in ticks of when the echo request went out */ + unsigned long echoReplyIn; /* time in ticks of when the reply was received */ + struct rdsEntry echoedData; /* data received in responce */ + Ptr options; + unsigned long userDataPtr; + } icmpEchoInfo; +}; +typedef pascal void (*ICMPEchoNotifyProcPtr)(struct ICMPParamBlock *iopb); + +#if GENERATINGCFM +typedef UniversalProcPtr ICMPEchoNotifyUPP; +#else +typedef ICMPEchoNotifyProcPtr ICMPEchoNotifyUPP; +#endif + +struct IPParamBlock { + struct QElem *qLink; + short qType; + short ioTrap; + Ptr ioCmdAddr; + IPIOCompletionUPP ioCompletion; + OSErr ioResult; + StringPtr ioNamePtr; + short ioVRefNum; + short ioCRefNum; + short csCode; /* standard I/O header */ + union { + struct { + ip_addr dest; /* echo to IP address */ + wdsEntry data; + short timeout; + Ptr options; + unsigned short optLength; + ICMPEchoNotifyUPP icmpCompletion; + unsigned long userDataPtr; + } IPEchoPB; + struct { + struct LAPStats *lapStatsPtr; + } LAPStatsPB; + } csParam; +}; +union LAPStatsAddrXlation { + struct arp_entry *arp_table; + struct nbp_entry *nbp_table; +}; +struct LAPStats { + short ifType; + char *ifString; + short ifMaxMTU; + long ifSpeed; + short ifPhyAddrLength; + char *ifPhysicalAddress; + union LAPStatsAddrXlation AddrXlation; + short slotNumber; +}; +typedef struct LAPStats LAPStats; + +#ifdef __APPLETALK__ +struct nbp_entry { + ip_addr ip_address; /* IP address */ + AddrBlock at_address; /* matching AppleTalk address */ + Boolean gateway; /* TRUE if entry for a gateway */ + Boolean valid; /* TRUE if LAP address is valid */ + Boolean probing; /* TRUE if NBP lookup pending */ + SInt8 afiller; /* Filler for proper byte alignment */ + long age; /* ticks since cache entry verified */ + long access; /* ticks since last access */ + SInt8 filler[116]; /* for internal use only !!! */ +}; +#endif +struct Enet_addr { + b_16 en_hi; + b_32 en_lo; +}; +typedef struct Enet_addr Enet_addr; + +struct arp_entry { + short age; /* cache aging field */ + b_16 protocol; /* Protocol type */ + ip_addr ip_address; /* IP address */ + Enet_addr en_address; /* matching Ethernet address */ +}; +typedef struct arp_entry arp_entry; + +/* number of ARP table entries */ + +enum { + ARP_TABLE_SIZE = 20 +}; + +enum { + NBP_TABLE_SIZE = 20, /* number of NBP table entries */ + NBP_MAX_NAME_SIZE = 16 + 10 + 2 +}; + +/* Command codes */ +enum { + TCPCreate = 30, + TCPPassiveOpen = 31, + TCPActiveOpen = 32, + TCPSend = 34, + TCPNoCopyRcv = 35, + TCPRcvBfrReturn = 36, + TCPRcv = 37, + TCPClose = 38, + TCPAbort = 39, + TCPStatus = 40, + TCPExtendedStat = 41, + TCPRelease = 42, + TCPGlobalInfo = 43, + TCPCtlMax = 49 +}; + +enum { + TCPClosing = 1, + TCPULPTimeout = 2, + TCPTerminate = 3, + TCPDataArrival = 4, + TCPUrgent = 5, + TCPICMPReceived = 6, + lastEvent = 32767 +}; + +typedef unsigned short TCPEventCode; + + +enum { + TCPRemoteAbort = 2, + TCPNetworkFailure = 3, + TCPSecPrecMismatch = 4, + TCPULPTimeoutTerminate = 5, + TCPULPAbort = 6, + TCPULPClose = 7, + TCPServiceError = 8, + lastReason = 32767 +}; + +typedef unsigned short TCPTerminationReason; + +typedef pascal void (*TCPNotifyProcPtr)(StreamPtr tcpStream, unsigned short eventCode, Ptr userDataPtr, unsigned short terminReason, struct ICMPReport *icmpMsg); + +#if GENERATINGCFM +typedef UniversalProcPtr TCPNotifyUPP; +#else +typedef TCPNotifyProcPtr TCPNotifyUPP; +#endif + +typedef unsigned short tcp_port; + +/* ValidityFlags */ + +enum { + timeoutValue = 0x80, + timeoutAction = 0x40, + typeOfService = 0x20, + precedence = 0x10 +}; + +/* TOSFlags */ +enum { + lowDelay = 0x01, + throughPut = 0x02, + reliability = 0x04 +}; + +struct TCPCreatePB { + Ptr rcvBuff; + unsigned long rcvBuffLen; + TCPNotifyUPP notifyProc; + Ptr userDataPtr; +}; +typedef struct TCPCreatePB TCPCreatePB; + +struct TCPOpenPB { + SInt8 ulpTimeoutValue; + SInt8 ulpTimeoutAction; + SInt8 validityFlags; + SInt8 commandTimeoutValue; + ip_addr remoteHost; + tcp_port remotePort; + ip_addr localHost; + tcp_port localPort; + SInt8 tosFlags; + SInt8 precedence; + Boolean dontFrag; + SInt8 timeToLive; + SInt8 security; + SInt8 optionCnt; + SInt8 options[40]; + Ptr userDataPtr; +}; +typedef struct TCPOpenPB TCPOpenPB; + +struct TCPSendPB { + SInt8 ulpTimeoutValue; + SInt8 ulpTimeoutAction; + SInt8 validityFlags; + Boolean pushFlag; + Boolean urgentFlag; + SInt8 filler; /* Filler for proper byte alignment */ + Ptr wdsPtr; + unsigned long sendFree; + unsigned short sendLength; + Ptr userDataPtr; +}; +typedef struct TCPSendPB TCPSendPB; + +/* for receive and return rcv buff calls */ +/* Note: the filler in the following structure is in a different location than */ +/* that specified in the Programmer's Guide. */ +struct TCPReceivePB { + SInt8 commandTimeoutValue; + Boolean markFlag; + Boolean urgentFlag; + SInt8 filler; /* Filler for proper byte alignment */ + Ptr rcvBuff; + unsigned short rcvBuffLen; + Ptr rdsPtr; + unsigned short rdsLength; + unsigned short secondTimeStamp; + Ptr userDataPtr; +}; +typedef struct TCPReceivePB TCPReceivePB; + +struct TCPClosePB { + SInt8 ulpTimeoutValue; + SInt8 ulpTimeoutAction; + SInt8 validityFlags; + SInt8 filler; /* Filler for proper byte alignment */ + Ptr userDataPtr; +}; +typedef struct TCPClosePB TCPClosePB; + +struct HistoBucket { + unsigned short value; + unsigned long counter; +}; +typedef struct HistoBucket HistoBucket; + + +enum { + NumOfHistoBuckets = 7 +}; + +enum { + ConnectionStateClosed = 0, + ConnectionStateListening = 2, + ConnectionStateSYNReceived = 4, + ConnectionStateSYNSent = 6, + ConnectionStateEstablished = 8, + ConnectionStateFINWait1 = 10, + ConnectionStateFINWait2 = 12, + ConnectionStateCloseWait = 14, + ConnectionStateClosing = 16, + ConnectionStateLastAck = 18, + ConnectionStateTimeWait = 20 +}; + +struct TCPConnectionStats { + unsigned long dataPktsRcvd; + unsigned long dataPktsSent; + unsigned long dataPktsResent; + unsigned long bytesRcvd; + unsigned long bytesRcvdDup; + unsigned long bytesRcvdPastWindow; + unsigned long bytesSent; + unsigned long bytesResent; + unsigned short numHistoBuckets; + struct HistoBucket sentSizeHisto[NumOfHistoBuckets]; + unsigned short lastRTT; + unsigned short tmrSRTT; + unsigned short rttVariance; + unsigned short tmrRTO; + SInt8 sendTries; + SInt8 sourchQuenchRcvd; +}; +typedef struct TCPConnectionStats TCPConnectionStats; + +struct TCPStatusPB { + SInt8 ulpTimeoutValue; + SInt8 ulpTimeoutAction; + long unused; + ip_addr remoteHost; + tcp_port remotePort; + ip_addr localHost; + tcp_port localPort; + SInt8 tosFlags; + SInt8 precedence; + SInt8 connectionState; + SInt8 filler; /* Filler for proper byte alignment */ + unsigned short sendWindow; + unsigned short rcvWindow; + unsigned short amtUnackedData; + unsigned short amtUnreadData; + Ptr securityLevelPtr; + unsigned long sendUnacked; + unsigned long sendNext; + unsigned long congestionWindow; + unsigned long rcvNext; + unsigned long srtt; + unsigned long lastRTT; + unsigned long sendMaxSegSize; + struct TCPConnectionStats *connStatPtr; + Ptr userDataPtr; +}; +typedef struct TCPStatusPB TCPStatusPB; + +struct TCPAbortPB { + Ptr userDataPtr; +}; +typedef struct TCPAbortPB TCPAbortPB; + +struct TCPParam { + unsigned long tcpRtoA; + unsigned long tcpRtoMin; + unsigned long tcpRtoMax; + unsigned long tcpMaxSegSize; + unsigned long tcpMaxConn; + unsigned long tcpMaxWindow; +}; +typedef struct TCPParam TCPParam; + +struct TCPStats { + unsigned long tcpConnAttempts; + unsigned long tcpConnOpened; + unsigned long tcpConnAccepted; + unsigned long tcpConnClosed; + unsigned long tcpConnAborted; + unsigned long tcpOctetsIn; + unsigned long tcpOctetsOut; + unsigned long tcpOctetsInDup; + unsigned long tcpOctetsRetrans; + unsigned long tcpInputPkts; + unsigned long tcpOutputPkts; + unsigned long tcpDupPkts; + unsigned long tcpRetransPkts; +}; +typedef struct TCPStats TCPStats; + +typedef StreamPtr *StreamPPtr; + +struct TCPGlobalInfoPB { + struct TCPParam *tcpParamPtr; + struct TCPStats *tcpStatsPtr; + StreamPPtr tcpCDBTable[1]; + Ptr userDataPtr; + unsigned short maxTCPConnections; +}; +typedef struct TCPGlobalInfoPB TCPGlobalInfoPB; + +typedef void (*TCPIOCompletionProcPtr)(struct TCPiopb *iopb); + +#if GENERATINGCFM +typedef UniversalProcPtr TCPIOCompletionUPP; +#else +typedef TCPIOCompletionProcPtr TCPIOCompletionUPP; +#endif + +struct TCPiopb { + SInt8 fill12[12]; + TCPIOCompletionUPP ioCompletion; + short ioResult; + Ptr ioNamePtr; + short ioVRefNum; + short ioCRefNum; + short csCode; + StreamPtr tcpStream; + union { + struct TCPCreatePB create; + struct TCPOpenPB open; + struct TCPSendPB send; + struct TCPReceivePB receive; + struct TCPClosePB close; + struct TCPAbortPB abort; + struct TCPStatusPB status; + struct TCPGlobalInfoPB globalInfo; + } csParam; +}; +typedef struct TCPiopb TCPiopb; + + +enum { + UDPCreate = 20, + UDPRead = 21, + UDPBfrReturn = 22, + UDPWrite = 23, + UDPRelease = 24, + UDPMaxMTUSize = 25, + UDPStatus = 26, + UDPMultiCreate = 27, + UDPMultiSend = 28, + UDPMultiRead = 29, + UDPCtlMax = 29 +}; + +enum { + UDPDataArrival = 1, + UDPICMPReceived = 2, + lastUDPEvent = 32767 +}; + +typedef unsigned short UDPEventCode; + +typedef pascal void (*UDPNotifyProcPtr)(StreamPtr udpStream, unsigned short eventCode, Ptr userDataPtr, struct ICMPReport *icmpMsg); + +#if GENERATINGCFM +typedef UniversalProcPtr UDPNotifyUPP; +#else +typedef UDPNotifyProcPtr UDPNotifyUPP; +#endif + +typedef unsigned short udp_port; + +/* for create and release calls */ +struct UDPCreatePB { + Ptr rcvBuff; + unsigned long rcvBuffLen; + UDPNotifyUPP notifyProc; + unsigned short localPort; + Ptr userDataPtr; + udp_port endingPort; +}; +typedef struct UDPCreatePB UDPCreatePB; + +struct UDPSendPB { + unsigned short reserved; + ip_addr remoteHost; + udp_port remotePort; + Ptr wdsPtr; + Boolean checkSum; + SInt8 filler; /* Filler for proper byte alignment */ + unsigned short sendLength; + Ptr userDataPtr; + udp_port localPort; +}; +typedef struct UDPSendPB UDPSendPB; + +/* for receive and buffer return calls */ +struct UDPReceivePB { + unsigned short timeOut; + ip_addr remoteHost; + udp_port remotePort; + Ptr rcvBuff; + unsigned short rcvBuffLen; + unsigned short secondTimeStamp; + Ptr userDataPtr; + ip_addr destHost; /* only for use with multi rcv */ + udp_port destPort; /* only for use with multi rcv */ +}; +typedef struct UDPReceivePB UDPReceivePB; + +struct UDPMTUPB { + unsigned short mtuSize; + ip_addr remoteHost; + Ptr userDataPtr; +}; +typedef struct UDPMTUPB UDPMTUPB; + +typedef struct UDPiopb UDPiopb; +typedef void (*UDPIOCompletionProcPtr)(struct UDPiopb *iopb); + +#if GENERATINGCFM +typedef UniversalProcPtr UDPIOCompletionUPP; +#else +typedef UDPIOCompletionProcPtr UDPIOCompletionUPP; +#endif + +struct UDPiopb { + SInt8 fill12[12]; + UDPIOCompletionUPP ioCompletion; + short ioResult; + Ptr ioNamePtr; + short ioVRefNum; + short ioCRefNum; + short csCode; + StreamPtr udpStream; + union { + struct UDPCreatePB create; + struct UDPSendPB send; + struct UDPReceivePB receive; + struct UDPMTUPB mtu; + } csParam; +}; + +enum { + uppGetIPIOCompletionProcInfo = kCStackBased + | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(struct GetAddrParamBlock*))), + uppIPIOCompletionProcInfo = kCStackBased + | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(struct ICMPParamBlock*))), + uppICMPEchoNotifyProcInfo = kPascalStackBased + | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(struct ICMPParamBlock*))), + uppTCPNotifyProcInfo = kPascalStackBased + | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(StreamPtr))) + | STACK_ROUTINE_PARAMETER(2, SIZE_CODE(sizeof(unsigned short))) + | STACK_ROUTINE_PARAMETER(3, SIZE_CODE(sizeof(Ptr))) + | STACK_ROUTINE_PARAMETER(4, SIZE_CODE(sizeof(unsigned short))) + | STACK_ROUTINE_PARAMETER(5, SIZE_CODE(sizeof(struct ICMPReport*))), + uppTCPIOCompletionProcInfo = kCStackBased + | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(struct TCPiopb*))), + uppUDPNotifyProcInfo = kPascalStackBased + | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(StreamPtr))) + | STACK_ROUTINE_PARAMETER(2, SIZE_CODE(sizeof(unsigned short))) + | STACK_ROUTINE_PARAMETER(3, SIZE_CODE(sizeof(Ptr))) + | STACK_ROUTINE_PARAMETER(4, SIZE_CODE(sizeof(struct ICMPReport*))), + uppUDPIOCompletionProcInfo = kCStackBased + | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(struct UDPiopb*))) +}; + +#if GENERATINGCFM +#define NewGetIPIOCompletionProc(userRoutine) \ + (GetIPIOCompletionUPP) NewRoutineDescriptor((ProcPtr)(userRoutine), uppGetIPIOCompletionProcInfo, GetCurrentArchitecture()) +#define NewIPIOCompletionProc(userRoutine) \ + (IPIOCompletionUPP) NewRoutineDescriptor((ProcPtr)(userRoutine), uppIPIOCompletionProcInfo, GetCurrentArchitecture()) +#define NewICMPEchoNotifyProc(userRoutine) \ + (ICMPEchoNotifyUPP) NewRoutineDescriptor((ProcPtr)(userRoutine), uppICMPEchoNotifyProcInfo, GetCurrentArchitecture()) +#define NewTCPNotifyProc(userRoutine) \ + (TCPNotifyUPP) NewRoutineDescriptor((ProcPtr)(userRoutine), uppTCPNotifyProcInfo, GetCurrentArchitecture()) +#define NewTCPIOCompletionProc(userRoutine) \ + (TCPIOCompletionUPP) NewRoutineDescriptor((ProcPtr)(userRoutine), uppTCPIOCompletionProcInfo, GetCurrentArchitecture()) +#define NewUDPNotifyProc(userRoutine) \ + (UDPNotifyUPP) NewRoutineDescriptor((ProcPtr)(userRoutine), uppUDPNotifyProcInfo, GetCurrentArchitecture()) +#define NewUDPIOCompletionProc(userRoutine) \ + (UDPIOCompletionUPP) NewRoutineDescriptor((ProcPtr)(userRoutine), uppUDPIOCompletionProcInfo, GetCurrentArchitecture()) +#else +#define NewGetIPIOCompletionProc(userRoutine) \ + ((GetIPIOCompletionUPP) (userRoutine)) +#define NewIPIOCompletionProc(userRoutine) \ + ((IPIOCompletionUPP) (userRoutine)) +#define NewICMPEchoNotifyProc(userRoutine) \ + ((ICMPEchoNotifyUPP) (userRoutine)) +#define NewTCPNotifyProc(userRoutine) \ + ((TCPNotifyUPP) (userRoutine)) +#define NewTCPIOCompletionProc(userRoutine) \ + ((TCPIOCompletionUPP) (userRoutine)) +#define NewUDPNotifyProc(userRoutine) \ + ((UDPNotifyUPP) (userRoutine)) +#define NewUDPIOCompletionProc(userRoutine) \ + ((UDPIOCompletionUPP) (userRoutine)) +#endif + +#if GENERATINGCFM +#define CallGetIPIOCompletionProc(userRoutine, iopb) \ + CallUniversalProc((UniversalProcPtr)(userRoutine), uppGetIPIOCompletionProcInfo, (iopb)) +#define CallIPIOCompletionProc(userRoutine, iopb) \ + CallUniversalProc((UniversalProcPtr)(userRoutine), uppIPIOCompletionProcInfo, (iopb)) +#define CallICMPEchoNotifyProc(userRoutine, iopb) \ + CallUniversalProc((UniversalProcPtr)(userRoutine), uppICMPEchoNotifyProcInfo, (iopb)) +#define CallTCPNotifyProc(userRoutine, tcpStream, eventCode, userDataPtr, terminReason, icmpMsg) \ + CallUniversalProc((UniversalProcPtr)(userRoutine), uppTCPNotifyProcInfo, (tcpStream), (eventCode), (userDataPtr), (terminReason), (icmpMsg)) +#define CallTCPIOCompletionProc(userRoutine, iopb) \ + CallUniversalProc((UniversalProcPtr)(userRoutine), uppTCPIOCompletionProcInfo, (iopb)) +#define CallUDPNotifyProc(userRoutine, udpStream, eventCode, userDataPtr, icmpMsg) \ + CallUniversalProc((UniversalProcPtr)(userRoutine), uppUDPNotifyProcInfo, (udpStream), (eventCode), (userDataPtr), (icmpMsg)) +#define CallUDPIOCompletionProc(userRoutine, iopb) \ + CallUniversalProc((UniversalProcPtr)(userRoutine), uppUDPIOCompletionProcInfo, (iopb)) +#else +#define CallGetIPIOCompletionProc(userRoutine, iopb) \ + (*(userRoutine))((iopb)) +#define CallIPIOCompletionProc(userRoutine, iopb) \ + (*(userRoutine))((iopb)) +#define CallICMPEchoNotifyProc(userRoutine, iopb) \ + (*(userRoutine))((iopb)) +#define CallTCPNotifyProc(userRoutine, tcpStream, eventCode, userDataPtr, terminReason, icmpMsg) \ + (*(userRoutine))((tcpStream), (eventCode), (userDataPtr), (terminReason), (icmpMsg)) +#define CallTCPIOCompletionProc(userRoutine, iopb) \ + (*(userRoutine))((iopb)) +#define CallUDPNotifyProc(userRoutine, udpStream, eventCode, userDataPtr, icmpMsg) \ + (*(userRoutine))((udpStream), (eventCode), (userDataPtr), (icmpMsg)) +#define CallUDPIOCompletionProc(userRoutine, iopb) \ + (*(userRoutine))((iopb)) +#endif + + +#if PRAGMA_IMPORT_SUPPORTED +#pragma import off +#endif + +#if PRAGMA_ALIGN_SUPPORTED +#pragma options align=reset +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __MACTCP__ */ --- main.c Thu Aug 18 22:03:48 2022 +++ main.c Thu Aug 18 22:03:48 2022 @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2022 joshua stein <jcs@jcs.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdio.h> +#include <string.h> + +#include "wikipedia.h" +#include "browser.h" +#include "focusable.h" +#include "util.h" + +MenuHandle file_menu, edit_menu; +bool quitting = false; + +void handle_menu(long menu_id); + +int +main(void) +{ + Handle mbar; + MenuHandle apple_menu; + EventRecord event; + WindowPtr event_win; + GrafPtr old_port; + struct focusable *focusable; + short event_in, n; + char key; + + InitGraf(&thePort); + InitFonts(); + FlushEvents(everyEvent, 0); + InitWindows(); + InitMenus(); + TEInit(); + InitDialogs(0); + InitCursor(); + MaxApplZone(); + + mbar = GetNewMBar(MBAR_ID); + SetMenuBar(mbar); + apple_menu = GetMHandle(APPLE_MENU_ID); + AddResMenu(apple_menu, 'DRVR'); + file_menu = GetMHandle(FILE_MENU_ID); + edit_menu = GetMHandle(EDIT_MENU_ID); + menu_defaults(); + DrawMenuBar(); + + browser_init(); + + while (!quitting) { + WaitNextEvent(everyEvent, &event, 5L, 0L); + + switch (event.what) { + case nullEvent: + for (n = 0; n < nfocusables; n++) { + if (focusables[n]->idle) + focusables[n]->idle(focusables[n], &event); + } + break; + case keyDown: + case autoKey: + key = event.message & charCodeMask; + if ((event.modifiers & cmdKey) != 0) + handle_menu(MenuKey(key)); + else if ((focusable = focusable_focused()) && + focusable->key_down) + focusable->key_down(focusable, &event); + break; + case mouseDown: + event_in = FindWindow(event.where, &event_win); + + switch (event_in) { + case inMenuBar: + handle_menu(MenuSelect(event.where)); + break; + case inSysWindow: + SystemClick(&event, event_win); + break; + case inDrag: + if ((focusable = focusable_find(event_win)) != NULL) { + if (!focusable_show(focusable)) + break; + + DragWindow(event_win, event.where, &screenBits.bounds); + } + break; + case inGoAway: + if (TrackGoAway(event_win, event.where)) { + if ((focusable = focusable_find(event_win)) != NULL) + focusable_close(focusable); + } + break; + case inContent: + if ((focusable = focusable_find(event_win)) != NULL) { + if (!focusable_show(focusable)) + break; + if (focusable->mouse_down) + focusable->mouse_down(focusable, &event); + } + break; + } + break; + case updateEvt: + event_win = (WindowPtr)event.message; + + GetPort(&old_port); + SetPort(event_win); + BeginUpdate(event_win); + + focusable = focusable_find(event_win); + if (focusable && focusable->update) + focusable->update(focusable, &event); + + EndUpdate(event_win); + SetPort(old_port); + break; + case activateEvt: + break; + case app4Evt: + if (HiWord(event.message) & (1 << 8)) { + /* multifinder suspend/resume */ + switch (event.message & (1 << 0)) { + case 0: + /* suspend */ + for (n = 0; n < nfocusables; n++) { + if (focusables[n]->suspend) + focusables[n]->suspend(focusables[n], &event); + } + break; + case 1: + /* resume */ + for (n = 0; n < nfocusables; n++) { + if (focusables[n]->resume) + focusables[n]->resume(focusables[n], &event); + } + break; + } + } + break; + } + } + + return 0; +} + +void +handle_menu(long menu_id) +{ + struct focusable *focused; + short menu, item; + + menu = HiWord(menu_id); + item = LoWord(menu_id); + + if ((focused = focusable_focused()) && focused->menu && + focused->menu(focused, menu, item)) + goto handled; + + switch (menu) { + case APPLE_MENU_ID: + switch (item) { + case APPLE_MENU_ABOUT_ID: { + VersRecHndl vers; + char vers_s[255]; + char short_vers[255] = { 0 }; + short vlen; + + if ((vers = (VersRecHndl)GetResource('vers', 1))) { + /* + * vers "long version string" is a pascal string after the + * short version pascal string + */ + HLock(vers); + vlen = (*vers)->shortVersion[0]; + memcpy(short_vers, (*vers)->shortVersion + vlen + 1, + sizeof((*vers)->shortVersion) - vlen - 1); + PtoCstr(short_vers); + snprintf(vers_s, sizeof(vers_s), "%s %s", PROGRAM_NAME, + short_vers); + ReleaseResource(vers); + note("%s", vers_s); + } else + warnx("Can't find version number!"); + break; + } + } + break; + case FILE_MENU_ID: + switch (item) { + case FILE_MENU_QUIT_ID: + if (focusables_quit()) + quitting = true; + break; + } + break; + } + +handled: + HiliteMenu(0); +} + +void +menu_defaults(void) +{ + DisableItem(edit_menu, EDIT_MENU_CUT_ID); + DisableItem(edit_menu, EDIT_MENU_COPY_ID); + DisableItem(edit_menu, EDIT_MENU_PASTE_ID); + DisableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID); +} --- tcp.c Thu Aug 18 21:15:34 2022 +++ tcp.c Thu Aug 18 21:15:34 2022 @@ -0,0 +1,594 @@ +#include <stdio.h> +#include <string.h> +#include "tcp.h" + +#define RCV_BUFFER_SIZE 1024 +#define TCP_BUFFER_SIZE 8192 +#define OPEN_TIMEOUT 60 + +/* define to enable SOCKS5 code */ +#undef SOCKS + +short gIPPDriverRefNum; + +unsigned long resolved_ip; + +OSErr +_TCPInit(void) +{ + ParamBlockRec pb; + OSErr osErr; + + memset(&pb, 0, sizeof(pb)); + + gIPPDriverRefNum = -1; + + pb.ioParam.ioCompletion = 0; + pb.ioParam.ioNamePtr = "\p.IPP"; + pb.ioParam.ioPermssn = fsCurPerm; + + osErr = PBOpen(&pb, false); + if (noErr == osErr) + gIPPDriverRefNum = pb.ioParam.ioRefNum; + + return osErr; +} + +OSErr +_TCPGetOurIP(ip_addr *ip, long *netMask) +{ + OSErr osErr; + GetAddrParamBlock pb; + + memset(&pb, 0, sizeof(pb)); + + pb.csCode = ipctlGetAddr; + pb.ioCRefNum = gIPPDriverRefNum; + pb.ioResult = 1; + + osErr = PBControl((ParmBlkPtr)&pb, true); + while (pb.ioResult > 0) + ; + + if (pb.ioResult != noErr) + return pb.ioResult; + + if (ip != NULL) + *ip = pb.ourAddress; + if (netMask != NULL) + *netMask = pb.ourNetMask; + + return osErr; +} + +OSErr +_TCPCreate(TCPiopb *pb, StreamPtr *stream, Ptr rcvBufPtr, long rcvBufLen, + TCPNotifyProc aNotifyProc, Ptr userDataPtr, + TCPIOCompletionProc ioCompletion, Boolean async) +{ + OSErr osErr; + + memset(pb, 0, sizeof(*pb)); + + pb->csCode = TCPCreate; + pb->ioCompletion = ioCompletion; + pb->ioCRefNum = gIPPDriverRefNum; + pb->ioResult = 1; + + pb->csParam.create.rcvBuff = rcvBufPtr; + pb->csParam.create.rcvBuffLen = rcvBufLen; + pb->csParam.create.notifyProc = aNotifyProc; + pb->csParam.create.userDataPtr = userDataPtr; + + osErr = PBControl((ParmBlkPtr)pb, async); + if (!async && (noErr == osErr)) { + *stream = pb->tcpStream; + } + + return osErr; +} + +/* listen for an incoming connection */ +OSErr +_TCPPassiveOpen(TCPiopb *pb, StreamPtr stream, ip_addr *remoteIP, + tcp_port *remotePort, ip_addr *localIP, tcp_port *localPort, + Ptr userData, TCPIOCompletionProc ioCompletion, Boolean async) +{ + OSErr osErr; + short index; + + memset(pb, 0, sizeof(*pb)); + + pb->csCode = TCPPassiveOpen; + pb->ioCompletion = ioCompletion; + pb->ioCRefNum = gIPPDriverRefNum; + pb->tcpStream = stream; + pb->ioResult = 1; + + pb->csParam.open.ulpTimeoutAction = 1; /* abort half-open connection */ + pb->csParam.open.ulpTimeoutValue = 5; /* after 5 seconds */ + pb->csParam.open.validityFlags = 0xC0; + pb->csParam.open.commandTimeoutValue = 0; + pb->csParam.open.remoteHost = 0; + pb->csParam.open.remotePort = 0; + pb->csParam.open.localHost = 0; + pb->csParam.open.localPort = *localPort; + pb->csParam.open.tosFlags = 0x1; /* low delay */ + pb->csParam.open.precedence = 0; + pb->csParam.open.dontFrag = 0; + pb->csParam.open.timeToLive = 0; + pb->csParam.open.security = 0; + pb->csParam.open.optionCnt = 0; + pb->csParam.open.userDataPtr = userData; + + osErr = PBControl((ParmBlkPtr) pb, async); + if (!async && (osErr == noErr)) { + if (remoteIP) + *remoteIP = pb->csParam.open.remoteHost; + if (remotePort) + *remotePort = pb->csParam.open.remotePort; + if (localIP) + *localIP = pb->csParam.open.localHost; + *localPort = pb->csParam.open.localPort; + } + + return osErr; +} + +/* make an outgoing connection */ +OSErr +_TCPActiveOpen(TCPiopb *pb, StreamPtr stream, ip_addr remoteIP, + tcp_port remotePort, ip_addr *localIP, tcp_port *localPort, + Ptr userData, TCPIOCompletionProc ioCompletion, Boolean async) +{ + OSErr osErr; + short index; + + memset(pb, 0, sizeof(*pb)); + + pb->csCode = TCPActiveOpen; + pb->ioCompletion = ioCompletion; + pb->ioCRefNum = gIPPDriverRefNum; + pb->tcpStream = stream; + pb->ioResult = 1; + + pb->csParam.open.ulpTimeoutValue = 30; + pb->csParam.open.ulpTimeoutAction = 1; + pb->csParam.open.validityFlags = 0xC0; +#if 0 + /* not available with this csCode */ + pb->csParam.open.commandTimeoutValue = 30; +#endif + pb->csParam.open.remoteHost = remoteIP; + pb->csParam.open.remotePort = remotePort; + pb->csParam.open.localHost = 0; + pb->csParam.open.localPort = *localPort; + pb->csParam.open.tosFlags = 0; + pb->csParam.open.precedence = 0; + pb->csParam.open.dontFrag = 0; + pb->csParam.open.timeToLive = 0; + pb->csParam.open.security = 0; + pb->csParam.open.optionCnt = 0; + for (index = 0; index < sizeof(pb->csParam.open.options); ++index) + pb->csParam.open.options[index] = 0; + pb->csParam.open.userDataPtr = userData; + + osErr = PBControl((ParmBlkPtr) pb, async); + if (!async && (noErr == osErr)) { + *localIP = pb->csParam.open.localHost; + *localPort = pb->csParam.open.localPort; + } + + return osErr; +} + +OSErr +_TCPSend(TCPiopb *pb, StreamPtr stream, wdsEntry *wdsPtr, Ptr userData, + TCPIOCompletionProc ioCompletion, Boolean async) +{ + memset(pb, 0, sizeof(*pb)); + + pb->csCode = TCPSend; + pb->ioCompletion = ioCompletion; + pb->ioCRefNum = gIPPDriverRefNum; + pb->tcpStream = stream; + pb->ioResult = 1; + + pb->csParam.send.ulpTimeoutValue = 30; + pb->csParam.send.ulpTimeoutAction = 1; + pb->csParam.send.validityFlags = 0xC0; + pb->csParam.send.pushFlag = 1; /* XXX */ + pb->csParam.send.urgentFlag = 0; + pb->csParam.send.wdsPtr = (Ptr)wdsPtr; + pb->csParam.send.sendFree = 0; + pb->csParam.send.sendLength = 0; + pb->csParam.send.userDataPtr = userData; + + return PBControl((ParmBlkPtr)pb, async); +} + +OSErr +_TCPNoCopyRcv(TCPiopb *pb, StreamPtr stream, Ptr rdsPtr, + unsigned short rdsLength, Ptr userData, TCPIOCompletionProc ioCompletion, + Boolean async) +{ + memset(pb, 0, sizeof(*pb)); + + pb->csCode = TCPNoCopyRcv; + pb->ioCompletion = ioCompletion; + pb->ioCRefNum = gIPPDriverRefNum; + pb->tcpStream = stream; + pb->ioResult = 1; + + pb->csParam.receive.commandTimeoutValue = 30; + pb->csParam.receive.urgentFlag = 0; + pb->csParam.receive.markFlag = 0; + pb->csParam.receive.rdsPtr = rdsPtr; + pb->csParam.receive.rdsLength = rdsLength; + pb->csParam.receive.userDataPtr = userData; + + return PBControl((ParmBlkPtr)pb, async); +} + +OSErr +_TCPRcv(TCPiopb *pb, StreamPtr stream, Ptr rcvBufPtr, + unsigned short *rcvBufLen, Ptr userData, TCPIOCompletionProc ioCompletion, + Boolean async) +{ + OSErr osErr; + + memset(pb, 0, sizeof(*pb)); + + pb->csCode = TCPRcv; + pb->ioCompletion = ioCompletion; + pb->ioCRefNum = gIPPDriverRefNum; + pb->tcpStream = stream; + pb->ioResult = 1; + + pb->csParam.receive.commandTimeoutValue = 30; + pb->csParam.receive.urgentFlag = 0; + pb->csParam.receive.markFlag = 0; + pb->csParam.receive.rcvBuff = rcvBufPtr; + pb->csParam.receive.rcvBuffLen = *rcvBufLen; + pb->csParam.receive.userDataPtr = userData; + + osErr = PBControl((ParmBlkPtr)pb, async); + if (!async) + *rcvBufLen = pb->csParam.receive.rcvBuffLen; + + return osErr; +} + +OSErr +_TCPBfrReturn(TCPiopb *pb, StreamPtr stream, Ptr rdsPtr, Ptr userData, + TCPIOCompletionProc ioCompletion, Boolean async) +{ + memset(pb, 0, sizeof(*pb)); + + pb->csCode = TCPRcvBfrReturn; + pb->ioCompletion = ioCompletion; + pb->ioCRefNum = gIPPDriverRefNum; + pb->tcpStream = stream; + pb->ioResult = 1; + + pb->csParam.receive.rdsPtr = rdsPtr; + pb->csParam.receive.userDataPtr = userData; + + return PBControl((ParmBlkPtr)pb, async); +} + +OSErr +_TCPClose(TCPiopb *pb, StreamPtr stream, Ptr userData, + TCPIOCompletionProc ioCompletion, Boolean async) +{ + memset(pb, 0, sizeof(*pb)); + + pb->csCode = TCPClose; + pb->ioCompletion = ioCompletion; + pb->ioCRefNum = gIPPDriverRefNum; + pb->tcpStream = stream; + pb->ioResult = 1; + + pb->csParam.close.ulpTimeoutValue = 30; + pb->csParam.close.ulpTimeoutAction = 1; + pb->csParam.close.validityFlags = 0xC0; + pb->csParam.close.userDataPtr = userData; + + return PBControl((ParmBlkPtr)pb, async); +} + +OSErr +_TCPAbort(TCPiopb *pb, StreamPtr stream, Ptr userData, + TCPIOCompletionProc ioCompletion, Boolean async) +{ + memset(pb, 0, sizeof(*pb)); + + pb->csCode = TCPAbort; + pb->ioCompletion = ioCompletion; + pb->ioCRefNum = gIPPDriverRefNum; + pb->tcpStream = stream; + pb->ioResult = 1; + + pb->csParam.abort.userDataPtr = userData; + + return PBControl((ParmBlkPtr)pb, async); +} + +OSErr +_TCPStatus(TCPiopb *pb, StreamPtr stream, struct TCPStatusPB *status, + Ptr userData, TCPIOCompletionProc ioCompletion, Boolean async) +{ + OSErr osErr; + + memset(pb, 0, sizeof(*pb)); + + pb->csCode = TCPStatus; + pb->ioCompletion = ioCompletion; + pb->ioCRefNum = gIPPDriverRefNum; + pb->tcpStream = stream; + pb->csParam.status.userDataPtr = userData; + pb->ioResult = 1; + + osErr = PBControl((ParmBlkPtr)pb, async); + if (!async && (noErr == osErr)) { + *status = pb->csParam.status; + } + + return osErr; +} + +OSErr +_TCPRelease(TCPiopb *pb, StreamPtr stream, Ptr userData, + TCPIOCompletionProc ioCompletion, Boolean async) +{ + OSErr osErr; + + memset(pb, 0, sizeof(*pb)); + + pb->csCode = TCPRelease; + pb->ioCompletion = ioCompletion; + pb->ioCRefNum = gIPPDriverRefNum; + pb->tcpStream = stream; + pb->ioResult = 1; + + pb->csParam.status.userDataPtr = userData; + + osErr = PBControl((ParmBlkPtr)pb, async); + + return osErr; +} + +OSErr +_UDPMaxMTUSize(UDPiopb *pb, short *mtu) +{ + OSErr osErr; + + memset(pb, 0, sizeof(*pb)); + + pb->csCode = UDPMaxMTUSize; + pb->ioCRefNum = gIPPDriverRefNum; + pb->ioResult = 1; + + pb->csParam.mtu.remoteHost = (ip_addr)0; + + osErr = PBControl((ParmBlkPtr)pb, false); + + if (osErr == noErr) + *mtu = pb->csParam.mtu.mtuSize; + + return osErr; +} + +OSErr +_UDPCreate(UDPiopb *pb, StreamPtr *stream, Ptr rcvBufPtr, long rcvBufLen, + UDPNotifyProc aNotifyProc, Ptr userDataPtr, + UDPIOCompletionProc ioCompletion, Boolean async) +{ + OSErr osErr; + + memset(pb, 0, sizeof(*pb)); + + pb->csCode = UDPCreate; + pb->ioCompletion = ioCompletion; + pb->ioCRefNum = gIPPDriverRefNum; + pb->ioResult = 1; + + pb->csParam.create.rcvBuff = rcvBufPtr; + pb->csParam.create.rcvBuffLen = rcvBufLen; + pb->csParam.create.notifyProc = aNotifyProc; + pb->csParam.create.userDataPtr = userDataPtr; + + osErr = PBControl((ParmBlkPtr)pb, async); + if (!async && (noErr == osErr)) { + *stream = pb->udpStream; + } + + return osErr; +} + +OSErr +_UDPSend(UDPiopb *pb, StreamPtr stream, wdsEntry *wdsPtr, ip_addr remoteIP, + udp_port remotePort, Ptr userData, UDPIOCompletionProc ioCompletion, + Boolean async) +{ + memset(pb, 0, sizeof(*pb)); + + pb->csCode = UDPWrite; + pb->ioCompletion = ioCompletion; + pb->ioCRefNum = gIPPDriverRefNum; + pb->udpStream = stream; + pb->ioResult = 1; + + pb->csParam.send.remoteHost = remoteIP; + pb->csParam.send.remotePort = remotePort; + pb->csParam.send.wdsPtr = (Ptr)wdsPtr; + pb->csParam.send.checkSum = 0; + pb->csParam.send.sendLength = 0; + pb->csParam.send.userDataPtr = userData; + + return PBControl((ParmBlkPtr)pb, async); +} + +OSErr +_UDPRelease(UDPiopb *pb, StreamPtr stream, Ptr userData, + UDPIOCompletionProc ioCompletion, Boolean async) +{ + OSErr osErr; + + memset(pb, 0, sizeof(*pb)); + + pb->csCode = UDPRelease; + pb->ioCompletion = ioCompletion; + pb->ioCRefNum = gIPPDriverRefNum; + pb->udpStream = stream; + pb->ioResult = 1; + + //pb->csParam.status.userDataPtr = userData; + + osErr = PBControl((ParmBlkPtr)pb, async); + + return osErr; +} + + +/* convenience functions */ + +unsigned long +ip2long(char *ip) +{ + unsigned long address = 0; + short dotcount = 0, i; + unsigned short b = 0; + + for (i = 0; ip[i] != 0; i++) { + if (ip[i] == '.') { + if (++dotcount > 3) + return (0); + address <<= 8; + address |= b; + b = 0; + } else if (ip[i] >= '0' && ip[i] <= '9') { + b *= 10; + b += (ip[i] - '0'); + if (b > 255) + return (0); + } else + return (0); + } + + if (dotcount != 3) + return (0); + address <<= 8; + address |= b; + return address; +} + +void +long2ip(unsigned long num, char *ip) +{ + unsigned char *tmp = (unsigned char *)&num; + sprintf(ip, "%d.%d.%d.%d", tmp[0], tmp[1], tmp[2], tmp[3]); +} + +#ifdef SOCKS +#define SOCKS_VERSION_SOCKS5 0x5 +#define SOCKS_METHOD_AUTH_NONE 0x0 +#define SOCKS_REQUEST_CONNECT 0x1 +#define SOCKS_REQUEST_ATYP_DOMAINNAME 0x3 +#define SOCKS_REPLY_SUCCESS 0x0 + +OSErr +SOCKS5TCPActiveOpen(TCPiopb *pb, StreamPtr stream, ip_addr socks_ip, + tcp_port socks_port, char *remote_host, tcp_port remote_port, + ip_addr *local_ip, tcp_port *local_port, Ptr user_data, + TCPIOCompletionProc io_completion, Boolean async) +{ + OSErr err; + TCPStatusPB status_pb; + wdsEntry wds[2]; + char data[255] = { 0 }; + unsigned short len, remote_host_len; + + remote_host_len = strlen(remote_host); + if (remote_host_len + 7 > sizeof(data)) + return -1; + + err = _TCPActiveOpen(pb, stream, socks_ip, socks_port, local_ip, + local_port, user_data, io_completion, async); + if (err != noErr) + return err; + + data[0] = SOCKS_VERSION_SOCKS5; + data[1] = 1; /* nmethods */ + data[2] = SOCKS_METHOD_AUTH_NONE; + + memset(&wds, 0, sizeof(wds)); + wds[0].ptr = (Ptr)&data; + wds[0].length = 3; + + err = _TCPSend(pb, stream, wds, nil, nil, false); + if (err) + goto fail; + + for (;;) { + err = _TCPStatus(pb, stream, &status_pb, nil, nil, false); + if (err != noErr) + goto fail; + + if (status_pb.amtUnreadData >= 2) + break; + } + + len = 2; + err = _TCPRcv(pb, stream, (Ptr)&data, &len, nil, nil, false); + if (err != noErr) + goto fail; + + if (data[0] != SOCKS_VERSION_SOCKS5 || data[1] != SOCKS_METHOD_AUTH_NONE) + goto fail; + + len = 0; + data[len++] = SOCKS_VERSION_SOCKS5; + data[len++] = SOCKS_REQUEST_CONNECT; + data[len++] = 0; /* reserved */ + data[len++] = SOCKS_REQUEST_ATYP_DOMAINNAME; + data[len++] = remote_host_len; + memcpy(data + len, remote_host, remote_host_len); + len += remote_host_len; + data[len++] = (remote_port >> 8); + data[len++] = (remote_port & 0xff); + + memset(&wds, 0, sizeof(wds)); + wds[0].ptr = (Ptr)&data; + wds[0].length = len; + + err = _TCPSend(pb, stream, wds, nil, nil, false); + if (err) + goto fail; + + for (;;) { + err = _TCPStatus(pb, stream, &status_pb, nil, nil, false); + if (err != noErr) + goto fail; + + if (status_pb.amtUnreadData >= 7) + break; + } + + len = status_pb.amtUnreadData; + if (len > sizeof(data)) + len = sizeof(data); + err = _TCPRcv(pb, stream, (Ptr)&data, &len, nil, nil, false); + if (err != noErr) + goto fail; + + if (data[0] != SOCKS_VERSION_SOCKS5 || data[1] != SOCKS_REPLY_SUCCESS) + goto fail; + + return noErr; + +fail: + _TCPClose(pb, stream, nil, nil, false); + return err; +} +#endif /* SOCKS */ --- tcp.h Thu Aug 18 21:08:51 2022 +++ tcp.h Thu Aug 18 21:08:51 2022 @@ -0,0 +1,70 @@ +/* + * Copyright (c) 1990-1992 by the University of Illinois Board of Trustees + */ + +#include "MacTCP.h" +#include "AddressXlation.h" + +#ifndef __TCP_H__ +#define __TCP_H__ + +typedef struct +{ + Handle next; + struct hostInfo hi; +} HostInfoQ, *HostInfoQPtr, **HostInfoQHandle; + +typedef ProcPtr TCPNotifyProc; +typedef ProcPtr UDPNotifyProc; +typedef void (*TCPIOCompletionProc)(struct TCPiopb *iopb); +typedef void (*UDPIOCompletionProc)(struct UDPiopb *iopb); + +OSErr _TCPInit(void); +OSErr _TCPGetOurIP(ip_addr *ip, long *netMask); +OSErr _TCPCreate(TCPiopb *pb, StreamPtr *stream, Ptr rcvBufPtr, + long rcvBufLen, TCPNotifyProc aNotifyProc, Ptr userDataPtr, + TCPIOCompletionProc ioCompletion, Boolean async); +OSErr _TCPPassiveOpen(TCPiopb *pb, StreamPtr stream, ip_addr *remoteIP, + tcp_port *remotePort, ip_addr *localIP, tcp_port *localPort, + Ptr userData, TCPIOCompletionProc ioCompletion, Boolean async); +OSErr _TCPActiveOpen(TCPiopb *pb, StreamPtr stream, ip_addr remoteIP, + tcp_port remotePort, ip_addr *localIP, tcp_port *localPort, + Ptr userData, TCPIOCompletionProc ioCompletion, Boolean async); +OSErr _TCPSend(TCPiopb *pb, StreamPtr stream, wdsEntry *wdsPtr, + Ptr userData, TCPIOCompletionProc ioCompletion, Boolean async); +OSErr _TCPNoCopyRcv(TCPiopb *pb, StreamPtr stream, Ptr rdsPtr, + unsigned short rdsLength, Ptr userData, TCPIOCompletionProc ioCompletion, + Boolean async); +OSErr _TCPRcv(TCPiopb *pb, StreamPtr stream, Ptr rcvBufPtr, + unsigned short *rcvBufLen, Ptr userData, TCPIOCompletionProc ioCompletion, + Boolean async); +OSErr _TCPBfrReturn(TCPiopb *pb, StreamPtr stream, Ptr rdsPtr, Ptr userData, + TCPIOCompletionProc ioCompletion, Boolean async); +OSErr _TCPClose(TCPiopb *pb, StreamPtr stream, Ptr userData, + TCPIOCompletionProc ioCompletion, Boolean async); +OSErr _TCPAbort(TCPiopb *pb, StreamPtr stream, Ptr userData, + TCPIOCompletionProc ioCompletion, Boolean async); +OSErr _TCPStatus(TCPiopb *pb, StreamPtr stream, struct TCPStatusPB *status, + Ptr userData, TCPIOCompletionProc ioCompletion, Boolean async); +OSErr _TCPRelease(TCPiopb *pb, StreamPtr stream, Ptr userData, + TCPIOCompletionProc ioCompletion, Boolean async); + +OSErr _UDPMaxMTUSize(UDPiopb *pb, short *mtu); +OSErr _UDPCreate(UDPiopb *pb, StreamPtr *stream, Ptr rcvBufPtr, + long rcvBufLen, UDPNotifyProc aNotifyProc, Ptr userDataPtr, + UDPIOCompletionProc ioCompletion, Boolean async); +OSErr _UDPSend(UDPiopb *pb, StreamPtr stream, wdsEntry *wdsPtr, + ip_addr remoteIP, udp_port remotePort, Ptr userData, + UDPIOCompletionProc ioCompletion, Boolean async); +OSErr _UDPRelease(UDPiopb *pb, StreamPtr stream, Ptr userData, + UDPIOCompletionProc ioCompletion, Boolean async); + +unsigned long ip2long(char *ip); +void long2ip(unsigned long num, char *ip); + +OSErr SOCKS5TCPActiveOpen(TCPiopb *pb, StreamPtr stream, ip_addr socks_ip, + tcp_port socks_port, char *remote_host, tcp_port remote_port, + ip_addr *local_ip, tcp_port *local_port, Ptr user_data, + TCPIOCompletionProc io_completion, Boolean async); + +#endif --- util.c Thu Aug 18 23:17:52 2022 +++ util.c Thu Aug 18 23:17:52 2022 @@ -0,0 +1,1523 @@ +/* + * Copyright (c) 2020-2022 joshua stein <jcs@jcs.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <GestaltEqu.h> +#include <Script.h> +#include <SetUpA4.h> +#include "util.h" + +/* ALRT resources */ +#define ASK_ALERT_ID 130 + +#define ERROR_STRING_SIZE 1024 +static char err_str[ERROR_STRING_SIZE]; + +/* basic DITL with an ok button (1), text area (2), and icon (3) */ +#define ALERT_DITL_ICON 3 +static const char alert_ditl[] = { + 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, + 0x00, 0xE6, 0x00, 0x5A, 0x01, 0x20, 0x04, 0x02, + 0x4F, 0x4B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, + 0x00, 0x32, 0x00, 0x40, 0x01, 0x21, 0x08, 0x02, + 0x5E, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, + 0x00, 0x0A, 0x00, 0x2A, 0x00, 0x2A, 0xA0, 0x02, + 0x00, 0x02 +}; +static Handle alert_ditl_h = NULL; + +/* DITL with a Yes button (1), No button (2), text (3), and icon (4) */ +static const char ask_ditl[] = { + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, + 0x00, 0xE6, 0x00, 0x5A, 0x01, 0x20, 0x04, 0x03, + 0x59, 0x65, 0x73, 0x21, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x46, 0x00, 0xA0, 0x00, 0x5A, 0x00, 0xDA, + 0x04, 0x02, 0x4E, 0x6F, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0A, 0x00, 0x32, 0x00, 0x41, 0x01, 0x22, + 0x08, 0x02, 0x5E, 0x30, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x2A, 0x00, 0x2A, + 0xA0, 0x02, 0x00, 0x01 +}; +static Handle ask_ditl_h = NULL; + +/* DITL with just a text view */ +static const char progress_ditl[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, + 0x00, 0x1E, 0x00, 0x32, 0x01, 0x3B, 0x08, 0x02, + 0x5E, 0x30 +}; +static Handle progress_ditl_h = NULL; +static DialogPtr progress_dialog = NULL; + +static TEHandle track_control_te = NULL; + +enum { + STOP_ALERT, + CAUTION_ALERT, + NOTE_ALERT +}; + +#ifdef MALLOC_DEBUG + +/* + * List of allocations, updated at xmalloc() and xfree(). If an address + * passed to xfree() isn't in the list, it indicates a double-free. + */ +#define MALLOC_MAP_SIZE 512 +struct malloc_map_e { + unsigned long addr; + unsigned long size; + char note[MALLOC_NOTE_SIZE]; +} malloc_map[MALLOC_MAP_SIZE]; + +/* + * On xfree(&), the pointer will be updated to point here. xfree_verify() + * should be called periodically to detect any writes to it, indicating + * a use-after-free. + */ +#define MALLOC_UAF_SIZE 256 +static const char malloc_uaf_zero[MALLOC_UAF_SIZE] = { 0 }; +static char malloc_uaf[MALLOC_UAF_SIZE]; + +static bool malloc_map_compact = false; + +#endif + +void vwarn(short alert_func, const char *format, va_list ap); + +/* + * Util helper needed to be called at program startup, to pre-allocate + * some things that we can't do during errors. + */ + +void +util_init(void) +{ + alert_ditl_h = xNewHandle(sizeof(alert_ditl)); + HLock(alert_ditl_h); + memcpy(*alert_ditl_h, alert_ditl, sizeof(alert_ditl)); + HUnlock(alert_ditl_h); + +#ifdef MALLOC_DEBUG + memset(&malloc_map, 0, sizeof(malloc_map)); + memset(&malloc_uaf, 0, sizeof(malloc_uaf)); +#endif +} + +/* + * Memory functions + */ + +#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4)) + +void * +xmalloc(size_t size, char *note) +{ + void *ptr; +#ifdef MALLOC_DEBUG + unsigned short n, j; +#endif + + if (size == 0) + panic("xmalloc: zero size"); + + ptr = NewPtr(size); + if (ptr == NULL) + panic("xmalloc(%lu) failed", size); + +#ifdef MALLOC_DEBUG + if (malloc_map_compact) { + for (n = 0; n < MALLOC_MAP_SIZE; n++) { + if (malloc_map[n].addr != 0) + continue; + + for (j = n + 1; j < MALLOC_MAP_SIZE; j++) { + if (malloc_map[j].addr == 0) + continue; + + malloc_map[n] = malloc_map[j]; + memset(&malloc_map[j], 0, sizeof(malloc_map[j])); + break; + } + } + + malloc_map_compact = false; + } + + for (n = 0; n <= MALLOC_MAP_SIZE; n++) { + if (n == MALLOC_MAP_SIZE) + panic("xmalloc(%lu): out of malloc map entries, likely a " + "memory leak", size); + if (malloc_map[n].addr == 0) { + malloc_map[n].addr = (unsigned long)ptr; + malloc_map[n].size = size; + strlcpy(malloc_map[n].note, note, sizeof(malloc_map[n].note)); + break; + } + n = n; + } +#endif + + return ptr; +} + +void +xfree(void *ptrptr) +{ + unsigned long *addr = (unsigned long *)ptrptr; + void *ptr = (void *)*addr; +#ifdef MALLOC_DEBUG + unsigned long n; + + if (*addr == (unsigned long)&malloc_uaf) + panic("xfree(%lu): double free()", *addr); + + for (n = 0; n <= MALLOC_MAP_SIZE; n++) { + if (n == MALLOC_MAP_SIZE) + panic("xfree(%lu): can't find in alloc map, likely " + "double free()", *addr); + if (malloc_map[n].addr == *addr) { + malloc_map[n].addr = 0; + malloc_map[n].size = 0; + malloc_map[n].note[0] = '\0'; + break; + } + } +#endif + + DisposePtr(ptr); + +#ifdef MALLOC_DEBUG + *addr = (unsigned long)&malloc_uaf; +#else + *addr = 0L; +#endif +} + +#ifdef MALLOC_DEBUG +void +xfree_verify(void) +{ + if (memcmp(malloc_uaf, malloc_uaf_zero, sizeof(malloc_uaf)) != 0) + panic("xfree_verify: use-after-free detected"); +} +#endif + +void * +xmalloczero(size_t size, char *note) +{ + void *ptr; + + ptr = xmalloc(size, note); + memset(ptr, 0, size); + + return ptr; +} + +void * +xcalloc(size_t nmemb, size_t size, char *note) +{ + void *ptr; + + if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && + nmemb > 0 && SIZE_MAX / nmemb < size) + panic("xcalloc(%lu, %lu) overflow", nmemb, size); + ptr = xmalloczero(nmemb * size, note); + if (ptr == NULL) + panic("xcalloc(%lu, %lu) failed", nmemb, size); + + return ptr; +} + +void * +xrealloc(void *src, size_t size) +{ + void *ptr, *tsrc; + unsigned long n; + char note[MALLOC_NOTE_SIZE] = "realloc from null"; + +#ifdef MALLOC_DEBUG + if (src != NULL) { + for (n = 0; n <= MALLOC_MAP_SIZE; n++) { + if (n == MALLOC_MAP_SIZE) { + panic("xrealloc(%lu): can't find in alloc map, likely " + "double free()", (unsigned long)src); + return NULL; + } + if (malloc_map[n].addr == (unsigned long)src) { + strlcpy(note, malloc_map[n].note, sizeof(note)); + break; + } + } + } +#endif + + ptr = xmalloc(size, note); + if (src != NULL) { + memcpy(ptr, src, size); + tsrc = src; + xfree(&tsrc); + } + + return ptr; +} + +void * +xreallocarray(void *optr, size_t nmemb, size_t size) +{ + if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && + nmemb > 0 && SIZE_MAX / nmemb < size) + panic("xreallocarray(%lu, %lu) failed", nmemb, size); + return xrealloc(optr, size * nmemb); +} + +char * +xstrdup(const char *str, char *note) +{ + char *cp; + size_t len; + + len = strlen(str); + + cp = xmalloc(len + 1, note); + strlcpy(cp, str, len + 1); + + return cp; +} + +/* + * String functions + */ + +short +getline(char *str, size_t len, char **ret) +{ + short i; + + for (i = 0; i < len; i++) { + if (str[i] == '\r' || i == len - 1) { + if (*ret == NULL) + *ret = xmalloc(i + 1, "getline"); + memcpy(*ret, str, i + 1); + (*ret)[i] = '\0'; + return i + 1; + } + } + + return 0; +} + +const char * +ordinal(unsigned short n) +{ + switch (n % 100) { + case 11: + case 12: + case 13: + return "th"; + default: + switch (n % 10) { + case 1: + return "st"; + case 2: + return "nd"; + case 3: + return "rd"; + default: + return "th"; + } + } +} + +size_t +rtrim(char *str, char *chars) +{ + size_t len, rlen, n, j; + + rlen = len = strlen(str); + + for (n = len; n > 0; n--) { + for (j = 0; chars[j] != '\0'; j++) { + if (str[n - 1] == chars[j]) { + rlen--; + str[n - 1] = '\0'; + goto next_in_str; + } + } + + break; +next_in_str: + continue; + } + + return rlen; +} + +long +strpos_quoted(char *str, char c) +{ + long pos; + unsigned char quot = 0; + + for (pos = 0; str[pos] != '\0'; pos++) { + if (quot) { + if (str[pos] == '\\') { + pos++; + continue; + } + if (str[pos] == quot) + quot = 0; + continue; + } else { + if (str[pos] == '"') { + quot = str[pos]; + continue; + } + if (str[pos] == c) + return pos; + } + } + + return -1; +} + +char * +OSTypeToString(OSType type) +{ + static char ostype_s[5]; + + ostype_s[0] = (unsigned char)((type >> 24) & 0xff); + ostype_s[1] = (unsigned char)((type >> 16) & 0xff); + ostype_s[2] = (unsigned char)((type >> 8) & 0xff); + ostype_s[3] = (unsigned char)(type & 0xff); + ostype_s[4] = 0; + + return ostype_s; +} + +/* + * BSD err(3) and warn(3) functions + */ + +void +vwarn(short alert_func, const char *format, va_list ap) +{ + Rect bounds; + short height, width, hit; + WindowPtr win, dialog; + + GetPort(&win); + + vsnprintf(err_str, ERROR_STRING_SIZE, format, ap); + + width = 300; + height = 100; + bounds.left = (screenBits.bounds.right - width) / 2; + bounds.right = bounds.left + width; + bounds.top = GetMBarHeight() + + ((screenBits.bounds.bottom - height) / 2.5); + bounds.bottom = bounds.top + height; + + ParamText(CtoPstr(err_str), "\p", "\p", "\p"); + + dialog = NewDialog(nil, &bounds, "\p", false, dBoxProc, + (WindowPtr)-1L, false, 0, alert_ditl_h); + +#if 0 + /* XXX: why doesn't changing this work? */ + GetDItem(dialog, ALERT_DITL_ICON, &itype, &ihandle, &irect); + switch (alert_func) { + case CAUTION_ALERT: + ihandle = GetIcon(cautionIcon); + break; + case NOTE_ALERT: + ihandle = GetIcon(noteIcon); + break; + default: + ihandle = GetIcon(stopIcon); + } + ihandle = GetIcon(cautionIcon); + SetDItem(dialog, ALERT_DITL_ICON, itype, ihandle, &irect); +#endif + + ShowWindow(dialog); + for (;;) { + ModalDialog(0, &hit); + if (hit == ok) + break; + } + DisposDialog(dialog); + + /* + * Pre-allocate for the next one while we have memory. We can't use + * xNewHandle because we might already be alerting about an OOM + * condition. + */ + DisposHandle(alert_ditl_h); + alert_ditl_h = NewHandle(sizeof(alert_ditl)); + if (alert_ditl_h == NULL) + ExitToShell(); + HLock(alert_ditl_h); + memcpy(*alert_ditl_h, alert_ditl, sizeof(alert_ditl)); + HUnlock(alert_ditl_h); + + SetPort(win); +} + +void +panic(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vwarn(STOP_ALERT, format, ap); + va_end(ap); + + ExitToShell(); +} + +void +err(short ret, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vwarn(STOP_ALERT, format, ap); + va_end(ap); + + ExitToShell(); +} + +void +warn(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vwarn(CAUTION_ALERT, format, ap); + va_end(ap); +} + +void +warnx(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vwarn(CAUTION_ALERT, format, ap); + va_end(ap); +} + +void +note(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vwarn(NOTE_ALERT, format, ap); + va_end(ap); +} + +short +ask(const char *format, ...) +{ + Rect bounds; + short height, width, hit; + WindowPtr win, dialog; + va_list ap; + + GetPort(&win); + + va_start(ap, format); + vsnprintf(err_str, ERROR_STRING_SIZE, format, ap); + va_end(ap); + + width = 300; + height = 100; + bounds.left = (screenBits.bounds.right - width) / 2; + bounds.right = bounds.left + width; + bounds.top = GetMBarHeight() + + ((screenBits.bounds.bottom - height) / 2.5); + bounds.bottom = bounds.top + height; + + ParamText(CtoPstr(err_str), "\p", "\p", "\p"); + + ask_ditl_h = xNewHandle(sizeof(ask_ditl)); + HLock(ask_ditl_h); + memcpy(*ask_ditl_h, ask_ditl, sizeof(ask_ditl)); + HUnlock(ask_ditl_h); + + dialog = NewDialog(nil, &bounds, "\p", false, dBoxProc, + (WindowPtr)-1L, false, 0, ask_ditl_h); + + ShowWindow(dialog); + for (;;) { + ModalDialog(0, &hit); + if (hit == 1 || hit == 2) + break; + } + DisposDialog(dialog); + DisposHandle(ask_ditl_h); + + SetPort(win); + + return (hit == 1); +} + +void +progress(char *format, ...) +{ + static Str255 progress_s; + Handle thandle; + va_list argptr; + Rect bounds = { 100, 90, 160, 420 }; /* tlbr */ + Rect trect; + short ttype; + + if (format == NULL) { + if (progress_dialog != NULL) { + DisposDialog(progress_dialog); + DisposHandle(progress_ditl_h); + progress_dialog = NULL; + } + return; + } + + va_start(argptr, format); + vsnprintf((char *)progress_s, 256, format, argptr); + va_end(argptr); + CtoPstr(progress_s); + + if (progress_dialog == NULL) { + progress_ditl_h = xNewHandle(sizeof(progress_ditl)); + HLock(progress_ditl_h); + memcpy(*progress_ditl_h, progress_ditl, sizeof(progress_ditl)); + HUnlock(progress_ditl_h); + + progress_dialog = NewDialog(nil, &bounds, "\p", false, dBoxProc, + (WindowPtr)-1L, false, 0, progress_ditl_h); + } + + GetDItem(progress_dialog, 1, &ttype, &thandle, &trect); + SetIText(thandle, progress_s); + + ShowWindow(progress_dialog); + DrawDialog(progress_dialog); +} + +void +window_rect(WindowPtr win, Rect *ret) +{ + *ret = (*(((WindowPeek)win)->strucRgn))->rgnBBox; + ret->left += 1; + ret->right -= 2; + ret->top -= 1; + ret->bottom -= 1; +} + +/* + * General Mac-specific non-GUI functions + */ +static unsigned long _xorshift_state = 0; +unsigned long +xorshift32(void) +{ + unsigned long x = _xorshift_state; + if (x == 0) + x = Ticks; + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + return _xorshift_state = x; +} + + +/* + * Error checking wrappers for Mac toolkit functions + */ + +Handle +xNewHandle(size_t size) +{ + Handle h; + + if (size == 0) + panic("Zero xNewHandle size"); + + h = NewHandle(size); + if (h == NULL) + panic("Failed to NewHandle(%lu)", size); + + return h; +} + +Handle +xGetResource(ResType type, short id) +{ + Handle h; + + h = GetResource(type, id); + if (h == NULL) + panic("Failed to find resource %d", id); + + return h; +} + +StringHandle +xGetString(short id) +{ + StringHandle h; + + h = GetString(id); + if (h == NULL) + panic("Failed to find STR resource %d", id); + + return h; +} + +char * +xGetStringAsChar(short id) +{ + StringHandle h; + char *out; + size_t l; + + h = xGetString(id); + HLock(h); + l = (*h)[0]; + out = xmalloc(l + 1, "xGetStringAsChar"); + memcpy((void *)out, (void *)(*h + 1), l); + out[l] = '\0'; + ReleaseResource(h); + + return out; +} + +long +xGetStringAsLong(short id) +{ + char *c; + long r; + + c = xGetStringAsChar(id); + r = atol(c); + xfree(&c); + return r; +} + +void +xSetHandleSize(Handle h, Size s) +{ + SetHandleSize(h, s); + if (MemError()) + panic("Failed to SetHandleSize to %ld", s); +} + +/* + * Filesystem utilities + */ + +short +getpath(short vRefNum, Str255 fileName, Str255 *ret, bool include_file) +{ + WDPBRec wdir; + HVolumeParam wvol; + DirInfo wcinfo; + char *name = NULL; + size_t retlen = 0, len; + char *tmpret = NULL, *tmp = NULL; + char *lastcolon; + + if (strchr((char *)fileName + 1, ':') != NULL) { + /* already a full path */ + memcpy(*ret, fileName, 256); + if (!include_file) { + PtoCstr(*ret); + lastcolon = strrchr((char *)*ret, ':'); + lastcolon[0] = '\0'; + CtoPstr(*ret); + } + return 0; + } + + name = xmalloc(FILENAME_MAX, "getpath"); + + wdir.ioVRefNum = wdir.ioWDVRefNum = vRefNum; + wdir.ioWDIndex = 0; + wdir.ioWDProcID = 0; + wdir.ioNamePtr = (StringPtr)name; + if (PBGetWDInfo(&wdir, 0) != noErr) { + warn("Failed looking up directory"); + xfree(&name); + return 1; + } + + wvol.ioNamePtr = (StringPtr)name; + wvol.ioVRefNum = vRefNum; + wvol.ioVolIndex = 0; + + if (PBHGetVInfoSync((HParmBlkPtr)&wvol) != noErr) { + warn("Failed getting volume info"); + xfree(&name); + return 1; + } + + if (wvol.ioVSigWord != 0x4244) { + warn("Unknown filesystem type 0x%x", wvol.ioVSigWord); + xfree(&name); + return 1; + } + + wcinfo.ioVRefNum = vRefNum; + wcinfo.ioNamePtr = (StringPtr)name; + wcinfo.ioFDirIndex = -1; + wcinfo.ioDrParID = wdir.ioWDDirID; + wcinfo.ioDrDirID = wdir.ioWDDirID; + + tmp = xmalloc(FILENAME_MAX, "getpath"); + tmpret = xmalloc(FILENAME_MAX, "getpath"); + + /* go backwards, prepending each folder's parent */ + while (wcinfo.ioDrParID != 1) { + wcinfo.ioDrDirID = wcinfo.ioDrParID; /* .. */ + + if (PBGetCatInfo((CInfoPBPtr)&wcinfo, 0) != noErr) + break; + + len = name[0]; + PtoCstr(name); + if (retlen == 0) { + retlen = len; + strlcpy(tmpret, (char *)name, FILENAME_MAX); + } else { + strlcpy(tmp, tmpret, FILENAME_MAX); + snprintf(tmpret, FILENAME_MAX, "%s:%s", name, tmp); + } + } + + if (include_file) { + /* append the original path */ + memcpy(name, fileName, FILENAME_MAX); + PtoCstr(name); + if (retlen == 0) + strlcpy(tmpret, name, FILENAME_MAX); + else { + strlcat(tmpret, ":", FILENAME_MAX); + strlcat(tmpret, name, FILENAME_MAX); + } + } else if (retlen == 0) { + (*ret)[0] = 0; + xfree(&tmp); + xfree(&tmpret); + xfree(&name); + return 0; + } + + CtoPstr(tmpret); + memcpy(*ret, tmpret, FILENAME_MAX); + xfree(&tmp); + xfree(&tmpret); + xfree(&name); + + return 0; +} + +short +stat(char *path, struct stat *sb) +{ + char *ppath; + short ret; + + ppath = xstrdup(path, "stat"); + CtoPstr(ppath); + ret = FStat((unsigned char *)ppath, sb); + xfree(&ppath); + + return ret; +} + +short +FStat(Str255 path, struct stat *sb) +{ + CInfoPBRec catblock = { 0 }; + short ret; + + catblock.hFileInfo.ioNamePtr = path; + ret = PBGetCatInfo(&catblock, 0); + if (ret != noErr) + return -1; + + /* data fork logical length + resource fork logical length */ + sb->st_size = catblock.hFileInfo.ioFlLgLen + + catblock.hFileInfo.ioFlRLgLen; + sb->st_ctime = catblock.hFileInfo.ioFlCrDat; + sb->st_mtime = catblock.hFileInfo.ioFlMdDat; + sb->st_flags = catblock.hFileInfo.ioFlAttrib; + + return 0; +} + +bool +FIsDir(Str255 path) +{ + struct stat st; + short ret; + + if (FStat(path, &st) != 0) + return false; + + /* bit 4 is set in ioFlAttrib if the item is a directory */ + if (st.st_flags & (1 << 4)) + return true; + + return false; +} + +OSErr +copy_file(Str255 source, Str255 dest, bool overwrite) +{ + FInfo fi; + IOParam pb; + short error, source_ref, dest_ref; + + /* copy data fork */ + + error = GetFInfo(source, 0, &fi); + if (error) + return error; + + error = Create(dest, 0, fi.fdCreator, fi.fdType); + if (error == dupFNErr && overwrite) { + error = FSDelete(dest, 0); + if (error) + return error; + error = Create(dest, 0, fi.fdCreator, fi.fdType); + } + if (error) + return error; + + memset(&pb, 0, sizeof(pb)); + pb.ioNamePtr = source; + pb.ioPermssn = fsRdPerm; + error = PBOpen(&pb, false); + if (error) + return error; + source_ref = pb.ioRefNum; + + error = FSOpen(dest, 0, &dest_ref); + if (error) { + FSClose(source_ref); + return error; + } + + error = copy_file_contents(source_ref, dest_ref); + + FSClose(source_ref); + FSClose(dest_ref); + + if (error) + return error; + + /* + * Copy resource fork, open source as shared read/write in case it's + * an open resource file. + */ + source_ref = OpenRFPerm(source, 0, fsRdWrShPerm); + + if (source_ref == -1 && ResError() == eofErr) { + /* no resource fork */ + FSClose(source_ref); + FSClose(dest_ref); + return 0; + } + + if (source_ref == -1) + return ResError(); + + CreateResFile(dest); + if (ResError()) { + FSClose(source_ref); + return ResError(); + } + error = OpenRF(dest, 0, &dest_ref); + if (error) { + FSClose(source_ref); + return error; + } + + error = copy_file_contents(source_ref, dest_ref); + + FSClose(source_ref); + FSClose(dest_ref); + + return error; +} + +OSErr +copy_file_contents(short source_ref, short dest_ref) +{ + char *buf; + short error; + long source_size, count; + + GetEOF(source_ref, &source_size); + error = SetFPos(source_ref, fsFromStart, 0); + if (error) + return error; + error = SetEOF(dest_ref, source_size); + if (error) + return error; + error = SetFPos(dest_ref, fsFromStart, 0); + if (error) + return error; + + buf = xmalloc(1024, "copy_file_contents"); + + while (source_size > 0) { + count = 1024; + if (count > source_size) + count = source_size; + error = FSRead(source_ref, &count, buf); + if (error && error != eofErr) + break; + source_size -= count; + error = FSWrite(dest_ref, &count, buf); + if (error && error != eofErr) + break; + } + + xfree(&buf); + + if (error && error != eofErr) + return error; + + return 0; +} + +/* read a \r-terminated line or the final non-line bytes of an open file */ +OSErr +FSReadLine(short frefnum, char *buf, size_t buflen) +{ + char tbuf; + size_t pos, fsize, rlen = 1, total_read = 0; + short error; + + GetFPos(frefnum, &pos); + GetEOF(frefnum, &fsize); + + for (; pos <= fsize; pos++) { + if (total_read > buflen) + return -1; + + error = FSRead(frefnum, &rlen, &tbuf); + if (error) + return -1; + + if (tbuf == '\r') + return total_read; + + buf[total_read++] = tbuf; + } + + /* nothing found until the end of the file */ + return total_read; +} + + +/* + * Gestalt functions + */ +char * +gestalt_machine_type(void) +{ + short error; + long resp; + + error = Gestalt(gestaltMachineType, &resp); + if (error) + return NULL; + switch (resp) { + case gestaltClassic: + return "Macintosh 128K"; + case gestaltMacXL: + return "Macintosh XL"; + case gestaltMac512KE: + return "Macintosh 512Ke"; + case gestaltMacPlus: + return "Macintosh Plus"; + case gestaltMacSE: + return "Macintosh SE"; + case gestaltMacII: + return "Macintosh II"; + case gestaltMacIIx: + return "Macintosh IIx"; + case gestaltMacIIcx: + return "Macintosh IIcx"; + case gestaltMacSE030: + return "Macintosh SE/30"; + case gestaltPortable: + return "Macintosh Portable"; + case gestaltMacIIci: + return "Macintosh IIci"; + case gestaltMacIIfx: + return "Macintosh IIfx"; + case gestaltMacClassic: + return "Macintosh Classic"; + case gestaltMacIIsi: + return "Macintosh IIsi"; + case gestaltMacLC: + return "Macintosh LC"; + } + + return NULL; +} + +char * +get_version(bool long_version) +{ + static char vers_s[256] = { 0 }; + char *v; + VersRecHndl vers; + short len; + + vers = (VersRecHndl)GetResource('vers', 1); + if (!vers) + return "?.?"; + + HLock(vers); + v = (char *)&(*vers)->shortVersion; + len = v[0]; + if (long_version) { + v += len + 1; + len = v[0]; + } + memcpy(vers_s, v, len + 1); + ReleaseResource(vers); + + PtoCstr(vers_s); + return vers_s; +} + +/* + * General Mac-specific GUI functions + */ + +short +FontHeight(short font_id, short size) +{ + FMInput f_in; + FMOutput *f_out; + + if (font_id == -1) + font_id = thePort->txFont; + if (size == -1) + size = thePort->txSize; + + f_in.family = font_id; + f_in.size = size; + f_in.face = 0; + f_in.needBits = false; + f_in.device = 0; + f_in.numer.h = f_in.numer.v = 1; + f_in.denom.h = f_in.denom.v = 1; + + f_out = FMSwapFont(&f_in); + + return (((f_out->leading + f_out->ascent + f_out->descent) * + f_out->numer.v) / f_out->denom.v); +} + +void +DrawGrowIconOnly(WindowPtr win) +{ + Rect r; + RgnHandle tmp; + + GetClip(tmp = NewRgn()); + r = win->portRect; + r.top = r.bottom - SCROLLBAR_WIDTH; + r.left = r.right - SCROLLBAR_WIDTH + 1; + ClipRect(&r); + DrawGrowIcon(win); + SetClip(tmp); + DisposeRgn(tmp); +} + +/* assumes a fixed-width style */ +short +TEGetWidth(short off, TEHandle te) +{ + TextStyle style; + short fheight, fwidth, ascent, old_font, old_size; + + TEGetStyle(off, &style, &fheight, &ascent, te); + + old_font = thePort->txFace; + old_size = thePort->txSize; + thePort->txFace = style.tsFont; + thePort->txSize = style.tsSize; + fwidth = CharWidth('m'); + thePort->txFace = old_font; + thePort->txSize = old_size; + + return fwidth; +} + +#define ceildiv(a,b) ((a / b) + (!!(a % b))) + +void +UpdateScrollbarForTE(ControlHandle control, TEHandle te, bool reset) +{ + size_t vlines, telines; + TERec *ter; + short fheight, fwidth, max, val, per_line, horiz, max_chars, n; + + HLock(te); + ter = *te; + + horiz = (((*control)->contrlRect.bottom - (*control)->contrlRect.top) < + ((*control)->contrlRect.right - (*control)->contrlRect.left)); + + if (horiz) { + fwidth = TEGetWidth(0, te); + per_line = ((ter->viewRect.right - ter->viewRect.left) / + fwidth) - 1; + for (max_chars = 0, n = 1; n < ter->nLines; n++) { + if (ter->lineStarts[n] - ter->lineStarts[n - 1] > max_chars) + max_chars = ter->lineStarts[n] - ter->lineStarts[n - 1]; + } + + if (max_chars <= per_line) + /* don't enable the scrollbar */ + max = 1; + else + max = max_chars - per_line + 1; + + if (reset) { + val = 1; + TESetSelect(0, 0, te); + } else { + val = (ter->viewRect.left - ter->destRect.left); + + if (val < 0) + val = 1; + else { + val = ceildiv(val, fwidth) + 1; + if (val > max) + max = val; + } + } + } else { + fheight = TEGetHeight(0, 0, te); + vlines = (ter->viewRect.bottom - ter->viewRect.top) / fheight; + telines = ter->nLines; + /* telines is inaccurate if the last line doesn't have any chars */ + //if (telines >= vlines) + // telines++; + max = telines - vlines + 1; + if (max < 1) + max = 1; + + if (reset) { + val = 1; + TESetSelect(0, 0, te); + } else { + val = (ter->viewRect.top - ter->destRect.top); + if (val < 0) + val = 1; + else { + val = ceildiv(val, fheight) + 1; + if (val > max) + max = val; + } + } + } + + /* + * Avoid SetCtlMax because it will redraw and then SetCtlValue will + * redraw again, which can cause a jump if we're trying to keep the + * scrollbar position in the same place (like at the bottom). + */ + (*control)->contrlMax = max; + SetCtlValue(control, val); + + HUnlock(te); +} + +void +SetTrackControlTE(TEHandle te) +{ + track_control_te = te; +} + +pascal void +TrackMouseDownInControl(ControlHandle control, short part) +{ + TERec *ter; + short page, val, adj, fheight, fwidth, horiz, per_line; + + if (track_control_te == NULL) + panic("TrackMouseDownInControl without SetTrackControlTE"); + + HLock(track_control_te); + ter = *track_control_te; + + horiz = (((*control)->contrlRect.bottom - (*control)->contrlRect.top) < + ((*control)->contrlRect.right - (*control)->contrlRect.left)); + + if (horiz) { + fwidth = TEGetWidth(0, track_control_te); + per_line = ((ter->viewRect.right - ter->viewRect.left) / + fwidth) - 1; + page = ceildiv(GetCtlMax(control) + per_line, + ((per_line * 75) / 100)) + 1; + } else { + /* keep 1 line of context between pages */ + fheight = TEGetHeight(0, 0, track_control_te); + page = ((ter->viewRect.bottom - ter->viewRect.top) / fheight) - 1; + } + + adj = 0; + switch (part) { + case inUpButton: + adj = -1; + break; + case inPageUp: + adj = -page; + break; + case inDownButton: + adj = 1; + break; + case inPageDown: + adj = page; + break; + } + + val = GetCtlValue(control); + if (val + adj < GetCtlMin(control)) + adj = -(val - GetCtlMin(control)); + if (val + adj > GetCtlMax(control)) + adj = (GetCtlMax(control) - val); + if (adj == 0) + return; + + if (horiz) + TEScroll(-adj * fwidth, 0, track_control_te); + else + TEScroll(0, -adj * fheight, track_control_te); + + SetCtlValue(control, val + adj); + + HUnlock(track_control_te); +} + +pascal bool +ModalDialogFilter(DialogPtr dlg, EventRecord *event, short *hit) +{ + WindowPtr event_win; + short event_in; + char key; + + switch (event->what) { + case mouseDown: + event_in = FindWindow(event->where, &event_win); + + switch (event_in) { + case inGoAway: + if (TrackGoAway(dlg, event->where)) { + *hit = -1; + return true; + } + } + break; + case keyDown: + key = event->message & charCodeMask; + + if (event->modifiers & cmdKey) { + switch (key) { + case 'x': + ZeroScrap(); + DlgCut(dlg); + break; + case 'c': + ZeroScrap(); + DlgCopy(dlg); + break; + case 'v': + if (TEFromScrap() == noErr) + DlgPaste(dlg); + break; + } + event->what = nullEvent; + return false; + } else if (key == 13 || key == 3) { + /* OK button */ + *hit = OK; + return true; + } + + break; + } + + return false; +} + +static short _password_dialog_ditl_id = -1; +static char *_password_dialog_storage = NULL; +static size_t _password_dialog_storage_len = 0; + +void +PasswordDialogFieldFilterSetup(short ditl_id, char *storage, size_t len) +{ + _password_dialog_ditl_id = ditl_id; + _password_dialog_storage = storage; + _password_dialog_storage_len = len; + + memset(_password_dialog_storage, 0, len); +} + +pascal bool +PasswordDialogFieldFilter(DialogPtr dlg, EventRecord *event, short *hit) +{ + DialogPeek dlgp; + short sel_start, sel_end; + char key; + + dlgp = (DialogPeek)dlg; + if (dlgp->editField == _password_dialog_ditl_id - 1) { + sel_start = (*(dlgp->textH))->selStart; + sel_end = (*(dlgp->textH))->selEnd; + + switch (event->what) { + case keyDown: + case autoKey: + key = event->message & charCodeMask; + if (event->modifiers & cmdKey) { + /* TODO: implement DlgPaste for cmd+v? */ + event->what = nullEvent; + return false; + } + + if (key == 8) { + /* backspace */ + if (sel_start == sel_end && sel_start > 0) + memmove(_password_dialog_storage + sel_start - 1, + _password_dialog_storage + sel_start, + _password_dialog_storage_len - sel_start - 1); + else if (sel_start != sel_end) + memmove(_password_dialog_storage + sel_start, + _password_dialog_storage + sel_end, + _password_dialog_storage_len - sel_end - 1); + } else if (sel_start >= _password_dialog_storage_len) { + event->what = nullEvent; + return false; + } else if (key >= ' ' && key <= '~') { + if (sel_start != sel_end) + /* delete selection before making space for new char */ + memmove(_password_dialog_storage + sel_start, + _password_dialog_storage + sel_end, + _password_dialog_storage_len - sel_end - 1); + memmove(_password_dialog_storage + sel_start + 1, + _password_dialog_storage + sel_start, + _password_dialog_storage_len - sel_start - 1); + _password_dialog_storage[sel_start] = key; + event->message = '•'; + } + _password_dialog_storage[(*(dlgp->textH))->teLength + 1] = '\0'; + sel_start = 0; + break; + } + } + + return ModalDialogFilter(dlg, event, hit); +} + +void +PasswordDialogFieldFinish(void) +{ + _password_dialog_ditl_id = -1; + _password_dialog_storage = NULL; + _password_dialog_storage_len = 0; +} + +/* (*(some_te))->caretHook = NullCaretHook; */ +pascal void +NullCaretHook(void) +{ + asm { + move.l (a7)+,d0 + rts + } +} + +static bool menu_bar_hidden = false; +static short old_mbar_height; +static Rect mbar_rect; +static RgnHandle mbar_save_region; + +void +HideMenuBar(void) +{ + RgnHandle mbar_region; + WindowPeek win; + + old_mbar_height = GetMBarHeight(); + SetRect(&mbar_rect, screenBits.bounds.left, screenBits.bounds.top, + screenBits.bounds.right, screenBits.bounds.top + old_mbar_height); + + mbar_save_region = NewRgn(); + mbar_region = NewRgn(); + + MBarHeight = 0; + CopyRgn(GetGrayRgn(), mbar_save_region); + RectRgn(mbar_region, &mbar_rect); + UnionRgn(GetGrayRgn(), mbar_region, GetGrayRgn()); + + win = (WindowPeek)FrontWindow(); + PaintOne(win, mbar_region); + PaintBehind(win, mbar_region); + CalcVis(win); + CalcVisBehind(win, mbar_region); + DisposeRgn(mbar_region); + + menu_bar_hidden = true; +} + +void +RestoreHiddenMenuBar(void) +{ + WindowPeek win; + + if (!menu_bar_hidden) + return; + + CopyRgn(mbar_save_region, GetGrayRgn()); + MBarHeight = old_mbar_height; + + RectRgn(mbar_save_region, &mbar_rect); + win = (WindowPeek)FrontWindow(); + CalcVis(win); + CalcVisBehind(win, mbar_save_region); + DisposeRgn(mbar_save_region); + + menu_bar_hidden = false; + + HiliteMenu(0); + DrawMenuBar(); +} --- util.h Thu Aug 18 22:36:49 2022 +++ util.h Thu Aug 18 22:36:49 2022 @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2020-2022 joshua stein <jcs@jcs.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __UTIL_H__ +#define __UTIL_H__ + +#include <stdlib.h> +#include <limits.h> +#include <time.h> + +#define MALLOC_DEBUG +#define MALLOC_NOTE_SIZE 32 + +#ifndef SIZE_MAX +#define SIZE_MAX ULONG_MAX +#endif + +#define nitems(what) (sizeof((what)) / sizeof((what)[0])) +#define member_size(type, member) sizeof(((type *)0)->member) + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define BOUND(a, min, max) ((a) > (max) ? (max) : ((a) < (min) ? (min) : (a))) + +#define EXPAND_TO_FIT(var, var_size, used_size, add, grow_amount) { \ + if ((used_size) + (add) >= (var_size)) { \ + while ((used_size) + (add) >= (var_size)) { \ + (var_size) += (grow_amount); \ + } \ + (var) = xrealloc((var), (var_size)); \ + } \ +} + +#define CHARS_TO_LONG(a,b,c,d) (unsigned long)(\ + ((unsigned long)((unsigned char)(a)) << 24) | \ + ((unsigned long)((unsigned char)(b)) << 16) | \ + ((unsigned long)((unsigned char)(c)) << 8) | \ + (unsigned long)((unsigned char)(d)) ) +#define CHARS_TO_SHORT(a,b) (unsigned short)(\ + ((unsigned short)((unsigned char)(a)) << 8) | \ + (unsigned short)((unsigned char)(b)) ) + +#define SCROLLBAR_WIDTH 16 + +/* GetMBarHeight() is not very useful */ +#define MENUBAR_HEIGHT 20 + +#define TICKS_PER_SEC 60L + +#define MAX_TEXTEDIT_SIZE 32767L + +#ifndef bool +typedef Boolean bool; +#endif +typedef signed long off_t; +typedef signed long ssize_t; +typedef unsigned char u_char; +typedef unsigned long u_int; +typedef unsigned char u_int8_t; +typedef unsigned short u_int16_t; +typedef unsigned long u_int32_t; + +#define BYTE_ORDER BIG_ENDIAN + +typedef struct { + short push[2], rts; + void *addr; +} tCodeStub; + +struct stat { + short st_mode; + ssize_t st_size; + time_t st_ctime; + time_t st_mtime; + unsigned char st_flags; +}; + +void util_init(void); + +void * xmalloc(size_t, char *note); +void xfree(void *ptrptr); +void xfree_verify(void); +void * xmalloczero(size_t, char *note); +void * xcalloc(size_t, size_t, char *note); +void * xrealloc(void *src, size_t size); +void * xreallocarray(void *, size_t, size_t); +char * xstrdup(const char *, char *note); + +short getline(char *str, size_t len, char **ret); +size_t strlcpy(char *dst, const char *src, size_t dsize); +size_t strlcat(char *dst, const char *src, size_t dsize); +const char * ordinal(unsigned short n); +size_t rtrim(char *str, char *chars); +long strpos_quoted(char *str, char c); +char * OSTypeToString(OSType type); + +unsigned long xorshift32(void); + +void panic(const char *format, ...); +void err(short ret, const char *format, ...); +void warnx(const char *format, ...); +void warn(const char *format, ...); +void note(const char *format, ...); +short ask(const char *format, ...); +#define ASK_YES 1 +#define ASK_NO 2 +void progress(char *format, ...); +void window_rect(WindowPtr win, Rect *ret); + +Handle xNewHandle(size_t size); +Handle xGetResource(ResType type, short id); +StringHandle xGetString(short id); +char * xGetStringAsChar(short id); +long xGetStringAsLong(short id); +void xSetHandleSize(Handle h, Size s); + +short getpath(short vRefNum, Str255 fileName, Str255 *ret, bool include_file); +bool FIsDir(Str255 path); +short stat(char *path, struct stat *sb); +short FStat(Str255 path, struct stat *sb); +OSErr copy_file(Str255 source, Str255 dest, bool overwrite); +OSErr copy_file_contents(short source_ref, short dest_ref); +OSErr FSReadLine(short frefnum, char *buf, size_t buflen); + +char * gestalt_machine_type(void); +char * get_version(bool long_version); + +short FontHeight(short font_id, short size); +void DrawGrowIconOnly(WindowPtr win); +short TEGetWidth(short off, TEHandle te); +void UpdateScrollbarForTE(ControlHandle scroller, TEHandle te, bool reset); +void SetTrackControlTE(TEHandle te); +pascal void TrackMouseDownInControl(ControlHandle control, short part); +pascal bool ModalDialogFilter(DialogPtr dlg, EventRecord *event, + short *hit); +void PasswordDialogFieldFilterSetup(short ditl_id, char *storage, + size_t len); +pascal bool PasswordDialogFieldFilter(DialogPtr dlg, EventRecord *event, + short *hit); +void PasswordDialogFieldFinish(void); +pascal void NullCaretHook(void); +void HideMenuBar(void); +void RestoreHiddenMenuBar(void); + +#endif --- wikipedia.h Thu Aug 18 21:06:48 2022 +++ wikipedia.h Thu Aug 18 21:06:48 2022 @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022 joshua stein <jcs@jcs.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define PROGRAM_NAME "Wikipedia" + +#define MBAR_ID 128 + +#define APPLE_MENU_ID 128 +#define APPLE_MENU_ABOUT_ID 1 + +#define FILE_MENU_ID 129 +#define FILE_MENU_QUIT_ID 1 + +#define EDIT_MENU_ID 130 +#define EDIT_MENU_CUT_ID 1 +#define EDIT_MENU_COPY_ID 2 +#define EDIT_MENU_PASTE_ID 3 +#define EDIT_MENU_SELECT_ALL_ID 4 + +extern MenuHandle file_menu, edit_menu; + +void menu_defaults(void);