Skip to content

Commit f847dc0

Browse files
Add native screenshot capture for Mac (POSIX) build
Enable pixel-perfect screenshot capture on the Mac build by reading directly from the LVGL framebuffer, matching the existing ESP32 behavior. Changes: - src/hasp_gui.cpp: Add POSIX file-based screenshot using fopen/fwrite/fclose, extend bitmap header guard to compile on POSIX, log full absolute path - src/hasp/hasp_dispatch.cpp: Enable the screenshot command for POSIX builds - src/drv/tft/tft_driver_sdl2.cpp: Add F12 keyboard shortcut via SDL event watch using a flag polled from guiLoop() for thread safety Usage: - MQTT: publish to hasp/<node>/command/screenshot - Keyboard: press F12 in the simulator window - Default output: screenshot.bmp in the working directory Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 57d2ebf commit f847dc0

3 files changed

Lines changed: 91 additions & 2 deletions

File tree

src/drv/tft/tft_driver_sdl2.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,26 @@
2222
#include "custom/bootlogo_template.h" // Sketch tab header for xbm images
2323
#endif
2424

25+
static volatile bool screenshot_requested = false;
26+
27+
static int screenshot_event_watch(void* userdata, SDL_Event* event)
28+
{
29+
(void)userdata;
30+
if(event->type == SDL_KEYDOWN && event->key.keysym.sym == SDLK_F12) {
31+
screenshot_requested = true;
32+
}
33+
return 1;
34+
}
35+
36+
bool gui_pop_screenshot_request(void)
37+
{
38+
if(screenshot_requested) {
39+
screenshot_requested = false;
40+
return true;
41+
}
42+
return false;
43+
}
44+
2545
namespace dev {
2646

2747
/**
@@ -75,6 +95,7 @@ void TftSdl::init(int32_t w, int h)
7595
* You have to call 'lv_tick_inc()' in periodically to inform LittelvGL about how much time were elapsed
7696
* Create an SDL thread to do this*/
7797
SDL_CreateThread(tick_thread, "tick", NULL);
98+
SDL_AddEventWatch(screenshot_event_watch, NULL);
7899

79100
#if HASP_USE_LVGL_TASK
80101
#error "SDL2 LVGL task is not implemented"

src/hasp/hasp_dispatch.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,14 @@ void dispatch_screenshot(const char*, const char* filename, uint8_t source)
644644
guiTakeScreenshot(filename);
645645
}
646646

647+
#elif defined(POSIX)
648+
649+
if(strlen(filename) == 0) {
650+
guiTakeScreenshot("screenshot.bmp");
651+
} else {
652+
guiTakeScreenshot(filename);
653+
}
654+
647655
#else
648656
LOG_WARNING(TAG_MSGR, D_FILE_SAVE_FAILED, filename);
649657
#endif

src/hasp_gui.cpp

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
File pFileOut;
2424
#endif
2525

26+
#if defined(POSIX) && USE_MONITOR
27+
bool gui_pop_screenshot_request(void);
28+
#endif
29+
2630
#if ESP32
2731
static SemaphoreHandle_t xGuiSemaphore = NULL;
2832
static TaskHandle_t g_lvgl_task_handle;
@@ -388,6 +392,12 @@ IRAM_ATTR void guiLoop(void)
388392
#if HASP_TARGET_ARDUINO
389393
// haspTouch.loop();
390394
#endif
395+
396+
#if defined(POSIX) && USE_MONITOR
397+
if(gui_pop_screenshot_request()) {
398+
guiTakeScreenshot("screenshot.bmp");
399+
}
400+
#endif
391401
}
392402

393403
void guiEverySecond(void)
@@ -618,7 +628,7 @@ bool guiSetConfig(const JsonObject& settings)
618628
#endif // HASP_USE_CONFIG
619629

620630
/* **************************** SCREENSHOTS ************************************** */
621-
#if HASP_USE_SPIFFS > 0 || HASP_USE_LITTLEFS > 0 || HASP_USE_HTTP > 0
631+
#if HASP_USE_SPIFFS > 0 || HASP_USE_LITTLEFS > 0 || HASP_USE_HTTP > 0 || defined(POSIX)
622632

623633
/** Send Bitmap Header.
624634
*
@@ -666,7 +676,7 @@ void gui_flush_not_complete()
666676
{
667677
LOG_WARNING(TAG_GUI, F("Pixelbuffer not completely sent"));
668678
}
669-
#endif // HASP_USE_SPIFFS > 0 || HASP_USE_LITTLEFS > 0 || HASP_USE_HTTP > 0
679+
#endif // HASP_USE_SPIFFS > 0 || HASP_USE_LITTLEFS > 0 || HASP_USE_HTTP > 0 || defined(POSIX)
670680

671681
#if HASP_USE_SPIFFS > 0 || HASP_USE_LITTLEFS > 0
672682
/* Flush VDB bytes to a file */
@@ -722,6 +732,56 @@ void guiTakeScreenshot(const char* pFileName)
722732
LOG_WARNING(TAG_GUI, F(D_FILE_SAVE_FAILED), pFileName);
723733
}
724734
}
735+
#elif defined(POSIX)
736+
static FILE* pFileOutPosix = NULL;
737+
738+
static void gui_screenshot_to_file(lv_disp_drv_t* disp, const lv_area_t* area, lv_color_t* color_p)
739+
{
740+
size_t len = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1);
741+
len *= sizeof(lv_color_t);
742+
size_t res = fwrite((uint8_t*)color_p, 1, len, pFileOutPosix);
743+
if(res != len) gui_flush_not_complete();
744+
745+
drv_display_flush_cb(disp, area, color_p);
746+
}
747+
748+
void guiTakeScreenshot(const char* pFileName)
749+
{
750+
uint8_t buffer[sizeof(bmp_header_t) + 2];
751+
gui_get_bitmap_header(buffer, sizeof(buffer));
752+
753+
pFileOutPosix = fopen(pFileName, "wb");
754+
if(pFileOutPosix) {
755+
756+
size_t len = fwrite(buffer, 1, sizeof(buffer), pFileOutPosix);
757+
if(len == sizeof(buffer)) {
758+
LOG_VERBOSE(TAG_GUI, F("Bitmap header written"));
759+
760+
lv_disp_t* disp = lv_disp_get_default();
761+
drv_display_flush_cb = disp->driver.flush_cb;
762+
disp->driver.flush_cb = gui_screenshot_to_file;
763+
764+
lv_obj_invalidate(lv_scr_act());
765+
lv_refr_now(NULL);
766+
disp->driver.flush_cb = drv_display_flush_cb;
767+
768+
char fullpath[PATH_MAX];
769+
if(realpath(pFileName, fullpath)) {
770+
LOG_VERBOSE(TAG_GUI, F("Bitmap data flushed to %s"), fullpath);
771+
} else {
772+
LOG_VERBOSE(TAG_GUI, F("Bitmap data flushed to %s"), pFileName);
773+
}
774+
775+
} else {
776+
LOG_ERROR(TAG_GUI, F("Data written does not match header size"));
777+
}
778+
fclose(pFileOutPosix);
779+
pFileOutPosix = NULL;
780+
781+
} else {
782+
LOG_WARNING(TAG_GUI, F(D_FILE_SAVE_FAILED), pFileName);
783+
}
784+
}
725785
#endif
726786

727787
#if HASP_USE_HTTP > 0

0 commit comments

Comments
 (0)