/* * Copyright (c) 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 "wi-fi.h" short nwifi_scan_networks = 0; struct wifi_network_entry wifi_menu_networks[MAX_WIFI_NETWORKS]; short nwifi_menu_networks = 0; void create_logger(void); pascal Boolean WiFiPasswordDialogFieldFilter(DialogPtr dlg, EventRecord *event, short *hit); void create_window(short ctlrefnum) { Str255 str; GrafPtr savePort, tport; memset(&wifi_menu_networks, 0, sizeof(wifi_menu_networks)); wifi_da.str_res_id = find_str_res_id(); GetPort(&savePort); /* screenBits is not available from a DA */ tport = (GrafPtr)NewPtr(sizeof(GrafPort)); OpenPort(tport); /* upper right corner, but leave space for desktop icon */ wifi_da.bounds.top = 45; wifi_da.bounds.bottom = wifi_da.bounds.top + DA_HEIGHT; wifi_da.bounds.left = tport->portRect.right - DA_WIDTH - 70; wifi_da.bounds.right = wifi_da.bounds.left + DA_WIDTH; ClosePort(tport); wifi_da.win = NewWindow(0L, &wifi_da.bounds, "\pWi-Fi", true, rDocProc, 0L, true, 0L); ((WindowPeek)(wifi_da.win))->windowKind = ctlrefnum; wifi_da.win_shadow.rowBytes = (((DA_WIDTH - 1) / 16) + 1) * 2; wifi_da.win_shadow.baseAddr = xmalloczero(wifi_da.win_shadow.rowBytes * DA_HEIGHT); SetRect(&wifi_da.win_shadow.bounds, 0, 0, DA_WIDTH, DA_HEIGHT); wifi_da.signal_icons = (SICNHandle)GetResource('SICN', OwnedResourceID(0)); if (wifi_da.signal_icons == NULL) panic("Can't find SICN"); SetRect(&wifi_da.icon_rect, 6, 4, 22, 20); /* ltrb */ wifi_da.ssid_menu = NewMenu(OwnedResourceID(10), "\p"); GetIndString(str, wifi_da.str_res_id, STR_FINDING_WIFI_ID); InsMenuItem(wifi_da.ssid_menu, str, 1); SetRect(&wifi_da.ssid_menu_rect, 30, 3, 170, 21); /* ltrb */ #if 0 /* system 6 does not seem to support popupMenuProc */ ssid_list = NewControl(wifi_da.win, &wifi_da.ssid_menu_rect, 0, true, 0, OwnedResourceID(SSID_MENU_ID), 0, popupMenuProc + popupFixedWidth, 0L); #endif update_window(true); if (wifi_scsi_id == WIFI_SCSI_ID_FINDING) wifi_scsi_id = scsi_find_wifi(); if (wifi_scsi_id == WIFI_SCSI_ID_NONE) { wifi_da.state = STATE_IDLE; GetIndString(str, wifi_da.str_res_id, STR_NO_DEVICE_FOUND_ID); SetItem(wifi_da.ssid_menu, 1, str); update_window(true); } else { scsi_wifi_info(wifi_scsi_id, &wifi_cur_info); wifi_da.state = STATE_SCANNING; scsi_wifi_scan(wifi_scsi_id); } SetPort(savePort); } void create_logger(void) { Str255 str; Rect bounds; bounds.top = 150; bounds.bottom = 320; bounds.left = 20; bounds.right = 400; GetIndString(str, wifi_da.str_res_id, STR_DEBUG_LOGS_ID); wifi_da.logger = NewWindow(0L, &bounds, str, true, rDocProc, 0L, false, 0L); ((WindowPeek)wifi_da.logger)->windowKind = dce->dCtlRefNum; } void update_window(bool force) { Str255 menuText; GrafPtr savePort; Point p; short i, icon, x, w; RgnHandle tmpRgn; Rect r, destRect; BitMap bm, cur_bits; GetPort(&savePort); SetPort(wifi_da.win); //logger_printf("redrawing window"); cur_bits = wifi_da.win->portBits; SetPortBits(&wifi_da.win_shadow); if (!force) BeginUpdate(wifi_da.win); EraseRect(&wifi_da.win->portRect); DrawControls(wifi_da.win); /* system 6 does not seem to support popupMenuProc, fake it */ FrameRect(&wifi_da.ssid_menu_rect); MoveTo(wifi_da.ssid_menu_rect.left + 1, wifi_da.ssid_menu_rect.bottom); LineTo(wifi_da.ssid_menu_rect.right, wifi_da.ssid_menu_rect.bottom); MoveTo(wifi_da.ssid_menu_rect.right, wifi_da.ssid_menu_rect.top + 1); LineTo(wifi_da.ssid_menu_rect.right, wifi_da.ssid_menu_rect.bottom); TextFont(systemFont); TextSize(0); GetItem(wifi_da.ssid_menu, 1, &menuText); x = 15; MoveTo(wifi_da.ssid_menu_rect.left + x, 16); for (i = 1; i <= menuText[0]; i++) { w = CharWidth(menuText[i]); if (i < menuText[0] && x + w >= (wifi_da.ssid_menu_rect.right - wifi_da.ssid_menu_rect.left - 14)) { /* truncate */ GetPen(&p); MoveTo(p.h - 1, p.v); DrawChar('É'); break; } DrawChar(menuText[i]); x += w; } HLock(wifi_da.signal_icons); if (wifi_scsi_id == WIFI_SCSI_ID_NONE) icon = SIGNAL_NO_DEVICE; else if (wifi_scsi_id == WIFI_SCSI_ID_FINDING) icon = SIGNAL_FINDING_DEVICE; else if (wifi_cur_info.ssid[0] == '\0') icon = SIGNAL_NONE; else if (wifi_cur_info.rssi >= -60) icon = SIGNAL_4; else if (wifi_cur_info.rssi >= -70) icon = SIGNAL_3; else if (wifi_cur_info.rssi >= -75) icon = SIGNAL_2; else icon = SIGNAL_1; bm.baseAddr = (Ptr)(*(wifi_da.signal_icons))[icon]; bm.rowBytes = 2; SetRect(&bm.bounds, 0, 0, 16, 16); CopyBits(&bm, &wifi_da.win->portBits, &bm.bounds, &wifi_da.icon_rect, srcOr, NULL); HUnlock(wifi_da.signal_icons); if (!force) EndUpdate(wifi_da.win); SetPortBits(&cur_bits); CopyBits(&wifi_da.win_shadow, &wifi_da.win->portBits, &wifi_da.win_shadow.bounds, &wifi_da.win_shadow.bounds, srcCopy, nil); ValidRect(&wifi_da.win->portRect); SetPort(savePort); } void update_wifi_cur_info(void) { struct wifi_network_entry old_info; if (wifi_scsi_id == WIFI_SCSI_ID_NONE) return; memcpy(&old_info, &wifi_cur_info, sizeof(old_info)); scsi_wifi_info(wifi_scsi_id, &wifi_cur_info); if (memcmp(&old_info, &wifi_cur_info, sizeof(old_info)) != 0) { if (memcmp(&old_info.ssid, &wifi_cur_info.ssid, sizeof(old_info.ssid)) == 0) { /* just updating RSSI */ logger_printf("update_wifi_cur_info: just updating rssi to %d", wifi_cur_info.rssi); update_window(true); } else { logger_printf("update_wifi_cur_info: updating ssid list"); update_wifi_ssid_list(false); } } } void update_wifi_ssid_list(bool update_networks) { Str255 str; char ssid[64]; short n, m, mitem; if (wifi_scsi_id == WIFI_SCSI_ID_NONE) return; if (update_networks) { nwifi_scan_networks = scsi_wifi_scan_results(wifi_scsi_id, wifi_scan_networks, nitems(wifi_scan_networks)); logger_printf("scsi_wifi_scan_results: %d networks", nwifi_scan_networks); if (nwifi_scan_networks == 0) logger_printf("no networks found during scan"); } if (nwifi_scan_networks == 0) { /* use our current network (or none) as the only one found */ memcpy(&wifi_scan_networks, &wifi_cur_info, sizeof(wifi_cur_info)); nwifi_scan_networks = 1; } logger_printf("current ssid: %s", wifi_cur_info.ssid[0] == '\0' ? "(none)" : wifi_cur_info.ssid); /* * now start building the ssid menu from wifi_scan_networks */ /* leave room for current network and trailing "(other network)" */ if (nwifi_scan_networks > MAX_WIFI_NETWORKS - 2) { nwifi_scan_networks = MAX_WIFI_NETWORKS - 2; logger_printf("capping wifi_scan_networks to %d", nwifi_scan_networks); } /* put the current network (if any) first in the menu */ logger_printf("menu[0]: %s", wifi_cur_info.ssid[0] ? wifi_cur_info.ssid : "(no network)"); memcpy(&wifi_menu_networks[0], &wifi_cur_info, sizeof(wifi_menu_networks[0])); nwifi_menu_networks = 1; /* add each additional network from the scan */ for (n = 0; n < nwifi_scan_networks; n++) { /* except current one, which we added first */ if (strcmp(wifi_scan_networks[n].ssid, wifi_cur_info.ssid) == 0) continue; memcpy(&wifi_menu_networks[nwifi_menu_networks], &wifi_scan_networks[n], sizeof(wifi_menu_networks[0])); logger_printf("menu[%d]: %s", nwifi_menu_networks, wifi_menu_networks[nwifi_menu_networks].ssid); nwifi_menu_networks++; } if (wifi_menu_networks[0].ssid[0] == '\0') { /* replace blank string with "(no network)" */ GetIndString(str, wifi_da.str_res_id, STR_NO_NETWORK_ID); PtoCstr(str); strlcpy(wifi_menu_networks[0].ssid, (char *)str, sizeof(wifi_menu_networks[0].ssid)); } /* add "other network" as last entry */ nwifi_menu_networks++; if (nwifi_menu_networks > MAX_WIFI_NETWORKS) panic("nwifi_menu_networks overflow"); GetIndString(str, wifi_da.str_res_id, STR_OTHER_NETWORK_ID); PtoCstr(str); strlcpy(wifi_menu_networks[nwifi_menu_networks - 1].ssid, (char *)str, sizeof(wifi_menu_networks[0].ssid)); wifi_menu_networks[nwifi_menu_networks - 1].flags = WIFI_NETWORK_FLAG_AUTH | WIFI_NETWORK_FLAG_HIDDEN; logger_printf("menu[%d]: %s", nwifi_menu_networks - 1, "(other network)"); /* * leave the first item and we'll set its text later but delete all * others */ while (CountMItems(wifi_da.ssid_menu) > 1) DelMenuItem(wifi_da.ssid_menu, 2); for (n = 0, mitem = 1; n < nwifi_menu_networks; n++) { /* * InsMenuItem supports meta chars in item text which can * have side effects, so insert a blank item and then set its * name with SetItem which does not suport meta chars. */ strlcpy(ssid, wifi_menu_networks[n].ssid, sizeof(ssid)); CtoPstr(ssid); if (n > 0) InsMenuItem(wifi_da.ssid_menu, "\p...", mitem); SetItem(wifi_da.ssid_menu, mitem, ssid); if (wifi_cur_info.ssid[0] == '\0' && n == 0) CheckItem(wifi_da.ssid_menu, mitem, true); else CheckItem(wifi_da.ssid_menu, mitem, (strcmp(wifi_menu_networks[n].ssid, wifi_cur_info.ssid) == 0)); mitem++; } update_window(true); } void activate_window(bool activate) { Rect r; GrafPtr savePort; #if 0 GetPort(&savePort); SetPort(win); r = win->portRect; r.top = r.bottom - 16; r.left = r.left - 16; InvalRect(&r); SetPort(savePort); #endif } void destroy_windows(void) { ReleaseResource(wifi_da.signal_icons); if (wifi_da.win) { DisposeWindow(wifi_da.win); wifi_da.win = 0; } if (wifi_da.logger) { DisposeWindow(wifi_da.logger); wifi_da.logger = 0; } } void window_mousedown(Point p) { Str255 str, password, ssid; Rect menu_r, icon_r, r, irect; GrafPtr savePort; short mitems, n, selitem, hit, itype; long new_net; struct wifi_network_entry *net; struct wifi_join_request wjr; DialogPtr dg; ControlHandle ihandle; GetPort(&savePort); SetPort(wifi_da.win); icon_r = wifi_da.icon_rect; LocalToGlobal(&icon_r); /* XXX: why does PtInRect not work? */ if (p.h >= icon_r.left && p.h <= icon_r.left + icon_r.right && p.v >= icon_r.top && p.v <= icon_r.top + icon_r.bottom) { if (wifi_da.logger) { DisposeWindow(wifi_da.logger); wifi_da.logger = 0; } else create_logger(); return; } menu_r = wifi_da.ssid_menu_rect; LocalToGlobal(&menu_r); if (!(p.h >= menu_r.left && p.h <= menu_r.left + menu_r.right && p.v >= menu_r.top && p.v <= menu_r.top + menu_r.bottom)) { SetPort(savePort); return; } if (wifi_scsi_id == WIFI_SCSI_ID_NONE) return; InsertMenu(wifi_da.ssid_menu, -1); mitems = CountMItems(wifi_da.ssid_menu); selitem = 1; for (n = 0; n < mitems; n++) { if (strcmp(wifi_menu_networks[n].ssid, wifi_cur_info.ssid) == 0) { selitem = n + 1; break; } } new_net = PopUpMenuSelect(wifi_da.ssid_menu, menu_r.top + 1, menu_r.left + 1, selitem); DeleteMenu((*(wifi_da.ssid_menu))->menuID); if (hiword(new_net) == 0 || loword(new_net) == selitem || loword(new_net) > nwifi_menu_networks) goto menu_done; net = &wifi_menu_networks[loword(new_net) - 1]; logger_printf("clicked %s (item %d)", wifi_menu_networks[loword(new_net) - 1].ssid, loword(new_net)); memset(&wjr, 0, sizeof(wjr)); if (net->flags & WIFI_NETWORK_FLAG_HIDDEN) { dg = GetNewDialog(OwnedResourceID(PROMPT_DIALOG_ID), 0L, (WindowPtr)-1L); center_in_screen(((DialogPeek)dg)->window.port.portRect.right, ((DialogPeek)dg)->window.port.portRect.bottom, false, &r); MoveWindow(dg, r.left, r.top, false); GetIndString(str, wifi_da.str_res_id, STR_SSID_DIALOG_TITLE_ID); SetWTitle(dg, str); /* continue button */ GetDItem(dg, ok, &itype, &ihandle, &irect); GetIndString(str, wifi_da.str_res_id, STR_CONTINUE_ID); SetCTitle(ihandle, str); /* cancel button */ GetDItem(dg, cancel, &itype, &ihandle, &irect); GetIndString(str, wifi_da.str_res_id, STR_CANCEL_ID); SetCTitle(ihandle, str); /* "SSID:" */ GetDItem(dg, PROMPT_DIALOG_INPUT_LABEL_ID, &itype, &ihandle, &irect); GetIndString(str, wifi_da.str_res_id, STR_SSID_ID); SetIText(ihandle, str); /* "Enter the Wi-Fi network SSID:" */ GetDItem(dg, PROMPT_DIALOG_TITLE_LABEL_ID, &itype, &ihandle, &irect); GetIndString(str, wifi_da.str_res_id, STR_ENTER_SSID_ID); SetIText(ihandle, str); ShowWindow(dg); SetPort(dg); /* outline continue button */ GetDItem(dg, ok, &itype, &ihandle, &irect); PenSize(3, 3); InsetRect(&irect, -4, -4); FrameRoundRect(&irect, 16, 16); PenNormal(); for (;;) { ModalDialog(ModalDialogFilter, &hit); if (hit == ok || hit == cancel) break; } if (hit != ok) { DisposDialog(dg); goto menu_done; } GetDItem(dg, PROMPT_DIALOG_INPUT_FIELD_ID, &itype, &ihandle, &irect); GetIText(ihandle, &ssid); PtoCstr(ssid); strlcpy(wjr.ssid, (char *)ssid, sizeof(wjr.ssid)); DisposDialog(dg); } else strlcpy(wjr.ssid, net->ssid, sizeof(wjr.ssid)); if (net->flags & WIFI_NETWORK_FLAG_AUTH) { dg = GetNewDialog(OwnedResourceID(PROMPT_DIALOG_ID), 0L, (WindowPtr)-1L); center_in_screen(((DialogPeek)dg)->window.port.portRect.right, ((DialogPeek)dg)->window.port.portRect.bottom, false, &r); MoveWindow(dg, r.left, r.top, false); GetIndString(str, wifi_da.str_res_id, STR_PASSWORD_DIALOG_TITLE_ID); SetWTitle(dg, str); /* continue button */ GetDItem(dg, ok, &itype, &ihandle, &irect); GetIndString(str, wifi_da.str_res_id, STR_CONTINUE_ID); SetCTitle(ihandle, str); /* cancel button */ GetDItem(dg, cancel, &itype, &ihandle, &irect); GetIndString(str, wifi_da.str_res_id, STR_CANCEL_ID); SetCTitle(ihandle, str); /* "Password:" */ GetDItem(dg, PROMPT_DIALOG_INPUT_LABEL_ID, &itype, &ihandle, &irect); GetIndString(str, wifi_da.str_res_id, STR_PASSWORD_ID); SetIText(ihandle, str); /* "\"^0\" requires a password:" */ GetDItem(dg, PROMPT_DIALOG_TITLE_LABEL_ID, &itype, &ihandle, &irect); GetIndString(str, wifi_da.str_res_id, STR_REQUIRES_PASSWORD_ID); SetIText(ihandle, str); strlcpy((char *)ssid, wjr.ssid, sizeof(ssid)); CtoPstr(ssid); ParamText(ssid, "\p", "\p", "\p"); ShowWindow(dg); SetPort(dg); GetDItem(dg, PROMPT_DIALOG_PASSWORD_STORAGE_ID, &itype, &ihandle, &irect); SetIText(ihandle, "\p"); /* outline ok button */ GetDItem(dg, ok, &itype, &ihandle, &irect); PenSize(3, 3); InsetRect(&irect, -4, -4); FrameRoundRect(&irect, 16, 16); PenNormal(); for (;;) { ModalDialog(WiFiPasswordDialogFieldFilter, &hit); if (hit == ok || hit == cancel) break; } if (hit != ok) { DisposDialog(dg); goto menu_done; } GetDItem(dg, PROMPT_DIALOG_PASSWORD_STORAGE_ID, &itype, &ihandle, &irect); GetIText(ihandle, &password); PtoCstr(password); //DEBUG_LOG(("pass is \"%s\"", password)); strlcpy(wjr.key, (char *)password, sizeof(wjr.key)); DisposDialog(dg); } else logger_printf("network %s does not have auth rssi:%d channel:%d flags:0x%x", net->ssid, (short)net->rssi, (short)net->channel, (short)net->flags); scsi_wifi_join(wifi_scsi_id, &wjr); /* force an update of the list */ scsi_wifi_info(wifi_scsi_id, &wifi_cur_info); update_wifi_ssid_list(false); menu_done: SetPort(savePort); } pascal Boolean WiFiPasswordDialogFieldFilter(DialogPtr dlg, EventRecord *event, short *hit) { Str255 password; DialogPeek dlgp; ControlHandle ihandle; Rect irect; short sel_start, sel_end, itype; char key; dlgp = (DialogPeek)dlg; switch (event->what) { case keyDown: case autoKey: sel_start = (*(dlgp->textH))->selStart; sel_end = (*(dlgp->textH))->selEnd; key = event->message & charCodeMask; if (event->modifiers & cmdKey) { /* TODO: implement DlgPaste for cmd+v? */ event->what = nullEvent; return FALSE; } GetDItem(dlg, PROMPT_DIALOG_PASSWORD_STORAGE_ID, &itype, &ihandle, &irect); GetIText(ihandle, &password); PtoCstr(password); if (key == 8) { /* backspace */ if (sel_start == sel_end && sel_start > 0) memmove(password + sel_start - 1, password + sel_start, sizeof(password) - sel_start - 1); else if (sel_start != sel_end) memmove(password + sel_start, password + sel_end, sizeof(password) - sel_end - 1); } else if (sel_start >= sizeof(password)) { event->what = nullEvent; return FALSE; } else if (key >= ' ' && key <= '~') { if (sel_start != sel_end) /* delete selection before making space for new char */ memmove(password + sel_start, password + sel_end, sizeof(password) - sel_end - 1); memmove(password + sel_start + 1, password + sel_start, sizeof(password) - sel_start - 1); password[sel_start] = key; event->message = '¥'; } password[(*(dlgp->textH))->teLength + 1] = '\0'; CtoPstr(password); SetIText(ihandle, password); sel_start = 0; break; } return ModalDialogFilter(dlg, event, hit); }