Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e92cb48
Add a display flush observer for screen streaming
jamesarich Aug 31, 2026
3592346
Invalidate the overlay layers on a requested full refresh
jamesarich Aug 31, 2026
1a0e950
Add remote input injection: virtual pointer and keypad devices
jamesarich Aug 31, 2026
1d8a400
Quiet the injection logging to a single debug line
jamesarich Aug 31, 2026
f9122a8
Wake the panel on injected input, and navigate by encoder rotation
jamesarich Aug 31, 2026
c7a4f69
Make remote input opt-in, and harden the observer and injectors
jamesarich Aug 31, 2026
1a8ede3
refactor(graphics): move screen mirroring into a DisplayMirror class
jamesarich Sep 9, 2026
9d44c2a
docs(graphics): state DisplayMirror::start's threading precondition
jamesarich Sep 9, 2026
cd7ef66
refactor(graphics): invert the mirror's dependency on the display driver
jamesarich Sep 16, 2026
2bf389b
Merge master: hook every flush variant, report stride and byte order
jamesarich Oct 2, 2026
7514a7d
Format the mirror flush callback
jamesarich Oct 2, 2026
b9ab2c6
Move the flush hook onto the driver instance and read LVGL's stride
jamesarich Oct 2, 2026
93bdf98
Let DisplayMirror own the long-press hold
jamesarich Oct 2, 2026
c3fc18e
Merge remote-tracking branch 'origin/master' into screen-mirror-poc
jamesarich Oct 16, 2026
e575cc9
fix(graphics): bind the mirror's input and refresh to its display, an…
jamesarich Oct 16, 2026
a013445
Merge branch 'master' into screen-mirror-poc
mverch67 Oct 19, 2026
4443134
Merge branch 'master' into screen-mirror-poc
mverch67 Oct 19, 2026
8bb03bc
fix(graphics): pass X11 flushes to the mirror's frame observer
jamesarich Oct 20, 2026
0b20b01
fix(graphics): mirror only RGB565 displays, and skip the X11 hook wit…
jamesarich Oct 20, 2026
04ef5c5
Merge branch 'master' into screen-mirror-poc
mverch67 Nov 1, 2026
0996862
feat(graphics): route mirrored keys through the input policy pipeline
jamesarich Nov 2, 2026
fbca1ae
fix(graphics): keep mirrored input in the active focus group
jamesarich Nov 2, 2026
a36ec08
fix(graphics): follow the group of the input devices the view manages
jamesarich Nov 2, 2026
41b0f00
fix(graphics): hold mirrored input until a slept panel is awake
jamesarich Nov 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions include/graphics/DeviceScreen.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ class DeviceScreen
void toggleDisplay(void);
bool prepareShutdown(uint32_t timeoutMs = 1000);

/** The display driver behind this screen, for hosts that drive it directly. */
DisplayDriver *getDisplayDriver(void) const { return gui->getDisplayDriver(); }

#if defined(ARDUINO_ARCH_ESP32)
int prepareSleep(void *);
int wakeUp(esp_sleep_wakeup_cause_t cause);
Expand Down
38 changes: 38 additions & 0 deletions include/graphics/DisplayMirror.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#pragma once

#include <cstdint>

class DisplayDriver;

