/* * Copyright (c) 2020-2023 joshua stein * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "subtext.h" #include "binkp.h" #include "console.h" #include "db.h" #include "focusable.h" #include "logger.h" #include "mail.h" #include "main_menu.h" #include "serial_local.h" #include "session.h" #include "settings.h" #include "telnet.h" #include "user.h" #include "uthread.h" #include "util.h" MenuHandle apple_menu, file_menu; short quitting = 0; struct db *db = NULL; bool blanker_on = false; unsigned long blanker_last_blank = 0; WindowPtr blanker_win = NULL; struct uthread *periodic_job_thread = NULL; #ifdef MALLOC_DEBUG MenuHandle debug_menu; #define DEBUG_MENU_DUMP_ID 999 #endif bool handle_menu(long menu_id); void handle_exit(void); void blanker_idle(void); void periodic_jobs(struct uthread *uthread, void *arg); int main(void) { Handle mbar; EventRecord event; WindowPtr event_win; GrafPtr old_port; AppFile finder_file; struct focusable *found_focusable; unsigned long zone_size, stack_size, heap_size; short event_in, n, finder_action, finder_count; char key, none = 0; bool ignore = false; uthread_init(); InitGraf(&thePort); InitFonts(); FlushEvents(everyEvent, 0); InitWindows(); InitMenus(); TEInit(); InitDialogs(0); InitCursor(); MaxApplZone(); util_init(); if (!(mbar = GetNewMBar(MBAR_ID))) panic("no mbar"); SetMenuBar(mbar); if (!(apple_menu = GetMHandle(APPLE_MENU_ID))) panic("no apple menu"); AddResMenu(apple_menu, 'DRVR'); if (!(file_menu = GetMHandle(FILE_MENU_ID))) panic("no file menu"); #ifdef MALLOC_DEBUG debug_menu = NewMenu(DEBUG_MENU_DUMP_ID, "\pDebug"); AppendMenu(debug_menu, "\pDump Allocations"); InsertMenu(debug_menu, 0); #endif DrawMenuBar(); /* see if we were started by double-clicking a .bbs file */ CountAppFiles(&finder_action, &finder_count); if (finder_count) { GetAppFiles(1, &finder_file); ClrAppFiles(1); finder_count = 0; db = db_open(finder_file.fName, finder_file.vRefNum, false); } else { GetNextEvent(everyEvent, &event); if (event.modifiers & cmdKey) /* don't open last-opened database */ ignore = true; db = db_open(*((Str255 *)&none), 0, ignore); } if (!db) panic("Failed to open database"); _atexit(handle_exit); logger_init(); logger_update_title(); if (db->config.syslog_ip) syslog_init(); zone_size = (unsigned long)CurStackBase - (unsigned long)ApplZone; stack_size = (unsigned long)CurStackBase - (unsigned long)ApplLimit; heap_size = (unsigned long)ApplLimit - (unsigned long)ApplZone; logger_printf("Initialized with %ldKB zone, %ldKB stack for " "%d threads, %ldKB heap", zone_size / 1024L, stack_size / 1024L, NUM_UTHREADS, heap_size / 1024L); logger_printf("[db] Updating username cache"); user_cache_usernames(); if (db->config.ipdb_path[0]) db->ipdb = ipdb_open(db->config.ipdb_path); db_cache_views(db); db_cache_boards(db); db_cache_folders(db); binkp_init(); telnet_init(); serial_init(); blanker_last_blank = Time; periodic_job_thread = uthread_add(periodic_jobs, NULL); while (!quitting) { telnet_idle(); serial_idle(); uthread_coordinate(); SystemTask(); if (!GetNextEvent(everyEvent, &event)) { blanker_idle(); continue; } switch (event.what) { case nullEvent: for (n = 0; n < nfocusables; n++) { if (focusables[n]->idle) focusables[n]->idle(focusables[n], &event); } break; case keyDown: case autoKey: if (blanker_on) { blanker_unblank(); break; } key = event.message & charCodeMask; if ((event.modifiers & cmdKey) != 0 && handle_menu(MenuKey(key)) == true) break; else if (nfocusables && focusables[0]->visible && focusables[0]->key_down) focusables[0]->key_down(focusables[0], &event); break; case mouseDown: event_in = FindWindow(event.where, &event_win); switch (event_in) { case inMenuBar: handle_menu(MenuSelect(event.where)); break; case inSysWindow: SystemClick(&event, event_win); break; case inDrag: SelectWindow(event_win); DragWindow(event_win, event.where, &screenBits.bounds); break; case inGrow: found_focusable = find_focusable(event_win); if (event_win != FrontWindow()) { if (found_focusable) show_focusable(found_focusable); } if (found_focusable && found_focusable->resize) found_focusable->resize(found_focusable, &event); break; case inGoAway: if (TrackGoAway(event_win, event.where)) { found_focusable = find_focusable(event_win); if (found_focusable && found_focusable->close) found_focusable->close(found_focusable, &event); } break; case inContent: if (blanker_on) { blanker_unblank(); break; } found_focusable = find_focusable(event_win); if (event_win != FrontWindow()) { if (found_focusable) show_focusable(found_focusable); } if (found_focusable && found_focusable->mouse_down) found_focusable->mouse_down(found_focusable, &event); break; } break; case updateEvt: event_win = (WindowPtr)event.message; GetPort(&old_port); SetPort(event_win); BeginUpdate(event_win); found_focusable = find_focusable(event_win); if (found_focusable && found_focusable->update) found_focusable->update(found_focusable, &event); EndUpdate(event_win); SetPort(old_port); break; case activateEvt: break; case app4Evt: if (HiWord(event.message) & (1 << 8)) { /* multifinder suspend/resume */ switch (event.message & (1 << 0)) { case 0: /* suspend */ for (n = 0; n < nfocusables; n++) { if (focusables[n]->suspend) focusables[n]->suspend(focusables[n], &event); } break; case 1: /* resume */ for (n = 0; n < nfocusables; n++) { if (focusables[n]->resume) focusables[n]->resume(focusables[n], &event); } break; } } break; } } return 0; } void handle_exit(void) { short n; blanker_unblank(); for (n = 0; n < nfocusables; n++) { if (focusables[n]->atexit) focusables[n]->atexit(focusables[n]); } binkp_atexit(); if (db->config.telnet_port) telnet_atexit(); if (db->config.modem_port) serial_atexit(); if (db->ipdb) ipdb_close(&db->ipdb); if (db->config.syslog_ip) syslog_deinit(); db_close(db); } bool handle_menu(long menu_id) { bool ret = false, quit; size_t n; switch (HiWord(menu_id)) { case APPLE_MENU_ID: switch (LoWord(menu_id)) { case APPLE_MENU_ABOUT_ID: about(PROGRAM_NAME); break; default: { Str255 da; GrafPtr save_port; GetItem(apple_menu, LoWord(menu_id), &da); GetPort(&save_port); OpenDeskAcc(da); SetPort(save_port); break; } } break; case FILE_MENU_ID: switch (LoWord(menu_id)) { case FILE_MENU_QUIT_ID: { int tnfocusables = nfocusables; struct focusable **tfocusables; ret = true; quit = true; if (nfocusables) { /* * nfocusables and focusables array will probably be * modified as each focusable quits */ tfocusables = xcalloc(sizeof(Ptr), tnfocusables); if (tfocusables == NULL) ExitToShell(); memcpy(tfocusables, focusables, sizeof(Ptr) * tnfocusables); for (n = 0; n < tnfocusables; n++) { if (tfocusables[n] && tfocusables[n]->quit && !tfocusables[n]->quit(tfocusables[n])) { quit = false; break; } } xfree(&tfocusables); } if (quit) ExitToShell(); break; } } break; case EDIT_MENU_ID: if (nfocusables && focusables[0]->visible && focusables[0]->menu) ret = focusables[0]->menu(focusables[0], HiWord(menu_id), LoWord(menu_id)); break; case BBS_MENU_ID: switch (LoWord(menu_id)) { case BBS_MENU_OPEN_CONSOLE_ID: console_init(); break; case BBS_MENU_RELOAD_VIEWS_ID: db_cache_views(db); break; } ret = true; break; #ifdef MALLOC_DEBUG case DEBUG_MENU_DUMP_ID: switch (LoWord(menu_id)) { case 1: xalloc_print(logger_printf); break; } ret = true; break; #endif } HiliteMenu(0); return ret; } void blanker_idle(void) { if (db->config.blanker_idle_seconds == 0) return; if (!blanker_on && !nsessions && (Time - blanker_last_blank > db->config.blanker_idle_seconds)) { HideMenuBar(); blanker_win = NewWindow(0L, &screenBits.bounds, "\p", false, plainDBox, (WindowPtr)-1L, true, 0); if (!blanker_win) panic("NewWindow failed"); ShowWindow(blanker_win); SetPort(blanker_win); HideCursor(); FillRect(&screenBits.bounds, black); blanker_last_blank = Time; blanker_on = true; return; } if (blanker_on && (nsessions || (Time - blanker_last_blank >= db->config.blanker_runtime_seconds))) blanker_unblank(); } void blanker_unblank(void) { unsigned long rt; if (!blanker_on || !blanker_win) return; rt = Time - blanker_last_blank; ShowCursor(); DisposeWindow(blanker_win); blanker_win = NULL; RestoreHiddenMenuBar(); blanker_last_blank = Time; blanker_on = false; /* * This will call back to blanker_unblank, so it must be done after * blanker_on = false */ logger_printf("[blanker] Blanked screen for %ld second%s", rt, rt == 1 ? "" : "s"); } void periodic_jobs(struct uthread *uthread, void *arg) { struct tm *date_tm; size_t n; short last_daily_job = -1; for (;;) { date_tm = localtime((time_t *)&Time); if (last_daily_job != date_tm->tm_mday) { if (nsessions != 0) goto sleep; if (date_tm->tm_year + 1900 < 2024) { logger_printf("[db] Bogus system clock (year %d), not " "pruning", date_tm->tm_year + 1900); last_daily_job = date_tm->tm_mday; goto sleep; } if (db->config.session_log_prune_days) { session_prune_logs(db->config.session_log_prune_days); uthread_yield(); } if (db->config.mail_prune_days) { mail_prune(db->config.mail_prune_days); uthread_yield(); } for (n = 0; n < db->nboards; n++) { board_prune_old_posts(&db->boards[n]); uthread_yield(); } last_daily_job = date_tm->tm_mday; } if (binkp_ready && (nsessions == 0 || binkp_next_poll == 0)) { /* * Only do polling when no one is on, or we were just woken up * from the sysop menu */ if (Time > binkp_next_poll || (!binkp_last_poll_error && binkp_packets_in_outbox())) { binkp_poll(); } } sleep: uthread_msleep((unsigned long)1000 * 10); } periodic_job_thread = NULL; }