/* * 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 "chatter.h" #include "irc.h" #include "tcp.h" #include "util.h" MenuHandle apple_menu, file_menu; NMRec notification = { 0 }; enum { CONFIG_TYPE_STRING, CONFIG_TYPE_SHORT, CONFIG_TYPE_PASSWORD }; struct config_field { char name[32]; short type; unsigned short min; unsigned short max; short ditl_id; short res_id; char sdefault[32]; char password_storage[256]; } config_fields[] = { { "Server Name", CONFIG_TYPE_STRING, 1, 0, CONNECT_SERVER_ID, STR_SERVER_ID, DEFAULT_SERVER_NAME }, { "Server Port", CONFIG_TYPE_SHORT, 1, 65535, CONNECT_PORT_ID, STR_PORT_ID, DEFAULT_PORT }, { "Server Password", CONFIG_TYPE_PASSWORD, 0, 0, CONNECT_PASSWORD_ID, STR_PASSWORD_ID, "" }, { "Nickname", CONFIG_TYPE_STRING, 1, 0, CONNECT_NICK_ID, STR_NICK_ID, "" }, { "Ident", CONFIG_TYPE_STRING, 1, 0, CONNECT_IDENT_ID, STR_IDENT_ID, DEFAULT_IDENT }, { "Realname", CONFIG_TYPE_STRING, 1, 0, CONNECT_REALNAME_ID, STR_REALNAME_ID, DEFAULT_REALNAME }, { "Channel", CONFIG_TYPE_STRING, 0, 0, CONNECT_CHANNEL_ID, STR_CHANNEL_ID, DEFAULT_CHANNEL }, }; void update_menu(void); void handle_exit(void); bool handle_menu(long menu_id); short show_connect_dialog(void); int 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"); update_menu(); 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); if (event.what != nullEvent) { event_in = FindWindow(event.where, &event_win); found_focusable = NULL; for (n = 0; n < nfocusables; n++) { if (focusables[n]->win == event_win) { found_focusable = focusables[n]; break; } } } 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: 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 (event_win != FrontWindow() && found_focusable) { cancel_notification(); focusable_show(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) focusable_close(found_focusable); break; case inContent: if (event_win != FrontWindow()) { if (found_focusable) { cancel_notification(); focusable_show(found_focusable); } } if (found_focusable && found_focusable->mouse_down) found_focusable->mouse_down(found_focusable, &event); break; } break; case updateEvt: case activateEvt: event_win = (WindowPtr)event.message; if (event.what == updateEvt) { GetPort(&old_port); SetPort(event_win); 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 */ for (n = 0; n < nfocusables; n++) { if (focusables[n]->resume) focusables[n]->resume(focusables[n], &event); } break; } } break; } } return 0; } short show_connect_dialog(void) { Str255 txt, server, ports, nick, ident, realname, channel, password; struct chatter *chatter; StringHandle h; DialogTHndl dlgh; DialogPtr dlg; Handle ihandle; Rect irect; size_t size, n, m; long port; short hit, itype, ret; /* center dialog in screen */ dlgh = (DialogTHndl)xGetResource('DLOG', CONNECT_DLOG_ID); HLock(dlgh); center_in_screen((**dlgh).boundsRect.right - (**dlgh).boundsRect.left, (**dlgh).boundsRect.bottom - (**dlgh).boundsRect.top, true, &(**dlgh).boundsRect); HUnlock(dlgh); if ((dlg = GetNewDialog(CONNECT_DLOG_ID, nil, (WindowPtr)-1)) == NULL) panic("Can't find connection DLOG %d", CONNECT_DLOG_ID); for (n = 0; n < nitems(config_fields); n++) { struct config_field *cf = &config_fields[n]; short sval; GetDItem(dlg, cf->ditl_id, &itype, &ihandle, &irect); if (cf->type == CONFIG_TYPE_PASSWORD) { PasswordDialogFieldFilterSetup(cf->ditl_id, cf->password_storage, sizeof(cf->password_storage)); } if (cf->res_id && (h = GetString(cf->res_id))) { HLock(h); if (cf->type == CONFIG_TYPE_PASSWORD) { size = (*h)[0]; if (size >= sizeof(cf->password_storage) - 1) size = 0; for (m = 0; m < size; m++) cf->password_storage[m] = '¥'; cf->password_storage[m] = '\0'; CtoPstr(cf->password_storage); SetIText(ihandle, cf->password_storage); memcpy(cf->password_storage, *h, size); cf->password_storage[size] = '\0'; PtoCstr(cf->password_storage); } else { SetIText(ihandle, *h); } HUnlock(h); ReleaseResource(h); } else if (cf->sdefault[0] != '\0') { strlcpy((char *)&txt, cf->sdefault, sizeof(txt)); CtoPstr(txt); SetIText(ihandle, txt); } } ShowWindow(dlg); get_input: ModalDialog(PasswordDialogFieldFilter, &hit); switch (hit) { case -1: DisposeDialog(dlg); ReleaseResource(dlgh); return; case OK: goto verify; default: goto get_input; } verify: for (n = 0; n < nitems(config_fields); n++) { struct config_field *cf = &config_fields[n]; long lval; short sval; if (cf->type == CONFIG_TYPE_PASSWORD) { memcpy((char *)&txt, cf->password_storage, sizeof(txt)); } else { GetDItem(dlg, cf->ditl_id, &itype, &ihandle, &irect); GetIText(ihandle, txt); PtoCstr(txt); } switch (cf->type) { case CONFIG_TYPE_STRING: case CONFIG_TYPE_PASSWORD: if (cf->min && strlen((char *)txt) < cf->min) { warn("%s is too short (minimum %d)", cf->name, cf->min); goto get_input; } if (cf->max && strlen((char *)txt) > cf->max) { warn("%s is too long (maximum %d)", cf->name, cf->max); goto get_input; } break; case CONFIG_TYPE_SHORT: lval = atol((char *)txt); if (lval < cf->min) { warn("%s must be at least %d", cf->name, cf->min); goto get_input; } if (lval > cf->max) { warn("%s must be less than %d", cf->name, cf->max); goto get_input; } break; } switch (cf->ditl_id) { case CONNECT_SERVER_ID: strlcpy((char *)&server, (char *)txt, sizeof(server)); break; case CONNECT_PORT_ID: port = atol((char *)txt); break; case CONNECT_PASSWORD_ID: strlcpy((char *)&password, (char *)txt, sizeof(password)); break; case CONNECT_NICK_ID: strlcpy((char *)&nick, (char *)txt, sizeof(nick)); break; case CONNECT_IDENT_ID: strlcpy((char *)&ident, (char *)txt, sizeof(ident)); break; case CONNECT_REALNAME_ID: strlcpy((char *)&realname, (char *)txt, sizeof(realname)); break; case CONNECT_CHANNEL_ID: strlcpy((char *)&channel, (char *)txt, sizeof(channel)); break; } if ((h = GetString(cf->res_id)) != NULL) { RmveResource(h); ReleaseResource(h); } size = strlen((char *)txt) + 1; h = (StringHandle)xNewHandle(size); strlcpy((char *)*h, (char *)txt, size); CtoPstr(*h); strlcpy((char *)txt, cf->name, sizeof(txt)); CtoPstr(txt); AddResource(h, 'STR ', cf->res_id, txt); ReleaseResource(h); } PasswordDialogFieldFinish(); DisposeDialog(dlg); ReleaseResource(dlgh); chatter_init((char *)&server, port, (char *)&password, (char *)&nick, (char *)&ident, (char *)&realname, (char *)&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: about(PROGRAM_NAME); 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; default: if (nfocusables && focusables[0]->visible && focusables[0]->menu) ret = focusables[0]->menu(focusables[0], HiWord(menu_id), LoWord(menu_id)); } HiliteMenu(0); return ret; } void update_menu(void) { } void handle_exit(void) { short n; while (nfocusables > 0) { if (focusables[n]->atexit) focusables[n]->atexit(focusables[n]); else focusable_close(focusables[n]); } 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)); }