Linux SDL: Use SDL2, more keys forwarded, allow resizing of gfx window

Fix Linux ARM64 build
Misc tweaks from Pipboy build
This commit is contained in:
Gordon Williams 2025-11-04 16:39:00 +00:00
parent a40827ddeb
commit 4e85b79ab4
6 changed files with 220 additions and 36 deletions

View File

@ -27,6 +27,8 @@
Add median-of-3 for Array.sort (1/3 of the compares for pre-sorted data)
Fix escape characters in strings inside templates inside templated strings `${"\n"}`
JIT: Fix floating point constants
Linux SDL: Use SDL2, more keys forwarded, allow resizing of gfx window
Fix Linux ARM64 build
2v27 : nRF5x: Ensure Bluetooth notifications work correctly when two separate connections use the same handle for their characteristics
nRF5x: Remove handlers from our handlers array when a device is disconnected

View File

@ -415,8 +415,37 @@ libs/graphics/lcd_js.c
ifeq ($(USE_LCD_SDL),1)
DEFINES += -DUSE_LCD_SDL
SOURCES += libs/graphics/lcd_sdl.c
LIBS += -lSDL
INCLUDE += -I/usr/include/SDL
# Prefer SDL2 if available
SDL2_CONFIG?=$(shell which sdl2-config 2>/dev/null)
ifdef MACOSX
$(info ********* MacOS build - using static SDL2 *********)
ifneq ($(SDL2_CONFIG),)
INCLUDE += $(shell $(SDL2_CONFIG) --cflags)
LIBS += $(shell $(SDL2_CONFIG) --static-libs)
else
# Fallback: try Mac platform defaults
# Link frameworks and ObjC runtime; SDL2 static lib name left to user's LIBS if needed
LIBS += -lobjc \
-Wl,-framework,Cocoa \
-Wl,-framework,OpenGL \
-Wl,-framework,IOKit \
-Wl,-framework,CoreFoundation \
-Wl,-framework,CoreVideo \
-lSDL2
INCLUDE += -I/usr/local/include/SDL2 -I/opt/homebrew/include/SDL2 -I/usr/include/SDL2
endif
else
$(info ********* Linux build - using dynamic SDL2 *********)
ifneq ($(SDL2_CONFIG),)
INCLUDE += $(shell $(SDL2_CONFIG) --cflags)
LIBS += $(shell $(SDL2_CONFIG) --libs)
else
# Fallback to typical SDL2 defaults; users can adjust via environment
LIBS += -lSDL2
INCLUDE += -I/usr/include/SDL2
endif
endif
endif
ifdef USE_LCD_FSMC

View File

