feat: wi-fi - expand support and fix issues, add GUI components
This commit is contained in:
1 parent
bbe1df62fd
commit
9eb21eb3e3
67 files changed
+4573
-245
No files matched your search
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <montauk/syscall.h>
|
||||
#include <montauk/string.h>
|
||||
#include <montauk/wifi.h>
|
||||
#include <gui/mtk.hpp>
|
||||
#include <gui/mtk/settings.hpp>
|
||||
#include <gui/standalone.hpp>
|
||||
@@ -17,8 +18,8 @@ extern "C" {
|
||||
|
||||
using namespace gui;
|
||||
|
||||
static constexpr int WIN_W = 560;
|
||||
static constexpr int WIN_H = 420;
|
||||
static constexpr int WIN_W = 620;
|
||||
static constexpr int WIN_H = 480;
|
||||
static constexpr int TAB_H = 36;
|
||||
static constexpr int FOOTER_H = 44;
|
||||
static constexpr int PAD = 16;
|
||||
@@ -31,8 +32,9 @@ static constexpr int FIELD_H = 32;
|
||||
|
||||
enum Tab {
|
||||
TAB_STATUS = 0,
|
||||
TAB_CONFIG = 1,
|
||||
TAB_COUNT = 2,
|
||||
TAB_WIFI = 1,
|
||||
TAB_CONFIG = 2,
|
||||
TAB_COUNT = 3,
|
||||
};
|
||||
|
||||
struct Field {
|
||||
@@ -43,6 +45,7 @@ struct Field {
|
||||
|
||||
static const char* const kTabLabels[TAB_COUNT] = {
|
||||
"Status",
|
||||
"Wi-Fi",
|
||||
"Configure",
|
||||
};
|
||||
|
||||
@@ -59,6 +62,26 @@ static Field g_fields[FIELD_COUNT] = {
|
||||
{"Gateway", {}, {}},
|
||||
{"DNS Server", {}, {}},
|
||||
};
|
||||
|
||||
// ---- Wi-Fi tab state -------------------------------------------------------
|
||||
|
||||
static constexpr int WIFI_MAX_NETWORKS = 32;
|
||||
static constexpr int WIFI_ROW_H = 34;
|
||||
|
||||
static montauk::abi::WifiInfo g_wifi = {};
|
||||
static montauk::abi::WifiNetwork g_wifi_nets[WIFI_MAX_NETWORKS];
|
||||
static int g_wifi_count = 0;
|
||||
static int g_wifi_selected = -1;
|
||||
static int g_wifi_scroll = 0; // first visible row
|
||||
static uint32_t g_wifi_generation = 0;
|
||||
static bool g_wifi_scanning = false;
|
||||
static bool g_wifi_joining = false;
|
||||
static char g_wifi_psk[montauk::wifi::PSK_CAP] = {};
|
||||
static mtk::TextInputState g_wifi_psk_input = {};
|
||||
static bool g_wifi_psk_focus = false;
|
||||
static bool g_wifi_remember = true;
|
||||
static montauk::wifi::SavedList g_wifi_saved = {};
|
||||
|
||||
static bool g_dirty = false;
|
||||
static char g_status[128] = {};
|
||||
static uint64_t g_status_time = 0;
|
||||
@@ -246,12 +269,15 @@ static void copy_cfg_to_fields() {
|
||||
g_dirty = false;
|
||||
}
|
||||
|
||||
static void wifi_refresh();
|
||||
|
||||
static void refresh_state(bool update_fields) {
|
||||
montauk::get_netcfg(&g_cfg);
|
||||
if (montauk::net_status(&g_net) < 0) {
|
||||
montauk::memset(&g_net, 0, sizeof(g_net));
|
||||
snprintf(g_net.driver, sizeof(g_net.driver), "Unavailable");
|
||||
}
|
||||
wifi_refresh();
|
||||
if (update_fields) copy_cfg_to_fields();
|
||||
g_last_refresh = montauk::get_milliseconds();
|
||||
}
|
||||
@@ -387,6 +413,434 @@ static void draw_status_tab(Canvas& c, const mtk::Theme& theme) {
|
||||
draw_kv(c, &y, "TX Packets", tx, theme);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Wi-Fi tab
|
||||
//
|
||||
// Laid out the way the Display app lays out its mode list: borderless rows on
|
||||
// the window background, a radio for the selection, and state as right-aligned
|
||||
// text rather than a pill. The block under the list keeps a fixed height so the
|
||||
// row count does not change as the selection moves between open and encrypted
|
||||
// networks.
|
||||
// ============================================================================
|
||||
|
||||
static int wifi_status_y() {
|
||||
return TAB_H + 20;
|
||||
}
|
||||
|
||||
static int wifi_separator_y() {
|
||||
return wifi_status_y() + system_font_height() * 2 + 26;
|
||||
}
|
||||
|
||||
static int wifi_list_header_y() {
|
||||
return wifi_separator_y() + 16;
|
||||
}
|
||||
|
||||
static int wifi_list_y() {
|
||||
return wifi_list_header_y() + system_font_height() + 10;
|
||||
}
|
||||
|
||||
// The passphrase field, the checkbox under it, and the padding around them.
|
||||
static int wifi_action_h() {
|
||||
return mtk::labeled_text_height(app_theme(), FIELD_H) + 10
|
||||
+ system_font_height() + 6;
|
||||
}
|
||||
|
||||
static Rect wifi_action_rect() {
|
||||
int h = wifi_action_h();
|
||||
return {PAD, footer_rect().y - 14 - h, g_win.width - PAD * 2, h};
|
||||
}
|
||||
|
||||
static Rect wifi_psk_input_rect() {
|
||||
Rect action = wifi_action_rect();
|
||||
return mtk::labeled_text_input_rect(action.x, action.y, action.w,
|
||||
app_theme(), FIELD_H);
|
||||
}
|
||||
|
||||
static Rect wifi_remember_rect() {
|
||||
Rect action = wifi_action_rect();
|
||||
int block = mtk::labeled_text_height(app_theme(), FIELD_H);
|
||||
return {action.x, action.y + block + 10, 240, system_font_height() + 6};
|
||||
}
|
||||
|
||||
static int wifi_visible_rows() {
|
||||
int available = wifi_action_rect().y - 14 - wifi_list_y();
|
||||
int rows = available / WIFI_ROW_H;
|
||||
if (rows < 1) rows = 1;
|
||||
if (rows > WIFI_MAX_NETWORKS) rows = WIFI_MAX_NETWORKS;
|
||||
return rows;
|
||||
}
|
||||
|
||||
static Rect wifi_row_rect(int visibleIndex) {
|
||||
return {PAD, wifi_list_y() + visibleIndex * WIFI_ROW_H,
|
||||
g_win.width - PAD * 2, WIFI_ROW_H};
|
||||
}
|
||||
|
||||
static void wifi_meta_text(char* out, size_t cap,
|
||||
const montauk::abi::WifiNetwork& net) {
|
||||
snprintf(out, cap, "%s - ch %d - %d dBm",
|
||||
montauk::wifi::security_name(net.security),
|
||||
(int)net.channel, (int)net.rssi);
|
||||
}
|
||||
|
||||
static void wifi_clamp_scroll() {
|
||||
int max_scroll = g_wifi_count - wifi_visible_rows();
|
||||
if (max_scroll < 0) max_scroll = 0;
|
||||
if (g_wifi_scroll > max_scroll) g_wifi_scroll = max_scroll;
|
||||
if (g_wifi_scroll < 0) g_wifi_scroll = 0;
|
||||
}
|
||||
|
||||
static Rect wifi_forget_button() {
|
||||
Rect refresh = status_refresh_button();
|
||||
return {refresh.x - (GAP + BUTTON_W) * 2, refresh.y, BUTTON_W, BUTTON_H};
|
||||
}
|
||||
|
||||
static Rect wifi_scan_button() {
|
||||
Rect refresh = status_refresh_button();
|
||||
return {refresh.x - GAP - BUTTON_W, refresh.y, BUTTON_W, BUTTON_H};
|
||||
}
|
||||
|
||||
static Rect wifi_connect_button() {
|
||||
return status_refresh_button();
|
||||
}
|
||||
|
||||
static bool wifi_selection_valid() {
|
||||
return g_wifi_selected >= 0 && g_wifi_selected < g_wifi_count;
|
||||
}
|
||||
|
||||
static bool wifi_selection_is_current() {
|
||||
return wifi_selection_valid() && g_wifi.connected
|
||||
&& montauk::streq(g_wifi_nets[g_wifi_selected].ssid, g_wifi.ssid);
|
||||
}
|
||||
|
||||
// True when joining the selection would need a passphrase typed in: encrypted,
|
||||
// and nothing remembered for it.
|
||||
static bool wifi_selection_needs_key() {
|
||||
if (!wifi_selection_valid()) return false;
|
||||
const montauk::abi::WifiNetwork& net = g_wifi_nets[g_wifi_selected];
|
||||
return montauk::wifi::needs_key(net.security);
|
||||
}
|
||||
|
||||
static void wifi_sort() {
|
||||
for (int i = 1; i < g_wifi_count; i++) {
|
||||
montauk::abi::WifiNetwork key = g_wifi_nets[i];
|
||||
int j = i - 1;
|
||||
while (j >= 0 && g_wifi_nets[j].rssi < key.rssi) {
|
||||
g_wifi_nets[j + 1] = g_wifi_nets[j];
|
||||
j--;
|
||||
}
|
||||
g_wifi_nets[j + 1] = key;
|
||||
}
|
||||
}
|
||||
|
||||
static void wifi_load_results() {
|
||||
char previous[montauk::wifi::SSID_CAP] = {};
|
||||
if (wifi_selection_valid())
|
||||
montauk::strncpy(previous, g_wifi_nets[g_wifi_selected].ssid, sizeof(previous));
|
||||
|
||||
montauk::abi::WifiNetwork raw[WIFI_MAX_NETWORKS];
|
||||
int n = montauk::wifi_results(raw, WIFI_MAX_NETWORKS);
|
||||
if (n < 0) n = 0;
|
||||
|
||||
// One row per network, not one per access point: a house with a repeater
|
||||
// answers on several BSSIDs and the strongest is the one worth joining.
|
||||
// Hidden networks have no SSID to join by, so they are left out entirely.
|
||||
g_wifi_count = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (!raw[i].ssid[0]) continue;
|
||||
|
||||
int existing = -1;
|
||||
for (int k = 0; k < g_wifi_count; k++) {
|
||||
if (montauk::streq(g_wifi_nets[k].ssid, raw[i].ssid)) { existing = k; break; }
|
||||
}
|
||||
if (existing >= 0) {
|
||||
if (raw[i].rssi > g_wifi_nets[existing].rssi)
|
||||
g_wifi_nets[existing] = raw[i];
|
||||
continue;
|
||||
}
|
||||
g_wifi_nets[g_wifi_count++] = raw[i];
|
||||
}
|
||||
wifi_sort();
|
||||
|
||||
// Keep the selection on the network it was on, wherever it moved to.
|
||||
g_wifi_selected = -1;
|
||||
if (previous[0]) {
|
||||
for (int i = 0; i < g_wifi_count; i++) {
|
||||
if (montauk::streq(g_wifi_nets[i].ssid, previous)) { g_wifi_selected = i; break; }
|
||||
}
|
||||
}
|
||||
wifi_clamp_scroll();
|
||||
}
|
||||
|
||||
// Keeps the adapter snapshot, the scan table and the saved list in step.
|
||||
static void wifi_refresh() {
|
||||
if (montauk::wifi_info(&g_wifi) != 0) {
|
||||
montauk::memset(&g_wifi, 0, sizeof(g_wifi));
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_wifi.scanGeneration != g_wifi_generation) {
|
||||
g_wifi_generation = g_wifi.scanGeneration;
|
||||
g_wifi_scanning = false;
|
||||
wifi_load_results();
|
||||
char msg[64];
|
||||
snprintf(msg, sizeof(msg), "Found %d network%s", g_wifi_count,
|
||||
g_wifi_count == 1 ? "" : "s");
|
||||
set_status(msg);
|
||||
} else if (g_wifi_scanning && !g_wifi.scanning) {
|
||||
g_wifi_scanning = false;
|
||||
}
|
||||
|
||||
if (g_wifi_joining) {
|
||||
if (g_wifi.connected) {
|
||||
g_wifi_joining = false;
|
||||
char msg[96];
|
||||
snprintf(msg, sizeof(msg), "Connected to %s", g_wifi.ssid);
|
||||
set_status(msg);
|
||||
} else if (g_wifi.lastError != 0 && !g_wifi.joining) {
|
||||
g_wifi_joining = false;
|
||||
set_status(montauk::wifi::error_message(g_wifi.lastError));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void wifi_start_scan() {
|
||||
int rc = montauk::wifi_scan_start(6000);
|
||||
if (rc < 0) {
|
||||
set_status("The Wi-Fi adapter is not ready");
|
||||
return;
|
||||
}
|
||||
g_wifi_scanning = true;
|
||||
set_status("Scanning for networks...");
|
||||
}
|
||||
|
||||
static void wifi_join_selected() {
|
||||
if (!wifi_selection_valid()) {
|
||||
set_status("Select a network first");
|
||||
return;
|
||||
}
|
||||
|
||||
const montauk::abi::WifiNetwork& net = g_wifi_nets[g_wifi_selected];
|
||||
const char* psk = "";
|
||||
if (montauk::wifi::needs_key(net.security)) {
|
||||
if (g_wifi_psk[0]) {
|
||||
psk = g_wifi_psk;
|
||||
} else {
|
||||
const montauk::wifi::SavedNetwork* saved =
|
||||
montauk::wifi::saved_find(&g_wifi_saved, net.ssid);
|
||||
if (saved && saved->psk[0]) {
|
||||
psk = saved->psk;
|
||||
} else {
|
||||
g_wifi_psk_focus = true;
|
||||
set_status("This network needs a password");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool save_failed = false;
|
||||
if (montauk::wifi::needs_key(net.security) && g_wifi_remember && psk[0]) {
|
||||
montauk::wifi::saved_set(&g_wifi_saved, net.ssid, psk);
|
||||
save_failed = montauk::wifi::saved_store(&g_wifi_saved) != 0;
|
||||
}
|
||||
|
||||
int rc = montauk::wifi_connect_async(net.ssid, psk);
|
||||
if (rc < 0) {
|
||||
set_status(montauk::wifi::error_message(rc));
|
||||
return;
|
||||
}
|
||||
|
||||
g_wifi_joining = true;
|
||||
char msg[96];
|
||||
if (save_failed) {
|
||||
snprintf(msg, sizeof(msg),
|
||||
"Connecting to %s, but the password could not be saved", net.ssid);
|
||||
} else {
|
||||
snprintf(msg, sizeof(msg), "Connecting to %s...", net.ssid);
|
||||
}
|
||||
set_status(msg);
|
||||
}
|
||||
|
||||
static void wifi_forget_selected() {
|
||||
if (!wifi_selection_valid()) return;
|
||||
const char* ssid = g_wifi_nets[g_wifi_selected].ssid;
|
||||
if (!montauk::wifi::saved_remove(&g_wifi_saved, ssid)) {
|
||||
set_status("That network was not saved");
|
||||
return;
|
||||
}
|
||||
bool save_failed = montauk::wifi::saved_store(&g_wifi_saved) != 0;
|
||||
g_wifi_psk[0] = '\0';
|
||||
mtk::text_input_reset(g_wifi_psk_input, 0);
|
||||
|
||||
char msg[96];
|
||||
if (save_failed) {
|
||||
snprintf(msg, sizeof(msg), "Could not update the saved network list");
|
||||
} else {
|
||||
snprintf(msg, sizeof(msg), "Forgot %s", ssid);
|
||||
}
|
||||
set_status(msg);
|
||||
}
|
||||
|
||||
static void draw_signal(Canvas& c, int x, int y, int8_t rssi, Color on, Color off) {
|
||||
int bars = montauk::wifi::signal_bars(rssi);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int h = 3 + i * 3;
|
||||
c.fill_rect(x + i * 4, y + 12 - h, 3, h, i < bars ? on : off);
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_wifi_tab(Canvas& c, const mtk::Theme& theme) {
|
||||
int fh = system_font_height();
|
||||
int y = wifi_status_y();
|
||||
|
||||
bool present = g_wifi.present != 0;
|
||||
bool ready = present && g_wifi.state == montauk::abi::WIFI_STATE_RUNNING;
|
||||
|
||||
Color dot = !present ? theme.danger
|
||||
: (g_wifi.connected ? Color::from_rgb(0x27, 0xA0, 0x58)
|
||||
: Color::from_rgb(0xD0, 0x9A, 0x2E));
|
||||
fill_circle(c, PAD + 6, y + fh / 2, 6, dot);
|
||||
|
||||
char headline[128];
|
||||
if (!present) {
|
||||
snprintf(headline, sizeof(headline), "No Wi-Fi adapter");
|
||||
} else if (g_wifi.connected) {
|
||||
snprintf(headline, sizeof(headline), "Connected to %s", g_wifi.ssid);
|
||||
} else if (g_wifi_joining) {
|
||||
snprintf(headline, sizeof(headline), "%s",
|
||||
montauk::wifi::conn_state_name(g_wifi.connState));
|
||||
} else {
|
||||
snprintf(headline, sizeof(headline), "%s",
|
||||
montauk::wifi::state_name(g_wifi.state));
|
||||
}
|
||||
draw_text_fit(c, PAD + 22, y, headline, g_win.width - PAD * 2 - 22,
|
||||
present ? theme.text : theme.danger);
|
||||
|
||||
char sub[160];
|
||||
if (!present) {
|
||||
snprintf(sub, sizeof(sub),
|
||||
"Nothing to configure until a supported adapter is fitted.");
|
||||
} else if (g_wifi.connected) {
|
||||
snprintf(sub, sizeof(sub), "Channel %d - firmware %s",
|
||||
(int)g_wifi.channel,
|
||||
g_wifi.fwVersion[0] ? g_wifi.fwVersion : "unknown");
|
||||
} else {
|
||||
snprintf(sub, sizeof(sub), "Firmware %s",
|
||||
g_wifi.fwVersion[0] ? g_wifi.fwVersion : "not loaded");
|
||||
}
|
||||
draw_text_fit(c, PAD + 22, y + fh + 3, sub, g_win.width - PAD * 2 - 22,
|
||||
theme.text_subtle);
|
||||
|
||||
mtk::draw_separator(c, PAD, wifi_separator_y(), g_win.width - PAD * 2, theme);
|
||||
|
||||
// List header, with the page indicator where the Display app puts its hint.
|
||||
int header_y = wifi_list_header_y();
|
||||
c.text(PAD, header_y, "NETWORKS IN RANGE", theme.text_muted);
|
||||
|
||||
int rows = wifi_visible_rows();
|
||||
char hint[48];
|
||||
hint[0] = '\0';
|
||||
if (g_wifi_scanning) {
|
||||
snprintf(hint, sizeof(hint), "Scanning...");
|
||||
} else if (g_wifi_count > rows) {
|
||||
int last = g_wifi_scroll + rows;
|
||||
if (last > g_wifi_count) last = g_wifi_count;
|
||||
snprintf(hint, sizeof(hint), "%d-%d of %d (scroll)",
|
||||
g_wifi_scroll + 1, last, g_wifi_count);
|
||||
}
|
||||
if (hint[0])
|
||||
c.text(g_win.width - PAD - text_width(hint), header_y, hint, theme.text_muted);
|
||||
|
||||
if (g_wifi_count == 0) {
|
||||
const char* empty = !ready ? "The adapter is not ready yet."
|
||||
: (g_wifi_scanning
|
||||
? "Looking for networks..."
|
||||
: "No networks found. Press Scan to look again.");
|
||||
c.text(PAD + 4, wifi_list_y() + 9, empty, theme.text_subtle);
|
||||
}
|
||||
|
||||
// Fixed right-hand column for the row state, so the signal/channel detail
|
||||
// beside it can never be drawn over the word "Connected".
|
||||
static constexpr int STATE_W = 84;
|
||||
|
||||
// The detail column starts at one x for every row, sized from the widest
|
||||
// line in the whole list: right-aligning each row instead would make short
|
||||
// strings ("ch 1") look indented, and sizing it from the visible rows only
|
||||
// would make the column jump about while scrolling.
|
||||
int meta_w = 0;
|
||||
for (int index = 0; index < g_wifi_count; index++) {
|
||||
char meta[48];
|
||||
wifi_meta_text(meta, sizeof(meta), g_wifi_nets[index]);
|
||||
int w = text_width(meta);
|
||||
if (w > meta_w) meta_w = w;
|
||||
}
|
||||
Rect list_row = wifi_row_rect(0);
|
||||
int meta_x = list_row.x + list_row.w - STATE_W - 12 - meta_w;
|
||||
|
||||
for (int row = 0; row < rows; row++) {
|
||||
int index = g_wifi_scroll + row;
|
||||
if (index >= g_wifi_count) break;
|
||||
|
||||
const montauk::abi::WifiNetwork& net = g_wifi_nets[index];
|
||||
Rect option = wifi_row_rect(row);
|
||||
bool selected = index == g_wifi_selected;
|
||||
bool current = g_wifi.connected && montauk::streq(net.ssid, g_wifi.ssid);
|
||||
|
||||
if (selected)
|
||||
c.fill_rounded_rect(option.x, option.y + 2, option.w, option.h - 4,
|
||||
theme.radius_md, theme.accent_soft);
|
||||
else if (option.contains(g_mouse_x, g_mouse_y))
|
||||
c.fill_rounded_rect(option.x, option.y + 2, option.w, option.h - 4,
|
||||
theme.radius_md, theme.surface_hover);
|
||||
|
||||
Rect radio = {option.x + 4, option.y, 18, option.h};
|
||||
mtk::draw_radio(c, radio, "", selected, theme);
|
||||
|
||||
int text_y = option.y + (option.h - fh) / 2;
|
||||
draw_signal(c, option.x + 34, option.y + (option.h - 12) / 2, net.rssi,
|
||||
current ? theme.accent : theme.text_muted, theme.border);
|
||||
|
||||
const char* state = current ? "Connected"
|
||||
: (montauk::wifi::saved_find(&g_wifi_saved, net.ssid)
|
||||
? "Saved" : nullptr);
|
||||
if (state)
|
||||
c.text(option.x + option.w - STATE_W, text_y, state,
|
||||
current ? theme.accent : theme.text_muted);
|
||||
|
||||
char meta[48];
|
||||
wifi_meta_text(meta, sizeof(meta), net);
|
||||
c.text(meta_x, text_y, meta, theme.text_subtle);
|
||||
|
||||
int ssid_x = option.x + 60;
|
||||
draw_text_fit(c, ssid_x, text_y, net.ssid, meta_x - 12 - ssid_x, theme.text);
|
||||
}
|
||||
|
||||
// The block below the list keeps its height whatever is in it: a passphrase
|
||||
// field when one is wanted, and a line explaining why not when it is not.
|
||||
Rect action = wifi_action_rect();
|
||||
if (wifi_selection_needs_key()) {
|
||||
const montauk::wifi::SavedNetwork* saved =
|
||||
montauk::wifi::saved_find(&g_wifi_saved,
|
||||
g_wifi_nets[g_wifi_selected].ssid);
|
||||
mtk::draw_labeled_text_field(c, action.x, action.y, action.w,
|
||||
saved ? "Password (saved)" : "Password",
|
||||
g_wifi_psk, g_wifi_psk_input.cursor,
|
||||
g_wifi_psk_focus, true, theme, FIELD_H,
|
||||
g_wifi_psk_input.selection_anchor);
|
||||
|
||||
Rect remember = wifi_remember_rect();
|
||||
mtk::draw_checkbox(c, remember, "Remember this network",
|
||||
mtk::check_state(g_wifi_remember), theme, true,
|
||||
remember.contains(g_mouse_x, g_mouse_y));
|
||||
} else {
|
||||
const char* line;
|
||||
if (!wifi_selection_valid())
|
||||
line = "";
|
||||
else
|
||||
line = "Open network - no password needed.";
|
||||
if (line[0]) c.text(action.x, action.y, line, theme.text_subtle);
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_config_tab(Canvas& c, const mtk::Theme& theme) {
|
||||
int y = config_header_y();
|
||||
c.text(PAD, y, "IPv4 Configuration", theme.text);
|
||||
@@ -418,10 +872,33 @@ static void draw_footer(Canvas& c, const mtk::Theme& theme) {
|
||||
const char* msg = status_visible()
|
||||
? g_status
|
||||
: (g_dirty ? "Unsaved static IPv4 changes" : "Network settings ready");
|
||||
int text_right = (g_tab == TAB_STATUS ? status_clear_button().x : config_dhcp_button().x) - GAP;
|
||||
int text_right = PAD;
|
||||
if (g_tab == TAB_STATUS) text_right = status_clear_button().x - GAP;
|
||||
else if (g_tab == TAB_WIFI) text_right = wifi_forget_button().x - GAP;
|
||||
else text_right = config_dhcp_button().x - GAP;
|
||||
draw_text_fit(c, PAD, foot.y + (FOOTER_H - system_font_height()) / 2,
|
||||
msg, text_right - PAD, g_dirty ? theme.text : theme.text_subtle);
|
||||
|
||||
if (g_tab == TAB_WIFI) {
|
||||
Rect forget = wifi_forget_button();
|
||||
Rect scan = wifi_scan_button();
|
||||
Rect connect = wifi_connect_button();
|
||||
bool saved = wifi_selection_valid()
|
||||
&& montauk::wifi::saved_find(&g_wifi_saved,
|
||||
g_wifi_nets[g_wifi_selected].ssid) != nullptr;
|
||||
bool connected = wifi_selection_is_current() || (g_wifi.connected && !wifi_selection_valid());
|
||||
|
||||
mtk::draw_button(c, forget, "Forget", mtk::BUTTON_SECONDARY,
|
||||
button_state(forget, saved), theme);
|
||||
mtk::draw_button(c, scan, g_wifi_scanning ? "Scanning" : "Scan",
|
||||
mtk::BUTTON_SECONDARY,
|
||||
button_state(scan, !g_wifi_scanning && g_wifi.present), theme);
|
||||
mtk::draw_button(c, connect, connected ? "Disconnect" : "Connect",
|
||||
mtk::BUTTON_PRIMARY,
|
||||
button_state(connect, g_wifi.present != 0), theme);
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_tab == TAB_STATUS) {
|
||||
Rect clear = status_clear_button();
|
||||
Rect dhcp = status_dhcp_button();
|
||||
@@ -455,12 +932,20 @@ static void render() {
|
||||
|
||||
if (g_tab == TAB_STATUS) {
|
||||
draw_status_tab(c, theme);
|
||||
} else if (g_tab == TAB_WIFI) {
|
||||
draw_wifi_tab(c, theme);
|
||||
} else {
|
||||
draw_config_tab(c, theme);
|
||||
}
|
||||
draw_footer(c, theme);
|
||||
if (g_tab == TAB_CONFIG)
|
||||
draw_config_context_menus(c, theme);
|
||||
if (g_tab == TAB_WIFI) {
|
||||
mtk::draw_text_input_context_menu(
|
||||
c, g_wifi_psk_input, theme,
|
||||
mtk::text_input_has_selection(g_wifi_psk_input, g_wifi_psk,
|
||||
(int)sizeof(g_wifi_psk)));
|
||||
}
|
||||
|
||||
host.present();
|
||||
}
|
||||
@@ -523,6 +1008,19 @@ static void set_tab(Tab tab) {
|
||||
return;
|
||||
}
|
||||
g_tab = tab;
|
||||
if (tab == TAB_WIFI) {
|
||||
g_focus_field = -1;
|
||||
g_wifi_psk_focus = false;
|
||||
montauk::wifi::saved_load(&g_wifi_saved);
|
||||
wifi_refresh();
|
||||
wifi_load_results();
|
||||
// Nothing on screen yet and an adapter that can look: go and find out.
|
||||
if (g_wifi_count == 0 && !g_wifi_scanning
|
||||
&& g_wifi.state == montauk::abi::WIFI_STATE_RUNNING) {
|
||||
wifi_start_scan();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (tab == TAB_CONFIG) {
|
||||
focus_field(0);
|
||||
} else {
|
||||
@@ -546,6 +1044,80 @@ static bool handle_mouse(int mx, int my, uint8_t buttons, uint8_t prev_buttons)
|
||||
}
|
||||
}
|
||||
|
||||
if (g_tab == TAB_WIFI) {
|
||||
// The passphrase field owns the click while a drag or its context menu
|
||||
// is in progress, the same rule the IPv4 fields follow.
|
||||
if (g_wifi_psk_input.context.open || g_wifi_psk_input.dragging) {
|
||||
int result = mtk::text_input_handle_mouse(
|
||||
g_wifi_psk_input, wifi_psk_input_rect(), g_wifi_psk,
|
||||
(int)sizeof(g_wifi_psk), mx, my, buttons, prev_buttons,
|
||||
g_win.width, g_win.height, g_wifi_psk_focus, true, nullptr);
|
||||
return result != mtk::TEXT_INPUT_NONE;
|
||||
}
|
||||
|
||||
if (!left_pressed && !right_pressed) return false;
|
||||
|
||||
if (wifi_selection_needs_key() && wifi_psk_input_rect().contains(mx, my)) {
|
||||
g_wifi_psk_focus = true;
|
||||
mtk::text_input_handle_mouse(g_wifi_psk_input, wifi_psk_input_rect(),
|
||||
g_wifi_psk, (int)sizeof(g_wifi_psk),
|
||||
mx, my, buttons, prev_buttons,
|
||||
g_win.width, g_win.height, true, true, nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!left_pressed) return false;
|
||||
|
||||
int rows = wifi_visible_rows();
|
||||
for (int row = 0; row < rows; row++) {
|
||||
int idx = g_wifi_scroll + row;
|
||||
if (idx >= g_wifi_count) break;
|
||||
if (!wifi_row_rect(row).contains(mx, my)) continue;
|
||||
|
||||
if (idx != g_wifi_selected) {
|
||||
g_wifi_selected = idx;
|
||||
// A different network means a different key; do not carry the
|
||||
// previous one over, but do offer the remembered one.
|
||||
g_wifi_psk[0] = '\0';
|
||||
mtk::text_input_reset(g_wifi_psk_input, 0);
|
||||
const montauk::wifi::SavedNetwork* saved =
|
||||
montauk::wifi::saved_find(&g_wifi_saved, g_wifi_nets[idx].ssid);
|
||||
if (saved) {
|
||||
montauk::strncpy(g_wifi_psk, saved->psk, sizeof(g_wifi_psk));
|
||||
mtk::text_input_reset(g_wifi_psk_input, str_len(g_wifi_psk));
|
||||
}
|
||||
}
|
||||
g_wifi_psk_focus = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (wifi_selection_needs_key() && wifi_remember_rect().contains(mx, my)) {
|
||||
g_wifi_remember = !g_wifi_remember;
|
||||
return true;
|
||||
}
|
||||
if (wifi_scan_button().contains(mx, my)) {
|
||||
if (!g_wifi_scanning) wifi_start_scan();
|
||||
return true;
|
||||
}
|
||||
if (wifi_forget_button().contains(mx, my)) {
|
||||
wifi_forget_selected();
|
||||
return true;
|
||||
}
|
||||
if (wifi_connect_button().contains(mx, my)) {
|
||||
if (wifi_selection_is_current() || (g_wifi.connected && !wifi_selection_valid())) {
|
||||
montauk::wifi_disconnect();
|
||||
g_wifi_joining = false;
|
||||
set_status("Disconnected");
|
||||
} else {
|
||||
wifi_join_selected();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
g_wifi_psk_focus = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (g_tab == TAB_STATUS) {
|
||||
if (!left_pressed) return false;
|
||||
if (status_refresh_button().contains(mx, my)) {
|
||||
@@ -631,6 +1203,42 @@ static bool handle_key(const montauk::abi::KeyEvent& key) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (g_tab == TAB_WIFI) {
|
||||
if (key.ascii == '\t') {
|
||||
set_tab(TAB_CONFIG);
|
||||
return true;
|
||||
}
|
||||
if (key.ascii == '\n' || key.ascii == '\r') {
|
||||
wifi_join_selected();
|
||||
return true;
|
||||
}
|
||||
if (key.scancode == 0x48 || key.scancode == 0x50) { // up / down
|
||||
int step = key.scancode == 0x48 ? -1 : 1;
|
||||
int next = g_wifi_selected < 0 ? 0 : g_wifi_selected + step;
|
||||
if (next < 0) next = 0;
|
||||
if (next >= g_wifi_count) next = g_wifi_count - 1;
|
||||
g_wifi_selected = next;
|
||||
if (g_wifi_selected >= 0) {
|
||||
if (g_wifi_selected < g_wifi_scroll)
|
||||
g_wifi_scroll = g_wifi_selected;
|
||||
if (g_wifi_selected >= g_wifi_scroll + wifi_visible_rows())
|
||||
g_wifi_scroll = g_wifi_selected - wifi_visible_rows() + 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (g_wifi_psk_focus && wifi_selection_needs_key()) {
|
||||
int result = mtk::text_input_key(g_wifi_psk_input, g_wifi_psk,
|
||||
(int)sizeof(g_wifi_psk), key, nullptr);
|
||||
return (result & mtk::TEXT_INPUT_CONSUMED) != 0;
|
||||
}
|
||||
if (key.ascii == 's' || key.ascii == 'S') {
|
||||
if (!g_wifi_scanning) wifi_start_scan();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key.ascii == '\t') {
|
||||
if (g_tab != TAB_CONFIG) {
|
||||
set_tab(TAB_CONFIG);
|
||||
@@ -690,7 +1298,9 @@ extern "C" void _start() {
|
||||
montauk::exit(1);
|
||||
}
|
||||
|
||||
montauk::wifi::saved_load(&g_wifi_saved);
|
||||
refresh_state(true);
|
||||
wifi_load_results();
|
||||
render();
|
||||
|
||||
while (g_win.id >= 0 && !g_win.closed) {
|
||||
@@ -701,7 +1311,10 @@ extern "C" void _start() {
|
||||
if (r < 0) break;
|
||||
if (r == 0) {
|
||||
uint64_t now = montauk::get_milliseconds();
|
||||
if (now - g_last_refresh >= 3000) {
|
||||
// The Wi-Fi tab has work in flight worth watching closely; the rest
|
||||
// of the app is happy with the slower cadence.
|
||||
bool wifi_busy = g_tab == TAB_WIFI && (g_wifi_scanning || g_wifi_joining);
|
||||
if (now - g_last_refresh >= (wifi_busy ? 400u : 3000u)) {
|
||||
refresh_state(!g_dirty);
|
||||
redraw = true;
|
||||
}
|
||||
@@ -718,6 +1331,12 @@ extern "C" void _start() {
|
||||
g_mouse_x = ev.mouse.x;
|
||||
g_mouse_y = ev.mouse.y;
|
||||
redraw = true;
|
||||
if (ev.mouse.scroll != 0 && g_tab == TAB_WIFI
|
||||
&& ev.mouse.y >= wifi_list_y()
|
||||
&& ev.mouse.y < wifi_action_rect().y) {
|
||||
g_wifi_scroll += ev.mouse.scroll > 0 ? -1 : 1;
|
||||
wifi_clamp_scroll();
|
||||
}
|
||||
if (handle_mouse(ev.mouse.x, ev.mouse.y,
|
||||
ev.mouse.buttons, ev.mouse.prev_buttons)) {
|
||||
redraw = true;
|
||||
|
||||
Reference in new issue
Block a user