Skip to content

Commit 4e85b79

Browse files
committed
Linux SDL: Use SDL2, more keys forwarded, allow resizing of gfx window
Fix Linux ARM64 build Misc tweaks from Pipboy build
1 parent a40827d commit 4e85b79

File tree

6 files changed

+220
-36
lines changed

6 files changed

+220
-36
lines changed

ChangeLog

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
Add median-of-3 for Array.sort (1/3 of the compares for pre-sorted data)
2828
Fix escape characters in strings inside templates inside templated strings `${"\n"}`
2929
JIT: Fix floating point constants
30+
Linux SDL: Use SDL2, more keys forwarded, allow resizing of gfx window
31+
Fix Linux ARM64 build
3032

3133
2v27 : nRF5x: Ensure Bluetooth notifications work correctly when two separate connections use the same handle for their characteristics
3234
nRF5x: Remove handlers from our handlers array when a device is disconnected

Makefile

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -415,8 +415,37 @@ libs/graphics/lcd_js.c
415415
ifeq ($(USE_LCD_SDL),1)
416416
DEFINES += -DUSE_LCD_SDL
417417
SOURCES += libs/graphics/lcd_sdl.c
418-
LIBS += -lSDL
419-
INCLUDE += -I/usr/include/SDL
418+
# Prefer SDL2 if available
419+
SDL2_CONFIG?=$(shell which sdl2-config 2>/dev/null)
420+
421+
ifdef MACOSX
422+
$(info ********* MacOS build - using static SDL2 *********)
423+
ifneq ($(SDL2_CONFIG),)
424+
INCLUDE += $(shell $(SDL2_CONFIG) --cflags)
425+
LIBS += $(shell $(SDL2_CONFIG) --static-libs)
426+
else
427+
# Fallback: try Mac platform defaults
428+
# Link frameworks and ObjC runtime; SDL2 static lib name left to user's LIBS if needed
429+
LIBS += -lobjc \
430+
-Wl,-framework,Cocoa \
431+
-Wl,-framework,OpenGL \
432+
-Wl,-framework,IOKit \
433+
-Wl,-framework,CoreFoundation \
434+
-Wl,-framework,CoreVideo \
435+
-lSDL2
436+
INCLUDE += -I/usr/local/include/SDL2 -I/opt/homebrew/include/SDL2 -I/usr/include/SDL2
437+
endif
438+
else
439+
$(info ********* Linux build - using dynamic SDL2 *********)
440+
ifneq ($(SDL2_CONFIG),)
441+
INCLUDE += $(shell $(SDL2_CONFIG) --cflags)
442+
LIBS += $(shell $(SDL2_CONFIG) --libs)
443+
else
444+
# Fallback to typical SDL2 defaults; users can adjust via environment
445+
LIBS += -lSDL2
446+
INCLUDE += -I/usr/include/SDL2
447+
endif
448+
endif
420449
endif
421450

422451
ifdef USE_LCD_FSMC

libs/graphics/lcd_sdl.c

Lines changed: 172 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,47 @@
1717
#include "jsparse.h"
1818
#include "jsinteractive.h"
1919
#include "lcd_sdl.h"
20-
#include <SDL/SDL.h>
20+
#include <SDL.h>
2121

2222
#define BPP 4
2323
#define DEPTH 32
2424

25-
SDL_Surface *screen = 0;
25+
SDL_Window *window = NULL;
26+
SDL_Renderer *renderer = NULL;
27+
SDL_Texture *texture = NULL; // holds uploaded framebuffer for rendering
28+
// 'screen' is our offscreen framebuffer at logical (gfx) size
29+
SDL_Surface *screen = NULL;
30+
int fbW = 0, fbH = 0; // logical framebuffer size
31+
int winW = 0, winH = 0; // current window (render) size
2632
bool needsFlip = false;
2733

