diff --git a/src/configuration.h b/src/configuration.h index 503ad68433..24206a4b0f 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -681,6 +681,16 @@ along with this program. If not, see . #define HAS_SCREEN_MIRROR 0 #endif +// Mirroring MUI (device-ui/LVGL) streams RGB565 dirty rects instead of the +// 1bpp framebuffer, and needs a device-ui carrying the flush observer. Until +// that lands upstream the whole path - queue, pool and input seam - compiles +// out, so a color build that does not opt in carries none of it. +#if HAS_SCREEN_MIRROR && HAS_TFT && defined(MESHTASTIC_MUI_MIRROR) +#define HAS_MUI_MIRROR 1 +#else +#define HAS_MUI_MIRROR 0 +#endif + #ifndef USE_ETHERNET_DEFAULT #define USE_ETHERNET_DEFAULT 0 #endif diff --git a/src/graphics/ScreenMirror.cpp b/src/graphics/ScreenMirror.cpp index 2628891709..f56274d498 100644 --- a/src/graphics/ScreenMirror.cpp +++ b/src/graphics/ScreenMirror.cpp @@ -40,7 +40,7 @@ void ScreenMirror::freeSnapshotLocked() } paletteSig = 0; paletteCount = 0; -#if HAS_TFT +#if HAS_MUI_MIRROR if (muiPool) { memaudit::add("display", -(int32_t)MUI_POOL_BYTES); free(muiPool); @@ -64,14 +64,13 @@ void ScreenMirror::setMirror(bool enabled) // Force an immediate frame so the client doesn't wait for the next // on-screen change. oneShot = true; -#if HAS_TFT +#if HAS_MUI_MIRROR if (muiRefresh) muiRefresh(); // MUI delivers "a frame" as a full-repaint rect burst #endif } else { oneShot = false; - freeSnapshotLocked(); - muiOwner = nullptr; + freeSnapshotLocked(); // also releases the MUI pool and rect ownership } if (changed) LOG_INFO("Screen mirror %s", enabled ? "enabled" : "disabled"); @@ -81,7 +80,7 @@ void ScreenMirror::requestFrame() { concurrency::LockGuard g(&lock); oneShot = true; -#if HAS_TFT +#if HAS_MUI_MIRROR if (muiRefresh) muiRefresh(); #endif @@ -212,7 +211,7 @@ bool ScreenMirror::copyPaletteChunk(uint32_t &clientPaletteSig, uint8_t &clientR return true; } -#if HAS_TFT +#if HAS_MUI_MIRROR void ScreenMirror::onMuiRect(int16_t x, int16_t y, uint16_t w, uint16_t h, const uint16_t *pixels) { uint32_t readyId = 0; @@ -309,7 +308,7 @@ bool ScreenMirror::copyMuiChunkLocked(meshtastic_DisplayFrame &out) bool ScreenMirror::hasChunkFor(const void *client, uint32_t clientFrameId, uint16_t clientOffset) { concurrency::LockGuard g(&lock); -#if HAS_TFT +#if HAS_MUI_MIRROR // One shared rect cursor means one consumer; another connection simply // sees no rects rather than stealing half of each frame. if (muiCount && (muiOwner == nullptr || muiOwner == client)) @@ -321,7 +320,7 @@ bool ScreenMirror::hasChunkFor(const void *client, uint32_t clientFrameId, uint1 bool ScreenMirror::copyChunk(const void *client, uint32_t &clientFrameId, uint16_t &clientOffset, meshtastic_DisplayFrame &out) { concurrency::LockGuard g(&lock); -#if HAS_TFT +#if HAS_MUI_MIRROR // MUI rects drain ahead of (and on MUI builds, instead of) mono snapshots. // Spike scope: the rect queue has a single consumer, not per-client cursors. if (muiCount) { diff --git a/src/graphics/ScreenMirror.h b/src/graphics/ScreenMirror.h index 3552f4723f..73d42a3aa9 100644 --- a/src/graphics/ScreenMirror.h +++ b/src/graphics/ScreenMirror.h @@ -58,7 +58,7 @@ class ScreenMirror /// when the client holds the full current palette (or coloring is off). bool copyPaletteChunk(uint32_t &clientPaletteSig, uint8_t &clientRegionOffset, meshtastic_DisplayPalette &out); -#if HAS_TFT +#if HAS_MUI_MIRROR /// MUI path: queues one LVGL dirty rect (native little-endian RGB565). /// Called on the LVGL thread via the device-ui flush observer; copies and returns. void onMuiRect(int16_t x, int16_t y, uint16_t w, uint16_t h, const uint16_t *pixels); @@ -101,7 +101,7 @@ class ScreenMirror uint16_t paletteDefaultOn = 0; uint16_t paletteDefaultOff = 0; -#if HAS_TFT +#if HAS_MUI_MIRROR // MUI dirty-rect queue: FIFO rect headers over a linear pixel pool, // compacted whenever it drains. Spike scope: single consumer. struct MuiRect { @@ -131,7 +131,7 @@ class ScreenMirror extern ScreenMirror screenMirror; -#if HAS_TFT +#if HAS_MUI_MIRROR /** * Routes a remote input event straight into device-ui's injection seam. * MUI builds never construct an InputBroker (Modules.cpp skips it when diff --git a/src/graphics/tftSetup.cpp b/src/graphics/tftSetup.cpp index 20e8c74bd2..703b410e5e 100644 --- a/src/graphics/tftSetup.cpp +++ b/src/graphics/tftSetup.cpp @@ -12,7 +12,7 @@ #include "graphics/driver/DisplayDriver.h" #include "graphics/driver/DisplayDriverConfig.h" #include "input/InputBroker.h" -#if defined(MESHTASTIC_MUI_MIRROR) +#if HAS_MUI_MIRROR #include "input/InputDriver.h" #endif #include "util/ISpiLock.h" @@ -321,12 +321,9 @@ class ReentrantSpiLock : public ISpiLock static ReentrantSpiLock reentrantSpiLock; -#if HAS_SCREEN_MIRROR && defined(MESHTASTIC_MUI_MIRROR) +#if HAS_MUI_MIRROR namespace graphics { -// Maps a remote input event onto device-ui's virtual LVGL devices. Note the -// LEFT/RIGHT cross-map: the broker's codes were modeled on LVGL keys but -// those two are swapped. // Reports MUI's logical panel geometry for DeviceMetadata.display. The BaseUI // `screen` object does not exist on MUI builds, so the dimensions come from // LVGL itself (already rotated to the logical orientation). @@ -347,6 +344,9 @@ bool muiDisplayInfo(uint16_t &width, uint16_t &height, bool &hasTouch) return width > 0 && height > 0; } +// Maps a remote input event onto device-ui's virtual LVGL devices. Note the +// LEFT/RIGHT cross-map: the broker's codes were modeled on LVGL keys but +// those two are swapped. bool muiInjectInputEvent(uint32_t eventCode, uint32_t kbChar, uint32_t touchX, uint32_t touchY) { if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) @@ -412,7 +412,7 @@ void tftSetup(void) I2CKeyboardScanner::setSecondaryBus(i2cProxy); #endif #ifndef ARCH_PORTDUINO -#if HAS_SCREEN_MIRROR && defined(MESHTASTIC_MUI_MIRROR) +#if HAS_MUI_MIRROR // Must precede DeviceScreen::init: device-ui only builds its virtual input // devices (and the default focus group they need) when injection is asked for. InputDriver::enableInjection(); @@ -420,7 +420,7 @@ void tftSetup(void) deviceScreen = &DeviceScreen::create(reentrantSpiLock); PacketAPI::create(PacketServer::init()); deviceScreen->init(new PacketClient); -#if HAS_SCREEN_MIRROR && defined(MESHTASTIC_MUI_MIRROR) +#if HAS_MUI_MIRROR // Stream MUI's dirty rects to local clients (see graphics::ScreenMirror). // Gated on MESHTASTIC_MUI_MIRROR until the device-ui flush observer merges // (jamesarich/device-ui screen-mirror-poc); the vendored pin lacks it. diff --git a/src/main.cpp b/src/main.cpp index c07cf94ea5..9e3207e530 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1394,7 +1394,7 @@ extern meshtastic_DeviceMetadata getDeviceMetadata() deviceMetadata.has_xeddsa = true; #endif -#if HAS_TFT && HAS_SCREEN_MIRROR && defined(MESHTASTIC_MUI_MIRROR) +#if HAS_MUI_MIRROR // MUI owns the panel and leaves `screen` null, so ask LVGL instead. uint16_t muiW = 0, muiH = 0; bool muiTouch = false; diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index dbe9b817c9..f915df4c81 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -2186,7 +2186,7 @@ void AdminModule::handleSendInputEvent(const meshtastic_AdminMessage_InputEvent // Wake the device if asleep powerFSM.trigger(EVENT_INPUT); -#if HAS_SCREEN_MIRROR && defined(MESHTASTIC_MUI_MIRROR) +#if HAS_MUI_MIRROR // MUI builds never construct an InputBroker (Modules.cpp skips it when // displaymode is COLOR), so remote input reaches the LVGL UI directly. if (graphics::muiInjectInputEvent(inputEvent.event_code, inputEvent.kb_char, inputEvent.touch_x, inputEvent.touch_y))