GCC Code Coverage Report


Directory: ./
File: oled/FPPMainMenu.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 46 326 14.1%
Functions: 4 30 13.3%
Branches: 42 602 7.0%

Line Branch Exec Source
1 #include "fpp-pch.h"
2
3 #include "fpp-json.h"
4
5 #include <net/if.h>
6 #include <unistd.h>
7
8 #include "FPPMainMenu.h"
9 #include "../Warnings.h"
10 #include "../common.h"
11 #include "../fppversion.h"
12 #include "../log.h"
13 #include "../settings.h"
14 #include "../CurlSocket.h"
15
16 #include "FPPStatusOLEDPage.h"
17 #include "NetworkOLEDPage.h"
18
19 ✗ static int curlBufferWriter(char* data, size_t size, size_t nmemb,
20 std::string* writerData) {
21 ✗ if (writerData == NULL) {
22 ✗ return 0;
23 }
24 ✗ writerData->append(data, size * nmemb);
25 ✗ return size * nmemb;
26 }
27
28 ✗ static std::string escapeURL(const std::string& url) {
29 ✗ CURL* curl = curl_easy_init();
30 ✗ curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
31 ✗ char* output = curl_easy_escape(curl, url.c_str(), url.length());
32 ✗ std::string ret = output;
33 ✗ curl_free(output);
34 ✗ curl_easy_cleanup(curl);
35 ✗ return ret;
36 ✗ }
37
38 4 static std::string doCurlGet(const std::string& url, int timeout = 250) {
39 4 const std::string buffer;
40
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 CURL* curl = curl_easy_init();
41
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 SetCurlCloseOnExec(curl);
42
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
43
44
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
45
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
46
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeout);
47
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlBufferWriter);
48
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
49
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 curl_easy_perform(curl);
50
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 curl_easy_cleanup(curl);
51 4 return buffer;
52 ✗ }
53
54 // For the routes that change state, which take POST (GET is deprecated).
55 ✗ static std::string doCurlPost(const std::string& url, int timeout = 250) {
56 ✗ std::string resp;
57 ✗ urlHelper("POST", url, "", resp, (timeout + 999) / 1000);
58 ✗ return resp;
59 ✗ }
60
61 class RebootPromptPage : public PromptOLEDPage {
62 public:
63 ✗ RebootPromptPage(OLEDPage* p) :
64 PromptOLEDPage("Reboot?", "Reboot FPPD?", "", { "OK", "Cancel" }),
65 ✗ parent(p) {}
66 ✗ RebootPromptPage(const std::string& msg1, const std::string& msg2, OLEDPage* p) :
67 PromptOLEDPage("Reboot?", msg1, msg2, { "OK", "Cancel" }),
68 ✗ parent(p) {}
69 ✗ virtual ~RebootPromptPage() {};
70
71 ✗ virtual void ItemSelected(const std::string& item) override {
72 ✗ if (item == "Cancel") {
73 ✗ SetCurrentPage(parent);
74 } else {
75 ✗ SetCurrentPage(nullptr);
76 ✗ clearDisplay();
77 ✗ printString(4, 17, "Rebooting...");
78 ✗ flushDisplay();
79 ✗ sync();
80 ✗ system("/sbin/reboot");
81 }
82 ✗ }
83
84 private:
85 OLEDPage* parent;
86 };
87
88 class ShutdownPromptPage : public PromptOLEDPage {
89 public:
90 ✗ ShutdownPromptPage(OLEDPage* p) :
91 PromptOLEDPage("Shutdown?", "Shutdown FPPD?", "", { "OK", "Cancel" }),
92 ✗ parent(p) {}
93 ShutdownPromptPage(const std::string& msg1, const std::string& msg2, OLEDPage* p) :
94 PromptOLEDPage("Shutdown?", msg1, msg2, { "OK", "Cancel" }),
95 parent(p) {}
96 ✗ virtual ~ShutdownPromptPage() {};
97
98 ✗ virtual void ItemSelected(const std::string& item) override {
99 ✗ if (item == "Cancel") {
100 ✗ SetCurrentPage(parent);
101 } else {
102 ✗ SetCurrentPage(nullptr);
103 ✗ clearDisplay();
104 ✗ printString(4, 17, "Shutdown...");
105 ✗ flushDisplay();
106 ✗ sync();
107 ✗ system("/sbin/shutdown -h now");
108 }
109 ✗ }
110
111 private:
112 OLEDPage* parent;
113 };
114
115 class BridgeStatsPage : public ListOLEDPage {
116 public:
117 ✗ BridgeStatsPage(OLEDPage* parent) :
118 ✗ ListOLEDPage("Bridge Stats", {}, parent) {};
119 ✗ virtual ~BridgeStatsPage() {}
120
121 ✗ virtual void displaying() override {
122 ✗ bool on = true;
123 ✗ doIteration(on);
124 ✗ }
125 ✗ virtual bool doIteration(bool& displayOn) override {
126 ✗ std::string d = doCurlGet("http://127.0.0.1:32322/fppd/e131stats", 10000);
127 ✗ Json::Value result;
128 ✗ if (LoadJsonFromString(d, result)) {
129 ✗ items.clear();
130 ✗ items.emplace_back("Univ Pckts Err");
131 ✗ for (int x = 0; x < result["universes"].size(); x++) {
132 ✗ Json::Value u = result["universes"][x];
133 ✗ std::string l = u["id"].asString();
134 ✗ l += ":";
135 ✗ if (l.length() < 8) {
136 ✗ l.append(8 - l.length(), ' ');
137 }
138 ✗ l += u["packetsReceived"].asString();
139 ✗ if (l.length() < 15) {
140 ✗ l.append(15 - l.length(), ' ');
141 }
142 ✗ l += u["errors"].asString();
143 ✗ items.push_back(l);
144 ✗ }
145 ✗ display();
146 }
147 ✗ return false;
148 ✗ }
149
150 private:
151 };
152
153 class ResetPage : public MenuOLEDPage {
154 public:
155 ✗ ResetPage(OLEDPage* parent) :
156 ✗ MenuOLEDPage("Reset FPP", { "Perform Reset", "Back", "-------------", "All", " Networking", " Configuration", " Outputs", " Settings", " Sequences", " Media", " Playlists", " Schedule" }, parent) {
157 ✗ this->autoDelete();
158 ✗ };
159 ✗ virtual ~ResetPage() {}
160
161 ✗ virtual void itemSelected(const std::string& item) override {
162 ✗ if (item == "Back") {
163 ✗ SetCurrentPage(parent);
164 ✗ return;
165 ✗ } else if (item == "Perform Reset") {
166 ✗ PromptOLEDPage* p = new PromptOLEDPage("Reset FPP?", "Reset FPP?", "", { "No", "Yes" }, [parent = this->parent, it = this->items](const std::string& i) {
167 ✗ LogInfo(VB_GENERAL, "Item: %s\n", i.c_str());
168 ✗ if (i == "No") {
169 ✗ SetCurrentPage(parent);
170 ✗ } else if (i == "Yes") {
171 ✗ performReset(parent, it);
172 }
173 ✗ });
174 ✗ p->autoDelete();
175 ✗ SetCurrentPage(p);
176 ✗ } else if (item == "All") {
177 ✗ for (int x = 4; x < items.size(); x++) {
178 ✗ items[x][0] = '*';
179 }
180 ✗ display();
181 ✗ } else if (item[0] != '-') {
182 ✗ for (auto& a : items) {
183 ✗ if (a == item) {
184 ✗ a[0] = item[0] == ' ' ? '*' : ' ';
185 }
186 }
187 ✗ display();
188 }
189 }
190
191 ✗ static void performReset(OLEDPage* parent, const std::vector<std::string>& items) {
192 ✗ std::string areas;
193 ✗ LogInfo(VB_GENERAL, "Performing reset\n");
194 ✗ for (int x = 4; x < items.size(); x++) {
195 ✗ if (items[x][0] == '*') {
196 ✗ std::string i = items[x].substr(2);
197 ✗ if (i == "Networking") {
198 ✗ areas += "networking,";
199 ✗ } else if (i == "Configuration") {
200 ✗ areas += "config,";
201 ✗ } else if (i == "Outputs") {
202 ✗ areas += "channeloutputs,";
203 ✗ } else if (i == "Settings") {
204 ✗ areas += "settings,";
205 ✗ } else if (i == "Sequences") {
206 ✗ areas += "sequences,";
207 ✗ } else if (i == "Media") {
208 ✗ areas += "media,";
209 ✗ } else if (i == "Playlists") {
210 ✗ areas += "playlists,";
211 ✗ } else if (i == "Schedule") {
212 ✗ areas += "schedule,";
213 }
214 ✗ }
215 }
216 ✗ if (!areas.empty()) {
217 ✗ areas = areas.substr(0, areas.length() - 1);
218 ✗ LogInfo(VB_GENERAL, "Areas: %s\n", areas.c_str());
219 ✗ std::string url = "http://127.0.0.1/resetConfig.php?areas=" + areas;
220 ✗ LogInfo(VB_GENERAL, "Reset URL: %s\n", url.c_str());
221 ✗ std::string d = doCurlPost(url, 10000);
222 ✗ LogInfo(VB_GENERAL, "%s\n", d.c_str());
223 ✗ }
224 ✗ SetCurrentPage(parent);
225 ✗ }
226 };
227
228 12 FPPMainMenu::FPPMainMenu(FPPStatusOLEDPage* p) :
229 MenuOLEDPage("Main Menu", { "FPP Mode", "Tethering", "Testing", "Reboot", "Shutdown", "Reset", "About", "Back" }, p),
230 12 aboutPage(nullptr),
231
3/6
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
48 statusPage(p) {
232 12 }
233 ✗ FPPMainMenu::~FPPMainMenu() {
234 ✗ if (aboutPage) {
235 ✗ delete aboutPage;
236 }
237 ✗ }
238
239 5 void FPPMainMenu::displaying() {
240
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 std::string mode = statusPage->getCurrentMode();
241 5 items.clear();
242
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
5 if (mode == "Bridge") {
243 ✗ items.push_back("Bridge Stats");
244
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 } else if (mode != "Remote") {
245
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
15 items.push_back("Start Playlist");
246 }
247
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
10 items.push_back("FPP Mode");
248
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (Has4DirectionControls()) {
249 ✗ bool hasEth0 = false;
250 struct if_nameindex *if_nidxs, *intf;
251
252 ✗ if_nidxs = if_nameindex();
253 ✗ if (if_nidxs != NULL) {
254 ✗ for (intf = if_nidxs; intf->if_index != 0 || intf->if_name != NULL; intf++) {
255 ✗ std::string n = intf->if_name;
256 ✗ if (n == "eth0") {
257 ✗ hasEth0 = true;
258 }
259 ✗ }
260 ✗ if_freenameindex(if_nidxs);
261 }
262 ✗ if (hasEth0) {
263 ✗ items.push_back("Network");
264 }
265 }
266
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
10 std::vector<std::string> it = { "Tethering", "Testing", "Reboot", "Shutdown", "Reset", "About", "Back" };
267
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
15 items.insert(std::end(items), std::begin(it), std::end(it));
268
269
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 MenuOLEDPage::displaying();
270 5 }
271
272 static std::vector<std::string> TEST_OPTIONS = {
273 " Enable Multisync", "Off", "White", "Red", "Green", "Blue", "R-G-B Cycle", "R-G-B-W Cycle", "R-G-B-W-N Cycle", "R-G-B Chase", "R-G-B-W Chase", "R-G-B-W-N Chase", "Back"
274 };
275
276 class TestingMenuOLEDPage : public MenuOLEDPage {
277 public:
278 ✗ TestingMenuOLEDPage(FPPStatusOLEDPage* sp, OLEDPage* parent) :
279 ✗ MenuOLEDPage("Test", TEST_OPTIONS, parent), statusPage(sp) {
280 ✗ if (sp->isMultiSyncTest()) {
281 ✗ items[0][0] = '*';
282 }
283 ✗ }
284
285 ✗ virtual void itemSelected(const std::string& item) override {
286 // printf("Item: %s\n", item.c_str());
287 ✗ if (item == " Enable Multisync") {
288 ✗ items[0][0] = '*';
289 ✗ statusPage->setMultiSyncTest(true);
290 ✗ display();
291 ✗ return;
292 ✗ } else if (item == "*Enable Multisync") {
293 ✗ items[0][0] = ' ';
294 ✗ statusPage->setMultiSyncTest(false);
295 ✗ display();
296 ✗ return;
297 ✗ } else if (item == "Back") {
298 ✗ SetCurrentPage(parent);
299 ✗ return;
300 }
301 ✗ statusPage->runTest(item, statusPage->isMultiSyncTest());
302 ✗ if (item == "Off") {
303 ✗ SetCurrentPage(statusPage);
304 ✗ return;
305 }
306 }
307
308 FPPStatusOLEDPage* statusPage;
309 };
310
311 4 void FPPMainMenu::itemSelected(const std::string& item) {
312
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (item == "Back") {
313 ✗ SetCurrentPage(parent);
314
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 } else if (item == "About") {
315 ✗ if (aboutPage == nullptr) {
316 ✗ std::string ver = "Version: ";
317 ✗ ver += getFPPMajorVersion();
318 ✗ ver += ".";
319 ✗ ver += getFPPMinorVersion();
320 ✗ std::string branch = "Branch: ";
321 ✗ branch += getFPPBranch();
322 ✗ aboutPage = new ListOLEDPage("About FPP", { ver, branch }, this);
323 ✗ }
324 ✗ SetCurrentPage(aboutPage);
325
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 } else if (item == "Reboot") {
326 ✗ RebootPromptPage* pg = new RebootPromptPage(this);
327 ✗ pg->autoDelete();
328 ✗ SetCurrentPage(pg);
329
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 } else if (item == "Shutdown") {
330 ✗ ShutdownPromptPage* pg = new ShutdownPromptPage(this);
331 ✗ pg->autoDelete();
332 ✗ SetCurrentPage(pg);
333
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 } else if (item == "Network") {
334 ✗ FPPNetworkOLEDPage* pg = new FPPNetworkOLEDPage(this);
335 ✗ SetCurrentPage(pg);
336
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 } else if (item == "Reset") {
337 ✗ ResetPage* pg = new ResetPage(this);
338 ✗ SetCurrentPage(pg);
339
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 } else if (item == "Testing") {
340 ✗ FPPStatusOLEDPage* sp = statusPage;
341 ✗ TestingMenuOLEDPage* pg = new TestingMenuOLEDPage(sp, this);
342 ✗ pg->autoDelete();
343 ✗ SetCurrentPage(pg);
344
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 } else if (item == "Tethering") {
345 ✗ FPPStatusOLEDPage* sp = statusPage;
346 std::vector<std::string> options = {
347 " Automatic",
348 " On",
349 " Off",
350 " Back"
351 ✗ };
352 ✗ int tm = getSettingInt("EnableTethering");
353 ✗ options[tm][0] = '*';
354 MenuOLEDPage* pg = new MenuOLEDPage(
355 ✗ "Tethering", options, [this, sp](const std::string& item) {
356 ✗ if (item != " Back") {
357 ✗ std::string nitem = item.substr(1);
358 ✗ std::string nvdata;
359 ✗ std::string nvresults;
360 ✗ std::string nv = "http://127.0.0.1/api/settings/EnableTethering";
361 ✗ if (nitem == "Automatic") {
362 ✗ nvdata = std::string("0");
363 ✗ } else if (nitem == "On") {
364 ✗ nvdata = std::string("1");
365 ✗ } else if (nitem == "Off") {
366 ✗ nvdata = std::string("2");
367 } else {
368 ✗ nvdata = std::string("0");
369 }
370 ✗ urlPut(nv, nvdata, nvresults);
371 ✗ nvdata = std::string("1");
372 ✗ urlPut("http://127.0.0.1/api/settings/rebootFlag", "1", nvresults);
373
374 ✗ RebootPromptPage* pg = new RebootPromptPage("Reboot Required", "Reboot FPPD?", this);
375 ✗ pg->autoDelete();
376 ✗ SetCurrentPage(pg);
377 ✗ } else {
378 ✗ SetCurrentPage(this);
379 }
380 ✗ },
381 ✗ this);
382 ✗ pg->autoDelete();
383 ✗ SetCurrentPage(pg);
384
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 } else if (item == "FPP Mode") {
385 ✗ FPPStatusOLEDPage* sp = statusPage;
386 std::vector<std::string> options = {
387 " Player",
388 " Remote",
389 " Back"
390 ✗ };
391 ✗ std::string mode = statusPage->getCurrentMode();
392 ✗ if (mode == "Remote") {
393 ✗ options[1][0] = '*';
394 } else {
395 ✗ options[0][0] = '*';
396 }
397 MenuOLEDPage* pg = new MenuOLEDPage(
398 ✗ "FPPD Mode", options, [this, sp](const std::string& item) {
399 ✗ if (item != " Back") {
400 ✗ std::string nitem = item.substr(1);
401 ✗ std::string nv = "player";
402 ✗ if (nitem == "Remote") {
403 ✗ nv = "remote";
404 }
405 ✗ std::string nvresults;
406 ✗ urlPut("http://127.0.0.1/api/settings/fppMode", nv, nvresults);
407 ✗ doCurlPost("http://127.0.0.1/api/system/fppd/restart", 10000);
408 ✗ SetCurrentPage(sp);
409 ✗ } else {
410 ✗ SetCurrentPage(this);
411 }
412 ✗ },
413 ✗ this);
414 ✗ pg->autoDelete();
415 ✗ SetCurrentPage(pg);
416
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 } else if (item == "Bridge Stats") {
417 ✗ BridgeStatsPage* pg = new BridgeStatsPage(this);
418 ✗ pg->autoDelete();
419 ✗ SetCurrentPage(pg);
420
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 } else if (item == "Start Playlist") {
421
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 std::string d = doCurlGet("http://127.0.0.1/api/playlists/playable", 10000);
422
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 Json::Value result;
423
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4 if (LoadJsonFromString(d, result)) {
424 ✗ std::vector<std::string> playlists;
425 ✗ playlists.push_back("-Stop Now-");
426 ✗ playlists.push_back("-Stop Gracefully-");
427 ✗ for (int x = 0; x < result.size(); x++) {
428 ✗ playlists.push_back(result[x].asString());
429 }
430 ✗ playlists.push_back("Back");
431 ✗ FPPStatusOLEDPage* sp = statusPage;
432 MenuOLEDPage* pg = new MenuOLEDPage(
433 ✗ "Playlist", playlists, [sp](const std::string& item) {
434 ✗ if (item == "-Stop Now-") {
435 ✗ std::string url = "http://127.0.0.1/api/playlists/stop";
436 ✗ doCurlPost(url, 1000);
437 ✗ } else if (item == "-Stop Gracefully-") {
438 ✗ std::string url = "http://127.0.0.1/api/playlists/stopgracefully";
439 ✗ doCurlPost(url, 1000);
440 ✗ } else if (item != "Back") {
441 ✗ std::string escItem = escapeURL(item);
442 ✗ std::string url = "http://127.0.0.1/api/playlist/" + escItem + "/start";
443 ✗ doCurlPost(url, 1000);
444 ✗ }
445 ✗ SetCurrentPage(sp);
446 ✗ },
447 ✗ this);
448 ✗ pg->autoDelete();
449 ✗ SetCurrentPage(pg);
450 ✗ }
451 4 }
452
0/8
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
4 }
453