AmendHub

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(&notification, 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(&notification);
474 }
475
476 void
477 cancel_notification(void)
478 {
479 if (notification.qType)
480 NMRemove(&notification);
481 memset(&notification, 0, sizeof(notification));
482 }