Download
vkoskiv
/MacNTP
/cdev.c
(View History)
vkoskiv Display MacNTP errors in cdev | Latest amendment: 19 on 2023-09-20 |
1 | /* |
2 | * Copyright (c) 2023 Valtteri Koskivuori <vkoskiv@gmail.com> |
3 | * |
4 | * Permission to use, copy, modify, and distribute this software for any |
5 | * purpose with or without fee is hereby granted, provided that the above |
6 | * copyright notice and this permission notice appear in all copies. |
7 | * |
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
15 | */ |
16 | |
17 | #include "rsrcid.h" |
18 | #include <OSUtils.h> |
19 | #include <GestaltEqu.h> |
20 | |
21 | /* |
22 | A basic cdev skeleton + implementation in pure C |
23 | For some reason, Symantec decided to embrace OOP for all their |
24 | cdev examples, and my copy didn't ship with the needed libraries |
25 | to build with those language extensions, so I wrote this C impl from |
26 | scratch. |
27 | It works, there are many bugs, but it does the job. |
28 | */ |
29 | |
30 | #define ntp1_box_id 1 |
31 | #define ntp2_box_id 2 |
32 | #define utc_box_id 3 |
33 | #define err_title_id 9 |
34 | #define err_box_id 10 |
35 | #define apply_btn_id 4 |
36 | |
37 | struct control { |
38 | short type; |
39 | Handle handle; |
40 | Rect rect; |
41 | }; |
42 | |
43 | struct state { |
44 | StringHandle ntp1; |
45 | StringHandle ntp2; |
46 | StringHandle utc; |
47 | StringHandle err; |
48 | DialogPtr dialog; |
49 | struct control ntp1_textbox; |
50 | struct control ntp2_textbox; |
51 | struct control utc_textbox; |
52 | struct control err_textbox; |
53 | struct control err_title; |
54 | }; |
55 | |
56 | // Prototypes |
57 | void got_cdev_error(void); |
58 | void got_memory_error(void); |
59 | void got_resource_error(void); |
60 | |
61 | int is_number(char c); |
62 | int validate_ntp_url(Str255 utc_str); |
63 | int validate_utc(Str255 utc_str); |
64 | |
65 | int utcstr_to_mins(Str255 utc, long *out); |
66 | |
67 | void *got_initDev(void *storage, DialogPtr dialog, short nitems); |
68 | void *got_hitDev(void *storage, short item); |
69 | void *got_closeDev(void *storage, short item); |
70 | void *got_nulDev(void *storage); |
71 | void *got_updateDev(void *storage); |
72 | void *got_activDev(void *storage); |
73 | void *got_deActivDev(void *storage); |
74 | void *got_keyEvtDev(void *storage, EventRecord *event, short item, short nitems, DialogPtr d); |
75 | void *got_keyEvent(void *storage, unsigned char c); |
76 | void *got_cmdKeyEvent(void *storage, unsigned char c, short item, short nitems, DialogPtr d); |
77 | void *got_macDev(void *storage); |
78 | void *got_undoDev(void *storage); |
79 | void *got_cutDev(void *storage); |
80 | void *got_copyDev(void *storage); |
81 | void *got_pasteDev(void *storage); |
82 | void *got_clearDev(void *storage); |
83 | |
84 | // Impls |
85 | |
86 | void got_cdev_error(void) { |
87 | SysBeep(30); |
88 | SysBeep(30); |
89 | } |
90 | void got_memory_error(void) { |
91 | SysBeep(30); |
92 | SysBeep(30); |
93 | } |
94 | void got_resource_error(void) { |
95 | SysBeep(30); |
96 | SysBeep(30); |
97 | } |
98 | |
99 | void *got_initDev(void *storage, DialogPtr dialog, short nitems) { |
100 | struct state *s; |
101 | storage = NewHandle(sizeof(struct state)); |
102 | if (!storage) { |
103 | SysBeep(1); |
104 | SysBeep(1); |
105 | SysBeep(1); |
106 | } |
107 | s = *(struct state **)storage; |
108 | s->ntp1 = GetString(NTP1_STR_ID); |
109 | s->ntp2 = GetString(NTP2_STR_ID); |
110 | s->utc = GetString(UTC_STR_ID); |
111 | s->err = GetString(ERR_STR_ID); |
112 | s->dialog = dialog; |
113 | if (!s->ntp1 || !s->ntp2 || !s->utc || !s->err) { |
114 | SysBeep(1); |
115 | SysBeep(1); |
116 | SysBeep(1); |
117 | } |
118 | SetHandleSize(s->ntp1, 256); |
119 | SetHandleSize(s->ntp2, 256); |
120 | SetHandleSize(s->utc, 256); |
121 | SetHandleSize(s->err, 256); |
122 | HLock(s->ntp1); |
123 | HLock(s->ntp2); |
124 | HLock(s->utc); |
125 | HLock(s->err); |
126 | // Grab our textboxes. The number is the one in our DITL |
127 | // but the dialog manager wants the full range num, so add nitems |
128 | GetDItem(s->dialog, |
129 | ntp1_box_id + nitems, |
130 | &s->ntp1_textbox.type, |
131 | &s->ntp1_textbox.handle, |
132 | &s->ntp1_textbox.rect); |
133 | HLock(s->ntp1_textbox.handle); |
134 | SetIText(s->ntp1_textbox.handle, *s->ntp1); |
135 | ShowDItem(s->dialog, ntp1_box_id); |
136 | InvalRect(&s->ntp1_textbox.rect); |
137 | SelIText(s->dialog, ntp1_box_id + nitems, 0, 32767); |
138 | |
139 | GetDItem(s->dialog, |
140 | ntp2_box_id + nitems, |
141 | &s->ntp2_textbox.type, |
142 | &s->ntp2_textbox.handle, |
143 | &s->ntp2_textbox.rect); |
144 | HLock(s->ntp2_textbox.handle); |
145 | SetIText(s->ntp2_textbox.handle, *s->ntp2); |
146 | ShowDItem(s->dialog, ntp2_box_id); |
147 | InvalRect(&s->ntp2_textbox.rect); |
148 | |
149 | GetDItem(s->dialog, |
150 | utc_box_id + nitems, |
151 | &s->utc_textbox.type, |
152 | &s->utc_textbox.handle, |
153 | &s->utc_textbox.rect); |
154 | HLock(s->utc_textbox.handle); |
155 | SetIText(s->utc_textbox.handle, *s->utc); |
156 | ShowDItem(s->dialog, utc_box_id); |
157 | InvalRect(&s->utc_textbox.rect); |
158 | |
159 | GetDItem(s->dialog, |
160 | err_box_id + nitems, |
161 | &s->err_textbox.type, |
162 | &s->err_textbox.handle, |
163 | &s->err_textbox.rect); |
164 | HLock(s->err_textbox.handle); |
165 | |
166 | GetDItem(s->dialog, |
167 | err_title_id + nitems, |
168 | &s->err_title.type, |
169 | &s->err_title.handle, |
170 | &s->err_title.rect); |
171 | HLock(s->err_title.handle); |
172 | |
173 | if (*s->err[0]) { |
174 | SetIText(s->err_textbox.handle, *s->err); |
175 | ShowDItem(s->dialog, err_box_id); |
176 | InvalRect(&s->err_textbox.rect); |
177 | } else { |
178 | HideDItem(s->dialog, err_box_id + nitems); |
179 | HideDItem(s->dialog, err_title_id + nitems); |
180 | } |
181 | |
182 | return storage; |
183 | } |
184 | |
185 | int validate_ntp_url(Str255 utc_str) { |
186 | // TODO |
187 | return true; |
188 | } |
189 | |
190 | int is_number(char c) { |
191 | return c >> 4 == 3 && (c & 0x0F) < 0xA; |
192 | } |
193 | |
194 | int validate_utc(Str255 utc_str) { |
195 | // Str255 is a pointer to a pascal string, where |
196 | // the first byte is the string length. |
197 | char scratch[6]; |
198 | short strlen; |
199 | char *str; |
200 | strlen = utc_str[0]; |
201 | str = (char *)&utc_str[1]; |
202 | if (strlen != 6) return false; |
203 | if (str[0] != '+' && str[0] != '-') return false; |
204 | if (!is_number(str[1]) || !is_number(str[2])) return false; |
205 | if (str[3] != ':') return false; |
206 | if (!is_number(str[4]) || !is_number(str[5])) return false; |
207 | |
208 | return true; |
209 | } |
210 | |
211 | int utcstr_to_mins(Str255 utc, long *out) { |
212 | long minutes; |
213 | if (!validate_utc(utc)) return false; |
214 | minutes = 0; |
215 | minutes += ((utc[2] - 0x30) * 10) * 60; |
216 | minutes += (utc[3] - 0x30) * 60; |
217 | minutes += (utc[5] - 0x30) * 10; |
218 | minutes += (utc[6] - 0x30); |
219 | if (utc[1] == '-') |
220 | minutes = -minutes; |
221 | if (out) *out = minutes; |
222 | return true; |
223 | } |
224 | |
225 | void *got_hitDev(void *storage, short item) { |
226 | Str255 ntp1_text; |
227 | Str255 ntp2_text; |
228 | Str255 utc_text; |
229 | long old_utc_offset; |
230 | long new_utc_offset; |
231 | long diff_secs; |
232 | unsigned long systime; |
233 | int updated; |
234 | struct state *s = *(struct state **)storage; |
235 | |
236 | updated = false; |
237 | if (item == apply_btn_id) { |
238 | GetIText(s->ntp1_textbox.handle, ntp1_text); |
239 | GetIText(s->ntp2_textbox.handle, ntp2_text); |
240 | GetIText(s->utc_textbox.handle, utc_text); |
241 | if (!validate_ntp_url(ntp1_text)) return storage; |
242 | if (!validate_ntp_url(ntp2_text)) return storage; |
243 | if (!validate_utc(utc_text)) { |
244 | SysBeep(1); |
245 | SysBeep(1); |
246 | return storage; |
247 | } |
248 | // All good to go, let's store these settings. |
249 | /* |
250 | Steps: |
251 | - Compare and update NTP timeservers, if need be. |
252 | - Get current UTC offset from rsrc |
253 | - Compute new UTC offset from text input |
254 | - If the value has changed, compute the offset and update |
255 | system clock. |
256 | */ |
257 | |
258 | if (!EqualString(s->ntp1, ntp1_text, true, true)) { |
259 | SetString(s->ntp1, ntp1_text); |
260 | ChangedResource(s->ntp1); |
261 | ReleaseResource(s->ntp1); |
262 | updated = true; |
263 | } |
264 | |
265 | if (!EqualString(s->ntp2, ntp2_text, true, true)) { |
266 | SetString(s->ntp2, ntp2_text); |
267 | ChangedResource(s->ntp2); |
268 | ReleaseResource(s->ntp2); |
269 | updated = true; |
270 | } |
271 | |
272 | // Update the utc offset, and tweak system clock if need be |
273 | if (!EqualString(s->utc, utc_text, true, true)) { |
274 | if (!utcstr_to_mins(utc_text, &new_utc_offset)) return storage; |
275 | if (!utcstr_to_mins(*s->utc, &old_utc_offset)) return storage; |
276 | SetString(s->utc, utc_text); |
277 | ChangedResource(s->utc); |
278 | ReleaseResource(s->utc); |
279 | updated = true; |
280 | diff_secs = (new_utc_offset - old_utc_offset) * 60; |
281 | ReadDateTime((unsigned long *)&systime); |
282 | systime += diff_secs; |
283 | SetDateTime(systime); |
284 | } |
285 | |
286 | if (updated) { |
287 | UpdateResFile(CurResFile()); |
288 | if (ResError()) { |
289 | //DebugStr("\pUpdateResFile failed"); |
290 | } |
291 | } |
292 | } |
293 | return storage; |
294 | } |
295 | void *got_closeDev(void *storage, short item) { |
296 | struct state *s = *(struct state **)storage; |
297 | HideDItem(s->dialog, ntp1_box_id); |
298 | HideDItem(s->dialog, ntp2_box_id); |
299 | HideDItem(s->dialog, utc_box_id); |
300 | DisposHandle(s->ntp1); |
301 | DisposHandle(s->ntp2); |
302 | DisposHandle(s->utc); |
303 | DisposHandle(s->err); |
304 | DisposHandle(storage); |
305 | return storage; |
306 | } |
307 | void *got_nulDev(void *storage) { |
308 | return storage; |
309 | } |
310 | void *got_updateDev(void *storage) { |
311 | struct state *s = *(struct state **)storage; |
312 | return storage; |
313 | } |
314 | void *got_activDev(void *storage) { |
315 | return storage; |
316 | } |
317 | void *got_deActivDev(void *storage) { |
318 | return storage; |
319 | } |
320 | void *got_keyEvtDev(void *storage, EventRecord *event, short item, short nitems, DialogPtr d) { |
321 | if (!(event->modifiers & cmdKey)) |
322 | return got_keyEvent(storage, (unsigned char)event->message); |
323 | else if (event->message != autoKey) { |
324 | event->what = nullEvent; |
325 | return got_cmdKeyEvent(storage, (unsigned char)event->message, item, nitems, d); |
326 | } |
327 | //FIXME: Can we reach this even? |
328 | SysBeep(30); |
329 | return storage; |
330 | } |
331 | |
332 | void *got_keyEvent(void *storage, unsigned char c) { |
333 | return storage; |
334 | } |
335 | |
336 | // I guess we can get actual undoDev, cutDev, etc |
337 | // events from the system, but we want to handle |
338 | // the key events as well. I assume that the |
339 | // system events are from the menu, and key events |
340 | // come from the keyboard. |
341 | // I guess Apple just did this for compatibility with |
342 | // existing cdevs, so I'll just patch these in here. |
343 | void *got_cmdKeyEvent(void *storage, unsigned char c, short item, short nitems, DialogPtr d) { |
344 | struct state *s = *(struct state **)storage; |
345 | switch (c) { |
346 | case 'z': case 'Z': |
347 | return got_undoDev(storage); |
348 | case 'x': case 'X': |
349 | return got_cutDev(storage); |
350 | case 'c': case 'C': |
351 | return got_copyDev(storage); |
352 | case 'v': case 'V': |
353 | return got_pasteDev(storage); |
354 | case 'a': case 'A': |
355 | //GetDItem(d, item - nitems, NULL, NULL, NULL); |
356 | // I tried a few permutations, but this call crashes the system every |
357 | // time and I can't be bothered to figure out why. |
358 | //SelIText(d, item - nitems, 0, 32767); |
359 | return storage; |
360 | } |
361 | SysBeep(30); |
362 | return storage; |
363 | } |
364 | |
365 | void *got_macDev(void *storage) { |
366 | long answer; |
367 | OSErr gestalt_err; |
368 | SysBeep(1); |
369 | gestalt_err = Gestalt(gestaltVersion, &answer); |
370 | if (gestalt_err) { |
371 | // Gestalt not available, it was introduced in 6.0.4 |
372 | // And MacTCP in 6.0.3, so we're just going to assume |
373 | // the user has at least 6.0.8 |
374 | return (void *)0; |
375 | } |
376 | |
377 | if (!Gestalt('mtcp', nil)) { |
378 | // Good, MacTCP is available. |
379 | return (void *)1; |
380 | } |
381 | return (void *)0; |
382 | } |
383 | void *got_undoDev(void *storage) { |
384 | return storage; |
385 | } |
386 | void *got_cutDev(void *storage) { |
387 | struct state *s = *(struct state **)storage; |
388 | DlgCut(s->dialog); |
389 | return storage; |
390 | } |
391 | void *got_copyDev(void *storage) { |
392 | struct state *s = *(struct state **)storage; |
393 | DlgCopy(s->dialog); |
394 | return storage; |
395 | } |
396 | void *got_pasteDev(void *storage) { |
397 | struct state *s = *(struct state **)storage; |
398 | DlgPaste(s->dialog); |
399 | return storage; |
400 | } |
401 | void *got_clearDev(void *storage) { |
402 | return storage; |
403 | } |
404 | |
405 | pascal void *main( |
406 | short msg, |
407 | short item, |
408 | short nitems, |
409 | short dialog_id, |
410 | EventRecord *event, |
411 | void *storage, |
412 | DialogPtr dp) { |
413 | |
414 | // Check for errors. We have to return the error value to ACK, I guess. |
415 | switch ((long)storage) { |
416 | case -1: // cdev error |
417 | got_cdev_error(); |
418 | return storage; |
419 | case 0: // memory error |
420 | got_memory_error(); |
421 | return storage; |
422 | case 1: // resource error |
423 | got_resource_error(); |
424 | return storage; |
425 | } |
426 | |
427 | // respond to message |
428 | switch (msg) { |
429 | case initDev: return got_initDev(storage, dp, nitems); |
430 | case hitDev: return got_hitDev(storage, item - nitems); |
431 | case closeDev: return got_closeDev(storage, item - nitems); |
432 | case nulDev: return got_nulDev(storage); |
433 | case updateDev: return got_updateDev(storage); |
434 | case activDev: return got_activDev(storage); |
435 | case deactivDev: return got_deActivDev(storage); |
436 | case keyEvtDev: return got_keyEvtDev(storage, event, item, nitems, dp); |
437 | case macDev: return got_macDev(storage); |
438 | case undoDev: return got_undoDev(storage); |
439 | case cutDev: return got_cutDev(storage); |
440 | case copyDev: return got_copyDev(storage); |
441 | case pasteDev: return got_pasteDev(storage); |
442 | case clearDev: return got_clearDev(storage); |
443 | } |
444 | return 0; |
445 | } |