GCC Code Coverage Report


Directory: ./
File: channeloutput/X11PanelMatrix.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 167 182 91.8%
Functions: 11 11 100.0%
Branches: 130 272 47.8%

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 <cmath>
18
19 #include <X11/Xutil.h>
20
21 #include "../log.h"
22
23 #include "GammaLUT.h"
24 #include "X11PanelMatrix.h"
25
26 #include "Plugin.h"
27 class X11PanelMatrixPlugin : public FPPPlugins::Plugin, public FPPPlugins::ChannelOutputPlugin {
28 public:
29 3 X11PanelMatrixPlugin() :
30
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
9 FPPPlugins::Plugin("X11PanelMatrix") {
31 3 }
32 3 virtual ChannelOutput* createChannelOutput(unsigned int startChannel, unsigned int channelCount) override {
33
2/6
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
6 return new X11PanelMatrixOutput(startChannel, channelCount);
34 }
35 };
36
37 extern "C" {
38 3 FPPPlugins::Plugin* createPlugin() {
39
2/6
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
6 return new X11PanelMatrixPlugin();
40 }
41 }
42
43 /////////////////////////////////////////////////////////////////////////////
44
45 /*
46 *
47 */
48 3 X11PanelMatrixOutput::X11PanelMatrixOutput(unsigned int startChannel,
49 3 unsigned int channelCount) :
50 ThreadedChannelOutput(startChannel, channelCount),
51
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
6 m_colorOrder("RGB"),
52 3 m_panelWidth(32),
53 3 m_panelHeight(16),
54 3 m_panels(0),
55 3 m_width(0),
56 3 m_height(0),
57 3 m_rows(0),
58 3 m_outputs(0),
59 3 m_longestChain(0),
60 6 m_invertedData(0) {
61
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 LogDebug(VB_CHANNELOUT, "X11PanelMatrixOutput::X11PanelMatrixOutput(%u, %u)\n",
62 startChannel, channelCount);
63
64
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 XInitThreads();
65
66 3 m_useDoubleBuffer = 1;
67 3 }
68
69 /*
70 *
71 */
72 12 X11PanelMatrixOutput::~X11PanelMatrixOutput() {
73 6 LogDebug(VB_CHANNELOUT, "X11PanelMatrixOutput::~X11PanelMatrixOutput()\n");
74
75
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
6 delete m_matrix;
76
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
6 delete m_panelMatrix;
77 12 }
78
79 /*
80 *
81 */
82 3 int X11PanelMatrixOutput::Init(Json::Value config) {
83
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 LogDebug(VB_CHANNELOUT, "X11PanelMatrixOutput::Init(JSON)\n");
84
85
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 m_panelWidth = config["panelWidth"].asInt();
86
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 m_panelHeight = config["panelHeight"].asInt();
87
88
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (!m_panelWidth)
89 ✗ m_panelWidth = 32;
90
91
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (!m_panelHeight)
92 ✗ m_panelHeight = 16;
93
94
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 m_invertedData = config["invertedData"].asInt();
95
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 m_colorOrder = config["colorOrder"].asString();
96
97
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
3 m_panelMatrix =
98
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 new PanelMatrix(m_panelWidth, m_panelHeight, m_invertedData);
99
100
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (!m_panelMatrix) {
101 ✗ LogErr(VB_CHANNELOUT, "Unable to create PanelMatrix\n");
102
103 ✗ return 0;
104 }
105
106
4/6
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 3 times.
6 for (int i = 0; i < config["panels"].size(); i++) {
107
3/6
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
3 Json::Value p = config["panels"][i];
108 3 char orientation = 'N';
109
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 std::string o = p["orientation"].asString();
110
111
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (!o.empty())
112 3 orientation = o[0];
113
114
4/8
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
3 if (p["colorOrder"].asString() == "")
115
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 p["colorOrder"] = m_colorOrder;
116
117 // a refused panel (out of range) must not count toward the outputs or chains below
118
4/8
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
6 if (!m_panelMatrix->AddPanel(p["outputNumber"].asInt(),
119
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 p["panelNumber"].asInt(), orientation,
120
4/8
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
3 p["xOffset"].asInt(), p["yOffset"].asInt(),
121
3/6
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
6 ColorOrderFromString(p["colorOrder"].asString()))) {
122 ✗ continue;
123 }
124
125
3/6
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
3 if (p["outputNumber"].asInt() > m_outputs)
126 ✗ m_outputs = p["outputNumber"].asInt();
127
128
3/6
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
3 if (p["panelNumber"].asInt() > m_longestChain)
129 ✗ m_longestChain = p["panelNumber"].asInt();
130
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 }
131
132 // Both of these are 0-based, so bump them up by 1 for comparisons
133 3 m_outputs++;
134 3 m_longestChain++;
135
136 3 m_panels = m_panelMatrix->PanelCount();
137
138 3 m_rows = m_panelHeight;
139
140 3 m_width = m_panelMatrix->Width();
141 3 m_height = m_panelMatrix->Height();
142
143 3 m_channelCount = m_width * m_height * 3;
144
145 3 int colorDepth = 8;
146
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
3 if (config.isMember("panelColorDepth")) {
147 ✗ colorDepth = config["panelColorDepth"].asInt();
148 }
149
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
3 if (colorDepth > 11 || colorDepth < 6) {
150 ✗ colorDepth = 11;
151 }
152
153
3/8
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
3 m_matrix = new Matrix(m_startChannel, m_width, m_height);
154
155
3/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1 times.
3 if (config.isMember("subMatrices")) {
156
4/6
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 2 times.
3 for (int i = 0; i < config["subMatrices"].size(); i++) {
157
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 Json::Value sm = config["subMatrices"][i];
158
159
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 m_matrix->AddSubMatrix(
160
1/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 sm["enabled"].asInt(),
161
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 sm["startChannel"].asInt() - 1,
162
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 sm["width"].asInt(),
163
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 sm["height"].asInt(),
164
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 sm["xOffset"].asInt(),
165
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 sm["yOffset"].asInt());
166 1 }
167 }
168
169
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 GammaLUT::Build(m_gammaCurve, GammaLUT::ParseConfig(config, 2.2f));
170
171
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 if (config.isMember("scale"))
172
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 m_scale = config["scale"].asInt();
173 else
174 ✗ m_scale = 10;
175
176
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 if (config.isMember("title"))
177
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 m_title = config["title"].asString();
178 else
179 ✗ m_title = "X11PanelMatrix";
180
181 3 m_scaleWidth = m_width * m_scale;
182 3 m_scaleHeight = m_height * m_scale;
183 3 m_imageData = (char*)calloc(m_scaleWidth * m_scaleHeight * 4, 1);
184
185 // Initialize X11 Window here
186 3 const char* dsp = getenv("DISPLAY");
187
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (dsp == nullptr) {
188 3 dsp = ":0";
189 }
190
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 m_display = XOpenDisplay(dsp);
191
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (!m_display) {
192 ✗ LogErr(VB_CHANNELOUT, "Unable to connect to X Server: %s\n", dsp);
193 ✗ return 0;
194 }
195
196 3 m_screen = DefaultScreen(m_display);
197
198 6 m_image = XCreateImage(m_display, CopyFromParent, 24, ZPixmap, 0,
199
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 m_imageData, m_scaleWidth, m_scaleHeight, 32, m_scaleWidth * 4);
200
201 3 int win_x = 100;
202 3 int win_y = 100;
203 XSetWindowAttributes attributes;
204
205 3 attributes.background_pixel = BlackPixel(m_display, m_screen);
206
207 XGCValues values;
208
209
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 m_pixmap = XCreatePixmap(m_display, XDefaultRootWindow(m_display),
210
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 m_scaleWidth, m_scaleHeight, 24);
211
212
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 m_gc = XCreateGC(m_display, m_pixmap, 0, &values);
213 3 int32_t tgc = reinterpret_cast<uintptr_t>(m_gc);
214
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (tgc < 0) {
215 ✗ LogErr(VB_CHANNELOUT, "Unable to create GC\n");
216 ✗ return 0;
217 }
218
219 6 m_window = XCreateWindow(
220 3 m_display, RootWindow(m_display, m_screen), win_x, win_y,
221 3 m_scaleWidth, m_scaleHeight, 5, 24, InputOutput,
222
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 DefaultVisual(m_display, m_screen), CWBackPixel, &attributes);
223
224
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 XMapWindow(m_display, m_window);
225
226
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 XStoreName(m_display, m_window, m_title.c_str());
227
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 XSetIconName(m_display, m_window, m_title.c_str());
228
229
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 XFlush(m_display);
230
231
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 return ThreadedChannelOutput::Init(config);
232 }
233
234 6 void X11PanelMatrixOutput::GetRequiredChannelRanges(const std::function<void(int, int)>& addRange) {
235 6 addRange(m_startChannel, m_startChannel + m_channelCount - 1);
236 6 }
237
238 /*
239 *
240 */
241 3 int X11PanelMatrixOutput::Close(void) {
242 3 LogDebug(VB_CHANNELOUT, "X11PanelMatrixOutput::Close()\n");
243
244 // Close X11 Window here
245
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (m_display) {
246 3 XLockDisplay(m_display);
247 3 XDestroyWindow(m_display, m_window);
248 3 XFreePixmap(m_display, m_pixmap);
249 3 XFreeGC(m_display, m_gc);
250 3 XDestroyImage(m_image);
251 3 XUnlockDisplay(m_display);
252 3 XCloseDisplay(m_display);
253 }
254 3 m_display = nullptr;
255
256 3 return ThreadedChannelOutput::Close();
257 }
258
259 // The submatrix overlay has to see the whole sequence buffer: a submatrix
260 // names its source and its enable flag by absolute channel, and those live
261 // outside this output's own range. RawSendData only ever gets the double
262 // buffered copy of this output's channels, so the overlay belongs here, on the
263 // absolute buffer, which is where the other Matrix users do it too.
264 27 void X11PanelMatrixOutput::PrepData(unsigned char* channelData) {
265 27 LogExcess(VB_CHANNELOUT, "X11PanelMatrixOutput::PrepData(%p)\n", channelData);
266 27 m_matrix->OverlaySubMatrices(channelData);
267 27 }
268
269 /*
270 *
271 */
272 21 int X11PanelMatrixOutput::RawSendData(unsigned char* channelData) {
273 21 LogExcess(VB_CHANNELOUT, "X11PanelMatrixOutput::RawSendData(%p)\n",
274 channelData);
275
276 unsigned char r;
277 unsigned char g;
278 unsigned char b;
279 unsigned char* c;
280 21 unsigned int stride = m_scaleWidth * 4;
281
282 // channelData is this output's channels only, already based at its start
283 // channel, and the panel pixel maps are relative to that same base
284
285
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 21 times.
42 for (int output = 0; output < m_outputs; output++) {
286 21 int panelsOnOutput = m_panelMatrix->m_outputPanels[output].size();
287
288
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 21 times.
42 for (int i = 0; i < panelsOnOutput; i++) {
289 21 int panel = m_panelMatrix->m_outputPanels[output][i];
290 21 int chain = m_panelMatrix->m_panels[panel].chain;
291 21 int py = m_panelMatrix->m_panels[panel].yOffset;
292 21 int px = m_panelMatrix->m_panels[panel].xOffset;
293
2/2
✓ Branch 0 taken 336 times.
✓ Branch 1 taken 21 times.
357 for (int y = 0; y < m_panelHeight; y++) {
294 336 int yw = y * m_panelWidth;
295
2/2
✓ Branch 0 taken 10752 times.
✓ Branch 1 taken 336 times.
11088 for (int x = 0; x < m_panelWidth; x++) {
296 10752 r = m_gammaCurve[channelData[m_panelMatrix->m_panels[panel].pixelMap[(yw + x) * 3]]];
297 10752 g = m_gammaCurve[channelData[m_panelMatrix->m_panels[panel].pixelMap[(yw + x) * 3 + 1]]];
298 10752 b = m_gammaCurve[channelData[m_panelMatrix->m_panels[panel].pixelMap[(yw + x) * 3 + 2]]];
299
300 10752 c = (unsigned char*)m_imageData + ((py + y) * m_scale * stride) + ((px + x) * 4 * m_scale);
301
302
2/2
✓ Branch 0 taken 10752 times.
✓ Branch 1 taken 10752 times.
21504 for (unsigned int t = 0; t < m_scale; t++) {
303
2/2
✓ Branch 0 taken 10752 times.
✓ Branch 1 taken 10752 times.
21504 for (unsigned int s = 0; s < m_scale; s++) {
304 10752 *(c++) = b;
305 10752 *(c++) = g;
306 10752 *(c++) = r;
307 10752 c++;
308 }
309 10752 c += stride - (m_scale * 4);
310 }
311 }
312 }
313 }
314 }
315
316 21 XLockDisplay(m_display);
317
318 21 XPutImage(m_display, m_window, m_gc, m_image, 0, 0, 0, 0, m_scaleWidth, m_scaleHeight);
319
320 21 XSync(m_display, True);
321 21 XFlush(m_display);
322
323 21 XUnlockDisplay(m_display);
324
325 21 return m_channelCount;
326 }
327
328 /*
329 *
330 */
331 6 void X11PanelMatrixOutput::DumpConfig(void) {
332 6 LogDebug(VB_CHANNELOUT, "X11PanelMatrixOutput::DumpConfig()\n");
333 6 LogDebug(VB_CHANNELOUT, " Panels : %d\n", m_panels);
334 6 LogDebug(VB_CHANNELOUT, " Width : %d\n", m_width);
335 6 LogDebug(VB_CHANNELOUT, " Height : %d\n", m_height);
336 6 LogDebug(VB_CHANNELOUT, " Scale : %d\n", m_scale);
337 6 LogDebug(VB_CHANNELOUT, " Scaled Width : %d\n", m_scaleWidth);
338 6 LogDebug(VB_CHANNELOUT, " Scaled Height : %d\n", m_scaleHeight);
339 6 }
340