@ -17,14 +17,47 @@
#include "jsparse.h"
#include "jsinteractive.h"
#include "lcd_sdl.h"
#include <SDL/SDL.h>
#include <SDL.h>
#define BPP 4
#define DEPTH 32
SDL_Surface *screen = 0;
SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;
SDL_Texture *texture = NULL; // holds uploaded framebuffer for rendering
// 'screen' is our offscreen framebuffer at logical (gfx) size
SDL_Surface *screen = NULL;
int fbW = 0, fbH = 0; // logical framebuffer size
int winW = 0, winH = 0; // current window (render) size
bool needsFlip = false;
// Compute a destination rect that preserves fb aspect ratio within the window
static void compute_draw_rect(int winW, int winH, int fbW, int fbH, SDL_Rect *dst) {
if (!dst) return;
if (winW <= 0 || winH <= 0) {
dst->x = 0; dst->y = 0; dst->w = 0; dst->h = 0; return;
}
if (fbW <= 0 || fbH <= 0) {
dst->x = 0; dst->y = 0; dst->w = winW; dst->h = winH; return;
}
// Compare winW/fbW vs winH/fbH by cross-multiplying to avoid floats
long long w = winW, h = winH, fw = fbW, fh = fbH;
int drawW, drawH;
if (w * fh > h * fw) {
// window is wider -> fit by height
drawH = (int)h;
drawW = (int)((h * fw) / fh);
} else {
// window is taller -> fit by width
drawW = (int)w;
drawH = (int)((w * fh) / fw);
}
dst->w = drawW;
dst->h = drawH;
dst->x = (winW - drawW) / 2;
dst->y = (winH - drawH) / 2;
}
uint32_t palette_web[256] = {
0x000000,0x000033,0x000066,0x000099,0x0000cc,0x0000ff,0x003300,0x003333,0x003366,0x003399,0x0033cc,
0x0033ff,0x006600,0x006633,0x006666,0x006699,0x0066cc,0x0066ff,0x009900,0x009933,0x009966,0x009999,
@ -51,7 +84,8 @@ unsigned int lcdGetPixel_SDL(JsGraphics *gfx, int x, int y) {
if (!screen) return 0;
if(SDL_MUSTLOCK(screen))
if(SDL_LockSurface(screen) < 0) return 0;
unsigned int *pixmem32 = ((unsigned int*)screen->pixels) + y*gfx->data.width + x;
int stride = screen->pitch / 4; // 32-bit pixels
unsigned int *pixmem32 = ((unsigned int*)screen->pixels) + y*stride + x;
unsigned int col = *pixmem32;
if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);
return col;
@ -65,12 +99,15 @@ void lcdSetPixel_SDL(JsGraphics *gfx, int x, int y, unsigned int col) {
if(SDL_LockSurface(screen) < 0) return;
if (gfx->data.bpp==8) col = palette_web[col&255];
if (gfx->data.bpp==16) {
int r = (col>>8)&0xF8;
int g = (col>>3)&0xFC;
int b = (col<<3)&0xFF;
unsigned int r = (col>>8)&0xF8;
unsigned int g = (col>>3)&0xFC;
unsigned int b = (col<<3)&0xFF;
col = (r<<16)|(g<<8)|b;
}
unsigned int *pixmem32 = ((unsigned int*)screen->pixels) + y*gfx->data.width + x;
// Ensure alpha channel is fully opaque for ARGB8888
col |= 0xFF000000u;
int stride = screen->pitch / 4; // 32-bit pixels
unsigned int *pixmem32 = ((unsigned int*)screen->pixels) + y*stride + x;
*pixmem32 = col;
if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);
needsFlip = true;
@ -88,12 +125,60 @@ void lcdInit_SDL(JsGraphics *gfx) {
jsExceptionHere(JSET_ERROR, "SDL_Init failed");
exit(1);
}
if (!(screen = SDL_SetVideoMode(gfx->data.width, gfx->data.height, 32, SDL_SWSURFACE | SDL_RESIZABLE))) {
jsExceptionHere(JSET_ERROR, "SDL_SetVideoMode failed");
// Help Linux WMs/Wayland display the app name/title correctly
SDL_SetHint(SDL_HINT_APP_NAME, "Espruino");
window = SDL_CreateWindow(
"Espruino",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
gfx->data.width, gfx->data.height,
SDL_WINDOW_RESIZABLE
);
if (!window) {
jsExceptionHere(JSET_ERROR, "SDL_CreateWindow failed");
SDL_Quit();
exit(1);
}
SDL_WM_SetCaption("Espruino", NULL);
// Ensure title is applied immediately and window is shown
SDL_SetWindowTitle(window, "Espruino");
SDL_ShowWindow(window);
// Create renderer (accelerated if possible, fallback to software)
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (!renderer) renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE);
if (!renderer) {
jsExceptionHere(JSET_ERROR, "SDL_CreateRenderer failed");
SDL_DestroyWindow(window);
SDL_Quit();
exit(1);
}
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest"); // keep pixels crisp
if (SDL_GetRendererOutputSize(renderer, &winW, &winH) != 0) {
SDL_GetWindowSize(window, &winW, &winH);
}
fbW = gfx->data.width;
fbH = gfx->data.height;
// Create offscreen framebuffer in a known pixel format
screen = SDL_CreateRGBSurfaceWithFormat(0, fbW, fbH, 32, SDL_PIXELFORMAT_ARGB8888);
if (!screen) {
jsExceptionHere(JSET_ERROR, "SDL_CreateRGBSurfaceWithFormat failed");
if (renderer) SDL_DestroyRenderer(renderer);
if (window) SDL_DestroyWindow(window);
SDL_Quit();
exit(1);
}
SDL_SetSurfaceBlendMode(screen, SDL_BLENDMODE_NONE);
// Create texture to upload the framebuffer each frame
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, fbW, fbH);
if (!texture) {
jsExceptionHere(JSET_ERROR, "SDL_CreateTexture failed");
if (screen) SDL_FreeSurface(screen);
if (renderer) SDL_DestroyRenderer(renderer);
if (window) SDL_DestroyWindow(window);
SDL_Quit();
exit(1);
}
SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_NONE);
// Force an initial present
needsFlip = true;
}
void lcdIdle_SDL() {
@ -102,10 +187,20 @@ void lcdIdle_SDL() {
SDL_Event event;
bool sendTouchEvent = false;
char *sendKeyEvent = NULL;
static int mouseX = 0, mouseY = 0;
if (needsFlip) {
needsFlip = false;
SDL_Flip(screen);
if (renderer && texture && screen) {
// Upload framebuffer contents to texture
SDL_UpdateTexture(texture, NULL, screen->pixels, screen->pitch);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
SDL_Rect dst;
compute_draw_rect(winW, winH, fbW, fbH, &dst);
SDL_RenderCopy(renderer, texture, NULL, &dst);
SDL_RenderPresent(renderer);
}
}
if (SDL_PollEvent(&event)) {
@ -113,14 +208,28 @@ void lcdIdle_SDL() {
case SDL_QUIT:
nativeQuit();
break;
case SDL_MOUSEMOTION:
if (down) {
sendTouchEvent = true;
case SDL_WINDOWEVENT:
if (event.window.event == SDL_WINDOWEVENT_RESIZED || event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
if (renderer && SDL_GetRendererOutputSize(renderer, &winW, &winH) != 0) {
SDL_GetWindowSize(window, &winW, &winH);
}
needsFlip = true;
} else if (event.window.event == SDL_WINDOWEVENT_EXPOSED) {
/* Some drivers/platforms don't send RESIZED/SIZE_CHANGED when window
is uncovered; EXPOSED indicates the window needs redraw. */
needsFlip = true;
}
break;
case SDL_MOUSEMOTION:
mouseX = event.motion.x;
mouseY = event.motion.y;
if (down) sendTouchEvent = true;
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
down = event.type == SDL_MOUSEBUTTONDOWN;
mouseX = event.button.x;
mouseY = event.button.y;
sendTouchEvent = true;
break;
case SDL_KEYDOWN:
@ -140,8 +249,22 @@ void lcdIdle_SDL() {
JsVar *E = jsvObjectGetChildIfExists(execInfo.root, "E");
if (E) {
JsVar *o = jsvNewObject();
jsvObjectSetChildAndUnLock(o,"x", jsvNewFromInteger(event.button.x));
jsvObjectSetChildAndUnLock(o,"y", jsvNewFromInteger(event.button.y));
int lx = mouseX, ly = mouseY;
if (fbW > 0 && fbH > 0 && winW > 0 && winH > 0) {
SDL_Rect dst;
compute_draw_rect(winW, winH, fbW, fbH, &dst);
long long rx = (long long)(mouseX - dst.x);
long long ry = (long long)(mouseY - dst.y);
// Scale from draw area back to framebuffer pixels
if (dst.w > 0) lx = (int)(rx * fbW / dst.w); else lx = 0;
if (dst.h > 0) ly = (int)(ry * fbH / dst.h); else ly = 0;
if (lx < 0) lx = 0;
if (lx >= fbW) lx = fbW-1;
if (ly < 0) ly = 0;
if (ly >= fbH) ly = fbH-1;
}
jsvObjectSetChildAndUnLock(o,"x", jsvNewFromInteger(lx));
jsvObjectSetChildAndUnLock(o,"y", jsvNewFromInteger(ly));
jsvObjectSetChildAndUnLock(o,"b", jsvNewFromInteger(down?1:0));
jsiQueueObjectCallbacks(E, JS_EVENT_PREFIX"touch", &o, 1);
jsvUnLock2(E,o);
@ -151,23 +274,40 @@ void lcdIdle_SDL() {
JsVar *E = jsvObjectGetChildIfExists(execInfo.root, "E");
if (E) {
JsVar *o = jsvNewObject();
jsvObjectSetChildAndUnLock(o,"keyCode", jsvNewFromInteger(event.key.keysym.scancode));
/* Backwards compatibility: historically (SDL 1.2) keyCode exposed the
keysym value. Preserve that behavior so existing code keeps working.
Also provide the scancode separately as `scanCode` for clients that
need the physical key position. */
jsvObjectSetChildAndUnLock(o,"keyCode", jsvNewFromInteger(event.key.keysym.sym));
jsvObjectSetChildAndUnLock(o,"scanCode", jsvNewFromInteger(event.key.keysym.scancode));
jsvObjectSetChildAndUnLock(o,"modifiers", jsvNewFromInteger(event.key.keysym.mod));
const char *name = NULL;
switch (event.key.keysym.sym) {
case SDLK_UP: name = "ArrowUp"; break;
case SDLK_DOWN: name = "ArrowDown"; break;
case SDLK_LEFT: name = "ArrowLeft"; break;
case SDLK_RIGHT: name = "ArrowRight"; break;
case SDLK_RETURN: name = "Enter"; break;
case SDLK_BACKSPACE: name = "Backspace"; break;
case SDLK_TAB: name = "Tab"; break;
case SDLK_DELETE: name = "Delete"; break;
case SDLK_HOME: name = "Home"; break;
case SDLK_END: name = "End"; break;
case SDLK_PAGEUP: name = "PageUp"; break;
case SDLK_PAGEDOWN: name = "PageDown"; break;
case SDLK_INSERT: name = "Insert"; break;
default:break;
char tmpkey[2] = {0,0};
int sym = event.key.keysym.sym;
if (sym >= SDLK_a && sym <= SDLK_z) {
tmpkey[0] = (char)('a' + (sym - SDLK_a));
name = tmpkey;
} else if (sym >= SDLK_0 && sym <= SDLK_9) {
tmpkey[0] = (char)('0' + (sym - SDLK_0));
name = tmpkey;
} else {
switch (sym) {
case SDLK_SPACE: name = "Space"; break;
case SDLK_UP: name = "ArrowUp"; break;
case SDLK_DOWN: name = "ArrowDown"; break;
case SDLK_LEFT: name = "ArrowLeft"; break;
case SDLK_RIGHT: name = "ArrowRight"; break;
case SDLK_RETURN: name = "Enter"; break;
case SDLK_BACKSPACE: name = "Backspace"; break;
case SDLK_TAB: name = "Tab"; break;
case SDLK_DELETE: name = "Delete"; break;
case SDLK_HOME: name = "Home"; break;
case SDLK_END: name = "End"; break;
case SDLK_PAGEUP: name = "PageUp"; break;
case SDLK_PAGEDOWN: name = "PageDown"; break;
case SDLK_INSERT: name = "Insert"; break;
default: break;
}
}
if (name) jsvObjectSetChildAndUnLock(o,"key", jsvNewFromString(name));
jsiQueueObjectCallbacks(E, sendKeyEvent, &o, 1);

View File

@ -474,6 +474,16 @@ if "SD" in board.devices:
if "pin_d3" in board.devices["SD"]: codeOutDevicePin("SD", "pin_d3", "SD_D3_PIN")
if "pin_cmd" in board.devices["SD"]: codeOutDevicePin("SD", "pin_cmd", "SD_CMD_PIN")
if "DAC" in board.devices:
if "pin_scl" in board.devices["DAC"]: codeOutDevicePin("DAC", "pin_scl", "DAC_SCL_PIN")
if "pin_sda" in board.devices["DAC"]: codeOutDevicePin("DAC", "pin_sda", "DAC_SDA_PIN")
if "pin_lrck" in board.devices["DAC"]: codeOutDevicePin("DAC", "pin_lrck", "DAC_LRCK_PIN")
if "pin_sclk" in board.devices["DAC"]: codeOutDevicePin("DAC", "pin_sclk", "DAC_SCLK_PIN")
if "pin_mclk" in board.devices["DAC"]: codeOutDevicePin("DAC", "pin_mclk", "DAC_MCLK_PIN")
if "pin_miso" in board.devices["DAC"]: codeOutDevicePin("DAC", "pin_miso", "DAC_MISO_PIN")
if "pin_mosi" in board.devices["DAC"]: codeOutDevicePin("DAC", "pin_mosi", "DAC_MOSI_PIN")
if "IR" in board.devices:
codeOutDevicePin("IR", "pin_anode", "IR_ANODE_PIN")
codeOutDevicePin("IR", "pin_cathode", "IR_CATHODE_PIN")
@ -551,6 +561,9 @@ if "SPIFLASH" in board.devices:
codeOut("#define SPIFLASH_BASE "+str(board.devices["SPIFLASH"]["memmap_base"])+"UL")
codeOutDevicePins("SPIFLASH", "SPIFLASH")
if "MISC" in board.devices:
codeOutDevicePins("MISC", "MISC")
for device in pinutils.OTHER_DEVICES:
if device in board.devices:
for entry in board.devices[device]:

View File

@ -24,7 +24,7 @@
#error USE_CALLFUNCTION_HACK is required to make i386 builds work correctly
#endif
#if defined(__x86_64__) || defined(__arm64__)
#if defined(__x86_64__) || defined(__arm64__) || defined(__aarch64__)
#define USE_SEPARATE_DOUBLES
#endif

View File

@ -2546,7 +2546,7 @@ JsVarInt jswrap_espruino_getBattery() {
/*JSON{
"type" : "staticmethod",
"#if" : "defined(PICO) || defined(ESPRUINOWIFI) || defined(ESPRUINOBOARD)",
"#if" : "defined(PICO) || defined(ESPRUINOWIFI) || defined(ESPRUINOBOARD) || defined(PIPBOY)",
"class" : "E",
"name" : "setRTCPrescaler",
"generate" : "jswrap_espruino_setRTCPrescaler",