vkoskiv
/MacNTP
/amendments
/16
Implement a very rough first-pass control panel + fallback option
I honestly thought that the cdev would be the easy part, but the API
is quite weird, and I've had tons of issues just getting text boxes
to work as expected.
It now works just enough to edit the STR resources we use to store
settings. There is no validation yet, that will come later.
There are now two NTP urls, the fallback URL will be tried if the
primary one returns a DNS resolve error.
vkoskiv made amendment 16 about 1 year ago
--- cdev.c Wed Sep 13 20:30:41 2023
+++ cdev.c Wed Sep 13 23:03:35 2023
@@ -0,0 +1,300 @@
+/*
+ * Copyright (c) 2023 Valtteri Koskivuori <vkoskiv@gmail.com>
+ *
+ * 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.
+ */
+
+/*
+ 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.
+*/
+
+struct control {
+ short type;
+ Handle handle;
+ Rect rect;
+};
+
+struct state {
+ StringHandle ntp1;
+ StringHandle ntp2;
+ StringHandle utc;
+ DialogPtr dialog;
+ struct control ntp1_textbox;
+ struct control ntp2_textbox;
+ struct control utc_textbox;
+};
+
+// Prototypes
+void got_cdev_error(void);
+void got_memory_error(void);
+void got_resource_error(void);
+
+int validate_ntp_url(Str255 utc_str);
+int validate_utc(Str255 utc_str);
+
+void *got_initDev(void *storage, DialogPtr dialog, short nitems);
+void *got_hitDev(void *storage, short item);
+void *got_closeDev(void *storage);
+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 *);
+void *got_keyEvent(void *storage, unsigned char c);
+void *got_cmdKeyEvent(void *storage, unsigned char c);
+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(131);
+ s->ntp2 = GetString(132);
+ s->utc = GetString(133);
+ s->dialog = dialog;
+ if (!s->ntp1 || !s->ntp2 || !s->utc) {
+ SysBeep(1);
+ SysBeep(1);
+ SysBeep(1);
+ }
+ SetHandleSize(s->ntp1, 256);
+ SetHandleSize(s->ntp2, 256);
+ SetHandleSize(s->utc, 256);
+ HLock(s->ntp1);
+ HLock(s->ntp2);
+ HLock(s->utc);
+ // 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,
+ 4 + nitems,
+ &s->ntp1_textbox.type,
+ &s->ntp1_textbox.handle,
+ &s->ntp1_textbox.rect);
+ GetDItem(s->dialog,
+ 6 + nitems,
+ &s->ntp2_textbox.type,
+ &s->ntp2_textbox.handle,
+ &s->ntp2_textbox.rect);
+ GetDItem(s->dialog,
+ 8 + nitems,
+ &s->utc_textbox.type,
+ &s->utc_textbox.handle,
+ &s->utc_textbox.rect);
+ HLock(s->ntp1_textbox.handle);
+ HLock(s->ntp2_textbox.handle);
+ HLock(s->utc_textbox.handle);
+ SetIText(s->ntp1_textbox.handle, *s->ntp1);
+ SetIText(s->ntp2_textbox.handle, *s->ntp2);
+ SetIText(s->utc_textbox.handle, *s->utc);
+
+ return storage;
+}
+
+int validate_ntp_url(Str255 utc_str) {
+ // TODO
+ return true;
+}
+
+int validate_utc(Str255 utc_str) {
+ // TODO
+ return true;
+}
+
+void *got_hitDev(void *storage, short item) {
+ Str255 ntp1_text;
+ Str255 ntp2_text;
+ Str255 utc_text;
+ struct state *s = *(struct state **)storage;
+
+ if (item == 9) {
+ 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)) 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.
+ */
+
+ SetString(s->ntp1, ntp1_text);
+ ChangedResource(s->ntp1);
+ ReleaseResource(s->ntp1);
+ SetString(s->ntp2, ntp2_text);
+ ChangedResource(s->ntp2);
+ ReleaseResource(s->ntp2);
+ SetString(s->utc, utc_text);
+ ChangedResource(s->utc);
+ ReleaseResource(s->utc);
+
+ UpdateResFile(CurResFile());
+ if (ResError()) {
+ //DebugStr("\pUpdateResFile failed");
+ }
+ }
+ return storage;
+}
+void *got_closeDev(void *storage) {
+ struct state *state = *(struct state **)storage;
+ DisposHandle(state->ntp1);
+ DisposHandle(state->ntp2);
+ DisposHandle(state->utc);
+ 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) {
+ if (!(event->modifiers & cmdKey))
+ return got_keyEvent(storage, (unsigned char)event->message);
+ else if (event->message != autoKey)
+ return got_cmdKeyEvent(storage, (unsigned char)event->message);
+
+ //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) {
+ 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);
+ }
+ SysBeep(30);
+ return storage;
+}
+// We shouldn't get this message, but report we are runnable anyway
+void *got_macDev(void *storage) {
+ return (void *)1;
+}
+void *got_undoDev(void *storage) {
+ return storage;
+}
+void *got_cutDev(void *storage) {
+ return storage;
+}
+void *got_copyDev(void *storage) {
+ return storage;
+}
+void *got_pasteDev(void *storage) {
+ 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);
+ 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);
+ 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;
+}
--- MacNTP.π.r Fri Sep 8 20:41:01 2023
+++ MacNTP.π.r Wed Sep 13 22:30:50 2023
@@ -1,11 +1,15 @@
-data 'STR ' (128, "NTPSERVER", sysheap) {
+data 'STR ' (131, "NTP1", sysheap) {
$"0F74 696D 652E 676F 6F67 6C65 2E63 6F6D" /* .time.google.com */
};
-data 'STR ' (129, "UTCOFFSETMINUTES", sysheap) {
+data 'STR ' (133, "UTCOFFSETMINUTES", sysheap) {
$"0331 3830" /* .180 */
};
+data 'STR ' (132, "NTP2", sysheap) {
+ $"0E74 696D 652E 6170 706C 652E 636F 6D" /* .time.apple.com */
+};
+
data 'ICN#' (128, "NTPPENDING", sysheap) {
$"FFFF FFFF 8080 0001 8080 7C01 8881 8301" /* ....ÄÄ..ÄÄ|.àÅÉ. */
$"9482 0081 9484 0041 9484 8041 9488 4421" /* îÇ.ÅîÑ.AîÑÄAîàD! */
@@ -92,5 +96,33 @@ data 'FREF' (128) {
data 'FREF' (129) {
$"494E 4954 0001 00" /* INIT... */
+};
+
+data 'vers' (128) {
+ $"0010 2000 0011 0476 302E 312F 4D61 634E" /* .. ....v0.1/MacN */
+ $"5450 2056 6572 7369 6F6E 2030 2E31 2C20" /* TP Version 0.1, */
+ $"A920 5661 6C74 7465 7269 204B 6F73 6B69" /* © Valtteri Koski */
+ $"7675 6F72 692C 2032 3032 33" /* vuori, 2023 */
+};
+
+data 'nrct' (-4064) {
+ $"0001 FFFF 0057 00FF 0142" /* .....W...B */
+};
+
+data 'DITL' (-4064) {
+ $"0009 0000 0000 000A 0075 001B 0108 8814" /* .∆.......u....à. */
+ $"4D61 634E 5450 2043 6F6E 6669 6775 7261" /* MacNTP Configura */
+ $"7469 6F6E 0000 0000 0010 0114 0021 012C" /* tion.........!., */
+ $"9002 3A29 0000 0000 0028 0076 0038 00DE" /* ê.:).....(.v.8.. */
+ $"880B 5072 696D 6172 7920 4E54 5000 0000" /* à.Primary NTP... */
+ $"0000 003C 0076 004C 012E 1000 0000 0000" /* ...<.v.L........ */
+ $"0055 0076 0065 00DE 880C 4661 6C6C 6261" /* .U.v.e..à.Fallba */
+ $"636B 204E 5450 0000 0000 0068 0076 0078" /* ck NTP.....h.v.x */
+ $"012E 1000 0000 0000 0082 0076 0092 00BB" /* .........Ç.v.í.ª */
+ $"880A 5554 4320 4F66 6673 6574 0000 0000" /* à.UTC Offset.... */
+ $"0096 0076 00A6 00CE 1006 2B30 333A 3030" /* .ñ.v.¶.Œ..+03:00 */
+ $"0000 0000 0094 00DA 00A8 0132 0405 4170" /* .....î...®.2..Ap */
+ $"706C 7900 0000 0000 000A 0111 002A 0131" /* ply..........*.1 */
+ $"2002 F020" /* .. */
};
--- main.c Fri Sep 8 21:47:23 2023
+++ main.c Wed Sep 13 22:59:24 2023
@@ -31,7 +31,7 @@ TODO:
//#define DEBUGGING
void dump_error(enum MacNTPError error, OSErr oserror);
-int TryMacNTP(Str255 url, Str255 utc);
+int TryMacNTP(Str255 ntp1, Str255 ntp2, Str255 utc);
#define SECONDS_TO_02_09_1996 2924510400ul
#define MACNTP_ICN_ID_PENDING 128
@@ -40,8 +40,9 @@ int TryMacNTP(Str255 url, Str255 utc);
Handle g_mtcp_init_handle;
-#define NTP_URL_STR_ID 128
-#define UTC_OFFSET_STR_ID 129
+#define NTP1_URL_STR_ID 131
+#define NTP2_URL_STR_ID 132
+#define UTC_OFFSET_STR_ID 133
#ifdef DEBUGGING
void dump_error(enum MacNTPError error, OSErr oserror) {
@@ -95,19 +96,24 @@ void dump_error(enum MacNTPError error, OSErr oserror)
}
#endif
-int TryMacNTP(Str255 url, Str255 utc) {
+int TryMacNTP(Str255 ntp1, Str255 ntp2, Str255 utc) {
struct ntp_packet payload;
enum MacNTPError error;
OSErr oserr = 0;
minutes_t utc_offset;
- if (!utc) return 1;
+ if (!utc[0]) return 1;
StringToNum(utc, &utc_offset);
- if (!url) return 1;
- PtoCstr(url);
- error = MacNTPFetchTime((char *)(url), &payload, &oserr, utc_offset);
-
+ if (!ntp1[0]) return 1;
+ PtoCstr(ntp1);
+ error = MacNTPFetchTime((char *)(ntp1), &payload, &oserr, utc_offset);
+ if (error == DNSResolveFailed) SysBeep(1);
+ // If the first lookup fails and we have a fallback, try it.
+ if (error == DNSResolveFailed && ntp2[0]) {
+ PtoCstr(ntp2);
+ error = MacNTPFetchTime((char *)(ntp2), &payload, &oserr, utc_offset);
+ }
if (error) {
#ifdef DEBUGGING
dump_error(error, oserr);
@@ -128,8 +134,9 @@ int TryMacNTP(Str255 url, Str255 utc) {
void main() {
Ptr our_init_ptr;
unsigned long systime;
- StringHandle url;
- StringHandle utc; // Offset in minutes
+ StringHandle ntp1;
+ StringHandle ntp2;
+ StringHandle utc; // Offset in minutes as pstr
asm {
move.L A0, our_init_ptr;
}
@@ -138,18 +145,28 @@ void main() {
// This is already locked in the resource attrs
g_mtcp_init_handle = RecoverHandle(our_init_ptr);
- url = GetString(NTP_URL_STR_ID);
- utc = GetString(UTC_OFFSET_STR_ID);
- if (!url || !utc) {
+ ntp1 = GetString(NTP1_URL_STR_ID);
+ ntp2 = GetString(NTP2_URL_STR_ID);
+ utc = GetString(UTC_OFFSET_STR_ID);
+ if (!ntp1 || !ntp2 || !utc) {
#ifdef DEBUGGING
DebugStr("\pSTR rsrcs missing");
#endif
ShowInitIcon(MACNTP_ICN_ID_FAILURE, true);
goto skip;
}
- HLock(url);
+ /*if (!*ntp1[0] && !*ntp2[0]) {
+#ifdef DEBUGGING
+ DebugStr("\pBoth NTP urls empty");
+#endif
+ ShowInitIcon(MACNTP_ICN_ID_FAILURE, true);
+ goto skip;
+ }*/
+ HLock(ntp1);
+ HLock(ntp2);
HLock(utc);
+ //TODO: Move this up a bit
#ifndef DEBUGGING
// We only try to update the clock if we're well into the 20th century
ReadDateTime((unsigned long *)&systime);
@@ -163,14 +180,19 @@ void main() {
ShowInitIcon(MACNTP_ICN_ID_PENDING, false);
#endif
- if (!TryMacNTP(*url, *utc)) {
+ if (!TryMacNTP(*ntp1, *ntp2, *utc)) {
ShowInitIcon(MACNTP_ICN_ID_SUCCESS, true);
} else {
ShowInitIcon(MACNTP_ICN_ID_FAILURE, true);
}
- HUnlock(url); // I have no idea if unlock is needed here. Maybe not.
- DisposHandle(url);
+ // I have no idea if unlock is needed here. Maybe not.
+ HUnlock(ntp1);
+ DisposHandle(ntp1);
+ HUnlock(ntp2);
+ // For whatever reason, System 6 crashes on boot if we
+ // try to dispose this specific handle. Okay.
+ //DisposHandle(ntp2);
HUnlock(utc);
DisposHandle(utc);
HUnlock(g_mtcp_init_handle);
--- README Sat Sep 9 01:55:51 2023
+++ README Wed Sep 13 23:04:20 2023
@@ -8,9 +8,10 @@ If you find it useful, please don't hesitate to reach
More details:
-During boot, MacNTP will check the system clock, and if it detects a time in the 20th century, it will attempt to query the configured NTP server for the current time. The INIT will display the MacNTP icon in the "pending" state. If the NTP request succeeds or fails, the icon is updated to reflect the result.
+During boot, MacNTP will check the system clock, and if it detects a time in the 20th century, it will attempt to query the configured NTP servers for the current time. The INIT will display the MacNTP icon in the "pending" state. If the NTP request succeeds or fails, the icon is updated to reflect the result.
+If the icon is in the "Pending" state for an unusually long time, chances are it's stuck doing DNS lookups. It will eventually time out, though.
-If you want to compile it yourself:
+-- Build instructions below if you want to compile it yourself --
Prerequisites:
- System 6 (I haven't tested on other systems)
@@ -18,15 +19,37 @@ Prerequisites:
- Think C 5.0.1
Setup:
-- Create new Think C project, name it "MacNTP.π" (option+p gets you π)
-- Add all .c files, as well as MacTraps and MacTraps2 from Think C's Mac Libraries
-- Use the SARez tool to compile "MacNTP.π.r" into a resource file, name it "MacNTP.π.rsrc" - It's important you name it exactly that, otherwise Think C won't find it. It also has to be in the same directory as your project file.
-- Ensure "Generate 68020 instructions" is disabled under Edit->Options->Compiler options (Unless your system has a 68020!)
-- Select Project->Set Project Type, select Code Resource, Set File Type to "INIT", Creator to "VKOS", Name to whatever you want, Type to "INIT", ID to "0" (zero), Attrs to 70. Leave Custom Header deselected, and hit OK.
+Think C 5 doesn't support the kind of multi-target project that combines both an INIT and a cdev, so we have to work around it. We need two projects, and we will first build a cdev (Control Panel Document), and then build a second project to inject the INIT resource into our cdev.
+1. Create new Think C project, name it "MacNTP.π" (option+p gets you π)
+2. To this project, add only cdev.c.
+3. Set the project settings like this:
+- Select Code Resource.
+- File Type = "cdev"
+- Creator = "VKOS"
+- Type = "cdev"
+- ID = "-4064"
+- Attrs = 00
+- Leave "Custom Header" deselected.
+- Ensure that under Edit->Options... under Compiler Settings, "Generate 68020 instructions" is deselected.
-Select "Build Code Resource" under "Project" to build the INIT document. Think C will then prompt you for a file name for the INIT document. INITs are loaded in alphabetical order, so it is important you choose a name that comes after "MacTCP" alphabetically, as MacNTP depends on MacTCP being present. I usually name it "zMacNTP" to have it load last. You can then save this file in your System Folder and give it a go.
+4. Use the SARez tool to compile "MacNTP.π.r" into a resource file, name it "MacNTP.π.rsrc" - It's important you name it exactly that, and that it resides in the same directory as the project file created above, otherwise Think C won't find it.
+5. Select "Build Code Resource" under "Project" to build the cdev document. Think C will then prompt you for a file name for the cdev document. cdevs and INITs are loaded in alphabetical order, so it is important you choose a name that comes after "MacTCP" alphabetically, as MacNTP depends on MacTCP being present. I usually name it "nMacNTP" to have it load last. Save this file somewhere, and close the MacNTP project.
-Caveat: Upon boot, MacNTP will check the system clock, and won't try updating it if the date is later than September 2nd, 1996. If you edit the UTC offset setting directly with ResEdit, you will need to set your clock back to before that date for it to update the system clock with the new offset.
+6. Create another project, call it "MacNTPINIT.π". Add all .c files except for "cdev.c", as well as MacTraps and MacTraps2 from Think C's Mac Libraries
+7. Again, ensure 68020 generation is disabled like in step 3, then select Project->Set Project Type, set the settings like this:
+- Select Code Resource
+- Set File Type to "INIT"
+- Creator to "VKOS"
+- Type to "INIT"
+- ID to "0" (zero)
+- Attrs to 70. Leave Custom Header deselected, and hit OK.
+8. Select "Build Code Resource" under "Project" to build the INIT resource, but when the save dialog appears, specify the same file as in step 5 with the same filename, and tick the "Merge" option. This way, Think C will build a freestanding INIT resource and inject it into the existing file, preserving the resources that were already copied there from MacNTP.π.rsrc when you built the file in step 5.
+9. Open the final file in ResEdit, hit File->Get Info For..., and untick the "No INITS" property if it is enabled, then save and quit. I haven't figured out a workaround for this in Think C.
+
+You *should* now have a file that contains both a cdev and a INIT resource, as well as various other resources that were copied in from MacNTP.π.rsrc. You can place this file in your System Folder, and give it a go.
+There is a decent chance I made a mistake in these instructions, or they are outdated, please don't hesitate to contact the author for assistance. This project was started in 2023, so development might still be active when you are reading this.
+
+Caveat: Upon boot, the MacNTP INIT will check the system clock, and won't try updating it if the date is later than September 2nd, 1996. If you edit the UTC offset setting (STR) directly with ResEdit, you will need to set your clock back to before that date for it to update the system clock with the new offset.
Don't hesitate to contact me, vkoskiv, on IRC (libera) if you run into any issues. I'm also @vkoskiv on Twitter and Discord. Also vkoskiv@gmail.com
--- ShowInitIcon.c Fri Sep 8 21:32:05 2023
+++ ShowInitIcon.c Tue Sep 12 21:58:09 2023
@@ -164,6 +164,6 @@ static void DrawBWIcon (short iconID, Rect *iconRect)
// Then the icon.
source.baseAddr = *icon;
CopyBits(&source, &destination, &source.bounds, iconRect, srcOr, nil);
- HUnlock(icon);
+ //HUnlock(icon);
}
}