• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lexus2k / lcdgfx / 25072430672

28 Apr 2026 07:09PM UTC coverage: 55.122% (+4.5%) from 50.577%
25072430672

push

github

lexus2k
chore(release): bump version to 1.3.0

Highlights since v1.2.0:

Documentation
  * Controller capability matrix (docs/controller_matrix.md, PR A1).
  * Migration guide v1 -> v2 (docs/migration_v1_v2.md, PR A2).

Bug fixes
  * SSD1325 fillRect 4-bit nibble-packing now respects setColor for
    odd columns (PR B1).
  * Linux sysfs GPIO fallback no longer leaks the export handle on
    early-return paths (PR B2).

New core API
  * lcdgfx::color helper namespace with constexpr to_rgb565 /
    to_rgb332 / from_rgb / gray (BT.601 weights), bit-identical to
    the existing RGB_COLOR16 / RGB_COLOR8 macros (PR C1).
  * Stateless nano_utf8_decode() with full validation
    (overlongs, surrogates, > U+10FFFF rejected) plus a Cyrillic
    proof secondary font ssd1306xled_font6x8_Cyrillic covering
    U+0410..U+042F (PR F1).

New GUI widgets
  * LcdGfxSlider     — horizontal value slider (PR F4).
  * LcdGfxSpinbox    — bounded integer spinbox (PR F5).
  * LcdGfxTextEntry  — single-line text-entry widget (PR F6).

Touch
  * LcdGfxXpt2046 templated SPI touch driver with median-of-3
    sampling, pressure detection and TouchCalibration helper
    (PR F2).
  * LcdGfxMenu::onTouch() / itemAtPoint() so existing menus can be
    driven by touch in addition to keys (PR F3).

Build / modernization
  * Opt-in CXXSTD knob in Makefile (default c++11) plus CI matrix
    that exercises C++14 and C++17 builds (PR E1).

New examples
  * gui/slider_demo, gui/spinbox_demo, gui/text_entry_demo,
    gui/menu_touch_demo, touch_demo, utf8_demo.

Tests
  * 363 -> 470 unit tests (107 new), all passing under
    make ARCH=linux SDL_EMULATION=y check.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

1060 of 1923 relevant lines covered (55.12%)

15861.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

6.96
/src/lcd_hal/linux/platform.cpp
1
/*
2
    MIT License
3

4
    Copyright (c) 2018-2020, Alexey Dynda
5

6
    Permission is hereby granted, free of charge, to any person obtaining a copy
7
    of this software and associated documentation files (the "Software"), to deal
8
    in the Software without restriction, including without limitation the rights
9
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
    copies of the Software, and to permit persons to whom the Software is
11
    furnished to do so, subject to the following conditions:
12

13
    The above copyright notice and this permission notice shall be included in all
14
    copies or substantial portions of the Software.
15

16
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
    SOFTWARE.
23
*/
24

25
#if ( defined(__linux__) || defined(__APPLE__) ) && !defined(ARDUINO)
26

27
#include "../io.h"
28

29
#include <sys/stat.h>
30
#include <sys/types.h>
31
#include <errno.h>
32
#include <fcntl.h>
33
#include <stdio.h>
34
#include <stdlib.h>
35
#include <unistd.h>
36

37
#include <sys/ioctl.h>
38
#if defined(__linux__)
39
#include <linux/i2c-dev.h>
40
#include <linux/spi/spidev.h>
41
#endif
42

43
#if __has_include(<gpiod.h>)
44
#define LCDGFX_USE_LIBGPIOD 1
45
#include <gpiod.h>
46
#else
47
#define LCDGFX_USE_LIBGPIOD 0
48
#endif
49

50
//#include <cstdlib>
51

52
#include <map>
53

54
#if defined(CONFIG_LINUX_SPI_AVAILABLE) && defined(CONFIG_LINUX_SPI_ENABLE) && !defined(SDL_EMULATION)
55
#define LINUX_SPI_AVAILABLE
56
#endif
57

58

59
#define MAX_GPIO_COUNT 256
60

61
#ifdef IN
62
#undef IN
63
#endif
64
#define IN 0
65

