/* * Copyright (c) 2023 Valtteri Koskivuori * * 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 "rsrcid.h" #include #include /* A basic cdev skeleton + implementation in pure C For some reason, Symantec decided to embrace OOP for all their cdev examples, and my copy didn't ship with the needed libraries to build with those language extensions, so I wrote this C impl from scratch. It works, there are many bugs, but it does the job. */ #define ntp1_box_id 1 #define ntp2_box_id 2 #define utc_box_id 3 #define err_title_id 9 #define err_box_id 10 #define apply_btn_id 4 struct control { short type; Handle handle; Rect rect; }; struct state { StringHandle ntp1; StringHandle ntp2; StringHandle utc; StringHandle err; DialogPtr dialog; struct control ntp1_textbox; struct control ntp2_textbox; struct control utc_textbox; struct control err_textbox; struct control err_title; }; // Prototypes void got_cdev_error(void); void got_memory_error(void); void got_resource_error(void); int is_number(char c); int validate_ntp_url(Str255 utc_str); int validate_utc(Str255 utc_str); int utcstr_to_mins(Str255 utc, long *out); void *got_initDev(void *storage, DialogPtr dialog, short nitems); void *got_hitDev(void *storage, short item); void *got_closeDev(void *storage, short item); void *got_nulDev(void *storage); void *got_updateDev(void *storage); void *got_activDev(void *storage); void *got_deActivDev(void *storage); void *got_keyEvtDev(void *storage, EventRecord *event, short item, short nitems, DialogPtr d); void *got_keyEvent(void *storage, unsigned char c); void *got_cmdKeyEvent(void *storage, unsigned char c, short item, short nitems, DialogPtr d); void *got_macDev(void *storage); void *got_undoDev(void *storage); void *got_cutDev(void *storage); void *got_copyDev(void *storage); void *got_pasteDev(void *storage); void *got_clearDev(void *storage); // Impls void got_cdev_error(void) { SysBeep(30); SysBeep(30); } void got_memory_error(void) { SysBeep(30); SysBeep(30); } void got_resource_error(void) { SysBeep(30); SysBeep(30); } void *got_initDev(void *storage, DialogPtr dialog, short nitems) { struct state *s; storage = NewHandle(sizeof(struct state)); if (!storage) { SysBeep(1); SysBeep(1); SysBeep(1); } s = *(struct state **)storage; s->ntp1 = GetString(NTP1_STR_ID); s->ntp2 = GetString(NTP2_STR_ID); s->utc = GetString(UTC_STR_ID); s->err = GetString(ERR_STR_ID); s->dialog = dialog; if (!s->ntp1 || !s->ntp2 || !s->utc || !s->err) { SysBeep(1); SysBeep(1); SysBeep(1); } SetHandleSize(s->ntp1, 256); SetHandleSize(s->ntp2, 256); SetHandleSize(s->utc, 256); SetHandleSize(s->err, 256); HLock(s->ntp1); HLock(s->ntp2); HLock(s->utc); HLock(s->err); // Grab our textboxes. The number is the one in our DITL // but the dialog manager wants the full range num, so add nitems GetDItem(s->dialog, ntp1_box_id + nitems, &s->ntp1_textbox.type, &s->ntp1_textbox.handle, &s->ntp1_textbox.rect); HLock(s->ntp1_textbox.handle); SetIText(s->ntp1_textbox.handle, *s->ntp1); ShowDItem(s->dialog, ntp1_box_id); InvalRect(&s->ntp1_textbox.rect); SelIText(s->dialog, ntp1_box_id + nitems, 0, 32767); GetDItem(s->dialog, ntp2_box_id + nitems, &s->ntp2_textbox.type, &s->ntp2_textbox.handle, &s->ntp2_textbox.rect); HLock(s->ntp2_textbox.handle); SetIText(s->ntp2_textbox.handle, *s->ntp2); ShowDItem(s->dialog, ntp2_box_id); InvalRect(&s->ntp2_textbox.rect); GetDItem(s->dialog, utc_box_id + nitems, &s->utc_textbox.type, &s->utc_textbox.handle, &s->utc_textbox.rect); HLock(s->utc_textbox.handle); SetIText(s->utc_textbox.handle, *s->utc); ShowDItem(s->dialog, utc_box_id); InvalRect(&s->utc_textbox.rect); GetDItem(s->dialog, err_box_id + nitems, &s->err_textbox.type, &s->err_textbox.handle, &s->err_textbox.rect); HLock(s->err_textbox.handle); GetDItem(s->dialog, err_title_id + nitems, &s->err_title.type, &s->err_title.handle, &s->err_title.rect); HLock(s->err_title.handle); if (*s->err[0]) { SetIText(s->err_textbox.handle, *s->err); ShowDItem(s->dialog, err_box_id); InvalRect(&s->err_textbox.rect); } else { HideDItem(s->dialog, err_box_id + nitems); HideDItem(s->dialog, err_title_id + nitems); } return storage; } int validate_ntp_url(Str255 utc_str) { // TODO return true; } int is_number(char c) { return c >> 4 == 3 && (c & 0x0F) < 0xA; } int validate_utc(Str255 utc_str) { // Str255 is a pointer to a pascal string, where // the first byte is the string length. char scratch[6]; short strlen; char *str; strlen = utc_str[0]; str = (char *)&utc_str[1]; if (strlen != 6) return false; if (str[0] != '+' && str[0] != '-') return false; if (!is_number(str[1]) || !is_number(str[2])) return false; if (str[3] != ':') return false; if (!is_number(str[4]) || !is_number(str[5])) return false; return true; } int utcstr_to_mins(Str255 utc, long *out) { long minutes; if (!validate_utc(utc)) return false; minutes = 0; minutes += ((utc[2] - 0x30) * 10) * 60; minutes += (utc[3] - 0x30) * 60; minutes += (utc[5] - 0x30) * 10; minutes += (utc[6] - 0x30); if (utc[1] == '-') minutes = -minutes; if (out) *out = minutes; return true; } void *got_hitDev(void *storage, short item) { Str255 ntp1_text; Str255 ntp2_text; Str255 utc_text; long old_utc_offset; long new_utc_offset; long diff_secs; unsigned long systime; int updated; struct state *s = *(struct state **)storage; updated = false; if (item == apply_btn_id) { GetIText(s->ntp1_textbox.handle, ntp1_text); GetIText(s->ntp2_textbox.handle, ntp2_text); GetIText(s->utc_textbox.handle, utc_text); if (!validate_ntp_url(ntp1_text)) return storage; if (!validate_ntp_url(ntp2_text)) return storage; if (!validate_utc(utc_text)) { SysBeep(1); SysBeep(1); return storage; } // All good to go, let's store these settings. /* Steps: - Compare and update NTP timeservers, if need be. - Get current UTC offset from rsrc - Compute new UTC offset from text input - If the value has changed, compute the offset and update system clock. */ if (!EqualString(s->ntp1, ntp1_text, true, true)) { SetString(s->ntp1, ntp1_text); ChangedResource(s->ntp1); ReleaseResource(s->ntp1); updated = true; } if (!EqualString(s->ntp2, ntp2_text, true, true)) { SetString(s->ntp2, ntp2_text); ChangedResource(s->ntp2); ReleaseResource(s->ntp2); updated = true; } // Update the utc offset, and tweak system clock if need be if (!EqualString(s->utc, utc_text, true, true)) { if (!utcstr_to_mins(utc_text, &new_utc_offset)) return storage; if (!utcstr_to_mins(*s->utc, &old_utc_offset)) return storage; SetString(s->utc, utc_text); ChangedResource(s->utc); ReleaseResource(s->utc); updated = true; diff_secs = (new_utc_offset - old_utc_offset) * 60; ReadDateTime((unsigned long *)&systime); systime += diff_secs; SetDateTime(systime); } if (updated) { UpdateResFile(CurResFile()); if (ResError()) { //DebugStr("\pUpdateResFile failed"); } } } return storage; } void *got_closeDev(void *storage, short item) { struct state *s = *(struct state **)storage; HideDItem(s->dialog, ntp1_box_id); HideDItem(s->dialog, ntp2_box_id); HideDItem(s->dialog, utc_box_id); DisposHandle(s->ntp1); DisposHandle(s->ntp2); DisposHandle(s->utc); DisposHandle(s->err); DisposHandle(storage); return storage; } void *got_nulDev(void *storage) { return storage; } void *got_updateDev(void *storage) { struct state *s = *(struct state **)storage; return storage; } void *got_activDev(void *storage) { return storage; } void *got_deActivDev(void *storage) { return storage; } void *got_keyEvtDev(void *storage, EventRecord *event, short item, short nitems, DialogPtr d) { if (!(event->modifiers & cmdKey)) return got_keyEvent(storage, (unsigned char)event->message); else if (event->message != autoKey) { event->what = nullEvent; return got_cmdKeyEvent(storage, (unsigned char)event->message, item, nitems, d); } //FIXME: Can we reach this even? SysBeep(30); return storage; } void *got_keyEvent(void *storage, unsigned char c) { return storage; } // I guess we can get actual undoDev, cutDev, etc // events from the system, but we want to handle // the key events as well. I assume that the // system events are from the menu, and key events // come from the keyboard. // I guess Apple just did this for compatibility with // existing cdevs, so I'll just patch these in here. void *got_cmdKeyEvent(void *storage, unsigned char c, short item, short nitems, DialogPtr d) { struct state *s = *(struct state **)storage; switch (c) { case 'z': case 'Z': return got_undoDev(storage); case 'x': case 'X': return got_cutDev(storage); case 'c': case 'C': return got_copyDev(storage); case 'v': case 'V': return got_pasteDev(storage); case 'a': case 'A': //GetDItem(d, item - nitems, NULL, NULL, NULL); // I tried a few permutations, but this call crashes the system every // time and I can't be bothered to figure out why. //SelIText(d, item - nitems, 0, 32767); return storage; } SysBeep(30); return storage; } void *got_macDev(void *storage) { long answer; OSErr gestalt_err; SysBeep(1); gestalt_err = Gestalt(gestaltVersion, &answer); if (gestalt_err) { // Gestalt not available, it was introduced in 6.0.4 // And MacTCP in 6.0.3, so we're just going to assume // the user has at least 6.0.8 return (void *)0; } if (!Gestalt('mtcp', nil)) { // Good, MacTCP is available. return (void *)1; } return (void *)0; } void *got_undoDev(void *storage) { return storage; } void *got_cutDev(void *storage) { struct state *s = *(struct state **)storage; DlgCut(s->dialog); return storage; } void *got_copyDev(void *storage) { struct state *s = *(struct state **)storage; DlgCopy(s->dialog); return storage; } void *got_pasteDev(void *storage) { struct state *s = *(struct state **)storage; DlgPaste(s->dialog); return storage; } void *got_clearDev(void *storage) { return storage; } pascal void *main( short msg, short item, short nitems, short dialog_id, EventRecord *event, void *storage, DialogPtr dp) { // Check for errors. We have to return the error value to ACK, I guess. switch ((long)storage) { case -1: // cdev error got_cdev_error(); return storage; case 0: // memory error got_memory_error(); return storage; case 1: // resource error got_resource_error(); return storage; } // respond to message switch (msg) { case initDev: return got_initDev(storage, dp, nitems); case hitDev: return got_hitDev(storage, item - nitems); case closeDev: return got_closeDev(storage, item - nitems); case nulDev: return got_nulDev(storage); case updateDev: return got_updateDev(storage); case activDev: return got_activDev(storage); case deactivDev: return got_deActivDev(storage); case keyEvtDev: return got_keyEvtDev(storage, event, item, nitems, dp); case macDev: return got_macDev(storage); case undoDev: return got_undoDev(storage); case cutDev: return got_cutDev(storage); case copyDev: return got_copyDev(storage); case pasteDev: return got_pasteDev(storage); case clearDev: return got_clearDev(storage); } return 0; }