/**
* @brief Streams the rendered screen to a host and feeds the host's input back
* into LVGL. Idle until start(); no driver knows this class exists.
*/
class DisplayMirror
{
public:
// LVGL thread: pixels is the area's top-left, rows stride pixels apart, in the
// byte order pixelsByteSwapped() reports. Copy, never write, and return.
using FrameObserver = void (*)(int16_t x, int16_t y, uint16_t width, uint16_t height, const uint16_t *pixels,
uint16_t stride);

// once, after DeviceScreen::init() and before the UI task starts
static void start(DisplayDriver *driver);

// nullptr stops capture; a flush may still be in flight for one UI tick
static void setFrameObserver(FrameObserver observer);

static bool pixelsByteSwapped(void);

// any thread; repaints the screen and its overlay layers
static void requestFullRefresh(void);

// remote input, from a single producer thread; dropped when the queue is full
static void injectTouch(int16_t x, int16_t y);
// held past this device's long-press threshold, so it behaves like a finger
static void injectLongPress(int16_t x, int16_t y);
static void injectKey(uint32_t key); // LV_KEY_* or a printable character
static void injectLongPressKey(uint32_t key);
// moves group focus; negative is backwards, clamped to a byte
static void injectEncoder(int16_t steps);
};
12 changes: 12 additions & 0 deletions include/graphics/driver/DisplayDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "graphics/DeviceGUI.h"
#include "graphics/LVGL/LVGLGraphics.h"
#include <cstdint>
#include <functional>

#define H_NORM_PX(h_scr_percent) ((int16_t)((screenWidth / 100.0) * (h_scr_percent)))
#define V_NORM_PX(v_scr_percent) ((int16_t)((screenHeight / 100.0) * (v_scr_percent)))
Expand Down Expand Up @@ -42,7 +43,17 @@ class DisplayDriver

lv_display_t *getDisplay(void) { return display; }

// pixels: top-left of the flushed area, rows stride pixels apart; LVGL thread
using FlushCallback =
std::function<void(int16_t x, int16_t y, uint16_t width, uint16_t height, const uint16_t *pixels, uint16_t stride)>;

// set once, before the UI task starts
void setFlushCB(FlushCallback cb) { flushCB = std::move(cb); }

protected:
// subclasses call this from their LVGL flush callback
void flush(lv_display_t *disp, const lv_area_t *area, const uint8_t *px_map);

LVGLGraphics lvgl;
LVGLDisplay *display;
LVGLTouch *touch;
Expand All @@ -52,4 +63,5 @@ class DisplayDriver

private:
static void displayToggleCb(void *displayDriver);
FlushCallback flushCB;
};
4 changes: 4 additions & 0 deletions include/graphics/driver/LGFXDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ template <class LGFX> void LGFXDriver<LGFX>::display_flush(lv_display_t *disp, c
{
uint32_t w = lv_area_get_width(area);
uint32_t h = lv_area_get_height(area);
static_cast<LGFXDriver *>(lv_display_get_driver_data(disp))->flush(disp, area, px_map);
{
ISpiLock::Guard bus;
lgfx->startWrite();
Expand All @@ -248,12 +249,14 @@ template <class LGFX> void LGFXDriver<LGFX>::display_flush(lv_display_t *disp, c
uint32_t h = lv_area_get_height(area);
lgfx->setAddrWindow(area->x1, area->y1, w, h);
lgfx->pushPixelsDMA((uint16_t *)px_map, w * h);
static_cast<LGFXDriver *>(lv_display_get_driver_data(disp))->flush(disp, area, px_map);
lv_display_flush_ready(disp);
}
#elif defined(USE_FULL_DOUBLE_BUFFER)
template <class LGFX> void LGFXDriver<LGFX>::display_flush(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
auto driver = static_cast<LGFXDriver *>(lv_display_get_driver_data(disp));
driver->flush(disp, area, px_map);
if (!driver->flushAreaValid) {
driver->flushArea = *area;
driver->flushAreaValid = true;
Expand Down Expand Up @@ -290,6 +293,7 @@ template <class LGFX> void LGFXDriver<LGFX>::display_flush(lv_display_t *disp, c
{
uint32_t w = lv_area_get_width(area);
uint32_t h = lv_area_get_height(area);
static_cast<LGFXDriver *>(lv_display_get_driver_data(disp))->flush(disp, area, px_map);
{
ISpiLock::Guard bus;
lgfx->pushImage(area->x1, area->y1, w, h, (uint16_t *)px_map);
Expand Down
Loading
Loading