66
#ifdef OUT
67
#undef OUT
68
#endif
69
#define OUT 1
70

71

72
#if LCDGFX_USE_LIBGPIOD
73
// libgpiod context
74
static struct gpiod_chip *lcdgfx_gpiod_chip = NULL;
75
static struct gpiod_line *lcdgfx_gpiod_lines[MAX_GPIO_COUNT] = {NULL};
76

77
static int lcdgfx_gpiod_init_chip(void) {
78
    if (!lcdgfx_gpiod_chip) {
79
        lcdgfx_gpiod_chip = gpiod_chip_open_by_number(0); // default to gpiochip0
80
        if (!lcdgfx_gpiod_chip) {
81
            fprintf(stderr,
82
                    "lcdgfx: failed to open gpiochip0 via libgpiod: %s%s\n"
83
                    "        - is /dev/gpiochip0 present?\n"
84
                    "        - does the user have access (try the gpio group, or run as root)?\n",
85
                    strerror(errno),
86
                    getuid() == 0 ? "" : " (running non-root)");
87
            return -1;
88
        }
89
    }
90
    return 0;
91
}
92

93
static struct gpiod_line *lcdgfx_gpiod_get_line(int pin) {
94
    if (pin < 0 || pin >= MAX_GPIO_COUNT) return NULL;
95
    if (!lcdgfx_gpiod_lines[pin]) {
96
        if (lcdgfx_gpiod_init_chip() < 0) return NULL;
97
        lcdgfx_gpiod_lines[pin] = gpiod_chip_get_line(lcdgfx_gpiod_chip, pin);
98
        if (!lcdgfx_gpiod_lines[pin]) {
99
            fprintf(stderr,
100
                    "lcdgfx: gpiod_chip_get_line failed for pin %d: %s\n",
101
                    pin, strerror(errno));
102
        }
103
    }
104
    return lcdgfx_gpiod_lines[pin];
105
}
106
#else
107
// sysfs GPIO fallback. This path is deprecated upstream and is broken on
108
// Linux kernels >= 6.6 where /sys/class/gpio is absent. Build with
109
// libgpiod-dev installed to get the modern path; this fallback exists only
110
// for old kernels and minimal images. See src/lcd_hal/README.md.
111
#endif
112

113
int gpio_export(int pin)
×
114
{
115
#if LCDGFX_USE_LIBGPIOD
116
    // No export needed for libgpiod
117
    return lcdgfx_gpiod_init_chip();
118
#else
119
    char buffer[4];
×
120
    ssize_t bytes_written;
×
121
    int fd;
×
122
    char path[64];
×
123

124
    snprintf(path, sizeof(path), "/sys/class/gpio/gpio%d", pin);
×
125

126
    if ( access(path, F_OK) == 0 )
×
127
    {
128
        return 0;
129
    }
130

131
    fd = open("/sys/class/gpio/export", O_WRONLY);
×
132
    if ( -1 == fd )
×
133
    {
134
        fprintf(stderr,
×
135
                "lcdgfx: failed to allocate gpio pin %d via sysfs: %s%s\n"
136
                "        sysfs GPIO is not available on Linux kernels >= 6.6.\n"
137
                "        Install libgpiod-dev and rebuild — lcdgfx will then\n"
138
                "        use the libgpiod path automatically.\n",
139
                pin, strerror(errno),
×
140
                getuid() == 0 ? "" : " (need to be root, or in the gpio group)");
×
141
        return (-1);
×
142
    }
143

144
    bytes_written = snprintf(buffer, sizeof(buffer), "%d", pin);
×
145
    if ( write(fd, buffer, bytes_written) < 0 )
×
146
    {
147
        fprintf(stderr, "Failed to allocate gpio pin[%d]: %s%s!\n", pin, strerror(errno),
×
148
                getuid() == 0 ? "" : ", need to be root");
×
149
        close(fd);
×
150
        return -1;
×
151
    }
152
    close(fd);
×
153
    return (0);
154
#endif
155
}
156

