Download
jcs
/subtext
/main.c
(View History)
jcs main: When MALLOC_DEBUG is defined, add a Debug menu to dump allocs | Latest amendment: 588 on 2024-02-15 |
1 | /* |
2 | * Copyright (c) 2020-2023 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 <stdio.h> |
18 | #include <string.h> |
19 | |
20 | #include "subtext.h" |
21 | #include "binkp.h" |
22 | #include "console.h" |
23 | #include "db.h" |
24 | #include "focusable.h" |
25 | #include "logger.h" |
26 | #include "mail.h" |
27 | #include "main_menu.h" |
28 | #include "serial_local.h" |
29 | #include "session.h" |
30 | #include "settings.h" |
31 | #include "telnet.h" |
32 | #include "user.h" |
33 | #include "uthread.h" |
34 | #include "util.h" |
35 | |
36 | MenuHandle apple_menu, file_menu; |
37 | short quitting = 0; |
38 | struct db *db = NULL; |
39 | bool blanker_on = false; |
40 | unsigned long blanker_last_blank = 0; |
41 | WindowPtr blanker_win = NULL; |
42 | struct uthread *periodic_job_thread = NULL; |
43 | |
44 | #ifdef MALLOC_DEBUG |
45 | MenuHandle debug_menu; |
46 | #define DEBUG_MENU_DUMP_ID 999 |
47 | #endif |
48 | |
49 | bool handle_menu(long menu_id); |
50 | void handle_exit(void); |
51 | void blanker_idle(void); |
52 | void periodic_jobs(struct uthread *uthread, void *arg); |
53 | |
54 | int |
55 | main(void) |
56 | { |
57 | Handle mbar; |
58 | EventRecord event; |
59 | WindowPtr event_win; |
60 | GrafPtr old_port; |
61 | AppFile finder_file; |
62 | struct focusable *found_focusable; |
63 | unsigned long zone_size, stack_size, heap_size; |
64 | short event_in, n, finder_action, finder_count; |
65 | char key, none = 0; |
66 | bool ignore = false; |
67 | |
68 | uthread_init(); |
69 | |
70 | InitGraf(&thePort); |
71 | InitFonts(); |
72 | FlushEvents(everyEvent, 0); |
73 | InitWindows(); |
74 | InitMenus(); |
75 | TEInit(); |
76 | InitDialogs(0); |
77 | InitCursor(); |
78 | MaxApplZone(); |
79 | |
80 | util_init(); |
81 | |
82 | if (!(mbar = GetNewMBar(MBAR_ID))) |
83 | panic("no mbar"); |
84 | SetMenuBar(mbar); |
85 | if (!(apple_menu = GetMHandle(APPLE_MENU_ID))) |
86 | panic("no apple menu"); |
87 | AddResMenu(apple_menu, 'DRVR'); |
88 | if (!(file_menu = GetMHandle(FILE_MENU_ID))) |
89 | panic("no file menu"); |
90 | #ifdef MALLOC_DEBUG |
91 | debug_menu = NewMenu(DEBUG_MENU_DUMP_ID, "\pDebug"); |
92 | AppendMenu(debug_menu, "\pDump Allocations"); |
93 | InsertMenu(debug_menu, 0); |
94 | #endif |
95 | DrawMenuBar(); |
96 | |
97 | /* see if we were started by double-clicking a .bbs file */ |
98 | CountAppFiles(&finder_action, &finder_count); |
99 | if (finder_count) { |
100 | GetAppFiles(1, &finder_file); |
101 | ClrAppFiles(1); |
102 | finder_count = 0; |
103 | db = db_open(finder_file.fName, finder_file.vRefNum, false); |
104 | } else { |
105 | GetNextEvent(everyEvent, &event); |
106 | if (event.modifiers & cmdKey) |
107 | /* don't open last-opened database */ |
108 | ignore = true; |
109 | |
110 | db = db_open(*((Str255 *)&none), 0, ignore); |
111 | } |
112 | |
113 | if (!db) |
114 | panic("Failed to open database"); |
115 | |
116 | _atexit(handle_exit); |
117 | |
118 | logger_init(); |
119 | logger_update_title(); |
120 | if (db->config.syslog_ip) |
121 | syslog_init(); |
122 | |
123 | zone_size = (unsigned long)CurStackBase - (unsigned long)ApplZone; |
124 | stack_size = (unsigned long)CurStackBase - (unsigned long)ApplLimit; |
125 | heap_size = (unsigned long)ApplLimit - (unsigned long)ApplZone; |
126 | logger_printf("Initialized with %ldKB zone, %ldKB stack for " |
127 | "%d threads, %ldKB heap", zone_size / 1024L, stack_size / 1024L, |
128 | NUM_UTHREADS, heap_size / 1024L); |
129 | |
130 | logger_printf("[db] Updating username cache"); |
131 | user_cache_usernames(); |
132 | |
133 | if (db->config.ipdb_path[0]) |
134 | db->ipdb = ipdb_open(db->config.ipdb_path); |
135 | |
136 | db_cache_views(db); |
137 | db_cache_boards(db); |
138 | db_cache_folders(db); |
139 | |
140 | binkp_init(); |
141 | telnet_init(); |
142 | serial_init(); |
143 | |
144 | blanker_last_blank = Time; |
145 | |
146 | periodic_job_thread = uthread_add(periodic_jobs, NULL); |
147 | |
148 | while (!quitting) { |
149 | telnet_idle(); |
150 | serial_idle(); |
151 | uthread_coordinate(); |
152 | |
153 | SystemTask(); |
154 | if (!GetNextEvent(everyEvent, &event)) { |
155 | blanker_idle(); |
156 | continue; |
157 | } |
158 | |
159 | switch (event.what) { |
160 | case nullEvent: |
161 | for (n = 0; n < nfocusables; n++) { |
162 | if (focusables[n]->idle) |
163 | focusables[n]->idle(focusables[n], &event); |
164 | } |
165 | break; |
166 | case keyDown: |
167 | case autoKey: |
168 | if (blanker_on) { |
169 | blanker_unblank(); |
170 | break; |
171 | } |
172 | key = event.message & charCodeMask; |
173 | if ((event.modifiers & cmdKey) != 0 && |
174 | handle_menu(MenuKey(key)) == true) |
175 | break; |
176 | else if (nfocusables && focusables[0]->visible && |
177 | focusables[0]->key_down) |
178 | focusables[0]->key_down(focusables[0], &event); |
179 | break; |
180 | case mouseDown: |
181 | event_in = FindWindow(event.where, &event_win); |
182 | |
183 | switch (event_in) { |
184 | case inMenuBar: |
185 | handle_menu(MenuSelect(event.where)); |
186 | break; |
187 | case inSysWindow: |
188 | SystemClick(&event, event_win); |
189 | break; |
190 | case inDrag: |
191 | SelectWindow(event_win); |
192 | DragWindow(event_win, event.where, &screenBits.bounds); |
193 | break; |
194 | case inGrow: |
195 | found_focusable = find_focusable(event_win); |
196 | if (event_win != FrontWindow()) { |
197 | if (found_focusable) |
198 | show_focusable(found_focusable); |
199 | } |
200 | if (found_focusable && found_focusable->resize) |
201 | found_focusable->resize(found_focusable, &event); |
202 | break; |
203 | case inGoAway: |
204 | if (TrackGoAway(event_win, event.where)) { |
205 | found_focusable = find_focusable(event_win); |
206 | if (found_focusable && found_focusable->close) |
207 | found_focusable->close(found_focusable, &event); |
208 | } |
209 | break; |
210 | case inContent: |
211 | if (blanker_on) { |
212 | blanker_unblank(); |
213 | break; |
214 | } |
215 | found_focusable = find_focusable(event_win); |
216 | if (event_win != FrontWindow()) { |
217 | if (found_focusable) |
218 | show_focusable(found_focusable); |
219 | } |
220 | if (found_focusable && found_focusable->mouse_down) |
221 | found_focusable->mouse_down(found_focusable, &event); |
222 | break; |
223 | } |
224 | break; |
225 | case updateEvt: |
226 | event_win = (WindowPtr)event.message; |
227 | |
228 | GetPort(&old_port); |
229 | SetPort(event_win); |
230 | BeginUpdate(event_win); |
231 | |
232 | found_focusable = find_focusable(event_win); |
233 | if (found_focusable && found_focusable->update) |
234 | found_focusable->update(found_focusable, &event); |
235 | |
236 | EndUpdate(event_win); |
237 | SetPort(old_port); |
238 | break; |
239 | case activateEvt: |
240 | break; |
241 | case app4Evt: |
242 | if (HiWord(event.message) & (1 << 8)) { |
243 | /* multifinder suspend/resume */ |
244 | switch (event.message & (1 << 0)) { |
245 | case 0: |
246 | /* suspend */ |
247 | for (n = 0; n < nfocusables; n++) { |
248 | if (focusables[n]->suspend) |
249 | focusables[n]->suspend(focusables[n], &event); |
250 | } |
251 | break; |
252 | case 1: |
253 | /* resume */ |
254 | for (n = 0; n < nfocusables; n++) { |
255 | if (focusables[n]->resume) |
256 | focusables[n]->resume(focusables[n], &event); |
257 | } |
258 | break; |
259 | } |
260 | } |
261 | break; |
262 | } |
263 | } |
264 | |
265 | return 0; |
266 | } |
267 | |
268 | void |
269 | handle_exit(void) |
270 | { |
271 | short n; |
272 | |
273 | blanker_unblank(); |
274 | |
275 | for (n = 0; n < nfocusables; n++) { |
276 | if (focusables[n]->atexit) |
277 | focusables[n]->atexit(focusables[n]); |
278 | } |
279 | |
280 | binkp_atexit(); |
281 | |
282 | if (db->config.telnet_port) |
283 | telnet_atexit(); |
284 | if (db->config.modem_port) |
285 | serial_atexit(); |
286 | if (db->ipdb) |
287 | ipdb_close(&db->ipdb); |
288 | if (db->config.syslog_ip) |
289 | syslog_deinit(); |
290 | |
291 | db_close(db); |
292 | } |
293 | |
294 | bool |
295 | handle_menu(long menu_id) |
296 | { |
297 | bool ret = false, quit; |
298 | size_t n; |
299 | |
300 | switch (HiWord(menu_id)) { |
301 | case APPLE_MENU_ID: |
302 | switch (LoWord(menu_id)) { |
303 | case APPLE_MENU_ABOUT_ID: |
304 | about(PROGRAM_NAME); |
305 | break; |
306 | default: { |
307 | Str255 da; |
308 | GrafPtr save_port; |
309 | |
310 | GetItem(apple_menu, LoWord(menu_id), &da); |
311 | GetPort(&save_port); |
312 | OpenDeskAcc(da); |
313 | SetPort(save_port); |
314 | break; |
315 | } |
316 | } |
317 | break; |
318 | case FILE_MENU_ID: |
319 | switch (LoWord(menu_id)) { |
320 | case FILE_MENU_QUIT_ID: { |
321 | int tnfocusables = nfocusables; |
322 | struct focusable **tfocusables; |
323 | |
324 | ret = true; |
325 | quit = true; |
326 | |
327 | if (nfocusables) { |
328 | /* |
329 | * nfocusables and focusables array will probably be |
330 | * modified as each focusable quits |
331 | */ |
332 | tfocusables = xcalloc(sizeof(Ptr), tnfocusables); |
333 | if (tfocusables == NULL) |
334 | ExitToShell(); |
335 | |
336 | memcpy(tfocusables, focusables, |
337 | sizeof(Ptr) * tnfocusables); |
338 | |
339 | for (n = 0; n < tnfocusables; n++) { |
340 | if (tfocusables[n] && tfocusables[n]->quit && |
341 | !tfocusables[n]->quit(tfocusables[n])) { |
342 | quit = false; |
343 | break; |
344 | } |
345 | } |
346 | |
347 | xfree(&tfocusables); |
348 | } |
349 | if (quit) |
350 | ExitToShell(); |
351 | break; |
352 | } |
353 | } |
354 | break; |
355 | case EDIT_MENU_ID: |
356 | if (nfocusables && focusables[0]->visible && focusables[0]->menu) |
357 | ret = focusables[0]->menu(focusables[0], HiWord(menu_id), |
358 | LoWord(menu_id)); |
359 | break; |
360 | case BBS_MENU_ID: |
361 | switch (LoWord(menu_id)) { |
362 | case BBS_MENU_OPEN_CONSOLE_ID: |
363 | console_init(); |
364 | break; |
365 | case BBS_MENU_RELOAD_VIEWS_ID: |
366 | db_cache_views(db); |
367 | break; |
368 | } |
369 | ret = true; |
370 | break; |
371 | #ifdef MALLOC_DEBUG |
372 | case DEBUG_MENU_DUMP_ID: |
373 | switch (LoWord(menu_id)) { |
374 | case 1: |
375 | xalloc_print(logger_printf); |
376 | break; |
377 | } |
378 | ret = true; |
379 | break; |
380 | #endif |
381 | } |
382 | |
383 | HiliteMenu(0); |
384 | return ret; |
385 | } |
386 | |
387 | void |
388 | blanker_idle(void) |
389 | { |
390 | if (db->config.blanker_idle_seconds == 0) |
391 | return; |
392 | |
393 | if (!blanker_on && !nsessions && |
394 | (Time - blanker_last_blank > db->config.blanker_idle_seconds)) { |
395 | HideMenuBar(); |
396 | |
397 | blanker_win = NewWindow(0L, &screenBits.bounds, "\p", false, |
398 | plainDBox, (WindowPtr)-1L, true, 0); |
399 | if (!blanker_win) |
400 | panic("NewWindow failed"); |
401 | ShowWindow(blanker_win); |
402 | SetPort(blanker_win); |
403 | HideCursor(); |
404 | FillRect(&screenBits.bounds, black); |
405 | blanker_last_blank = Time; |
406 | blanker_on = true; |
407 | return; |
408 | } |
409 | |
410 | if (blanker_on && (nsessions || |
411 | (Time - blanker_last_blank >= db->config.blanker_runtime_seconds))) |
412 | blanker_unblank(); |
413 | } |
414 | |
415 | void |
416 | blanker_unblank(void) |
417 | { |
418 | unsigned long rt; |
419 | |
420 | if (!blanker_on || !blanker_win) |
421 | return; |
422 | |
423 | rt = Time - blanker_last_blank; |
424 | |
425 | ShowCursor(); |
426 | DisposeWindow(blanker_win); |
427 | blanker_win = NULL; |
428 | RestoreHiddenMenuBar(); |
429 | blanker_last_blank = Time; |
430 | blanker_on = false; |
431 | |
432 | /* |
433 | * This will call back to blanker_unblank, so it must be done after |
434 | * blanker_on = false |
435 | */ |
436 | logger_printf("[blanker] Blanked screen for %ld second%s", |
437 | rt, rt == 1 ? "" : "s"); |
438 | } |
439 | |
440 | void |
441 | periodic_jobs(struct uthread *uthread, void *arg) |
442 | { |
443 | struct tm *date_tm; |
444 | size_t n; |
445 | short last_daily_job = -1; |
446 | |
447 | for (;;) { |
448 | date_tm = localtime((time_t *)&Time); |
449 | |
450 | if (last_daily_job != date_tm->tm_mday) { |
451 | if (nsessions != 0) |
452 | goto sleep; |
453 | |
454 | if (date_tm->tm_year + 1900 < 2024) { |
455 | logger_printf("[db] Bogus system clock (year %d), not " |
456 | "pruning", date_tm->tm_year + 1900); |
457 | last_daily_job = date_tm->tm_mday; |
458 | goto sleep; |
459 | } |
460 | |
461 | if (db->config.session_log_prune_days) { |
462 | session_prune_logs(db->config.session_log_prune_days); |
463 | uthread_yield(); |
464 | } |
465 | |
466 | if (db->config.mail_prune_days) { |
467 | mail_prune(db->config.mail_prune_days); |
468 | uthread_yield(); |
469 | } |
470 | |
471 | for (n = 0; n < db->nboards; n++) { |
472 | board_prune_old_posts(&db->boards[n]); |
473 | uthread_yield(); |
474 | } |
475 | |
476 | last_daily_job = date_tm->tm_mday; |
477 | } |
478 | |
479 | if (binkp_ready && (nsessions == 0 || binkp_next_poll == 0)) { |
480 | /* |
481 | * Only do polling when no one is on, or we were just woken up |
482 | * from the sysop menu |
483 | */ |
484 | if (Time > binkp_next_poll || |
485 | (!binkp_last_poll_error && binkp_packets_in_outbox())) { |
486 | binkp_poll(); |
487 | } |
488 | } |
489 | |
490 | sleep: |
491 | uthread_msleep((unsigned long)1000 * 10); |
492 | } |
493 | |
494 | periodic_job_thread = NULL; |
495 | } |