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
2632bool 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+
2861uint32_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
99184void 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 );
0 commit comments