157
int gpio_unexport(int pin)
×
158
{
159
#if LCDGFX_USE_LIBGPIOD
160
    if (pin >= 0 && pin < MAX_GPIO_COUNT && lcdgfx_gpiod_lines[pin]) {
161
        gpiod_line_release(lcdgfx_gpiod_lines[pin]);
162
        lcdgfx_gpiod_lines[pin] = NULL;
163
    }
164
    return 0;
165
#else
166
    char buffer[4];
×
167
    ssize_t bytes_written;
×
168
    int fd;
×
169

170
    fd = open("/sys/class/gpio/unexport", O_WRONLY);
×
171
    if ( -1 == fd )
×
172
    {
173
        fprintf(stderr, "Failed to free gpio pin resources!\n");
×
174
        return (-1);
×
175
    }
176

177
    bytes_written = snprintf(buffer, sizeof(buffer), "%d", pin);
×
178
    if ( write(fd, buffer, bytes_written) < 0 )
×
179
    {
180
        fprintf(stderr, "Failed to free gpio pin resources!\n");
×
181
    }
182
    close(fd);
×
183
    return (0);
184
#endif
185
}
186

187
int gpio_direction(int pin, int dir)
×
188
{
189
#if LCDGFX_USE_LIBGPIOD
190
    struct gpiod_line *line = lcdgfx_gpiod_get_line(pin);
191
    if (!line) {
192
        fprintf(stderr, "libgpiod: Failed to get line for pin %d\n", pin);
193
        return -1;
194
    }
195
    // Release if already requested so we can re-request with new direction
196
    gpiod_line_release(line);
197
    int ret = 0;
198
    if (dir == OUT) {
199
        ret = gpiod_line_request_output(line, "lcdgfx", 0);
200
    } else {
201
        ret = gpiod_line_request_input(line, "lcdgfx");
202
    }
203
    if (ret < 0) {
204
        fprintf(stderr, "libgpiod: Failed to set direction for pin %d\n", pin);
205
        return -1;
206
    }
207
    return 0;
208
#else
209
    static const char s_directions_str[] = "in\0out";
×
210

211
    char path[64];
×
212
    int fd;
×
213

214
    snprintf(path, sizeof(path), "/sys/class/gpio/gpio%d/direction", pin);
×
215
    fd = open(path, O_WRONLY);
×
216
    if ( -1 == fd )
×
217
    {
218
        fprintf(stderr, "Failed to set gpio pin direction1[%d]: %s!\n", pin, strerror(errno));
×
219
        return (-1);
×
220
    }
221

222
    if ( -1 == write(fd, &s_directions_str[IN == dir ? 0 : 3], IN == dir ? 2 : 3) )
×
223
    {
224
        fprintf(stderr, "Failed to set gpio pin direction2[%d]: %s!\n", pin, strerror(errno));
×
225
        return (-1);
×
226
    }
227

228
    close(fd);
×
229
    return (0);
230
#endif
231
}
232

233
int gpio_read(int pin)
×
234
{
235
#if LCDGFX_USE_LIBGPIOD
236
    struct gpiod_line *line = lcdgfx_gpiod_get_line(pin);
237
    if (!line) {
238
        fprintf(stderr, "libgpiod: Failed to get line for pin %d\n", pin);
239
        return -1;
240
    }
241
    int value = gpiod_line_get_value(line);
242
    return value;
243
#else
244
    char path[32];
×
245
    char value_str[3];
×
246
    int fd;
×
247

248
    snprintf(path, sizeof(path), "/sys/class/gpio/gpio%d/value", pin);
×
249
    fd = open(path, O_RDONLY);
×
250
    if ( -1 == fd )
×
251
    {
252
        fprintf(stderr, "Failed to read gpio pin value!\n");
×
253
        return (-1);
×
254
    }
255

256
    if ( -1 == read(fd, value_str, 3) )
×
257
    {
258
        fprintf(stderr, "Failed to read gpio pin value!\n");
×
259
        return (-1);
×
260
    }
261

262
    close(fd);
×
263

264
    return (atoi(value_str));
×
265
#endif
266
}
267

