diff --git a/build/bloom-typography.json b/build/bloom-typography.json index 2f53820f8..179fb4d10 100644 --- a/build/bloom-typography.json +++ b/build/bloom-typography.json @@ -96,7 +96,7 @@ "unclassified_data": "unchanged", "fragment_kinds": ["copy", "data"], "maximum_fragments": 8, - "classified_copy_call_sites": 82, - "classified_mixed_call_sites": 14 + "classified_copy_call_sites": 86, + "classified_mixed_call_sites": 15 } } diff --git a/docs/1.0_TEST_MATRIX.md b/docs/1.0_TEST_MATRIX.md index 3bf88adf1..c078e3769 100644 --- a/docs/1.0_TEST_MATRIX.md +++ b/docs/1.0_TEST_MATRIX.md @@ -50,7 +50,7 @@ shoulder button. | Network services | Desired/runtime separation; carrier without IPv4 remains acquiring/not-ready; canonical DHCP restart and bounded post-address listener reconciliation; SSH shell and SFTP independent key-only states; canonical transfer roots and modes; Saves read-only; private roots denied; traversal/symlink/path bounds; same-filesystem atomic publication; authorized-key import; quiesce/health; stale FTP/Telnet/FileBrowser/inherited-Samba/WPS/hotspot flags cannot activate listeners; process-aware listener audit; Samba hidden until a materially different source-build candidate passes feasibility | | Wi-Fi profile store | JFFS2 private root; stdin-only bounded replacement and `bloomctl network profile upsert ID`; requested/document ID binding; explicit `network forget ID`; inactive-ID deletion with active-profile refusal; active forget only after a separately confirmed fallback connection; schema/unknown-field/count/size enforcement; WPA2-PSK, explicit open, and manual hidden WPA2; WEP and hidden open denial; unique IDs/SSIDs; redacted status; explicit local credential-free UI list with one active marker; mode-0600 atomic single-profile supplicant projection; symlink, occupied-slot, live-lock, invalid-profile, and prior-byte preservation | | Wi-Fi scan API | Asynchronous start; redacted status; explicit ID-scoped local results; cancel and terminal discard; one active worker; five-second rate limit; 15-second runtime; 64-KiB raw ceiling; 32 unique printable SSIDs; eight-operation history; open/WPA2-PSK/unsupported classification; duplicate signal reconciliation; backend failure, timeout, oversize, unsafe-path, stale/live-lock, and unsupported-hardware handling | -| Wi-Fi UI | Original Mini hidden; Settings, Shell Quick Settings, and GameSwitcher Quick Settings converge on `--settings-wifi`; direct radio toggle removed; bounded fixed-argv profile-list load outside render; connection state and saved-network detail; selectable Scan/Cancel row and read-only discovered networks; active-only 100-ms SDL wake; deferred GameSwitcher until cancellation discard; single-flight nonblocking fixed-argv scan client; 16-KiB output and five-second deadline; child process-group cleanup; controller-owned start/status/results/discard and cancel-during-start sequencing; 250-ms status opportunities; exact profile/scan/ack schemas; 16 profiles and 32 networks; unique bounded IDs/SSIDs; one active profile; fixed scan phases/errors; terminal history discarded; host-only open/WPA2 profile form with shared keyboard, masked secret, fixed profile-upsert argv, escaped stdin JSON, strict ID/SSID/PSK validation, explicit secret clearing, nonblocking submit/poll, 4-KiB acknowledgement cap, exact upsert response, timeout process-group cleanup, exact saved-ID reuse, 104-bit no-follow random new handles, collision/security/full-store rejection, and one editing/submitting controller with 100-ms poll opportunities, exact cancellation, terminal clearing, and bounded focus return; credential and unknown-field rejection; passwords absent from arguments, logs, screenshots, durable Shell state, and evidence | +| Wi-Fi UI | Original Mini hidden; Settings, Shell Quick Settings, and GameSwitcher Quick Settings converge on `--settings-wifi`; direct radio toggle removed; bounded fixed-argv profile-list load outside render; connection state and saved-network detail; selectable Scan/Cancel row and read-only discovered networks; active-only 100-ms SDL wake; deferred GameSwitcher until cancellation discard; single-flight nonblocking fixed-argv scan client; 16-KiB output and five-second deadline; child process-group cleanup; controller-owned start/status/results/discard and cancel-during-start sequencing; 250-ms status opportunities; exact profile/scan/ack schemas; 16 profiles and 32 networks; unique bounded IDs/SSIDs; one active profile; fixed scan phases/errors; terminal history discarded; open/WPA2 profile form with shared keyboard, masked secret, fixed profile-upsert argv, escaped stdin JSON, strict ID/SSID/PSK validation, explicit secret clearing, nonblocking submit/poll, 4-KiB acknowledgement cap, exact upsert response, timeout process-group cleanup, exact saved-ID reuse, 104-bit no-follow random new handles, collision/security/full-store rejection, one editing/submitting controller with 100-ms poll opportunities, exact cancellation, terminal clearing, bounded focus return, supported scan-result selection, explicit saved-not-connected result, and no implicit association; credential and unknown-field rejection; passwords absent from arguments, logs, screenshots, durable Shell state, and evidence | | GameSwitcher | MENU open/toggle; Left/Right select; L1/R1 page; A resume; X actions; START Quick Settings; B Home; no inherited brightness/view/delete behavior | | Apps/Tweaks | Every app disposition recorded; normal configuration requires no Tweaks; supervised launch remains bounded | | Persistent state | Versioned atomic publication; exact restoration across Details/game/GameSwitcher/Quick Settings; malformed/oversized/symlink input defaults without Safe Mode | diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index b9853c00e..5fee12fad 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -603,7 +603,16 @@ single-flight upsert request. It admits input only while editing, polls only at every terminal result, and kills and reaps the exact request process group on cancel or destroy. The controller retains only the bounded non-secret list row needed to return focus after completion, failure, or cancellation. It remains -unreachable from the rendered Shell in this host-only slice. +unreachable from the rendered Shell in its host-only foundation slice. + +The rendered integration activates that controller only for a supported row in +the already-bounded scan result list. WPA2 uses the shared masked keyboard; +explicit open networks expose no secret field. START stores the profile through +the controller's fixed stdin-only upsert request, B deletes one password byte or +cancels at the empty form, and MENU cancels before GameSwitcher. Terminal state +returns to the exact bounded row and reports `Network saved - not connected` on +success. No result from this surface projects, associates, restarts, enables, or +disables the station. Profile association reuses the one guarded network-transaction authority rather than adding a second recovery mechanism. `begin` copies the exact current diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 970136c8a..60e7becf4 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -413,6 +413,10 @@ request process group on cancellation or teardown. Its `return_item` is a bounded non-secret row index for deterministic focus restoration; it is not a profile, SSID, or association authority. The controller remains host-only until the rendered integration slice adds explicit input and cancellation ownership. +That integration may open the form only from a supported bounded scan row. It +must label a successful upsert as saved but not connected, return focus to the +originating row, cancel before GameSwitcher or Shell teardown, and never invoke +profile projection, association, radio power, DHCP, or listener reconciliation. The NET-04 scan slice is asynchronous and keeps result data only under `/tmp`: diff --git a/docs/SECURITY.md b/docs/SECURITY.md index df3c95ac3..877aefd2f 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -183,6 +183,9 @@ submission clears the password before returning; every terminal, cancellation, and teardown path clears the complete form; and cancellation kills and reaps the exact child process group. Only a bounded non-secret focus row survives a terminal transition. This controller has no rendered or association consumer in -its initial slice. +its initial slice. The rendered follow-up invokes it only from a supported +bounded scan row, renders password state solely through the masked label, and +returns a non-secret `saved - not connected` result. It adds no projection, +association, network transaction, radio, DHCP, listener, reboot, or update path. Security-sensitive migrations and repair operations must be bounded, logged, reversible where possible, and validated before activation. diff --git a/docs/program/BloomOS_Work_Program_v3.json b/docs/program/BloomOS_Work_Program_v3.json index 384d2dcde..d06ac309f 100644 --- a/docs/program/BloomOS_Work_Program_v3.json +++ b/docs/program/BloomOS_Work_Program_v3.json @@ -889,12 +889,13 @@ 550, 551, 552, - 553 + 553, + 554 ], "risk_tier": 1, "deployment_tier": "hot", "device_gate_required": true, - "last_audited_main": "7c67002caf89e767d318d97f751a0b92e93d827e", + "last_audited_main": "ad66ede878c07e2c7877361ca08feb22b6890f97", "evidence": [ "issue #544 tracks NET-05 on current main after merged NET-04A-D and UI-05; the audit found that Bloom Shell still treats Wi-Fi as an inline toggle whose A and left/right handlers synchronously call bloom-network enable or disable instead of opening the bounded profile and scan UI", "the first host-safe slice adds an explicit local credential-free projection of at most 16 validated profile IDs, printable SSIDs, security classes, hidden flags, and one active marker; the private service never returns a PSK and the Shell model rejects unknown or credential-bearing fields", @@ -914,7 +915,9 @@ "GitHub PR #551 passed format in 14 seconds, the complete 58-test affected shell selector in 50 seconds, AddressSanitizer in 1 minute 15 seconds, native tests in 1 minute 20 seconds, the affected Bloom Shell ARM build in 1 minute 23 seconds, and scoped static analysis in 2 minutes 18 seconds; it merged by squash as 49f0a8096bff9a9157e4ce176201d2f532bb74dd", "GitHub PR #552 adds a host-only selection boundary that requires a current validated profile snapshot, reuses an exact saved SSID only when its security matches, creates a 104-bit high-entropy no-follow random handle for a new network, and rejects malformed state, unsupported security, full stores, security mismatch, unavailable state, and bounded collision exhaustion before any form or request can be created", "GitHub PR #552 passed formatting in 13 seconds, the complete 58-test affected shell selector in 45 seconds, the affected Bloom Shell ARM build in 1 minute 12 seconds, native tests in 1 minute 15 seconds, AddressSanitizer in 1 minute 22 seconds, and scoped static analysis in 1 minute 45 seconds; it merged by squash as 7c67002caf89e767d318d97f751a0b92e93d827e", - "GitHub PR #553 gives the credential form one host-only lifecycle owner with editing and submitting states, 100-ms monotonic poll opportunities, input rejection outside editing, exact request cancellation and teardown, terminal form clearing, and a bounded non-secret return row for deterministic focus restoration; it still has no rendered, association, radio, or device consumer" + "GitHub PR #553 gives the credential form one host-only lifecycle owner with editing and submitting states, 100-ms monotonic poll opportunities, input rejection outside editing, exact request cancellation and teardown, terminal form clearing, and a bounded non-secret return row for deterministic focus restoration; it still has no rendered, association, radio, or device consumer", + "GitHub PR #553 passed formatting in 16 seconds, the complete 58-test affected shell selector in 55 seconds, native tests in 1 minute 8 seconds, AddressSanitizer in 1 minute 19 seconds, the affected Bloom Shell ARM build in 1 minute 29 seconds, and scoped static analysis in 2 minutes 16 seconds; it merged by squash as ad66ede878c07e2c7877361ca08feb22b6890f97", + "GitHub PR #554 opens the controller only from a supported bounded scan row, uses the shared masked keyboard for WPA2, stores through the fixed nonblocking upsert request, cancels before GameSwitcher or focus return, reports success explicitly as saved but not connected, and adds no projection, association, radio, DHCP, listener, reboot, update, or device action" ] }, { diff --git a/src/bloomShell/main.c b/src/bloomShell/main.c index 90d78158e..6287b2435 100644 --- a/src/bloomShell/main.c +++ b/src/bloomShell/main.c @@ -2043,7 +2043,7 @@ static void draw_wifi_details(SDL_Surface *screen, const BloomUiLayout *layout, const BloomShellQuickValues *quick_values, const BloomShellWifiUi *wifi, const BloomShellWifiController *controller, - const BloomUiFocus *focus) + const BloomUiFocus *focus, int profile_result) { SDL_Color cream = {243, 226, 189, 0}; SDL_Color sand = {214, 187, 137, 0}; @@ -2063,12 +2063,17 @@ static void draw_wifi_details(SDL_Surface *screen, const BloomUiLayout *layout, layout->content.y + layout->row_height, layout->content.width - 40, sand); render_copy_label(screen, compact_font, - controller->state == BLOOM_SHELL_WIFI_CONTROLLER_COMPLETE + profile_result > 0 + ? "Network saved - not connected" + : profile_result < 0 + ? "Network could not be saved" + : controller->state == BLOOM_SHELL_WIFI_CONTROLLER_COMPLETE ? "Available networks" : "Saved networks", layout->content.x + 20, layout->content.y + layout->row_height * 2, - layout->content.width - 40, sand); + layout->content.width - 40, + profile_result < 0 ? (SDL_Color){226, 169, 59, 0} : sand); int top = layout->content.y + layout->row_height * 3; size_t visible_rows = layout->visible_rows > 3 ? layout->visible_rows - 3 : 1; for (size_t row = 0; @@ -2137,6 +2142,62 @@ static void draw_wifi_details(SDL_Surface *screen, const BloomUiLayout *layout, } } +static void draw_wifi_profile_form( + SDL_Surface *screen, const BloomUiLayout *layout, TTF_Font *font, + const BloomUiTheme *theme, + const BloomShellWifiProfileController *controller) +{ + SDL_Color cream = {243, 226, 189, 0}; + SDL_Color sand = {214, 187, 137, 0}; + if (controller->state == BLOOM_SHELL_WIFI_PROFILE_CONTROLLER_SUBMITTING) { + BloomUiRect panel = layout->content; + if (bloom_ui_render_panel(screen, theme, &panel, 0) != 0) + return; + render_copy_label(screen, font, "Saving network...", + layout->content.x + 20, + layout->content.y + layout->row_height, + layout->content.width - 40, cream); + render_copy_label(screen, font, "B Cancel", + layout->content.x + 20, + layout->content.y + layout->row_height * 2, + layout->content.width - 40, sand); + return; + } + if (controller->state != BLOOM_SHELL_WIFI_PROFILE_CONTROLLER_EDITING) + return; + const BloomShellWifiProfileForm *form = &controller->form; + if (form->security == BLOOM_SHELL_WIFI_SECURITY_WPA2_PSK) { + if (bloom_ui_render_keyboard(screen, layout, &form->keyboard) != 0) + return; + } + else { + BloomUiRect panel = layout->content; + if (bloom_ui_render_panel(screen, theme, &panel, 0) != 0) + return; + } + const BloomUiTextFragment network_label[] = { + {BLOOM_UI_TEXT_FRAGMENT_COPY, "Network: "}, + {BLOOM_UI_TEXT_FRAGMENT_DATA, form->ssid}, + }; + render_fragment_label(screen, font, network_label, 2, + layout->content.x + 16, layout->content.y + 8, + layout->content.width - 32, cream); + char label[96]; + if (bloom_shell_wifi_profile_form_label(form, label, sizeof(label)) == 0) + render_copy_label(screen, font, label, layout->content.x + 16, + layout->content.y + 40, + layout->content.width - 32, sand); + render_copy_label( + screen, font, + form->security == BLOOM_SHELL_WIFI_SECURITY_WPA2_PSK + ? "A Type B Delete X Mode START Save" + : "START Save B Cancel", + layout->content.x + 16, layout->content.y + 72, + layout->content.width - 32, cream); + if (form->security == BLOOM_SHELL_WIFI_SECURITY_WPA2_PSK) + draw_keyboard_glyphs(screen, layout, font, &form->keyboard); +} + static void draw_remote_key_remove_confirm(SDL_Surface *screen, const BloomUiLayout *layout, TTF_Font *font, const BloomUiDialogFocus *dialog) @@ -2734,7 +2795,9 @@ static void draw(SDL_Surface *screen, SDL_Surface *video, const BloomUiLayout *l const BloomShellQuickValues *quick_values, const BloomShellNetworkCredential *network_credential, const BloomShellWifiUi *wifi, - const BloomShellWifiController *wifi_controller, int wifi_open, + const BloomShellWifiController *wifi_controller, + const BloomShellWifiProfileController *wifi_profile_controller, + int wifi_profile_result, int wifi_open, const BloomUiFocus *wifi_focus, const BloomShellRemoteKeys *remote_keys, int remote_keys_open, const BloomUiFocus *remote_keys_focus, int remote_keys_result, @@ -3114,6 +3177,15 @@ static void draw(SDL_Surface *screen, SDL_Surface *video, const BloomUiLayout *l theme); const char *footer = quick_settings ? "Left/Right Change A Toggle/Open B/START Close" + : bloom_shell_wifi_profile_controller_active( + wifi_profile_controller) + ? wifi_profile_controller->state == + BLOOM_SHELL_WIFI_PROFILE_CONTROLLER_SUBMITTING + ? "B Cancel" + : wifi_profile_controller->form.security == + BLOOM_SHELL_WIFI_SECURITY_WPA2_PSK + ? "A Type B Delete X Mode START Save" + : "START Save B Cancel" : wifi_open ? bloom_shell_wifi_controller_active(wifi_controller) ? "A Cancel B Settings START Quick" @@ -3154,7 +3226,11 @@ static void draw(SDL_Surface *screen, SDL_Surface *video, const BloomUiLayout *l remote_keys_result); if (wifi_open && !quick_settings) draw_wifi_details(screen, layout, font, compact_font, theme, quick_values, - wifi, wifi_controller, wifi_focus); + wifi, wifi_controller, wifi_focus, wifi_profile_result); + if (bloom_shell_wifi_profile_controller_active(wifi_profile_controller) && + !quick_settings) + draw_wifi_profile_form(screen, layout, font, theme, + wifi_profile_controller); if (ra_form_open) draw_ra_form(screen, layout, font, ra_form); if (search->open) @@ -3494,6 +3570,9 @@ int main(int argc, char **argv) bloom_shell_wifi_ui_init(&wifi_ui); BloomShellWifiController wifi_controller; bloom_shell_wifi_controller_init(&wifi_controller); + BloomShellWifiProfileController wifi_profile_controller; + bloom_shell_wifi_profile_controller_init(&wifi_profile_controller); + int wifi_profile_result = 0; int wifi_game_switcher_pending = 0; int wifi_open = wifi_requested && !safe_mode && capabilities.wifi; if (wifi_open) @@ -3577,7 +3656,7 @@ int main(int argc, char **argv) immutable_compact_font, destination, &root, &games_browser, &favorites_focus, &recent_focus, &all_games_focus, &collections, games, &achievement_index, favorites, recents, has_recent, &search, browse, game_list_view, &library_menu, &filter_picker, &details, jump_open, jump_letter, &settings_focus, &apps_focus, apps, &status, &capabilities, &quick_values, &network_credential, &wifi_ui, &wifi_controller, - wifi_open, &wifi_focus, + &wifi_profile_controller, wifi_profile_result, wifi_open, &wifi_focus, &remote_keys, remote_keys_open, &remote_keys_focus, remote_keys_result, remote_key_remove_confirm_open, &remote_key_remove_dialog, &ra_values, support_export_result, @@ -3600,6 +3679,12 @@ int main(int argc, char **argv) while (running) { if (game_switcher_requested) { game_switcher_requested = 0; + if (bloom_shell_wifi_profile_controller_active( + &wifi_profile_controller)) { + bloom_shell_wifi_profile_controller_cancel( + &wifi_profile_controller); + wifi_profile_result = 0; + } if (bloom_shell_wifi_controller_active(&wifi_controller) || wifi_ui.scan_id[0] != '\0') { wifi_game_switcher_pending = 1; @@ -3683,11 +3768,30 @@ int main(int argc, char **argv) else if (event.type == WIFI_EVENT_TYPE) { bloom_shell_wifi_controller_poll(&wifi_controller, BLOOMCTL_BINARY, &wifi_ui, SDL_GetTicks()); + if (wifi_profile_controller.state == + BLOOM_SHELL_WIFI_PROFILE_CONTROLLER_SUBMITTING) { + int profile_poll = bloom_shell_wifi_profile_controller_poll( + &wifi_profile_controller, SDL_GetTicks()); + if (profile_poll != 0) { + wifi_profile_result = profile_poll > 0 ? 1 : -1; + if (profile_poll > 0) + bloom_shell_wifi_profiles_load(BLOOMCTL_BINARY, &wifi_ui); + bloom_ui_focus_set_count( + &wifi_focus, + wifi_detail_item_count(&wifi_ui, &wifi_controller), + layout.visible_rows > 3 ? layout.visible_rows - 3 : 1); + focus_select( + &wifi_focus, wifi_profile_controller.return_item, + layout.visible_rows > 3 ? layout.visible_rows - 3 : 1); + } + } bloom_ui_focus_set_count( &wifi_focus, wifi_detail_item_count(&wifi_ui, &wifi_controller), layout.visible_rows > 3 ? layout.visible_rows - 3 : 1); - if (!bloom_shell_wifi_controller_active(&wifi_controller)) { + if (!bloom_shell_wifi_controller_active(&wifi_controller) && + wifi_profile_controller.state != + BLOOM_SHELL_WIFI_PROFILE_CONTROLLER_SUBMITTING) { if (wifi_timer != NULL) { SDL_RemoveTimer(wifi_timer); wifi_timer = NULL; @@ -3776,6 +3880,88 @@ int main(int argc, char **argv) } else if (library_menu.operations.open && action != BLOOM_UI_ACTION_NONE) continue; + else if (bloom_shell_wifi_profile_controller_active( + &wifi_profile_controller) && + action == BLOOM_UI_ACTION_GAME_SWITCHER) { + bloom_shell_wifi_profile_controller_cancel( + &wifi_profile_controller); + wifi_profile_result = 0; + if (run_game_switcher_child( + destination == BLOOM_UI_DESTINATION_ROOT) > 0) { + exit_code = LAUNCH_READY_EXIT; + running = 0; + } + } + else if (bloom_shell_wifi_profile_controller_active( + &wifi_profile_controller) && + action == BLOOM_UI_ACTION_QUICK_SETTINGS) { + if (wifi_profile_controller.state == + BLOOM_SHELL_WIFI_PROFILE_CONTROLLER_EDITING) { + if (bloom_shell_wifi_profile_controller_submit( + &wifi_profile_controller, BLOOMCTL_BINARY, + SDL_GetTicks()) == 0) { + if (wifi_timer_start(&wifi_timer) != 0) { + bloom_shell_wifi_profile_controller_cancel( + &wifi_profile_controller); + wifi_profile_result = -1; + } + } + else + wifi_profile_result = -1; + } + } + else if (bloom_shell_wifi_profile_controller_active( + &wifi_profile_controller) && + action == BLOOM_UI_ACTION_BACK) { + if (wifi_profile_controller.state == + BLOOM_SHELL_WIFI_PROFILE_CONTROLLER_EDITING && + wifi_profile_controller.form.security == + BLOOM_SHELL_WIFI_SECURITY_WPA2_PSK && + wifi_profile_controller.form.psk[0] != '\0') + bloom_shell_wifi_profile_controller_backspace( + &wifi_profile_controller); + else { + bloom_shell_wifi_profile_controller_cancel( + &wifi_profile_controller); + wifi_profile_result = 0; + focus_select( + &wifi_focus, wifi_profile_controller.return_item, + layout.visible_rows > 3 ? layout.visible_rows - 3 : 1); + } + } + else if (bloom_shell_wifi_profile_controller_active( + &wifi_profile_controller) && + action == BLOOM_UI_ACTION_FOCUS_UP) + bloom_shell_wifi_profile_controller_move( + &wifi_profile_controller, 0, -1); + else if (bloom_shell_wifi_profile_controller_active( + &wifi_profile_controller) && + action == BLOOM_UI_ACTION_FOCUS_DOWN) + bloom_shell_wifi_profile_controller_move( + &wifi_profile_controller, 0, 1); + else if (bloom_shell_wifi_profile_controller_active( + &wifi_profile_controller) && + action == BLOOM_UI_ACTION_FOCUS_LEFT) + bloom_shell_wifi_profile_controller_move( + &wifi_profile_controller, -1, 0); + else if (bloom_shell_wifi_profile_controller_active( + &wifi_profile_controller) && + action == BLOOM_UI_ACTION_FOCUS_RIGHT) + bloom_shell_wifi_profile_controller_move( + &wifi_profile_controller, 1, 0); + else if (bloom_shell_wifi_profile_controller_active( + &wifi_profile_controller) && + action == BLOOM_UI_ACTION_CONFIRM) + bloom_shell_wifi_profile_controller_append( + &wifi_profile_controller); + else if (bloom_shell_wifi_profile_controller_active( + &wifi_profile_controller) && + action == BLOOM_UI_ACTION_CONTEXT) + bloom_shell_wifi_profile_controller_cycle_mode( + &wifi_profile_controller); + else if (bloom_shell_wifi_profile_controller_active( + &wifi_profile_controller)) + continue; else if (quick_settings && action == BLOOM_UI_ACTION_GAME_SWITCHER) { quick_settings = 0; if (bloom_shell_wifi_controller_active(&wifi_controller) || @@ -3829,6 +4015,7 @@ int main(int argc, char **argv) bloom_ui_focus_init( &wifi_focus, wifi_detail_item_count(&wifi_ui, &wifi_controller)); + wifi_profile_result = 0; wifi_open = 1; } } @@ -3872,6 +4059,7 @@ int main(int argc, char **argv) layout.visible_rows > 3 ? layout.visible_rows - 3 : 1); else if (wifi_open && action == BLOOM_UI_ACTION_CONFIRM && wifi_focus.selected == 0) { + wifi_profile_result = 0; int result = bloom_shell_wifi_controller_active(&wifi_controller) || wifi_ui.scan_id[0] != '\0' ? bloom_shell_wifi_controller_cancel( @@ -3887,6 +4075,16 @@ int main(int argc, char **argv) wifi_detail_item_count(&wifi_ui, &wifi_controller), layout.visible_rows > 3 ? layout.visible_rows - 3 : 1); } + else if (wifi_open && action == BLOOM_UI_ACTION_CONFIRM && + wifi_controller.state == BLOOM_SHELL_WIFI_CONTROLLER_COMPLETE && + wifi_focus.selected > 0 && + wifi_focus.selected - 1 < wifi_ui.network_count) { + wifi_profile_result = 0; + if (bloom_shell_wifi_profile_controller_begin( + &wifi_profile_controller, &wifi_ui, + wifi_focus.selected - 1) != 0) + wifi_profile_result = -1; + } else if (wifi_open && action == BLOOM_UI_ACTION_BACK) { if (bloom_shell_wifi_controller_active(&wifi_controller) || wifi_ui.scan_id[0] != '\0') { @@ -5048,6 +5246,7 @@ int main(int argc, char **argv) bloom_ui_focus_init( &wifi_focus, wifi_detail_item_count(&wifi_ui, &wifi_controller)); + wifi_profile_result = 0; wifi_open = 1; } else if (action == BLOOM_UI_ACTION_CONFIRM && @@ -5193,7 +5392,8 @@ int main(int argc, char **argv) immutable_font, immutable_compact_font, destination, &root, &games_browser, &favorites_focus, &recent_focus, &all_games_focus, &collections, games, &achievement_index, favorites, recents, has_recent, &search, browse, game_list_view, &library_menu, &filter_picker, &details, jump_open, jump_letter, &settings_focus, &apps_focus, apps, &status, &capabilities, &quick_values, &network_credential, - &wifi_ui, &wifi_controller, wifi_open, &wifi_focus, &remote_keys, + &wifi_ui, &wifi_controller, &wifi_profile_controller, + wifi_profile_result, wifi_open, &wifi_focus, &remote_keys, remote_keys_open, &remote_keys_focus, remote_keys_result, remote_key_remove_confirm_open, &remote_key_remove_dialog, &ra_values, support_export_result, @@ -5220,7 +5420,8 @@ int main(int argc, char **argv) &games_browser, &favorites_focus, &recent_focus, &all_games_focus, &collections, games, &achievement_index, favorites, recents, has_recent, &search, browse, game_list_view, &library_menu, &filter_picker, &details, jump_open, jump_letter, &settings_focus, &apps_focus, apps, &status, &capabilities, &quick_values, &network_credential, - &wifi_ui, &wifi_controller, wifi_open, &wifi_focus, &remote_keys, + &wifi_ui, &wifi_controller, &wifi_profile_controller, + wifi_profile_result, wifi_open, &wifi_focus, &remote_keys, remote_keys_open, &remote_keys_focus, remote_keys_result, remote_key_remove_confirm_open, &remote_key_remove_dialog, &ra_values, support_export_result, @@ -5245,7 +5446,8 @@ int main(int argc, char **argv) &games_browser, &favorites_focus, &recent_focus, &all_games_focus, &collections, games, &achievement_index, favorites, recents, has_recent, &search, browse, game_list_view, &library_menu, &filter_picker, &details, jump_open, jump_letter, &settings_focus, &apps_focus, apps, &status, &capabilities, &quick_values, &network_credential, - &wifi_ui, &wifi_controller, wifi_open, &wifi_focus, &remote_keys, + &wifi_ui, &wifi_controller, &wifi_profile_controller, + wifi_profile_result, wifi_open, &wifi_focus, &remote_keys, remote_keys_open, &remote_keys_focus, remote_keys_result, remote_key_remove_confirm_open, &remote_key_remove_dialog, &ra_values, support_export_result, @@ -5280,6 +5482,7 @@ int main(int argc, char **argv) SDL_RemoveTimer(game_switcher_wake_timer); bloom_shell_library_refresh_destroy(&library_menu.refresh); bloom_shell_wifi_controller_destroy(&wifi_controller); + bloom_shell_wifi_profile_controller_destroy(&wifi_profile_controller); if (media_ready) bloom_shell_media_runtime_destroy(&media_runtime); if (theme_media_ready) diff --git a/test/shell/bloom_shell_settings.bats b/test/shell/bloom_shell_settings.bats index 1fa901ea6..81f915833 100644 --- a/test/shell/bloom_shell_settings.bats +++ b/test/shell/bloom_shell_settings.bats @@ -99,6 +99,19 @@ grep -F 'WIFI_EVENT_TYPE' "$shell" grep -F 'wifi_focus.selected == 0' "$shell" grep -F 'BLOOM_SHELL_WIFI_CONTROLLER_COMPLETE' "$shell" + grep -F 'draw_wifi_profile_form(' "$shell" + grep -F '"Network saved - not connected"' "$shell" + grep -F 'bloom_shell_wifi_profile_controller_begin(' "$shell" + grep -F 'bloom_shell_wifi_profile_controller_submit(' "$shell" + grep -F 'bloom_shell_wifi_profile_controller_poll(' "$shell" + grep -F 'bloom_shell_wifi_profile_controller_cancel(' "$shell" + grep -F 'bloom_shell_wifi_profile_controller_destroy(' "$shell" + grep -F 'BLOOM_SHELL_WIFI_PROFILE_CONTROLLER_SUBMITTING' "$shell" + grep -F 'wifi_profile_controller.return_item' "$shell" + form_render=$(sed -n '/static void draw_wifi_profile_form(/,/^}/p' "$shell") + [[ "$form_render" != *'psk'* ]] + ! grep -F 'network connect' "$shell" + ! grep -F 'wifi-profile-connect' "$shell" grep -F 'if (quick_capabilities.wifi && row == 2)' "$switcher" grep -F 'appState.exit_to_wifi = true;' "$switcher" ! grep -F 'run_network_request' "$model"