| 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 |
} |