/* * Copyright (c) 2021-2022 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 "chatter.h" #include "irc.h" #include "settings.h" #include "tcp.h" #include "util.h" NMRec notification = { 0 }; struct settings settings; MenuHandle apple_menu, file_menu, edit_menu, view_menu, ignore_menu, window_menu; #ifdef MALLOC_DEBUG MenuHandle debug_menu; #define DEBUG_MENU_DUMP_ID 999 static short xalloc_frefnum = 0; size_t xalloc_printf(const char *format, ...); #endif void update_menu(void); void handle_exit(void); bool handle_menu(long menu_id); void show_connect_dialog(void); void wallops_about(void); short main(void) { Handle mbar; EventRecord event; WindowPtr event_win; GrafPtr old_port; AppFile finder_file; struct focusable *found_focusable; short event_in, n; char key; long wait_ticks; short wait_type; SetApplLimit(GetApplLimit() - (1024 * 8)); InitGraf(&thePort); InitFonts(); FlushEvents(everyEvent, 0); InitWindows(); InitMenus(); TEInit(); InitDialogs(0); InitCursor(); MaxApplZone(); util_init(); _atexit(handle_exit); 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"); if (!(edit_menu = GetMHandle(EDIT_MENU_ID))) panic("No Edit menu"); if (!(view_menu = GetMHandle(VIEW_MENU_ID))) panic("No View menu"); if (!(ignore_menu = GetMenu(IGNORE_MENU_ID))) panic("No Ignore menu"); InsertMenu(ignore_menu, -1); if (!(window_menu = GetMenu(WINDOW_MENU_ID))) panic("No Window menu"); update_menu(); #ifdef MALLOC_DEBUG debug_menu = NewMenu(DEBUG_MENU_DUMP_ID, "\pDebug"); AppendMenu(debug_menu, "\pDump Allocations"); InsertMenu(debug_menu, 0); #endif DrawMenuBar(); show_connect_dialog(); for (;;) { wait_type = WAIT_TYPE_BACKGROUND; for (n = 0; n < nfocusables; n++) { if (focusables[n]->wait_type) wait_type |= focusables[n]->wait_type(focusables[n]); else if (focusables[n]->visible) wait_type |= WAIT_TYPE_FOREGROUND; } if (wait_type & WAIT_TYPE_URGENT) wait_ticks = 0; else if (wait_type & WAIT_TYPE_FOREGROUND) wait_ticks = 5L; else wait_ticks = 60L; WaitNextEvent(everyEvent, &event, wait_ticks, 0L); 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: key = event.message & charCodeMask; if ((event.modifiers & cmdKey) != 0 && handle_menu(MenuKey(key)) == true) break; 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); found_focusable = focusable_find(event_win); if (found_focusable) { cancel_notification(); focusable_show(found_focusable); } switch (event_in) { case inMenuBar: handle_menu(MenuSelect(event.where)); break; case inSysWindow: SystemClick(&event, event_win); break; case inDrag: DragWindow(event_win, event.where, &screenBits.bounds); break; case inGrow: if (found_focusable && found_focusable->resize) found_focusable->resize(found_focusable, &event); break; case inGoAway: if (TrackGoAway(event_win, event.where) && found_focusable) focusable_close(found_focusable); break; case inContent: if (found_focusable && found_focusable->mouse_down) found_focusable->mouse_down(found_focusable, &event); break; } update_menu(); break; case updateEvt: case activateEvt: event_win = (WindowPtr)event.message; found_focusable = focusable_find(event_win); GetPort(&old_port); SetPort(event_win); if (event.what == updateEvt) BeginUpdate(event_win); if (found_focusable && found_focusable->update) found_focusable->update(found_focusable, &event); if (event.what == updateEvt) EndUpdate(event_win); SetPort(old_port); 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 */ cancel_notification(); for (n = 0; n < nfocusables; n++) { if (focusables[n]->resume) focusables[n]->resume(focusables[n], &event); } break; } } break; } } return 0; } void show_connect_dialog(void) { bool valid; valid = settings_edit(!settings_load()); update_menu(); if (!valid) return; chatter_init(settings.server, settings.port, settings.password, settings.nick, settings.ident, settings.realname, settings.hide_motd, settings.channel); } bool handle_menu(long menu_id) { size_t n; bool ret = false; bool quit = true; switch (HiWord(menu_id)) { case APPLE_MENU_ID: switch (LoWord(menu_id)) { case APPLE_MENU_ABOUT_ID: wallops_about(); ret = true; break; default: { Str255 da; GrafPtr save_port; GetItem(apple_menu, LoWord(menu_id), &da); GetPort(&save_port); OpenDeskAcc(da); SetPort(save_port); ret = true; break; } } break; case FILE_MENU_ID: switch (LoWord(menu_id)) { case FILE_MENU_CONNECT_ID: show_connect_dialog(); ret = true; break; case FILE_MENU_QUIT_ID: ret = true; if (focusables_quit()) ExitToShell(); break; } break; case VIEW_MENU_ID: break; case IGNORE_MENU_ID: { short current; GetItemMark(ignore_menu, LoWord(menu_id), ¤t); switch (LoWord(menu_id)) { case IGNORE_MENU_JOINS_ID: if (current) settings.ignores &= ~(IGNORE_JOINS); else settings.ignores |= (IGNORE_JOINS); break; case IGNORE_MENU_QUITS_ID: if (current) settings.ignores &= ~(IGNORE_QUITS); else settings.ignores |= (IGNORE_QUITS); break; case IGNORE_MENU_NICKS_ID: if (current) settings.ignores &= ~(IGNORE_NICKS); else settings.ignores |= (IGNORE_NICKS); break; } settings_save(&settings); update_menu(); ret = true; break; } case WINDOW_MENU_ID: switch (LoWord(menu_id)) { case WINDOW_MENU_HIDE_ID: { Str255 text; struct focusable **tfocusables = NULL; /* hiding and showing adjusts order, so duplicate order */ tfocusables = xmalloc(sizeof(struct focusable *) * nfocusables); memcpy(tfocusables, focusables, sizeof(struct focusable *) * nfocusables); GetItem(window_menu, WINDOW_MENU_HIDE_ID, &text); if (memcmp((char *)text + 1, "Hide", 4) == 0) { for (n = 0; n < nfocusables; n++) { if (tfocusables[n]->visible) focusable_hide(tfocusables[n]); } } else { for (n = 0; n < nfocusables; n++) { if (!tfocusables[n]->visible) focusable_show(tfocusables[n]); } } xfree(&tfocusables); update_menu(); ret = true; break; } default: if (LoWord(menu_id) >= WINDOW_MENU_N_ID) { for (n = 0; n < nfocusables; n++) { if (focusables[n]->id == LoWord(menu_id) - WINDOW_MENU_N_ID) { focusable_show(focusables[n]); break; } } ret = true; } } break; #ifdef MALLOC_DEBUG case DEBUG_MENU_DUMP_ID: switch (LoWord(menu_id)) { case 1: { Str255 vname; short error, vrefnum; error = GetVol(&vname, &vrefnum); if (error) panic("Failed to get volume: %d", error); PtoCstr(vname); strlcat((char *)vname, ":", sizeof(vname)); strlcat((char *)vname, PROGRAM_NAME, sizeof(vname)); strlcat((char *)vname, "_xalloc.txt", sizeof(vname)); CtoPstr(vname); error = Create(vname, vrefnum, 'TEXT', 'TEXT'); if (error && error != dupFNErr) panic("Failed to create file: %d", error); error = FSOpen(vname, vrefnum, &xalloc_frefnum); if (error) panic("Failed to open file: %d", error); error = SetEOF(xalloc_frefnum, 0); if (error) panic("Failed to truncate file: %d", error); xalloc_print(xalloc_printf); FSClose(xalloc_frefnum); break; } } ret = true; break; #endif } if (!ret && nfocusables && focusables[0]->visible && focusables[0]->menu) ret = focusables[0]->menu(focusables[0], HiWord(menu_id), LoWord(menu_id)); HiliteMenu(0); return ret; } #ifdef MALLOC_DEBUG size_t xalloc_printf(const char *format, ...) { static char buf[256]; short error; va_list va; size_t len; va_start(va, format); len = vsnprintf(buf, sizeof(buf), format, va); va_end(va); error = FSWrite(xalloc_frefnum, &len, buf); if (error) panic("Failed to write: %d", error); len = 1; error = FSWrite(xalloc_frefnum, &len, "\r"); if (error) panic("Failed to write: %d", error); return len; } #endif void update_menu(void) { short n; bool hidden = false; for (n = 0; n < nfocusables; n++) { if (!focusables[n]->visible) { hidden = true; break; } } EnableItem(view_menu, WINDOW_MENU_HIDE_ID); if (hidden) DisableItem(view_menu, VIEW_MENU_CLOSE_ID); CheckItem(ignore_menu, IGNORE_MENU_JOINS_ID, !!(settings.ignores & IGNORE_JOINS)); CheckItem(ignore_menu, IGNORE_MENU_QUITS_ID, !!(settings.ignores & IGNORE_QUITS)); CheckItem(ignore_menu, IGNORE_MENU_NICKS_ID, !!(settings.ignores & IGNORE_NICKS)); if (hidden) SetItem(window_menu, WINDOW_MENU_HIDE_ID, "\pShow Windows"); else SetItem(window_menu, WINDOW_MENU_HIDE_ID, "\pHide Windows"); if (nfocusables && focusables[0]->visible && focusables[0]->update_menu) { focusables[0]->update_menu(focusables[0]); return; } DisableItem(edit_menu, EDIT_MENU_CUT_ID); DisableItem(edit_menu, EDIT_MENU_COPY_ID); DisableItem(edit_menu, EDIT_MENU_PASTE_ID); if (hidden) EnableItem(window_menu, WINDOW_MENU_HIDE_ID); else DisableItem(window_menu, WINDOW_MENU_HIDE_ID); } void handle_exit(void) { focusables_quit(); while (!SLIST_EMPTY(&irc_connections_list)) irc_dealloc_connection(SLIST_FIRST(&irc_connections_list)); } void notify(void) { memset(¬ification, 0, sizeof(notification)); notification.qType = nmType; notification.nmMark = 1; notification.nmSound = (Handle)-1; notification.nmIcon = GetResource('SICN', NOTIFICATION_ICON_ID); NMInstall(¬ification); } void cancel_notification(void) { if (notification.qType) NMRemove(¬ification); memset(¬ification, 0, sizeof(notification)); } void wallops_about(void) { char vers_s[255]; char short_vers[255] = { 0 }; DialogPtr dp; StringHandle dast; VersRecHndl vers; Rect bounds; short hit, vlen, n; Handle h, ihandle; vers = (VersRecHndl)GetResource('vers', 1); if (!vers) panic("No vers"); /* * vers "long version string" is a pascal string after the * short version pascal string */ HLock(vers); vlen = (*vers)->shortVersion[0]; memcpy(short_vers, (*vers)->shortVersion + vlen + 1, sizeof((*vers)->shortVersion) - vlen - 1); PtoCstr(short_vers); snprintf(vers_s, sizeof(vers_s), "%s %s", PROGRAM_NAME, short_vers); for (n = 0; n < sizeof(vers_s); n++) { if (vers_s[n] == '©') { vers_s[n - 1] = '\r'; break; } } ReleaseResource(vers); CtoPstr(vers_s); dp = GetNewDialog(ABOUT_DLOG_ID, 0L, (WindowPtr)-1L); ParamText(vers_s, "\p" HOMEPAGE, "\pI miss you, Carl", "\p"); center_in_screen(((DialogPeek)dp)->window.port.portRect.right, ((DialogPeek)dp)->window.port.portRect.bottom, false, &bounds); MoveWindow(dp, bounds.left, bounds.top, false); SetPort(dp); ShowWindow(dp); for (;;) { ModalDialog(ModalDialogFilter, &hit); if (hit == ok) break; } DisposDialog(dp); }