268
int gpio_write(int pin, int value)
×
269
{
270
#if LCDGFX_USE_LIBGPIOD
271
    struct gpiod_line *line = lcdgfx_gpiod_get_line(pin);
272
    if (!line) {
273
        fprintf(stderr, "libgpiod: Failed to get line for pin %d\n", pin);
274
        return -1;
275
    }
276
    int ret = gpiod_line_set_value(line, value);
277
    return ret;
278
#else
279
    static const char s_values_str[] = "01";
×
280

281
    char path[64];
×
282
    int fd;
×
283

284
    snprintf(path, sizeof(path), "/sys/class/gpio/gpio%d/value", pin);
×
285
    fd = open(path, O_WRONLY);
×
286
    if ( -1 == fd )
×
287
    {
288
        fprintf(stderr, "Failed to set gpio pin value[%d]: %s%s!\n", pin, strerror(errno),
×
289
                getuid() == 0 ? "" : ", need to be root");
×
290
        return (-1);
×
291
    }
292

293
    if ( 1 != write(fd, &s_values_str[LOW == value ? 0 : 1], 1) )
×
294
    {
295
        fprintf(stderr, "Failed to set gpio pin value[%d]: %s%s!\n", pin, strerror(errno),
×
296
                getuid() == 0 ? "" : ", need to be root");
×
297
        return (-1);
×
298
    }
299

300
    close(fd);
×
301
    return (0);
302
#endif
303
}
304

305
#if defined(__KERNEL__) // ============== KERNEL
306

307
int lcd_gpioRead(int pin)
308
{
309
    // TODO: Not implemented
310
    return LCD_LOW;
311
}
312

313
void lcd_gpioWrite(int pin, int level)
314
{
315
    // TODO: Not implemented
316
}
317

318
void lcd_delay(unsigned long ms)
319
{
320
    // TODO: Not implemented
321
}
322

323
void lcd_delayUs(unsigned long us)
324
{
325
    // TODO: Not implemented
326
}
327

328
int lcd_adcRead(int pin)
329
{
330
    // TODO: Not implemented
331
    return 0;
332
}
333

334
uint32_t lcd_millis(void)
335
{
336
    // TODO: Not implemented
337
    return 0;
338
}
339

340
uint32_t lcd_micros(void)
341
{
342
    // TODO: Not implemented
343
    return 0;
344
}
345

346
void lcd_gpioMode(int pin, int mode)
347
{
348
    // TODO: Not implemented
349
}
350

351
#elif !defined(SDL_EMULATION)
352

353
#ifdef LINUX_SPI_AVAILABLE
354
typedef struct
355
{
356
    void (*on_pin_change)(void *);
357
    void *arg;
358
} SPinEvent;
359
#endif
360

361
static uint8_t s_exported_pin[MAX_GPIO_COUNT] = {0};
362
static uint8_t s_pin_mode[MAX_GPIO_COUNT] = {0};
363
#ifdef LINUX_SPI_AVAILABLE
364
std::map<int, SPinEvent> s_events;
365
#endif
366

367
void lcd_gpioMode(int pin, int mode)
368
{
369
    if ( !s_exported_pin[pin] )
370
    {
371
        if ( gpio_export(pin) < 0 )
372
        {
373
            return;
374
        }
375
        s_exported_pin[pin] = 1;
376
    }
377
    if ( mode == LCD_GPIO_OUTPUT )
378
    {
379
        gpio_direction(pin, OUT);
380
        s_pin_mode[pin] = 1;
381
    }
382
    if ( mode == LCD_GPIO_INPUT )
383
    {
384
        gpio_direction(pin, IN);
385
        s_pin_mode[pin] = 0;
386
    }
387
}
388

389
void lcd_gpioWrite(int pin, int level)
390
{
391
#ifdef LINUX_SPI_AVAILABLE
392
    if ( s_events.find(pin) != s_events.end() )
393
    {
394
        s_events[pin].on_pin_change(s_events[pin].arg);
395
    }
396
#endif
397

398
    if ( !s_exported_pin[pin] )
399
    {
400
        if ( gpio_export(pin) < 0 )
401
        {
402
            return;
403
        }
404
        s_exported_pin[pin] = 1;
405
    }
406
    if ( !s_pin_mode[pin] )
407
    {
408
        pinMode(pin, OUTPUT);
409
    }
410
    gpio_write(pin, level);
411
}
412