34+
// Compute a destination rect that preserves fb aspect ratio within the window
35+
static void compute_draw_rect(int winW, int winH, int fbW, int fbH, SDL_Rect *dst) {
36+
if (!dst) return;
37+
if (winW <= 0 || winH <= 0) {
38+
dst->x = 0; dst->y = 0; dst->w = 0; dst->h = 0; return;
39+
}
40+
if (fbW <= 0 || fbH <= 0) {
41+
dst->x = 0; dst->y = 0; dst->w = winW; dst->h = winH; return;
42+
}
43+
// Compare winW/fbW vs winH/fbH by cross-multiplying to avoid floats
44+
long long w = winW, h = winH, fw = fbW, fh = fbH;
45+
int drawW, drawH;
46+
if (w * fh > h * fw) {
47+
// window is wider -> fit by height
48+
drawH = (int)h;
49+
drawW = (int)((h * fw) / fh);
50+
} else {
51+
// window is taller -> fit by width
52+
drawW = (int)w;
53+
drawH = (int)((w * fh) / fw);
54+
}
55+
dst->w = drawW;
56+
dst->h = drawH;
57+
dst->x = (winW - drawW) / 2;
58+
dst->y = (winH - drawH) / 2;
59+
}
60+
2861
uint32_t palette_web[256] = {
2962
0x000000,0x000033,0x000066,0x000099,0x0000cc,0x0000ff,0x003300,0x003333,0x003366,0x003399,0x0033cc,
3063
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) {
5184
if (!screen) return 0;
5285
if(SDL_MUSTLOCK(screen))
5386
if(SDL_LockSurface(screen) < 0) return 0;
54-
unsigned int *pixmem32 = ((unsigned int*)screen->pixels) + y*gfx->data.width + x;
87+
int stride = screen->pitch / 4; // 32-bit pixels
88+
unsigned int *pixmem32 = ((unsigned int*)screen->pixels) + y*stride + x;
5589
unsigned int col = *pixmem32;
5690
if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);
5791
return col;
@@ -65,12 +99,15 @@ void lcdSetPixel_SDL(JsGraphics *gfx, int x, int y, unsigned int col) {
6599
if(SDL_LockSurface(screen) < 0) return;
66100
if (gfx->data.bpp==8) col = palette_web[col&255];
67101
if (gfx->data.bpp==16) {
68-
int r = (col>>8)&0xF8;
69-
int g = (col>>3)&0xFC;
70-
int b = (col<<3)&0xFF;
102+
unsigned int r = (col>>8)&0xF8;
103+
unsigned int g = (col>>3)&0xFC;
104+
unsigned int b = (col<<3)&0xFF;
71105
col = (r<<16)|(g<<8)|b;
72106
}
73-
unsigned int *pixmem32 = ((unsigned int*)screen->pixels) + y*gfx->data.width + x;
107+
// Ensure alpha channel is fully opaque for ARGB8888
108+
col |= 0xFF000000u;
109+
int stride = screen->pitch / 4; // 32-bit pixels
110+
unsigned int *pixmem32 = ((unsigned int*)screen->pixels) + y*stride + x;
74111
*pixmem32 = col;
75112
if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);
76113
needsFlip = true;
@@ -88,12 +125,60 @@ void lcdInit_SDL(JsGraphics *gfx) {
88125
jsExceptionHere(JSET_ERROR, "SDL_Init failed");
89126
exit(1);
90127
}
91-
if (!(screen = SDL_SetVideoMode(gfx->data.width, gfx->data.height, 32, SDL_SWSURFACE | SDL_RESIZABLE))) {
92-
jsExceptionHere(JSET_ERROR, "SDL_SetVideoMode failed");
128+
// Help Linux WMs/Wayland display the app name/title correctly
129+
SDL_SetHint(SDL_HINT_APP_NAME, "Espruino");
130+
window = SDL_CreateWindow(
131+
"Espruino",
132+
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
133+
gfx->data.width, gfx->data.height,
134+
SDL_WINDOW_RESIZABLE
135+
);
136+
if (!window) {
137+
jsExceptionHere(JSET_ERROR, "SDL_CreateWindow failed");
138+
SDL_Quit();
139+
exit(1);
140+
}
141+
// Ensure title is applied immediately and window is shown
142+
SDL_SetWindowTitle(window, "Espruino");
143+
SDL_ShowWindow(window);
144+
// Create renderer (accelerated if possible, fallback to software)
145+
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
146+
if (!renderer) renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE);
147+
if (!renderer) {
148+
jsExceptionHere(JSET_ERROR, "SDL_CreateRenderer failed");
149+
SDL_DestroyWindow(window);
150+
SDL_Quit();
151+
exit(1);
152+
}
153+
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest"); // keep pixels crisp
154+
if (SDL_GetRendererOutputSize(renderer, &winW, &winH) != 0) {
155+
SDL_GetWindowSize(window, &winW, &winH);
156+
}
157+
fbW = gfx->data.width;
158+
fbH = gfx->data.height;
159+
// Create offscreen framebuffer in a known pixel format
160+
screen = SDL_CreateRGBSurfaceWithFormat(0, fbW, fbH, 32, SDL_PIXELFORMAT_ARGB8888);
161+
if (!screen) {
162+
jsExceptionHere(JSET_ERROR, "SDL_CreateRGBSurfaceWithFormat failed");
163+
if (renderer) SDL_DestroyRenderer(renderer);
164+
if (window) SDL_DestroyWindow(window);
165+
SDL_Quit();
166+
exit(1);
167+
}
168+
SDL_SetSurfaceBlendMode(screen, SDL_BLENDMODE_NONE);
169+
// Create texture to upload the framebuffer each frame
170+
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, fbW, fbH);
171+
if (!texture) {
172+
jsExceptionHere(JSET_ERROR, "SDL_CreateTexture failed");
173+
if (screen) SDL_FreeSurface(screen);
174+
if (renderer) SDL_DestroyRenderer(renderer);
175+
if (window) SDL_DestroyWindow(window);
93176
SDL_Quit();
94177
exit(1);
95178
}
96-
SDL_WM_SetCaption("Espruino", NULL);
179+
SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_NONE);
180+
// Force an initial present
181+
needsFlip = true;
97182
}
98183

