Files
vanilla/gui/menu/menu_connection.c
T
itsmattkc 1b2c4fa86c large scale android improvements
- Port vanilla-pipe on Android for users with rooted devices and compatible Wi-Fi hardware
- Drop OpenSSL as a build dependency (only used for hostap's crypto backend, but didn't work on Android so now we're using hostap's internal crypto)
- Improvements to event loop (targets 180Hz input polling + 60Hz video display)
- Implement MediaCodec hardware decoding on Android
- Use smaller and simpler DHCP client
2026-08-26 14:02:12 -07:00

350 lines
12 KiB
C

#include "menu_connection.h"
#ifdef _WIN32
#include <winsock2.h>
typedef uint32_t in_addr_t;
#else
#include <arpa/inet.h>
#ifdef ANDROID
#include <net/if.h>
#endif
#endif
#include <dirent.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include "config.h"
#include "lang.h"
#include "menu_common.h"
#include "menu_settings.h"
#include "pipemgmt.h"
#include "platform.h"
#include "ui/ui_anim.h"
#include "ui/ui_util.h"
static int bglayer;
static int fglayer;
static int error_lbl;
static int ip_textedit;
#define MAX_WIRELESS_ENTRIES 4
static size_t detected_wireless_interface_count = 0;
static char detected_wireless_interfaces[256][256];
#define MAX_DETECTED_WIRELESS_INTERFACES (sizeof(detected_wireless_interfaces) / sizeof(detected_wireless_interfaces[0]))
static int intf_start = 0; // NOTE: Not currently implemented, but may be used for pagination in the future
static int intf_buttons[MAX_WIRELESS_ENTRIES];
static const char *checkmark = 0;//"checkmark.svg";
static vui_callback_t g_continue_callback = 0;
static void *g_continue_callback_data = 0;
static vui_callback_t g_fail_callback = 0;
static void *g_fail_callback_data = 0;
static void return_to_settings(vui_context_t *vui, int btn, void *v)
{
int layer = (intptr_t) v;
vui_callback_t cb = g_fail_callback ? g_fail_callback : vpi_menu_settings;
void *cb_data = g_fail_callback ? g_fail_callback_data : 0;
vui_transition_fade_layer_out(vui, layer, cb, cb_data);
}
static void return_to_connection(vui_context_t *vui, int btn, void *v)
{
int layer = (intptr_t) v;
vui_callback_t cb;
void *cb_data;
if (g_continue_callback) {
cb = g_continue_callback;
cb_data = g_continue_callback_data;
} else {
cb = vpi_menu_settings;
cb_data = NULL;
layer = bglayer; // Returning to settings, so we should fade out the entire background
}
vui_transition_fade_layer_out(vui, layer, cb, cb_data);
}
static void add_wireless_interface(const char *intf)
{
// Sanity checks
if (!intf || !intf[0] || detected_wireless_interface_count >= MAX_DETECTED_WIRELESS_INTERFACES) {
return;
}
// Ensure interface hasn't already been added
for (size_t i = 0; i < detected_wireless_interface_count; i++) {
if (!strcmp(detected_wireless_interfaces[i], intf)) {
return;
}
}
// Add interface
vui_strncpy(
detected_wireless_interfaces[detected_wireless_interface_count],
intf,
sizeof(detected_wireless_interfaces[detected_wireless_interface_count]));
detected_wireless_interface_count++;
}
static void populate_wireless_intf(void)
{
#ifdef ANDROID
static const char *android_prefixes[] = {"wlan", "wifi", "swlan", "mlan", "ath"};
static const size_t android_prefix_count = sizeof(android_prefixes) / sizeof(android_prefixes[0]);
#endif
struct dirent *dir;
DIR *d = opendir("/sys/class/net");
char buf[1024];
detected_wireless_interface_count = 0;
if (d) {
while ((dir = readdir(d)) != 0) {
const char *intf = dir->d_name;
if (intf[0] != '.') {
snprintf(buf, sizeof(buf), "/sys/class/net/%s/wireless", intf);
DIR *wifi_check = opendir(buf);
if (wifi_check) {
add_wireless_interface(intf);
closedir(wifi_check);
} else {
#ifdef ANDROID
// Fallback heuristic approach for Android
for (size_t prefix = 0; prefix < android_prefix_count; prefix++) {
const char *p = android_prefixes[prefix];
if (!strncmp(intf, p, strlen(p))) {
add_wireless_interface(intf);
break;
}
}
#endif
}
}
}
closedir(d);
}
#ifdef ANDROID
// Some Android releases may restrict /sys/class/net altogether. Probe
// heuristically.
for (size_t prefix = 0; prefix < android_prefix_count; prefix++) {
for (unsigned int index = 0; index < 8; index++) {
char intf[IF_NAMESIZE];
snprintf(intf, sizeof(intf), "%s%u", android_prefixes[prefix], index);
if (if_nametoindex(intf)) {
add_wireless_interface(intf);
}
}
}
#endif
}
static void intf_pressed(vui_context_t *vui, int button, void *v)
{
for (int i = 0; i < MAX_WIRELESS_ENTRIES; i++) {
if (button == intf_buttons[i]) {
vpi_config.server_address = VANILLA_ADDRESS_LOCAL;
vui_strncpy(vpi_config.wireless_interface, detected_wireless_interfaces[intf_start + i], sizeof(vpi_config.wireless_interface));
vpi_config.connection_setup = 1;
vpi_stop_pipe();
vpi_config_save();
break;
}
}
return_to_connection(vui, button, v);
}
static void update_wireless_buttons(vui_context_t *vui)
{
for (int i = 0; i < MAX_WIRELESS_ENTRIES; i++) {
const char *intf = detected_wireless_interfaces[intf_start + i];
int b = intf_buttons[i];
if (intf[0] == 0) {
vui_button_update_visible(vui, b, 0);
} else {
vui_button_update_visible(vui, b, 1);
vui_button_update_text(vui, b, intf);
int sel = !strcmp(intf, vpi_config.wireless_interface);
vui_button_update_checked(vui, b, sel);
vui_button_update_icon(vui, b, sel ? checkmark : 0);
}
}
}
static void local_connection_menu(vui_context_t *vui, void *v)
{
vui_reset(vui);
int bglayer = vui_layer_create(vui);
fglayer = vui_layer_create(vui);
vui_rect_t bkg_rect;
int margin;
vpi_menu_create_background(vui, bglayer, &bkg_rect, &margin);
const int lbl_margin = margin * 6;
vui_label_create(vui, bkg_rect.x + lbl_margin, bkg_rect.y + lbl_margin, bkg_rect.w - lbl_margin - lbl_margin, bkg_rect.h - lbl_margin - lbl_margin, lang(VPI_LANG_LOCAL_CONNECTION_HELP), vui_color_create(1,1,1,1), VUI_FONT_SIZE_NORMAL, fglayer);
// Detect wireless interfaces
memset(detected_wireless_interfaces, 0, sizeof(detected_wireless_interfaces));
populate_wireless_intf();
const int button_w = bkg_rect.w/2;
const int button_h = BTN_SZ*2/3;
const int button_x = bkg_rect.x+bkg_rect.w/2-button_w/2;
const int button_y = bkg_rect.y+bkg_rect.h*2/5;
for (int i = 0; i < MAX_WIRELESS_ENTRIES; i++) {
intf_buttons[i] = vui_button_create(vui, button_x, button_y+button_h*i, button_w, button_h, "intf", 0, VUI_BUTTON_STYLE_BUTTON, fglayer, intf_pressed, (void *) (intptr_t) fglayer);
}
update_wireless_buttons(vui);
vpi_menu_create_back_button(vui, fglayer, return_to_connection, (void *) (intptr_t) fglayer);
vui_transition_fade_layer_in(vui, fglayer, 0, 0);
}
static void check_ip_address(vui_context_t *vui, int btn, void *v)
{
char buf[256];
vui_textedit_get_text(vui, ip_textedit, buf, sizeof(buf));
in_addr_t ip = inet_addr(buf);
int valid = (ip != (in_addr_t) -1);
vui_label_update_visible(vui, error_lbl, !valid);
if (valid) {
vpi_config.server_address = ip;
vpi_config.connection_setup = 1;
vpi_stop_pipe();
vpi_config_save();
return_to_connection(vui, btn, v);
}
}
static void via_server_connection_menu(vui_context_t *vui, void *v)
{
vui_reset(vui);
int bglayer = vui_layer_create(vui);
fglayer = vui_layer_create(vui);
vui_rect_t bkg_rect;
int margin;
vpi_menu_create_background(vui, bglayer, &bkg_rect, &margin);
const int lbl_margin = margin * 6;
vui_label_create(vui, bkg_rect.x + lbl_margin, bkg_rect.y + lbl_margin, bkg_rect.w - lbl_margin - lbl_margin, bkg_rect.h - lbl_margin - lbl_margin, lang(VPI_LANG_SERVER_CONNECTION_HELP), vui_color_create(1,1,1,1), VUI_FONT_SIZE_NORMAL, fglayer);
const int textedit_w = bkg_rect.w/2;
const int textedit_h = vui_get_font_height(vui, VUI_FONT_SIZE_NORMAL);
const int textedit_x = bkg_rect.x + bkg_rect.w/2 - textedit_w/2;
const char *ip_text;
if (vpi_config.server_address == VANILLA_ADDRESS_LOCAL) {
ip_text = 0;
} else {
struct in_addr in;
in.s_addr = vpi_config.server_address;
ip_text = inet_ntoa(in);
}
ip_textedit = vui_textedit_create(vui, textedit_x, bkg_rect.y + bkg_rect.h * 3 / 7, textedit_w, textedit_h, ip_text, VUI_FONT_SIZE_NORMAL, 0, fglayer);
error_lbl = vui_label_create(vui, textedit_x, bkg_rect.y + bkg_rect.h * 4 / 7, textedit_w, BTN_SZ, "Invalid IP address", vui_color_create(1,0,0,1), VUI_FONT_SIZE_SMALL, fglayer);
vui_label_update_visible(vui, error_lbl, 0);
vui_button_create(vui, textedit_x, bkg_rect.y + bkg_rect.h * 5 / 7, textedit_w, BTN_SZ, lang(VPI_LANG_OK_BTN), 0, VUI_BUTTON_STYLE_BUTTON, fglayer, check_ip_address, (void *) (intptr_t) fglayer);
vpi_menu_create_back_button(vui, fglayer, return_to_connection, (void *) (intptr_t) fglayer);
vui_transition_fade_layer_in(vui, fglayer, 0, 0);
}
void connection_btn_pressed(vui_context_t *vui, int btn, void *v)
{
vui_callback_t cb = (vui_callback_t) v;
vui_transition_fade_layer_out(vui, fglayer, cb, 0);
}
void vpi_menu_connection_and_return_to(vui_context_t *vui, int fade_fglayer, vui_callback_t success_callback, void *success_data, vui_callback_t fail_callback, void *fail_data)
{
vui_reset(vui);
bglayer = vui_layer_create(vui);
fglayer = vui_layer_create(vui);
vui_rect_t bkg_rect;
int margin;
vpi_menu_create_background(vui, bglayer, &bkg_rect, &margin);
const int lbl_margin = margin * 6;
vui_label_create(vui, bkg_rect.x + lbl_margin, bkg_rect.y + lbl_margin, bkg_rect.w - lbl_margin - lbl_margin, bkg_rect.h - lbl_margin - lbl_margin, lang(VPI_LANG_CONNECTION_HELP_1), vui_color_create(1,1,1,1), VUI_FONT_SIZE_NORMAL, fglayer);
const int btn_w = bkg_rect.w/2;
const int btn_y = bkg_rect.y + bkg_rect.h * 2 / 4;
const int btn_x = bkg_rect.x + bkg_rect.w/2 - btn_w/2;
int is_local = vpi_config.server_address == VANILLA_ADDRESS_LOCAL;
int is_via_server = vpi_config.server_address != VANILLA_ADDRESS_LOCAL;
int local_available = 1;
const int lbl2_width = bkg_rect.w*2/3;
const char *lbl2_txt;
#ifdef VANILLA_PIPE_AVAILABLE
lbl2_txt = lang(VPI_LANG_CONNECTION_HELP_2);
#else
lbl2_txt = lang(VPI_LANG_NO_LOCAL_AVAILABLE);
is_local = 0;
local_available = 0;
#endif
#ifdef ANDROID
if (local_available && !vpi_android_has_root_access()) {
lbl2_txt = lang(VPI_LANG_ANDROID_ROOT_REQUIRED);
is_local = 0;
local_available = 0;
}
#endif
vui_label_create(vui, bkg_rect.x + bkg_rect.w/2 - lbl2_width/2, bkg_rect.y + lbl_margin + bkg_rect.h/8, lbl2_width, bkg_rect.h - lbl_margin - lbl_margin, lbl2_txt, vui_color_create(0.66f,0.66f,0.66f,1), VUI_FONT_SIZE_SMALL, fglayer);
int local_btn = vui_button_create(vui, btn_x, btn_y, btn_w, BTN_SZ, lang(VPI_LANG_LOCAL), is_local ? checkmark : 0, VUI_BUTTON_STYLE_BUTTON, fglayer, connection_btn_pressed, local_connection_menu);
int via_server_btn = vui_button_create(vui, btn_x, btn_y + BTN_SZ, btn_w, BTN_SZ, lang(VPI_LANG_VIA_SERVER), is_via_server ? checkmark : 0, VUI_BUTTON_STYLE_BUTTON, fglayer, connection_btn_pressed, via_server_connection_menu);
vui_button_update_checkable(vui, local_btn, 1);
vui_button_update_checkable(vui, via_server_btn, 1);
vui_button_update_checked(vui, local_btn, is_local);
vui_button_update_checked(vui, via_server_btn, is_via_server);
vui_button_update_enabled(vui, local_btn, local_available);
// Back button
vpi_menu_create_back_button(vui, fglayer, return_to_settings, (void *) (intptr_t) bglayer);
vui_transition_fade_layer_in(vui, fade_fglayer ? fglayer : bglayer, 0, 0);
g_continue_callback = success_callback;
g_continue_callback_data = success_data;
g_fail_callback = fail_callback;
g_fail_callback_data = fail_data;
}
void vpi_menu_connection(vui_context_t *vui, void *v)
{
vpi_menu_connection_and_return_to(vui, (intptr_t) v, 0, 0, 0, 0);
}