Download
jcs
/wallops
/main.c
(View History)
jcs *: Smoother screen/tab updates, fix close/quit/dealloc sequences | Latest amendment: 47 on 2023-01-17 |
1 | /* |
2 | * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org> |
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 <string.h> |
18 | #include "chatter.h" |
19 | #include "irc.h" |
20 | #include "tcp.h" |
21 | #include "util.h" |
22 | |
23 | MenuHandle apple_menu, file_menu; |
24 | NMRec notification = { 0 }; |
25 | |
26 | enum { |
27 | CONFIG_TYPE_STRING, |
28 | CONFIG_TYPE_SHORT, |
29 | CONFIG_TYPE_PASSWORD |
30 | }; |
31 | |
32 | struct config_field { |
33 | char name[32]; |
34 | short type; |
35 | unsigned short min; |
36 | unsigned short max; |
37 | short ditl_id; |
38 | short res_id; |
39 | char sdefault[32]; |
40 | char password_storage[256]; |
41 | } config_fields[] = { |
42 | { "Server Name", CONFIG_TYPE_STRING, 1, 0, |
43 | CONNECT_SERVER_ID, STR_SERVER_ID, DEFAULT_SERVER_NAME }, |
44 | { "Server Port", CONFIG_TYPE_SHORT, 1, 65535, |
45 | CONNECT_PORT_ID, STR_PORT_ID, DEFAULT_PORT }, |
46 | { "Server Password", CONFIG_TYPE_PASSWORD, 0, 0, |
47 | CONNECT_PASSWORD_ID, STR_PASSWORD_ID, "" }, |
48 | { "Nickname", CONFIG_TYPE_STRING, 1, 0, |
49 | CONNECT_NICK_ID, STR_NICK_ID, "" }, |
50 | { "Ident", CONFIG_TYPE_STRING, 1, 0, |
51 | CONNECT_IDENT_ID, STR_IDENT_ID, DEFAULT_IDENT }, |
52 | { "Realname", CONFIG_TYPE_STRING, 1, 0, |
53 | CONNECT_REALNAME_ID, STR_REALNAME_ID, DEFAULT_REALNAME }, |
54 | { "Channel", CONFIG_TYPE_STRING, 0, 0, |
55 | CONNECT_CHANNEL_ID, STR_CHANNEL_ID, DEFAULT_CHANNEL }, |
56 | }; |
57 | |
58 | void update_menu(void); |
59 | void handle_exit(void); |
60 | bool handle_menu(long menu_id); |
61 | short show_connect_dialog(void); |
62 | |
63 | int |
64 | main(void) |
65 | { |
66 | Handle mbar; |
67 | EventRecord event; |
68 | WindowPtr event_win; |
69 | GrafPtr old_port; |
70 | AppFile finder_file; |
71 | struct focusable *found_focusable; |
72 | short event_in, n; |
73 | char key; |
74 | long wait_ticks; |
75 | short wait_type; |
76 | |
77 | SetApplLimit(GetApplLimit() - (1024 * 8)); |
78 | |
79 | InitGraf(&thePort); |
80 | InitFonts(); |
81 | FlushEvents(everyEvent, 0); |
82 | InitWindows(); |
83 | InitMenus(); |
84 | TEInit(); |
85 | InitDialogs(0); |
86 | InitCursor(); |
87 | MaxApplZone(); |
88 | |
89 | util_init(); |
90 | _atexit(handle_exit); |
91 | |
92 | if (!(mbar = GetNewMBar(MBAR_ID))) |
93 | panic("no mbar"); |
94 | SetMenuBar(mbar); |
95 | if (!(apple_menu = GetMHandle(APPLE_MENU_ID))) |
96 | panic("no apple menu"); |
97 | AddResMenu(apple_menu, 'DRVR'); |
98 | if (!(file_menu = GetMHandle(FILE_MENU_ID))) |
99 | panic("no file menu"); |
100 | update_menu(); |
101 | DrawMenuBar(); |
102 | |
103 | show_connect_dialog(); |
104 | |
105 | for (;;) { |
106 | wait_type = WAIT_TYPE_BACKGROUND; |
107 | for (n = 0; n < nfocusables; n++) { |
108 | if (focusables[n]->wait_type) |
109 | wait_type |= focusables[n]->wait_type(focusables[n]); |
110 | else if (focusables[n]->visible) |
111 | wait_type |= WAIT_TYPE_FOREGROUND; |
112 | } |
113 | |
114 | if (wait_type & WAIT_TYPE_URGENT) |
115 | wait_ticks = 0; |
116 | else if (wait_type & WAIT_TYPE_FOREGROUND) |
117 | wait_ticks = 5L; |
118 | else |
119 | wait_ticks = 60L; |
120 | |
121 | WaitNextEvent(everyEvent, &event, wait_ticks, 0L); |
122 | |
123 | if (event.what != nullEvent) { |
124 | event_in = FindWindow(event.where, &event_win); |
125 | found_focusable = NULL; |
126 | for (n = 0; n < nfocusables; n++) { |
127 | if (focusables[n]->win == event_win) { |
128 | found_focusable = focusables[n]; |
129 | break; |
130 | } |
131 | } |
132 | } |
133 | |
134 | switch (event.what) { |
135 | case nullEvent: |
136 | for (n = 0; n < nfocusables; n++) { |
137 | if (focusables[n]->idle) |
138 | focusables[n]->idle(focusables[n], &event); |
139 | } |
140 | break; |
141 | case keyDown: |
142 | case autoKey: |
143 | key = event.message & charCodeMask; |
144 | if ((event.modifiers & cmdKey) != 0 && |
145 | handle_menu(MenuKey(key)) == true) |
146 | break; |
147 | if (nfocusables && focusables[0]->visible && |
148 | focusables[0]->key_down) |
149 | focusables[0]->key_down(focusables[0], &event); |
150 | break; |
151 | case mouseDown: |
152 | switch (event_in) { |
153 | case inMenuBar: |
154 | handle_menu(MenuSelect(event.where)); |
155 | break; |
156 | case inSysWindow: |
157 | SystemClick(&event, event_win); |
158 | break; |
159 | case inDrag: |
160 | DragWindow(event_win, event.where, &screenBits.bounds); |
161 | break; |
162 | case inGrow: |
163 | if (event_win != FrontWindow() && found_focusable) { |
164 | cancel_notification(); |
165 | focusable_show(found_focusable); |
166 | } |
167 | if (found_focusable && found_focusable->resize) |
168 | found_focusable->resize(found_focusable, &event); |
169 | break; |
170 | case inGoAway: |
171 | if (TrackGoAway(event_win, event.where) && found_focusable) |
172 | focusable_close(found_focusable); |
173 | break; |
174 | case inContent: |
175 | if (event_win != FrontWindow()) { |
176 | if (found_focusable) { |
177 | cancel_notification(); |
178 | focusable_show(found_focusable); |
179 | } |
180 | } |
181 | if (found_focusable && found_focusable->mouse_down) |
182 | found_focusable->mouse_down(found_focusable, &event); |
183 | break; |
184 | } |
185 | break; |
186 | case updateEvt: |
187 | case activateEvt: |
188 | event_win = (WindowPtr)event.message; |
189 | |
190 | if (event.what == updateEvt) { |
191 | GetPort(&old_port); |
192 | SetPort(event_win); |
193 | BeginUpdate(event_win); |
194 | } |
195 | |
196 | if (found_focusable && found_focusable->update) |
197 | found_focusable->update(found_focusable, &event); |
198 | |
199 | if (event.what == updateEvt) { |
200 | EndUpdate(event_win); |
201 | SetPort(old_port); |
202 | } |
203 | break; |
204 | case app4Evt: |
205 | if (HiWord(event.message) & (1 << 8)) { |
206 | /* multifinder suspend/resume */ |
207 | switch (event.message & (1 << 0)) { |
208 | case 0: |
209 | /* suspend */ |
210 | for (n = 0; n < nfocusables; n++) { |
211 | if (focusables[n]->suspend) |
212 | focusables[n]->suspend(focusables[n], &event); |
213 | } |
214 | break; |
215 | case 1: |
216 | /* resume */ |
217 | for (n = 0; n < nfocusables; n++) { |
218 | if (focusables[n]->resume) |
219 | focusables[n]->resume(focusables[n], &event); |
220 | } |
221 | break; |
222 | } |
223 | } |
224 | break; |
225 | } |
226 | } |
227 | |
228 | return 0; |
229 | } |
230 | |
231 | short |
232 | show_connect_dialog(void) |
233 | { |
234 | Str255 txt, server, ports, nick, ident, realname, channel, password; |
235 | struct chatter *chatter; |
236 | StringHandle h; |
237 | DialogTHndl dlgh; |
238 | DialogPtr dlg; |
239 | Handle ihandle; |
240 | Rect irect; |
241 | size_t size, n, m; |
242 | long port; |
243 | short hit, itype, ret; |
244 | |
245 | /* center dialog in screen */ |
246 | dlgh = (DialogTHndl)xGetResource('DLOG', CONNECT_DLOG_ID); |
247 | HLock(dlgh); |
248 | center_in_screen((**dlgh).boundsRect.right - (**dlgh).boundsRect.left, |
249 | (**dlgh).boundsRect.bottom - (**dlgh).boundsRect.top, |
250 | true, &(**dlgh).boundsRect); |
251 | HUnlock(dlgh); |
252 | |
253 | if ((dlg = GetNewDialog(CONNECT_DLOG_ID, nil, (WindowPtr)-1)) == NULL) |
254 | panic("Can't find connection DLOG %d", CONNECT_DLOG_ID); |
255 | |
256 | for (n = 0; n < nitems(config_fields); n++) { |
257 | struct config_field *cf = &config_fields[n]; |
258 | short sval; |
259 | |
260 | GetDItem(dlg, cf->ditl_id, &itype, &ihandle, &irect); |
261 | |
262 | if (cf->type == CONFIG_TYPE_PASSWORD) { |
263 | PasswordDialogFieldFilterSetup(cf->ditl_id, |
264 | cf->password_storage, sizeof(cf->password_storage)); |
265 | } |
266 | |
267 | if (cf->res_id && (h = GetString(cf->res_id))) { |
268 | HLock(h); |
269 | if (cf->type == CONFIG_TYPE_PASSWORD) { |
270 | size = (*h)[0]; |
271 | if (size >= sizeof(cf->password_storage) - 1) |
272 | size = 0; |
273 | for (m = 0; m < size; m++) |
274 | cf->password_storage[m] = '•'; |
275 | cf->password_storage[m] = '\0'; |
276 | CtoPstr(cf->password_storage); |
277 | SetIText(ihandle, cf->password_storage); |
278 | memcpy(cf->password_storage, *h, size); |
279 | cf->password_storage[size] = '\0'; |
280 | PtoCstr(cf->password_storage); |
281 | } else { |
282 | SetIText(ihandle, *h); |
283 | } |
284 | HUnlock(h); |
285 | ReleaseResource(h); |
286 | } else if (cf->sdefault[0] != '\0') { |
287 | strlcpy((char *)&txt, cf->sdefault, sizeof(txt)); |
288 | CtoPstr(txt); |
289 | SetIText(ihandle, txt); |
290 | } |
291 | } |
292 | |
293 | ShowWindow(dlg); |
294 | |
295 | get_input: |
296 | ModalDialog(PasswordDialogFieldFilter, &hit); |
297 | switch (hit) { |
298 | case -1: |
299 | DisposeDialog(dlg); |
300 | ReleaseResource(dlgh); |
301 | return; |
302 | case OK: |
303 | goto verify; |
304 | default: |
305 | goto get_input; |
306 | } |
307 | |
308 | verify: |
309 | for (n = 0; n < nitems(config_fields); n++) { |
310 | struct config_field *cf = &config_fields[n]; |
311 | long lval; |
312 | short sval; |
313 | |
314 | if (cf->type == CONFIG_TYPE_PASSWORD) { |
315 | memcpy((char *)&txt, cf->password_storage, sizeof(txt)); |
316 | } else { |
317 | GetDItem(dlg, cf->ditl_id, &itype, &ihandle, &irect); |
318 | GetIText(ihandle, txt); |
319 | PtoCstr(txt); |
320 | } |
321 | |
322 | switch (cf->type) { |
323 | case CONFIG_TYPE_STRING: |
324 | case CONFIG_TYPE_PASSWORD: |
325 | if (cf->min && strlen((char *)txt) < cf->min) { |
326 | warn("%s is too short (minimum %d)", cf->name, cf->min); |
327 | goto get_input; |
328 | } |
329 | if (cf->max && strlen((char *)txt) > cf->max) { |
330 | warn("%s is too long (maximum %d)", cf->name, cf->max); |
331 | goto get_input; |
332 | } |
333 | break; |
334 | case CONFIG_TYPE_SHORT: |
335 | lval = atol((char *)txt); |
336 | if (lval < cf->min) { |
337 | warn("%s must be at least %d", cf->name, cf->min); |
338 | goto get_input; |
339 | } |
340 | if (lval > cf->max) { |
341 | warn("%s must be less than %d", cf->name, cf->max); |
342 | goto get_input; |
343 | } |
344 | break; |
345 | } |
346 | |
347 | switch (cf->ditl_id) { |
348 | case CONNECT_SERVER_ID: |
349 | strlcpy((char *)&server, (char *)txt, sizeof(server)); |
350 | break; |
351 | case CONNECT_PORT_ID: |
352 | port = atol((char *)txt); |
353 | break; |
354 | case CONNECT_PASSWORD_ID: |
355 | strlcpy((char *)&password, (char *)txt, sizeof(password)); |
356 | break; |
357 | case CONNECT_NICK_ID: |
358 | strlcpy((char *)&nick, (char *)txt, sizeof(nick)); |
359 | break; |
360 | case CONNECT_IDENT_ID: |
361 | strlcpy((char *)&ident, (char *)txt, sizeof(ident)); |
362 | break; |
363 | case CONNECT_REALNAME_ID: |
364 | strlcpy((char *)&realname, (char *)txt, sizeof(realname)); |
365 | break; |
366 | case CONNECT_CHANNEL_ID: |
367 | strlcpy((char *)&channel, (char *)txt, sizeof(channel)); |
368 | break; |
369 | } |
370 | |
371 | if ((h = GetString(cf->res_id)) != NULL) { |
372 | RmveResource(h); |
373 | ReleaseResource(h); |
374 | } |
375 | size = strlen((char *)txt) + 1; |
376 | h = (StringHandle)xNewHandle(size); |
377 | strlcpy((char *)*h, (char *)txt, size); |
378 | CtoPstr(*h); |
379 | strlcpy((char *)txt, cf->name, sizeof(txt)); |
380 | CtoPstr(txt); |
381 | AddResource(h, 'STR ', cf->res_id, txt); |
382 | ReleaseResource(h); |
383 | } |
384 | |
385 | PasswordDialogFieldFinish(); |
386 | DisposeDialog(dlg); |
387 | ReleaseResource(dlgh); |
388 | |
389 | chatter_init((char *)&server, port, (char *)&password, |
390 | (char *)&nick, (char *)&ident, (char *)&realname, (char *)&channel); |
391 | } |
392 | |
393 | bool |
394 | handle_menu(long menu_id) |
395 | { |
396 | size_t n; |
397 | bool ret = false; |
398 | bool quit = true; |
399 | |
400 | switch (HiWord(menu_id)) { |
401 | case APPLE_MENU_ID: |
402 | switch (LoWord(menu_id)) { |
403 | case APPLE_MENU_ABOUT_ID: |
404 | about(PROGRAM_NAME); |
405 | ret = true; |
406 | break; |
407 | default: { |
408 | Str255 da; |
409 | GrafPtr save_port; |
410 | |
411 | GetItem(apple_menu, LoWord(menu_id), &da); |
412 | GetPort(&save_port); |
413 | OpenDeskAcc(da); |
414 | SetPort(save_port); |
415 | |
416 | ret = true; |
417 | break; |
418 | } |
419 | } |
420 | break; |
421 | case FILE_MENU_ID: |
422 | switch (LoWord(menu_id)) { |
423 | case FILE_MENU_CONNECT_ID: |
424 | show_connect_dialog(); |
425 | ret = true; |
426 | break; |
427 | case FILE_MENU_QUIT_ID: |
428 | ret = true; |
429 | if (focusables_quit()) |
430 | ExitToShell(); |
431 | break; |
432 | } |
433 | break; |
434 | default: |
435 | if (nfocusables && focusables[0]->visible && focusables[0]->menu) |
436 | ret = focusables[0]->menu(focusables[0], HiWord(menu_id), |
437 | LoWord(menu_id)); |
438 | } |
439 | |
440 | HiliteMenu(0); |
441 | return ret; |
442 | } |
443 | |
444 | void |
445 | update_menu(void) |
446 | { |
447 | } |
448 | |
449 | void |
450 | handle_exit(void) |
451 | { |
452 | short n; |
453 | |
454 | while (nfocusables > 0) { |
455 | if (focusables[n]->atexit) |
456 | focusables[n]->atexit(focusables[n]); |
457 | else |
458 | focusable_close(focusables[n]); |
459 | } |
460 | |
461 | while (!SLIST_EMPTY(&irc_connections_list)) |
462 | irc_dealloc_connection(SLIST_FIRST(&irc_connections_list)); |
463 | } |
464 | |
465 | void |
466 | notify(void) |
467 | { |
468 | memset(¬ification, 0, sizeof(notification)); |
469 | notification.qType = nmType; |
470 | notification.nmMark = 1; |
471 | notification.nmSound = (Handle)-1; |
472 | notification.nmIcon = GetResource('SICN', NOTIFICATION_ICON_ID); |
473 | NMInstall(¬ification); |
474 | } |
475 | |
476 | void |
477 | cancel_notification(void) |
478 | { |
479 | if (notification.qType) |
480 | NMRemove(¬ification); |
481 | memset(¬ification, 0, sizeof(notification)); |
482 | } |