99184
void lcdIdle_SDL() {
@@ -102,25 +187,49 @@ void lcdIdle_SDL() {
102187
SDL_Event event;
103188
bool sendTouchEvent = false;
104189
char *sendKeyEvent = NULL;
190+
static int mouseX = 0, mouseY = 0;
105191

106192
if (needsFlip) {
107193
needsFlip = false;
108-
SDL_Flip(screen);
194+
if (renderer && texture && screen) {
195+
// Upload framebuffer contents to texture
196+
SDL_UpdateTexture(texture, NULL, screen->pixels, screen->pitch);
197+
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
198+
SDL_RenderClear(renderer);
199+
SDL_Rect dst;
200+
compute_draw_rect(winW, winH, fbW, fbH, &dst);
201+
SDL_RenderCopy(renderer, texture, NULL, &dst);
202+
SDL_RenderPresent(renderer);
203+
}
109204
}
110205

111206
if (SDL_PollEvent(&event)) {
112207
switch (event.type) {
113208
case SDL_QUIT:
114209
nativeQuit();
115210
break;
116-
case SDL_MOUSEMOTION:
117-
if (down) {
118-
sendTouchEvent = true;
211+
case SDL_WINDOWEVENT:
212+
if (event.window.event == SDL_WINDOWEVENT_RESIZED || event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
213+
if (renderer && SDL_GetRendererOutputSize(renderer, &winW, &winH) != 0) {
214+
SDL_GetWindowSize(window, &winW, &winH);
215+
}
216+
needsFlip = true;
217+
} else if (event.window.event == SDL_WINDOWEVENT_EXPOSED) {
218+
/* Some drivers/platforms don't send RESIZED/SIZE_CHANGED when window
219+
is uncovered; EXPOSED indicates the window needs redraw. */
220+
needsFlip = true;
119221
}
120222
break;
223+
case SDL_MOUSEMOTION:
224+
mouseX = event.motion.x;
225+
mouseY = event.motion.y;
226+
if (down) sendTouchEvent = true;
227+
break;
121228
case SDL_MOUSEBUTTONDOWN:
122229
case SDL_MOUSEBUTTONUP:
123230
down = event.type == SDL_MOUSEBUTTONDOWN;
231+
mouseX = event.button.x;
232+
mouseY = event.button.y;
124233
sendTouchEvent = true;
125234
break;
126235
case SDL_KEYDOWN:
@@ -140,8 +249,22 @@ void lcdIdle_SDL() {
140249
JsVar *E = jsvObjectGetChildIfExists(execInfo.root, "E");
141250
if (E) {
142251
JsVar *o = jsvNewObject();
143-
jsvObjectSetChildAndUnLock(o,"x", jsvNewFromInteger(event.button.x));
144-
jsvObjectSetChildAndUnLock(o,"y", jsvNewFromInteger(event.button.y));
252+
int lx = mouseX, ly = mouseY;
253+
if (fbW > 0 && fbH > 0 && winW > 0 && winH > 0) {
254+
SDL_Rect dst;
255+
compute_draw_rect(winW, winH, fbW, fbH, &dst);
256+
long long rx = (long long)(mouseX - dst.x);
257+
long long ry = (long long)(mouseY - dst.y);
258+
// Scale from draw area back to framebuffer pixels
259+
if (dst.w > 0) lx = (int)(rx * fbW / dst.w); else lx = 0;
260+
if (dst.h > 0) ly = (int)(ry * fbH / dst.h); else ly = 0;
261+
if (lx < 0) lx = 0;
262+
if (lx >= fbW) lx = fbW-1;
263+
if (ly < 0) ly = 0;
264+
if (ly >= fbH) ly = fbH-1;
265+
}
266+
jsvObjectSetChildAndUnLock(o,"x", jsvNewFromInteger(lx));
267+
jsvObjectSetChildAndUnLock(o,"y", jsvNewFromInteger(ly));
145268
jsvObjectSetChildAndUnLock(o,"b", jsvNewFromInteger(down?1:0));
146269
jsiQueueObjectCallbacks(E, JS_EVENT_PREFIX"touch", &o, 1);
147270
jsvUnLock2(E,o);
@@ -151,23 +274,40 @@ void lcdIdle_SDL() {
151274
JsVar *E = jsvObjectGetChildIfExists(execInfo.root, "E");
152275
if (E) {
153276
JsVar *o = jsvNewObject();
154-
jsvObjectSetChildAndUnLock(o,"keyCode", jsvNewFromInteger(event.key.keysym.scancode));
277+
/* Backwards compatibility: historically (SDL 1.2) keyCode exposed the
278+
keysym value. Preserve that behavior so existing code keeps working.
279+
Also provide the scancode separately as `scanCode` for clients that
280+
need the physical key position. */
281+
jsvObjectSetChildAndUnLock(o,"keyCode", jsvNewFromInteger(event.key.keysym.sym));
282+
jsvObjectSetChildAndUnLock(o,"scanCode", jsvNewFromInteger(event.key.keysym.scancode));
283+
jsvObjectSetChildAndUnLock(o,"modifiers", jsvNewFromInteger(event.key.keysym.mod));
155284
const char *name = NULL;
156-
switch (event.key.keysym.sym) {
157-
case SDLK_UP: name = "ArrowUp"; break;
158-
case SDLK_DOWN: name = "ArrowDown"; break;
159-
case SDLK_LEFT: name = "ArrowLeft"; break;
160-
case SDLK_RIGHT: name = "ArrowRight"; break;
161-
case SDLK_RETURN: name = "Enter"; break;
162-
case SDLK_BACKSPACE: name = "Backspace"; break;
163-
case SDLK_TAB: name = "Tab"; break;
164-
case SDLK_DELETE: name = "Delete"; break;
165-
case SDLK_HOME: name = "Home"; break;
166-
case SDLK_END: name = "End"; break;
167-
case SDLK_PAGEUP: name = "PageUp"; break;
168-
case SDLK_PAGEDOWN: name = "PageDown"; break;
169-
case SDLK_INSERT: name = "Insert"; break;
170-
default:break;
285+
char tmpkey[2] = {0,0};
286+
int sym = event.key.keysym.sym;
287+
if (sym >= SDLK_a && sym <= SDLK_z) {
288+
tmpkey[0] = (char)('a' + (sym - SDLK_a));
289+
name = tmpkey;
290+
} else if (sym >= SDLK_0 && sym <= SDLK_9) {
291+
tmpkey[0] = (char)('0' + (sym - SDLK_0));
292+
name = tmpkey;
293+
} else {
294+
switch (sym) {
295+
case SDLK_SPACE: name = "Space"; break;
296+
case SDLK_UP: name = "ArrowUp"; break;
297+
case SDLK_DOWN: name = "ArrowDown"; break;
298+
case SDLK_LEFT: name = "ArrowLeft"; break;
299+
case SDLK_RIGHT: name = "ArrowRight"; break;
300+
case SDLK_RETURN: name = "Enter"; break;
301+
case SDLK_BACKSPACE: name = "Backspace"; break;
302+
case SDLK_TAB: name = "Tab"; break;
303+
case SDLK_DELETE: name = "Delete"; break;
304+
case SDLK_HOME: name = "Home"; break;
305+
case SDLK_END: name = "End"; break;
306+
case SDLK_PAGEUP: name = "PageUp"; break;
307+
case SDLK_PAGEDOWN: name = "PageDown"; break;
308+
case SDLK_INSERT: name = "Insert"; break;
309+
default: break;
310+
}
171311
}
172312
if (name) jsvObjectSetChildAndUnLock(o,"key", jsvNewFromString(name));
173313
jsiQueueObjectCallbacks(E, sendKeyEvent, &o, 1);

scripts/build_platform_config.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,16 @@ def codeOutDevicePins(device, definition_name):
474474
if "pin_d3" in board.devices["SD"]: codeOutDevicePin("SD", "pin_d3", "SD_D3_PIN")
475475
if "pin_cmd" in board.devices["SD"]: codeOutDevicePin("SD", "pin_cmd", "SD_CMD_PIN")
476476

477+
478+
if "DAC" in board.devices:
479+
if "pin_scl" in board.devices["DAC"]: codeOutDevicePin("DAC", "pin_scl", "DAC_SCL_PIN")
480+
if "pin_sda" in board.devices["DAC"]: codeOutDevicePin("DAC", "pin_sda", "DAC_SDA_PIN")
481+
if "pin_lrck" in board.devices["DAC"]: codeOutDevicePin("DAC", "pin_lrck", "DAC_LRCK_PIN")
482+
if "pin_sclk" in board.devices["DAC"]: codeOutDevicePin("DAC", "pin_sclk", "DAC_SCLK_PIN")
483+
if "pin_mclk" in board.devices["DAC"]: codeOutDevicePin("DAC", "pin_mclk", "DAC_MCLK_PIN")
484+
if "pin_miso" in board.devices["DAC"]: codeOutDevicePin("DAC", "pin_miso", "DAC_MISO_PIN")
485+
if "pin_mosi" in board.devices["DAC"]: codeOutDevicePin("DAC", "pin_mosi", "DAC_MOSI_PIN")
486+
477487
if "IR" in board.devices:
478488
codeOutDevicePin("IR", "pin_anode", "IR_ANODE_PIN")
479489
codeOutDevicePin("IR", "pin_cathode", "IR_CATHODE_PIN")
@@ -551,6 +561,9 @@ def codeOutDevicePins(device, definition_name):
551561
codeOut("#define SPIFLASH_BASE "+str(board.devices["SPIFLASH"]["memmap_base"])+"UL")
552562
codeOutDevicePins("SPIFLASH", "SPIFLASH")
553563

564+
if "MISC" in board.devices:
565+
codeOutDevicePins("MISC", "MISC")
566+
554567
for device in pinutils.OTHER_DEVICES:
555568
if device in board.devices:
556569
for entry in board.devices[device]:

src/jsnative.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
#error USE_CALLFUNCTION_HACK is required to make i386 builds work correctly
2525
#endif
2626

27-
#if defined(__x86_64__) || defined(__arm64__)
27+
#if defined(__x86_64__) || defined(__arm64__) || defined(__aarch64__)
2828
#define USE_SEPARATE_DOUBLES
2929
#endif
3030

src/jswrap_espruino.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2546,7 +2546,7 @@ JsVarInt jswrap_espruino_getBattery() {
25462546

25472547
/*JSON{
25482548
"type" : "staticmethod",
2549-
"#if" : "defined(PICO) || defined(ESPRUINOWIFI) || defined(ESPRUINOBOARD)",
2549+
"#if" : "defined(PICO) || defined(ESPRUINOWIFI) || defined(ESPRUINOBOARD) || defined(PIPBOY)",
25502550
"class" : "E",
25512551
"name" : "setRTCPrescaler",
25522552
"generate" : "jswrap_espruino_setRTCPrescaler",

0 commit comments

Comments
 (0)