413
void lcd_registerGpioEvent(int pin, void (*on_pin_change)(void *), void *arg)
414
{
415
#ifdef LINUX_SPI_AVAILABLE
416
    s_events[pin].arg = arg;
417
    s_events[pin].on_pin_change = on_pin_change;
418
#endif
419
}
420

421
void lcd_unregisterGpioEvent(int pin)
422
{
423
    s_events.erase(pin);
424
}
425

426
int lcd_gpioRead(int pin)
427
{
428
    return gpio_read(pin) ? LCD_HIGH : LCD_LOW;
429
}
430

431
int lcd_adcRead(int pin)
432
{
433
    // NOT IMPLEMENTED
434
    return 0;
435
}
436

437
void lcd_delay(unsigned long ms)
438
{
439
    usleep(ms * 1000);
440
}
441

442
void lcd_delayUs(unsigned long us)
443
{
444
    usleep(us);
445
}
446

447
uint32_t lcd_millis(void)
448
{
449
    struct timespec ts;
450
    clock_gettime(CLOCK_MONOTONIC, &ts);
451
    return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
452
}
453

454
uint32_t lcd_micros(void)
455
{
456
    struct timespec ts;
457
    clock_gettime(CLOCK_MONOTONIC, &ts);
458
    return ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
459
}
460

461
int lcd_gfx_min(int a, int b)
462
{
463
    return a < b ? a : b;
464
}
465
int lcd_gfx_max(int a, int b)
466
{
467
    return a > b ? a : b;
468
}
469

470
#else // SDL_EMULATION
471

472
int lcd_gpioRead(int pin)
×
473
{
474
    return sdl_read_digital(pin);
×
475
}
476

477
int lcd_adcRead(int pin)
×
478
{
479
    return sdl_read_analog(pin);
×
480
}
481

482
void lcd_gpioWrite(int pin, int level)
12,127✔
483
{
484
    sdl_write_digital(pin, level);
12,127✔
485
}
12,127✔
486

487
void lcd_gpioMode(int pin, int mode)
×
488
{
489
    // TODO: Not implemented
490
}
×
491

492
void lcd_delay(unsigned long ms)
432✔
493
{
494
    usleep(ms * 1000);
432✔
495
}
432✔
496

497
void lcd_delayUs(unsigned long us)
×
498
{
499
    usleep(us);
×
500
}
×
501

502
uint32_t lcd_millis(void)
×
503
{
504
    struct timespec ts;
×
505
    clock_gettime(CLOCK_MONOTONIC, &ts);
×
506
    return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
×
507
}
508

509
uint32_t lcd_micros(void)
×
510
{
511
    struct timespec ts;
×
512
    clock_gettime(CLOCK_MONOTONIC, &ts);
×
513
    return ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
×
514
};
515

516
int lcd_gfx_min(int a, int b)
×
517
{
518
    return a < b ? a : b;
×
519
}
520
int lcd_gfx_max(int a, int b)
×
521
{
522
    return a > b ? a : b;
×
523
}
524

525
#endif // SDL_EMULATION
526

527
void lcd_randomSeed(int seed)
×
528
{
529
    // TODO: Not implemented
530
}
×
531

532
void lcd_attachInterrupt(int pin, void (*interrupt)(void), int level)
×
533
{
534
    // TODO: Not implemented
535
}
×
536

537
uint8_t lcd_pgmReadByte(const void *ptr)
26,566✔
538
{
539
    return *reinterpret_cast<const uint8_t *>(ptr);
26,566✔
540
}
541

542
uint16_t lcd_eepromReadWord(const void *ptr)
×
543
{
544
    // TODO: Not implemented
545
    return 0;
×
546
}
547

548
void lcd_eepromWriteWord(const void *ptr, uint16_t val)
×
549
{
550
    // TODO: Not implemented
551
}
×
552

553
int lcd_random(int v)
×
554
{
555
    return rand() % v;
×
556
}
557

558
int lcd_random(int min, int max)
×
559
{
560
    return rand() % (max - min + 1) + min;
×
561
}
562

563
#endif // __linux__
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc