GCC Code Coverage Report


Directory: ./
File: fpp.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 147 170 86.5%
Functions: 6 6 100.0%
Branches: 127 238 53.4%

Line Branch Exec Source
1 /*
2 * This file is part of the Falcon Player (FPP) and is Copyright (C)
3 * 2013-2022 by the Falcon Player Developers.
4 *
5 * The Falcon Player (FPP) is free software, and is covered under
6 * multiple Open Source licenses. Please see the included 'LICENSES'
7 * file for descriptions of what files are covered by each license.
8 *
9 * This source file is covered under the GPL v2 as described in the
10 * included LICENSE.GPL file.
11 */
12
13 #include "fpp-pch.h"
14
15 #include "fpp-json.h"
16
17 #include <sys/socket.h>
18 #include <sys/stat.h>
19 #include <sys/un.h>
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <string>
26 #include <unistd.h>
27
28 #if __has_include(<xf86drm.h>)
29 #include <xf86drm.h>
30 #include <xf86drmMode.h>
31 #include <libdrm/drm_fourcc.h>
32 #define HAS_KMS_FB
33 #endif
34
35 #include "common.h"
36 #include "fppversion.h"
37 #include "settings.h"
38 #include "commands/Commands.h"
39
40 #include "fpp.h"
41
42 int socket_fd;
43 struct sockaddr_un server_address;
44 struct sockaddr_un client_address;
45 int bytes_received, bytes_sent;
46
47 char command[8192];
48 // fppd's replies are up to 1500 bytes (MAX_RESPONSE_SIZE in command.cpp); one
49 // byte is kept back for the terminating NUL added after recvfrom().
50 char response[2048];
51
52 void SendCommand(const char* com);
53 void SetupDomainSocket(void);
54 void Usage(char* appname);
55
56 socklen_t address_length;
57
58 #ifdef HAS_KMS_FB
59 9 static inline std::string FourCCToString(uint32_t f) {
60 9 char buf[5] = { (char)(f & 0xff),
61 9 (char)((f >> 8) & 0xff),
62 9 (char)((f >> 16) & 0xff),
63 9 (char)((f >> 24) & 0xff),
64 9 0 };
65
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
18 return std::string(buf);
66 }
67 #endif
68
69 3 void GetFrameBufferDevices(Json::Value& v, bool debug) {
70 #ifdef HAS_KMS_FB
71
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 3 times.
33 for (int cn = 0; cn < 10; cn++) {
72
1/2
✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
30 std::string path = "/dev/dri/card" + std::to_string(cn);
73
3/4
✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 28 times.
30 if (FileExists(path)) {
74
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 int fd = open(path.c_str(), O_RDWR | O_CLOEXEC);
75
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (fd < 0) continue;
76
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
77
78
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 drmModeResPtr res = drmModeGetResources(fd);
79
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!res) {
80 ✗ close(fd);
81 ✗ continue;
82 }
83
84
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 for (int ci = 0; ci < res->count_connectors; ci++) {
85
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 drmModeConnectorPtr conn = drmModeGetConnector(fd, res->connectors[ci]);
86
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!conn) continue;
87
88
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 const char* typeName = drmModeGetConnectorTypeName(conn->connector_type);
89
4/8
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
6 std::string cname = std::string(typeName ? typeName : "Unknown") + "-" + std::to_string(conn->connector_type_id);
90
91
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (debug) {
92
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 Json::Value v2(Json::objectValue);
93
94 // Get formats from primary plane if connector has an active CRTC
95
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (conn->encoder_id) {
96
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 drmModeEncoderPtr enc = drmModeGetEncoder(fd, conn->encoder_id);
97
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 if (enc && enc->crtc_id) {
98 1 int crtcIndex = -1;
99
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 for (int k = 0; k < res->count_crtcs; k++) {
100
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (res->crtcs[k] == enc->crtc_id) {
101 1 crtcIndex = k;
102 1 break;
103 }
104 }
105
106
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (crtcIndex >= 0) {
107
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 drmModePlaneResPtr planes = drmModeGetPlaneResources(fd);
108
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (planes) {
109 1 std::set<std::string> formats;
110
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 for (uint32_t pi = 0; pi < planes->count_planes; pi++) {
111
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 drmModePlanePtr plane = drmModeGetPlane(fd, planes->planes[pi]);
112
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 if (plane && (plane->possible_crtcs & (1u << crtcIndex))) {
113
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1 times.
10 for (uint32_t fi = 0; fi < plane->count_formats; fi++) {
114
2/4
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
9 formats.insert(FourCCToString(plane->formats[fi]));
115 }
116
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 drmModeFreePlane(plane);
117 1 break; // primary plane found
118 }
119 ✗ if (plane) drmModeFreePlane(plane);
120 }
121
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 drmModeFreePlaneResources(planes);
122
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1 times.
10 for (auto& a : formats) {
123
3/6
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 9 times.
✗ Branch 5 not taken.
9 v2["formats"].append(a);
124 }
125 1 }
126 }
127
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 drmModeFreeEncoder(enc);
128 }
129 }
130
131
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (int mi = 0; mi < conn->count_modes; mi++) {
132 1 drmModeModeInfo& m = conn->modes[mi];
133
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 std::string modeStr = std::to_string(m.hdisplay) + "x" + std::to_string(m.vdisplay);
134
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (m.vrefresh) {
135
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 modeStr += "@" + std::to_string(m.vrefresh);
136 }
137
3/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 v2["modes"].append(modeStr);
138 1 }
139
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 v[cname] = v2;
140 1 } else {
141
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 v.append(cname);
142 }
143
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 drmModeFreeConnector(conn);
144 2 }
145
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 drmModeFreeResources(res);
146
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 close(fd);
147 }
148
1/2
✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
30 }
149 #endif
150
151
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 if (v.size() == 0) {
152 #ifdef PLATFORM_OSX
153 if (debug) {
154 v["fb0"] = Json::Value(Json::objectValue);
155 v["fb1"] = Json::Value(Json::objectValue);
156 v["fb2"] = Json::Value(Json::objectValue);
157 } else {
158 v.append("fb0");
159 v.append("fb1");
160 v.append("fb2");
161 }
162 #else
163
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 std::string devString = getSetting("framebufferControlSocketPath", "/dev") + "/";
164
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 1 times.
11 for (int x = 0; x < 10; x++) {
165
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 std::string fb = "fb";
166
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 fb += std::to_string(x);
167
4/6
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 9 times.
10 if (FileExists(devString + fb)) {
168
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (debug) {
169 ✗ Json::Value v2(Json::objectValue);
170 ✗ v[fb] = v2;
171 ✗ } else {
172
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 v.append(fb);
173 }
174 }
175 10 }
176 #endif
177 1 }
178 3 }
179
180 23 int main(int argc, char* argv[]) {
181 23 LoadSettings(argv[0]);
182 23 memset(command, 0, sizeof(command));
183 23 SetupDomainSocket();
184
1/2
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
23 if (argc > 1) {
185 // Shutdown fppd daemon used by scripts/fppd_stop
186
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 21 times.
23 if (strncmp(argv[1], "-q", 2) == 0) {
187 2 snprintf(command, sizeof(command), "q");
188 2 SendCommand(command);
189 }
190 // Reload schedule example "fpp -R"
191
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 19 times.
21 else if (strncmp(argv[1], "-R", 2) == 0) {
192 2 snprintf(command, sizeof(command), "R");
193 2 SendCommand(command);
194 }
195 // Set the log level of every logger, or of one:
196 // "fpp --log-level debug" "fpp --log-level MediaOut debug"
197 // The socket command is LogLevel,LOGGER,LEVEL; this used to send
198 // LogLevel,LEVEL, which fppd always refused.
199
4/4
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 12 times.
19 else if ((strcmp(argv[1], "--log-level") == 0) && argc == 3) {
200 2 snprintf(command, sizeof(command), "LogLevel,All,%s,", argv[2]);
201 2 SendCommand(command);
202
3/4
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
17 } else if ((strcmp(argv[1], "--log-level") == 0) && argc == 4) {
203 12 snprintf(command, sizeof(command), "LogLevel,%s,%s,", argv[2], argv[3]);
204 12 SendCommand(command);
205 }
206 // Configure the given GPIO to the given mode
207
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
5 else if ((strncmp(argv[1], "-G", 2) == 0) && argc == 3) {
208 ✗ snprintf(command, sizeof(command), "SetupExtGPIO,%s,", argv[2]);
209 ✗ SendCommand(command);
210
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
5 } else if ((strncmp(argv[1], "-G", 2) == 0) && argc == 4) {
211 ✗ snprintf(command, sizeof(command), "SetupExtGPIO,%s,%s,", argv[2], argv[3]);
212 ✗ SendCommand(command);
213 // Set the given GPIO to the given value
214
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
5 } else if ((strncmp(argv[1], "-g", 2) == 0) && argc == 3) {
215 ✗ snprintf(command, sizeof(command), "ExtGPIO,%s", argv[2]);
216 ✗ SendCommand(command);
217
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
5 } else if ((strncmp(argv[1], "-g", 2) == 0) && argc == 5) {
218 ✗ snprintf(command, sizeof(command), "ExtGPIO,%s,%s,%s", argv[2], argv[3], argv[4]);
219 ✗ SendCommand(command);
220
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 } else if (strncmp(argv[1], "-r", 2) == 0) {
221 // Restart fppd daemon
222 ✗ snprintf(command, sizeof(command), "restart");
223 ✗ SendCommand(command);
224
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
5 } else if ((strncmp(argv[1], "-FBdebug", 8) == 0)) {
225
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 Json::Value val;
226
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 GetFrameBufferDevices(val, true);
227
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (val.size() == 0) {
228 ✗ printf("[]");
229 } else {
230
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 std::string js = SaveJsonToString(val);
231
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 printf("%s", js.c_str());
232 1 }
233
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
5 } else if ((strncmp(argv[1], "-FB", 3) == 0)) {
234
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 Json::Value val;
235
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 GetFrameBufferDevices(val, false);
236
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (val.size() == 0) {
237 ✗ printf("[]");
238 } else {
239
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 std::string js = SaveJsonToString(val);
240
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 printf("%s", js.c_str());
241 2 }
242 2 } else {
243 2 Usage(argv[0]);
244 }
245 } else {
246 ✗ return 0;
247 }
248
249 23 return 0;
250 }
251
252 /*
253 *
254 */
255 23 void SetupDomainSocket(void) {
256
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 23 times.
23 if ((socket_fd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
257 ✗ perror("fpp client socket");
258 ✗ return;
259 }
260
261 23 mkdir(FPP_SOCKET_PATH, 0777);
262 23 chmod(FPP_SOCKET_PATH, 0777);
263
264 23 memset(&client_address, 0, sizeof(struct sockaddr_un));
265 23 client_address.sun_family = AF_UNIX;
266 23 strcpy(client_address.sun_path, FPP_CLIENT_SOCKET);
267
268 23 unlink(FPP_CLIENT_SOCKET);
269 23 if (bind(socket_fd, (const struct sockaddr*)&client_address,
270
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 23 times.
23 sizeof(struct sockaddr_un)) < 0) {
271 ✗ perror("fpp client bind");
272 ✗ return;
273 }
274 23 memset(&server_address, 0, sizeof(struct sockaddr_un));
275 23 server_address.sun_family = AF_UNIX;
276 23 strcpy(server_address.sun_path, FPP_SERVER_SOCKET);
277
278 23 symlink(FPP_SERVER_SOCKET, FPP_SERVER_SOCKET_OLD);
279 23 symlink(FPP_CLIENT_SOCKET, FPP_CLIENT_SOCKET_OLD);
280 }
281
282 /*
283 *
284 */
285 18 void SendCommand(const char* com) {
286 18 int max_timeout = 4000;
287 18 int i = 0;
288 18 bytes_sent = sendto(socket_fd, com, strlen(com), 0,
289 (struct sockaddr*)&server_address,
290 sizeof(struct sockaddr_un));
291
292 18 address_length = sizeof(struct sockaddr_un);
293
2/2
✓ Branch 0 taken 7652 times.
✓ Branch 1 taken 1 times.
7653 while (i < max_timeout) {
294 7652 i++;
295 7652 bytes_received = recvfrom(socket_fd, response, sizeof(response) - 1, MSG_DONTWAIT,
296 (struct sockaddr*)&(server_address),
297 &address_length);
298
4/6
✓ Branch 0 taken 7635 times.
✓ Branch 1 taken 17 times.
✓ Branch 2 taken 7635 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 7635 times.
✗ Branch 5 not taken.
7652 if ((bytes_received > 0) || ((bytes_received < 0) && (errno != EAGAIN))) {
299 break;
300 }
301 7635 usleep(500);
302 }
303
304 18 close(socket_fd);
305 18 unlink(FPP_CLIENT_SOCKET);
306
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 1 times.
18 if (bytes_received > 0) {
307 17 response[bytes_received] = '\0';
308 17 printf("%s", response);
309 } else {
310 1 printf("false");
311 }
312 18 }
313
314 /*
315 *
316 */
317 2 void Usage(char* appname) {
318 2 printf("Usage: %s [OPTION...]\n"
319 "\n"
320 "fpp is the Falcon Player CLI helper utility. It can be used to query\n"
321 "certain information and send commands to a running fppd daemon.\n"
322 "\n"
323 "Options:\n"
324 " -q - Shutdown fppd daemon\n"
325 " -R - Reload schedule config file\n"
326 " -G GPIO MODE - Configure the given GPIO to MODE. MODEs include:\n"
327 " Input - Set to Input. For PiFace inputs this only enables the pull-up\n"
328 " Output - Set to Output. (This is not needed for PiFace outputs)\n"
329 " SoftPWM - Set to Software PWM.\n"
330 " -g GPIO MODE VALUE - Set the given GPIO to VALUE applicable to the given MODEs defined above\n"
331 " VALUE is ignored for Input mode\n"
332 " -FB - Query usable framebuffer devices\n"
333 " --log-level [LOGGER] LEVEL - Set the log level of every logger, or of LOGGER\n"
334 " (General, MediaOut, ...): error, warn, info, debug, excess\n\n\n"
335 " The following commands are now deprecated and the FPP API should be used instead, by POST. Eg\n"
336 " curl -X POST -d '[\"PLAYLISTNAME\", \"true\"]' 'http://<fpp hostname>/api/command/Start%20Playlist'\n"
337 " Below, /api/command/<COMMAND>/<ARG1>/<ARG2> lists a command's arguments in order: they go in the POST\n"
338 " body as a JSON array. Run as a GET, that path form is deprecated and FPP 11 will refuse it.\n"
339 " Further information on the FPP API can be found at http://<fpp hostname>/api/\n\n"
340 " -V - Print version information\n"
341 " /api/fppd/version\n"
342 " -r - Restart fppd daemon\n"
343 " /api/system/fppd/restart\n"
344 " -s - Get fppd status\n"
345 " /api/system/status\n"
346 " -c PLAYLIST_ACTION - Perform a playlist action.\n"
347 " next - skip to next item in the playlist\n"
348 " /api/command/Next Playlist Item\n"
349 " prev - jump back to previous item\n"
350 " /api/command/Prev Playlist Item\n"
351 " stop - stop the playlist immediately\n"
352 " /api/command/Stop Now\n"
353 " graceful - stop the playlist gracefully\n"
354 " /api/command/Stop Gracefully\n"
355 " pause - pause a sequence (not media)\n"
356 " /api/command/Pause Playlist\n"
357 " step - single-step a paused sequence\n"
358 " No direct replacement\n"
359 " stepback - step a paused sequence backwards\n"
360 " No direct replacement\n"
361 " -p PLAYLISTNAME [STARTITEM] - Play Playlist PLAYLISTNAME in repeat mode\n"
362 " /api/command/Start Playlist/<PLAYLISTNAME>/<1 or STARTITEM>/true\n"
363 " -P PLAYLISTNAME [STARTITEM] - Play Playlist PLAYLISTNAME once, optionally\n"
364 " starting on item STARTITEM in the playlist\n"
365 " /api/command/Start Playlist/<PLAYLISTNAME>/<1 or STARTITEM>/false\n"
366 " -S - Stop Playlist gracefully\n"
367 " /api/command/Stop Gracefully\n"
368 " -L - Stop Playlist gracefully after current loop\n"
369 " /api/command/Stop Gracefully/true\n"
370 " -d - Stop Playlist immediately\n"
371 " /api/command/Stop Now\n"
372 " -e EFFECTNAME[,CH[,LOOP]] - Start Effect EFFECTNAME with optional\n"
373 " start channel set to CH and optional\n"
374 " looping if LOOP is set to 1\n"
375 " /api/command/Effect Start/EFFECTNAME/[CH]/[True for Loop]\n"
376 " -E EFFECTNAME - Stop Effect EFFECTNAME\n"
377 " /api/command/Effect Stop/EFFECTNAME\n"
378 " -C FPPCOMMAND ARG1 ARG2 ... - Trigger the FPP Command\n"
379 " /api/command/<FPPCOMMAND/ARG1/ARG2/etc\n"
380 " -t CommandPresetSlot - Trigger FPP Command Preset via Slot # or Preset Name\n"
381 " /api/command/Trigger Command Preset Slot/<Slot #>\n"
382 " /api/command/Trigger Command Preset/<Preset Name>\n"
383 " -v VOLUME - Set volume to 'VOLUME'\n"
384 " /api/command/Volume Set/VOLUME\n"
385 "\n",
386 appname);
387 2 }
388