diff --git a/src/gsmenu/colmenu.c b/src/gsmenu/colmenu.c index 81a437d..1860959 100644 --- a/src/gsmenu/colmenu.c +++ b/src/gsmenu/colmenu.c @@ -17,6 +17,14 @@ extern lv_indev_t * indev_drv; extern int audio_get_enabled(void); extern void audio_set_enabled(int enabled); +/* Restream C API (gstrtpreceiver.cpp on device, stubs in simulator.c) — declared + * here rather than including gstrtpreceiver.h, which drags in gst/gst.h. */ +extern bool restream_get_enabled(void); +extern void restream_set_enabled(bool enabled); +extern void restream_scan_clients(char * buf, size_t buf_len); +extern const char * restream_get_manual_ip(void); +extern void restream_set_manual_ip(const char * ip); + static colstack_t * g_cs; /* True while the command-error dialog is up, so async teardowns (e.g. the text @@ -103,6 +111,24 @@ char * colmenu_get(const char * type, const char * page, const char * param, cha return strdup(audio_get_enabled() ? "1" : "0"); } + /* Restream is configured from pixelpilot.yaml and lives entirely inside the + * app — gsmenu.sh has no say in it. Serve its rows from the C API, here on + * read and in do_set() on write, so no shell round-trip happens at all. */ + if(strcmp(param, "restream_enabled") == 0) { + return strdup(restream_get_enabled() ? "1" : "0"); + } + if(strcmp(param, "restream_target") == 0) { + /* scan_clients already returns the dropdown's option list: "Auto" followed + * by one discovered/pinned IP per line. Empty manual ip == auto-discover. */ + if(opts) { + char clients[1024] = {0}; + restream_scan_clients(clients, sizeof(clients)); + *opts = strdup(clients); + } + const char * ip = restream_get_manual_ip(); + return strdup(ip && ip[0] ? ip : "Auto"); + } + char errf[] = "/tmp/gsmenu_gerr_XXXXXX"; int efd = mkstemp(errf); if(efd >= 0) close(efd); @@ -373,6 +399,18 @@ static void do_set_done(void * ctx, int rc) static void do_set(void * ctx, const char * value) { bind_ctx_t * b = ctx; + + /* Restream applies to the running app only (see colmenu_get) — apply it + * straight away and skip the gsmenu.sh set entirely. */ + if(b->param && strncmp(b->param, "restream_", 9) == 0) { + if(strcmp(b->param, "restream_enabled") == 0) + restream_set_enabled(value && strcmp(value, "on") == 0); + else if(strcmp(b->param, "restream_target") == 0) + restream_set_manual_ip(value ? value : ""); /* "Auto" → auto-discover */ + if(b->on_change) b->on_change(value ? value : ""); + return; + } + char cmd[320]; snprintf(cmd, sizeof(cmd), "gsmenu.sh set %s %s %s \"%s\"", b->type, b->page, b->param, value ? value : ""); diff --git a/src/gsmenu/colmenu_pages.c b/src/gsmenu/colmenu_pages.c index d4bc1d3..cd18e1a 100644 --- a/src/gsmenu/colmenu_pages.c +++ b/src/gsmenu/colmenu_pages.c @@ -442,13 +442,25 @@ static const colmenu_item_t sys_dvr_items[] = { { .kind=COLMENU_SWITCH, .label="Record OSD in DVR", .param="dvr_osd", .on_change=on_dvr_osd }, }; static const colmenu_page_t sys_dvr_page = { "DVR", "gs", "system", sys_dvr_items, 8 }; + +/* Restream to phone/laptop over the local WiFi. Both rows are served by the app + * itself, not gsmenu.sh — colmenu.c intercepts the "restream_" params on read + * and write. The Target dropdown's options are the discovered clients, with + * "Auto" (pick whoever shows up) as the first entry. */ +static const colmenu_item_t restream_items[] = { + { .kind=COLMENU_SWITCH, .icon=LV_SYMBOL_WIFI, .label="Enabled", .param="restream_enabled" }, + { .kind=COLMENU_DROPDOWN, .icon=LV_SYMBOL_WIFI, .label="Target", .param="restream_target" }, +}; +static const colmenu_page_t sys_restream_page = { "Restream", "gs", "system", restream_items, 2 }; + static const colmenu_item_t system_items[] = { { .kind=COLMENU_SUBMENU, .icon=LV_SYMBOL_WIFI, .label="Receiver", .sub=&sys_receiver_page }, { .kind=COLMENU_SUBMENU, .icon=LV_SYMBOL_AUDIO, .label="Audio", .sub=&sys_audio_page }, { .kind=COLMENU_SUBMENU, .icon=LV_SYMBOL_IMAGE, .label="Display", .sub=&sys_display_page }, { .kind=COLMENU_SUBMENU, .icon=LV_SYMBOL_VIDEO, .label="DVR", .sub=&sys_dvr_page }, + { .kind=COLMENU_SUBMENU, .icon=LV_SYMBOL_WIFI, .label="Restream", .sub=&sys_restream_page }, }; -static const colmenu_page_t system_page = { "System", "gs", "system", system_items, 4 }; +static const colmenu_page_t system_page = { "System", "gs", "system", system_items, 5 }; /* WiFi. The WiFi page shows the live connection (get gs wifi ssid) — entering the * connected network gives Disconnect / Forget. "Networks" lists only AVAILABLE diff --git a/src/gstrtpreceiver.cpp b/src/gstrtpreceiver.cpp index dc746a8..4c6bc9d 100644 --- a/src/gstrtpreceiver.cpp +++ b/src/gstrtpreceiver.cpp @@ -2476,6 +2476,16 @@ void restream_scan_clients(char* buf, size_t buf_len) { combined += '\n'; combined += manual_ip; } + // Callers hand this straight to an LVGL dropdown, which splits on '\n', so a + // buffer too small to hold every entry must still end on a line boundary — + // a half-copied address would show up as a selectable, bogus target. Drop + // the partial tail rather than offering it (no trailing '\n' either, which + // would render as an empty option). + if (combined.size() >= buf_len) { + combined.resize(buf_len - 1); + const size_t last_nl = combined.find_last_of('\n'); + combined.resize(last_nl == std::string::npos ? 0 : last_nl); + } strncpy(buf, combined.c_str(), buf_len - 1); buf[buf_len - 1] = '\0'; } diff --git a/src/simulator.c b/src/simulator.c index 2e3240d..9c1484c 100644 --- a/src/simulator.c +++ b/src/simulator.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "lvgl/lvgl.h" #include "menu.h" #include "input.h" @@ -38,16 +39,25 @@ void my_log_cb(lv_log_level_t level, const char * buf) } // Simulator stubs for restream API (real impl lives in gstrtpreceiver.cpp, -// which is not part of the simulator build) -bool restream_get_enabled() { return false; } -void restream_set_enabled(bool enabled) { (void)enabled; } +// which is not part of the simulator build). Stateful so the menu rows can +// actually be exercised; the client list is faked. +static bool sim_restream_enabled = false; +static char sim_restream_ip[64] = ""; +bool restream_get_enabled() { return sim_restream_enabled; } +void restream_set_enabled(bool enabled) { sim_restream_enabled = enabled; } +void restream_scan_clients(char* buf, size_t buf_len) { + if (buf && buf_len) snprintf(buf, buf_len, "Auto\n192.168.1.23\n192.168.1.47"); +} +const char* restream_get_manual_ip() { return sim_restream_ip; } +void restream_set_manual_ip(const char* ip) { + if (ip && ip[0] && strcmp(ip, "Auto") != 0) snprintf(sim_restream_ip, sizeof(sim_restream_ip), "%s", ip); + else sim_restream_ip[0] = '\0'; +} + int audio_get_enabled(void) { return 0; } void audio_set_enabled(int enabled) { (void)enabled; } void audio_set_device(const char* device) { (void)device; } void audio_set_volume(int percent) { (void)percent; } -void restream_scan_clients(char* buf, size_t buf_len) { if (buf && buf_len) buf[0] = '\0'; } -const char* restream_get_manual_ip() { return ""; } -void restream_set_manual_ip(const char* ip) { (void)ip; } int main(int argc, char **argv) {