//"use strict"; // See browser tests for examples (tests/runner.py, search for sdl_). Run with // python tests/runner.py browser // Notes: // SDL_VIDEORESIZE: This is sent when the canvas is resized. Note that the user // cannot manually do so, so this is only sent when the // program manually resizes it (emscripten_set_canvas_size // or otherwise). var LibrarySDL = { $SDL__deps: ['$FS', '$PATH', '$Browser', 'SDL_GetTicks'], $SDL: { defaults: { width: 320, height: 200, copyOnLock: true }, version: null, surfaces: {}, // A pool of freed canvas elements. Reusing them avoids GC pauses. canvasPool: [], events: [], fonts: [null], // The currently preloaded audio elements ready to be played audios: [null], rwops: [null], // The currently playing audio element. There's only one music track. music: { audio: null, volume: 1.0 }, mixerFrequency: 22050, mixerFormat: {{{ cDefine('AUDIO_S16LSB') }}}, //0x8010, // AUDIO_S16LSB mixerNumChannels: 2, mixerChunkSize: 1024, channelMinimumNumber: 0, GL: false, // Set to true if we call SDL_SetVideoMode with SDL_OPENGL, and if so, we do not create 2D canvases&contexts for blitting // Note that images loaded before SDL_SetVideoMode will not get this optimization // all possible GL attributes, with their default value glAttributes: { 0: 3, /* SDL_GL_RED_SIZE */ 1: 3, /* SDL_GL_GREEN_SIZE */ 2: 2, /* SDL_GL_BLUE_SIZE */ 3: 0, /* SDL_GL_ALPHA_SIZE */ 4: 0, /* SDL_GL_BUFFER_SIZE */ 5: 1, /* SDL_GL_DOUBLEBUFFER */ 6: 16, /* SDL_GL_DEPTH_SIZE */ 7: 0, /* SDL_GL_STENCIL_SIZE */ 8: 0, /* SDL_GL_ACCUM_RED_SIZE */ 9: 0, /* SDL_GL_ACCUM_GREEN_SIZE */ 10: 0, /* SDL_GL_ACCUM_BLUE_SIZE */ 11: 0, /* SDL_GL_ACCUM_ALPHA_SIZE */ 12: 0, /* SDL_GL_STEREO */ 13: 0, /* SDL_GL_MULTISAMPLEBUFFERS */ 14: 0, /* SDL_GL_MULTISAMPLESAMPLES */ 15: 1, /* SDL_GL_ACCELERATED_VISUAL */ 16: 0, /* SDL_GL_RETAINED_BACKING */ 17: 0, /* SDL_GL_CONTEXT_MAJOR_VERSION */ 18: 0 /* SDL_GL_CONTEXT_MINOR_VERSION */ }, keyboardState: null, keyboardMap: {}, canRequestFullscreen: false, isRequestingFullscreen: false, textInput: false, startTime: null, initFlags: 0, // The flags passed to SDL_Init buttonState: 0, modState: 0, DOMButtons: [0, 0, 0], DOMEventToSDLEvent: {}, TOUCH_DEFAULT_ID: 0, // Our default deviceID for touch events (we get nothing from the browser) eventHandler: null, eventHandlerContext: null, keyCodes: { // DOM code ==> SDL code. See https://developer.mozilla.org/en/Document_Object_Model_%28DOM%29/KeyboardEvent and SDL_keycode.h // For keys that don't have unicode value, we map DOM codes with the corresponding scan codes + 1024 (using "| 1 << 10") 16: 225 | 1<<10, // shift 17: 224 | 1<<10, // control (right, or left) 18: 226 | 1<<10, // alt 20: 57 | 1<<10, // caps lock 33: 75 | 1<<10, // pagedup 34: 78 | 1<<10, // pagedown 35: 77 | 1<<10, // end 36: 74 | 1<<10, // home 37: 80 | 1<<10, // left arrow 38: 82 | 1<<10, // up arrow 39: 79 | 1<<10, // right arrow 40: 81 | 1<<10, // down arrow 44: 316, // print screen 45: 73 | 1<<10, // insert 46: 127, // SDLK_DEL == '\177' 91: 227 | 1<<10, // windows key or super key on linux (doesn't work on Mac) 93: 101 | 1<<10, // application 96: 98 | 1<<10, // keypad 0 97: 89 | 1<<10, // keypad 1 98: 90 | 1<<10, // keypad 2 99: 91 | 1<<10, // keypad 3 100: 92 | 1<<10, // keypad 4 101: 93 | 1<<10, // keypad 5 102: 94 | 1<<10, // keypad 6 103: 95 | 1<<10, // keypad 7 104: 96 | 1<<10, // keypad 8 105: 97 | 1<<10, // keypad 9 106: 85 | 1<<10, // keypad multiply 107: 87 | 1<<10, // keypad plus 109: 86 | 1<<10, // keypad minus 110: 99 | 1<<10, // keypad decimal point 111: 84 | 1<<10, // keypad divide 112: 58 | 1<<10, // F1 113: 59 | 1<<10, // F2 114: 60 | 1<<10, // F3 115: 61 | 1<<10, // F4 116: 62 | 1<<10, // F5 117: 63 | 1<<10, // F6 118: 64 | 1<<10, // F7 119: 65 | 1<<10, // F8 120: 66 | 1<<10, // F9 121: 67 | 1<<10, // F10 122: 68 | 1<<10, // F11 123: 69 | 1<<10, // F12 124: 104 | 1<<10, // F13 125: 105 | 1<<10, // F14 126: 106 | 1<<10, // F15 127: 107 | 1<<10, // F16 128: 108 | 1<<10, // F17 129: 109 | 1<<10, // F18 130: 110 | 1<<10, // F19 131: 111 | 1<<10, // F20 132: 112 | 1<<10, // F21 133: 113 | 1<<10, // F22 134: 114 | 1<<10, // F23 135: 115 | 1<<10, // F24 144: 83 | 1<<10, // keypad num lock 160: 94, // caret 161: 33, // exclaim 162: 34, // double quote 163: 35, // hash 164: 36, // dollar 165: 37, // percent 166: 38, // ampersand 167: 95, // underscore 168: 40, // open parenthesis 169: 41, // close parenthesis 170: 42, // asterix 171: 43, // plus 172: 124, // pipe 173: 45, // minus 174: 123, // open curly bracket 175: 125, // close curly bracket 176: 126, // tilde 181: 127, // audio mute 182: 129, // audio volume down 183: 128, // audio volume up 188: 44, // comma 190: 46, // period 191: 47, // slash (/) 192: 96, // backtick/backquote (`) 219: 91, // open square bracket 220: 92, // back slash 221: 93, // close square bracket 222: 39, // quote }, scanCodes: { // SDL keycode ==> SDL scancode. See SDL_scancode.h 8: 42, // backspace 9: 43, // tab 13: 40, // return 27: 41, // escape 32: 44, // space 35: 204, // hash 39: 53, // grave 44: 54, // comma 46: 55, // period 47: 56, // slash 48: 39, // 0 49: 30, // 1 50: 31, // 2 51: 32, // 3 52: 33, // 4 53: 34, // 5 54: 35, // 6 55: 36, // 7 56: 37, // 8 57: 38, // 9 58: 203, // colon 59: 51, // semicolon 61: 46, // equals 91: 47, // left bracket 92: 49, // backslash 93: 48, // right bracket 96: 52, // apostrophe 97: 4, // A 98: 5, // B 99: 6, // C 100: 7, // D 101: 8, // E 102: 9, // F 103: 10, // G 104: 11, // H 105: 12, // I 106: 13, // J 107: 14, // K 108: 15, // L 109: 16, // M 110: 17, // N 111: 18, // O 112: 19, // P 113: 20, // Q 114: 21, // R 115: 22, // S 116: 23, // T 117: 24, // U 118: 25, // V 119: 26, // W 120: 27, // X 121: 28, // Y 122: 29, // Z 127: 76, // delete 305: 224, // ctrl 308: 226, // alt 316: 70, // print screen }, loadRect: function(rect) { return { x: {{{ makeGetValue('rect + ' + C_STRUCTS.SDL_Rect.x, '0', 'i32') }}}, y: {{{ makeGetValue('rect + ' + C_STRUCTS.SDL_Rect.y, '0', 'i32') }}}, w: {{{ makeGetValue('rect + ' + C_STRUCTS.SDL_Rect.w, '0', 'i32') }}}, h: {{{ makeGetValue('rect + ' + C_STRUCTS.SDL_Rect.h, '0', 'i32') }}} }; }, // Load SDL color into a CSS-style color specification loadColorToCSSRGB: function(color) { var rgba = {{{ makeGetValue('color', '0', 'i32') }}}; return 'rgb(' + (rgba&255) + ',' + ((rgba >> 8)&255) + ',' + ((rgba >> 16)&255) + ')'; }, loadColorToCSSRGBA: function(color) { var rgba = {{{ makeGetValue('color', '0', 'i32') }}}; return 'rgba(' + (rgba&255) + ',' + ((rgba >> 8)&255) + ',' + ((rgba >> 16)&255) + ',' + (((rgba >> 24)&255)/255) + ')'; }, translateColorToCSSRGBA: function(rgba) { return 'rgba(' + (rgba&0xff) + ',' + (rgba>>8 & 0xff) + ',' + (rgba>>16 & 0xff) + ',' + (rgba>>>24)/0xff + ')'; }, translateRGBAToCSSRGBA: function(r, g, b, a) { return 'rgba(' + (r&0xff) + ',' + (g&0xff) + ',' + (b&0xff) + ',' + (a&0xff)/255 + ')'; }, translateRGBAToColor: function(r, g, b, a) { return r | g << 8 | b << 16 | a << 24; }, makeSurface: function(width, height, flags, usePageCanvas, source, rmask, gmask, bmask, amask) { flags = flags || 0; var is_SDL_HWSURFACE = flags & 0x00000001; var is_SDL_HWPALETTE = flags & 0x00200000; var is_SDL_OPENGL = flags & 0x04000000; var surf = _malloc({{{ C_STRUCTS.SDL_Surface.__size__ }}}); var pixelFormat = _malloc({{{ C_STRUCTS.SDL_PixelFormat.__size__ }}}); //surface with SDL_HWPALETTE flag is 8bpp surface (1 byte) var bpp = is_SDL_HWPALETTE ? 1 : 4; var buffer = 0; // preemptively initialize this for software surfaces, // otherwise it will be lazily initialized inside of SDL_LockSurface if (!is_SDL_HWSURFACE && !is_SDL_OPENGL) { buffer = _malloc(width * height * 4); } {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.flags, 'flags', 'i32') }}}; {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.format, 'pixelFormat', 'void*') }}}; {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.w, 'width', 'i32') }}}; {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.h, 'height', 'i32') }}}; {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.pitch, 'width * bpp', 'i32') }}}; // assuming RGBA or indexed for now, // since that is what ImageData gives us in browsers {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.pixels, 'buffer', 'void*') }}}; {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.clip_rect+C_STRUCTS.SDL_Rect.x, '0', 'i32') }}}; {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.clip_rect+C_STRUCTS.SDL_Rect.y, '0', 'i32') }}}; {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.clip_rect+C_STRUCTS.SDL_Rect.w, 'Module["canvas"].width', 'i32') }}}; {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.clip_rect+C_STRUCTS.SDL_Rect.h, 'Module["canvas"].height', 'i32') }}}; {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.refcount, '1', 'i32') }}}; {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.format, cDefine('SDL_PIXELFORMAT_RGBA8888'), 'i32') }}}; {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.palette, '0', 'i32') }}};// TODO {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.BitsPerPixel, 'bpp * 8', 'i8') }}}; {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.BytesPerPixel, 'bpp', 'i8') }}}; {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.Rmask, 'rmask || 0x000000ff', 'i32') }}}; {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.Gmask, 'gmask || 0x0000ff00', 'i32') }}}; {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.Bmask, 'bmask || 0x00ff0000', 'i32') }}}; {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.Amask, 'amask || 0xff000000', 'i32') }}}; // Decide if we want to use WebGL or not SDL.GL = SDL.GL || is_SDL_OPENGL; var canvas; if (!usePageCanvas) { if (SDL.canvasPool.length > 0) { canvas = SDL.canvasPool.pop(); } else { canvas = document.createElement('canvas'); } canvas.width = width; canvas.height = height; } else { canvas = Module['canvas']; } var webGLContextAttributes = { antialias: ((SDL.glAttributes[13 /*SDL_GL_MULTISAMPLEBUFFERS*/] != 0) && (SDL.glAttributes[14 /*SDL_GL_MULTISAMPLESAMPLES*/] > 1)), depth: (SDL.glAttributes[6 /*SDL_GL_DEPTH_SIZE*/] > 0), stencil: (SDL.glAttributes[7 /*SDL_GL_STENCIL_SIZE*/] > 0) }; var ctx = Browser.createContext(canvas, is_SDL_OPENGL, usePageCanvas, webGLContextAttributes); SDL.surfaces[surf] = { width: width, height: height, canvas: canvas, ctx: ctx, surf: surf, buffer: buffer, pixelFormat: pixelFormat, alpha: 255, flags: flags, locked: 0, usePageCanvas: usePageCanvas, source: source, isFlagSet: function(flag) { return flags & flag; } }; return surf; }, // Copy data from the C++-accessible storage to the canvas backing // for surface with HWPALETTE flag(8bpp depth) copyIndexedColorData: function(surfData, rX, rY, rW, rH) { // HWPALETTE works with palette // setted by SDL_SetColors if (!surfData.colors) { return; } var fullWidth = Module['canvas'].width; var fullHeight = Module['canvas'].height; var startX = rX || 0; var startY = rY || 0; var endX = (rW || (fullWidth - startX)) + startX; var endY = (rH || (fullHeight - startY)) + startY; var buffer = surfData.buffer; var data = surfData.image.data; var colors = surfData.colors; for (var y = startY; y < endY; ++y) { var indexBase = y * fullWidth; var colorBase = indexBase * 4; for (var x = startX; x < endX; ++x) { // HWPALETTE have only 256 colors (not rgba) var index = {{{ makeGetValue('buffer + indexBase + x', '0', 'i8', null, true) }}} * 3; var colorOffset = colorBase + x * 4; data[colorOffset ] = colors[index ]; data[colorOffset +1] = colors[index +1]; data[colorOffset +2] = colors[index +2]; //unused: data[colorOffset +3] = color[index +3]; } } }, freeSurface: function(surf) { var refcountPointer = surf + {{{ C_STRUCTS.SDL_Surface.refcount }}}; var refcount = {{{ makeGetValue('refcountPointer', '0', 'i32') }}}; if (refcount > 1) { {{{ makeSetValue('refcountPointer', '0', 'refcount - 1', 'i32') }}}; return; } var info = SDL.surfaces[surf]; if (!info.usePageCanvas && info.canvas) SDL.canvasPool.push(info.canvas); if (info.buffer) _free(info.buffer); _free(info.pixelFormat); _free(surf); SDL.surfaces[surf] = null; if (surf === SDL.screen) { SDL.screen = null; } }, // the browser sends out touchstart events with the whole group of touches // even if we received a previous touchstart for a specific touch identifier. // You can test this by pressing one finger to the screen, then another. You'll // receive two touchstart events, the first with a touches count of 1 the second // with a touches count of two. // SDL sends out a new touchstart event for only each newly started touch so to // emulate this, we keep track of previously started touches. downFingers: {}, savedKeydown: null, receiveEvent: function(event) { function unpressAllPressedKeys() { // Un-press all pressed keys: TODO for (var code in SDL.keyboardMap) { SDL.events.push({ type: 'keyup', keyCode: SDL.keyboardMap[code] }); } }; switch(event.type) { case 'touchstart': case 'touchmove': { event.preventDefault(); var touches = []; // Clear out any touchstart events that we've already processed if (event.type === 'touchstart') { for (var i = 0; i < event.touches.length; i++) { var touch = event.touches[i]; if (SDL.downFingers[touch.identifier] != true) { SDL.downFingers[touch.identifier] = true; touches.push(touch); } } } else { touches = event.touches; } var firstTouch = touches[0]; if (event.type == 'touchstart') { SDL.DOMButtons[0] = 1; } var mouseEventType; switch(event.type) { case 'touchstart': mouseEventType = 'mousedown'; break; case 'touchmove': mouseEventType = 'mousemove'; break; } var mouseEvent = { type: mouseEventType, button: 0, pageX: firstTouch.clientX, pageY: firstTouch.clientY }; SDL.events.push(mouseEvent); for (var i = 0; i < touches.length; i++) { var touch = touches[i]; SDL.events.push({ type: event.type, touch: touch }); }; break; } case 'touchend': { event.preventDefault(); // Remove the entry in the SDL.downFingers hash // because the finger is no longer down. for(var i = 0; i < event.changedTouches.length; i++) { var touch = event.changedTouches[i]; if (SDL.downFingers[touch.identifier] === true) { delete SDL.downFingers[touch.identifier]; } } var mouseEvent = { type: 'mouseup', button: 0, pageX: event.changedTouches[0].clientX, pageY: event.changedTouches[0].clientY }; SDL.DOMButtons[0] = 0; SDL.events.push(mouseEvent); for (var i = 0; i < event.changedTouches.length; i++) { var touch = event.changedTouches[i]; SDL.events.push({ type: 'touchend', touch: touch }); }; break; } case 'mousemove': if (SDL.DOMButtons[0] === 1) { SDL.events.push({ type: 'touchmove', touch: { identifier: 0, deviceID: {{{ cDefine('SDL_TOUCH_MOUSEID') }}}, pageX: event.pageX, pageY: event.pageY } }); } if (Browser.pointerLock) { // workaround for firefox bug 750111 if ('mozMovementX' in event) { event['movementX'] = event['mozMovementX']; event['movementY'] = event['mozMovementY']; } // workaround for Firefox bug 782777 if (event['movementX'] == 0 && event['movementY'] == 0) { // ignore a mousemove event if it doesn't contain any movement info // (without pointer lock, we infer movement from pageX/pageY, so this check is unnecessary) event.preventDefault(); return; } } // fall through case 'keydown': case 'keyup': case 'keypress': case 'mousedown': case 'mouseup': case 'DOMMouseScroll': case 'mousewheel': case 'wheel': // If we preventDefault on keydown events, the subsequent keypress events // won't fire. However, it's fine (and in some cases necessary) to // preventDefault for keys that don't generate a character. Otherwise, // preventDefault is the right thing to do in general. if (event.type !== 'keydown' || (!SDL.unicode && !SDL.textInput) || (event.keyCode === 8 /* backspace */ || event.keyCode === 9 /* tab */)) { event.preventDefault(); } if (event.type == 'DOMMouseScroll' || event.type == 'mousewheel' || event.type == 'wheel') { // Simulate old-style SDL events representing mouse wheel input as buttons var button = Browser.getMouseWheelDelta(event) > 0 ? 4 : 3; var event1 = { type: 'mousedown', button: button, pageX: event.pageX, pageY: event.pageY }; SDL.events.push(event1); var event2 = { type: 'mouseup', button: button, pageX: event.pageX, pageY: event.pageY }; SDL.events.push(event2); // Convert DOMMouseScroll events to wheel events for new style SDL events. if (event.type == 'DOMMouseScroll') { SDL.events.push({ type: 'wheel', deltaX: 0, deltaY: -event.detail, }); break; } else if (event.type == 'mousewheel') { SDL.events.push({ type: 'wheel', deltaX: 0, deltaY: event.wheelDelta, }); break; } } else if (event.type == 'mousedown') { SDL.DOMButtons[event.button] = 1; SDL.events.push({ type: 'touchstart', touch: { identifier: 0, deviceID: {{{ cDefine('SDL_TOUCH_MOUSEID') }}}, pageX: event.pageX, pageY: event.pageY } }); } else if (event.type == 'mouseup') { // ignore extra ups, can happen if we leave the canvas while pressing down, then return, // since we add a mouseup in that case if (!SDL.DOMButtons[event.button]) { return; } SDL.events.push({ type: 'touchend', touch: { identifier: 0, deviceID: {{{ cDefine('SDL_TOUCH_MOUSEID') }}}, pageX: event.pageX, pageY: event.pageY } }); SDL.DOMButtons[event.button] = 0; } // We can only request fullscreen as the result of user input. // Due to this limitation, we toggle a boolean on keydown which // SDL_WM_ToggleFullScreen will check and subsequently set another // flag indicating for us to request fullscreen on the following // keyup. This isn't perfect, but it enables SDL_WM_ToggleFullScreen // to work as the result of a keypress (which is an extremely // common use case). if (event.type === 'keydown' || event.type === 'mousedown') { SDL.canRequestFullscreen = true; } else if (event.type === 'keyup' || event.type === 'mouseup') { if (SDL.isRequestingFullscreen) { Module['requestFullScreen'](true, true); SDL.isRequestingFullscreen = false; } SDL.canRequestFullscreen = false; } // SDL expects a unicode character to be passed to its keydown events. // Unfortunately, the browser APIs only provide a charCode property on // keypress events, so we must backfill in keydown events with their // subsequent keypress event's charCode. if (event.type === 'keypress' && SDL.savedKeydown) { // charCode is read-only SDL.savedKeydown.keypressCharCode = event.charCode; SDL.savedKeydown = null; } else if (event.type === 'keydown') { SDL.savedKeydown = event; } // Don't push keypress events unless SDL_StartTextInput has been called. if (event.type !== 'keypress' || SDL.textInput) { SDL.events.push(event); } break; case 'mouseout': // Un-press all pressed mouse buttons, because we might miss the release outside of the canvas for (var i = 0; i < 3; i++) { if (SDL.DOMButtons[i]) { SDL.events.push({ type: 'mouseup', button: i, pageX: event.pageX, pageY: event.pageY }); SDL.DOMButtons[i] = 0; } } event.preventDefault(); break; case 'focus': SDL.events.push(event); event.preventDefault(); break; case 'blur': SDL.events.push(event); unpressAllPressedKeys(); event.preventDefault(); break; case 'visibilitychange': SDL.events.push({ type: 'visibilitychange', visible: !document.hidden }); unpressAllPressedKeys(); event.preventDefault(); break; case 'unload': if (Browser.mainLoop.runner) { SDL.events.push(event); // Force-run a main event loop, since otherwise this event will never be caught! Browser.mainLoop.runner(); } return; case 'resize': SDL.events.push(event); // manually triggered resize event doesn't have a preventDefault member if (event.preventDefault) { event.preventDefault(); } break; } if (SDL.events.length >= 10000) { Module.printErr('SDL event queue full, dropping events'); SDL.events = SDL.events.slice(0, 10000); } // If we have a handler installed, this will push the events to the app // instead of the app polling for them. SDL.flushEventsToHandler(); return; }, handleEvent: function(event) { if (event.handled) return; event.handled = true; switch (event.type) { case 'touchstart': case 'touchend': case 'touchmove': { Browser.calculateMouseEvent(event); break; } case 'keydown': case 'keyup': { var down = event.type === 'keydown'; var code = event.keyCode; if (code >= 65 && code <= 90) { code += 32; // make lowercase for SDL } else { code = SDL.keyCodes[event.keyCode] || event.keyCode; } {{{ makeSetValue('SDL.keyboardState', 'code', 'down', 'i8') }}}; // TODO: lmeta, rmeta, numlock, capslock, KMOD_MODE, KMOD_RESERVED SDL.modState = ({{{ makeGetValue('SDL.keyboardState', '1248', 'i8') }}} ? 0x0040 | 0x0080 : 0) | // KMOD_LCTRL & KMOD_RCTRL ({{{ makeGetValue('SDL.keyboardState', '1249', 'i8') }}} ? 0x0001 | 0x0002 : 0) | // KMOD_LSHIFT & KMOD_RSHIFT ({{{ makeGetValue('SDL.keyboardState', '1250', 'i8') }}} ? 0x0100 | 0x0200 : 0); // KMOD_LALT & KMOD_RALT if (down) { SDL.keyboardMap[code] = event.keyCode; // save the DOM input, which we can use to unpress it during blur } else { delete SDL.keyboardMap[code]; } break; } case 'mousedown': case 'mouseup': if (event.type == 'mousedown') { // SDL_BUTTON(x) is defined as (1 << ((x)-1)). SDL buttons are 1-3, // and DOM buttons are 0-2, so this means that the below formula is // correct. SDL.buttonState |= 1 << event.button; } else if (event.type == 'mouseup') { SDL.buttonState &= ~(1 << event.button); } // fall through case 'mousemove': { Browser.calculateMouseEvent(event); break; } } }, flushEventsToHandler: function() { if (!SDL.eventHandler) return; // All SDLEvents take the same amount of memory var sdlEventPtr = allocate({{{ C_STRUCTS.SDL_KeyboardEvent.__size__ }}}, "i8", ALLOC_STACK); while (SDL.pollEvent(sdlEventPtr)) { Runtime.dynCall('iii', SDL.eventHandler, [SDL.eventHandlerContext, sdlEventPtr]); } }, pollEvent: function(ptr) { if (SDL.initFlags & 0x200 && SDL.joystickEventState) { // If SDL_INIT_JOYSTICK was supplied AND the joystick system is configured // to automatically query for events, query for joystick events. SDL.queryJoysticks(); } if (SDL.events.length === 0) return 0; if (ptr) { SDL.makeCEvent(SDL.events.shift(), ptr); } return 1; }, makeCEvent: function(event, ptr) { if (typeof event === 'number') { // This is a pointer to a native C event that was SDL_PushEvent'ed _memcpy(ptr, event, {{{ C_STRUCTS.SDL_KeyboardEvent.__size__ }}}); // XXX return; } SDL.handleEvent(event); switch (event.type) { case 'keydown': case 'keyup': { var down = event.type === 'keydown'; //Module.print('Received key event: ' + event.keyCode); var key = event.keyCode; if (key >= 65 && key <= 90) { key += 32; // make lowercase for SDL } else { key = SDL.keyCodes[event.keyCode] || event.keyCode; } var scan; if (key >= 1024) { scan = key - 1024; } else { scan = SDL.scanCodes[key] || key; } {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.state, 'down ? 1 : 0', 'i8') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.repeat, '0', 'i8') }}}; // TODO {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.keysym + C_STRUCTS.SDL_Keysym.scancode, 'scan', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.keysym + C_STRUCTS.SDL_Keysym.sym, 'key', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.keysym + C_STRUCTS.SDL_Keysym.mod, 'SDL.modState', 'i16') }}}; // some non-character keys (e.g. backspace and tab) won't have keypressCharCode set, fill in with the keyCode. {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.keysym + C_STRUCTS.SDL_Keysym.unicode, 'event.keypressCharCode || key', 'i32') }}}; break; } case 'keypress': { {{{ makeSetValue('ptr', C_STRUCTS.SDL_TextInputEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; // Not filling in windowID for now var cStr = intArrayFromString(String.fromCharCode(event.charCode)); for (var i = 0; i < cStr.length; ++i) { {{{ makeSetValue('ptr', C_STRUCTS.SDL_TextInputEvent.text + ' + i', 'cStr[i]', 'i8') }}}; } break; } case 'mousedown': case 'mouseup': case 'mousemove': { if (event.type != 'mousemove') { var down = event.type === 'mousedown'; {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.timestamp, '0', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.windowID, '0', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.which, '0', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.button, 'event.button+1', 'i8') }}}; // DOM buttons are 0-2, SDL 1-3 {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.state, 'down ? 1 : 0', 'i8') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.x, 'Browser.mouseX', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.y, 'Browser.mouseY', 'i32') }}}; } else { {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.timestamp, '0', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.windowID, '0', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.which, '0', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.state, 'SDL.buttonState', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.x, 'Browser.mouseX', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.y, 'Browser.mouseY', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.xrel, 'Browser.mouseMovementX', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.yrel, 'Browser.mouseMovementY', 'i32') }}}; } break; } case 'wheel': { {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseWheelEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseWheelEvent.x, 'event.deltaX', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseWheelEvent.y, 'event.deltaY', 'i32') }}}; break; } case 'touchstart': case 'touchend': case 'touchmove': { var touch = event.touch; if (!Browser.touches[touch.identifier]) break; var w = Module['canvas'].width; var h = Module['canvas'].height; var x = Browser.touches[touch.identifier].x / w; var y = Browser.touches[touch.identifier].y / h; var lx = Browser.lastTouches[touch.identifier].x / w; var ly = Browser.lastTouches[touch.identifier].y / h; var dx = x - lx; var dy = y - ly; if (touch['deviceID'] === undefined) touch.deviceID = SDL.TOUCH_DEFAULT_ID; if (dx === 0 && dy === 0 && event.type === 'touchmove') return; // don't send these if nothing happened {{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.timestamp, '_SDL_GetTicks()', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.touchId, 'touch.deviceID', 'i64') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.fingerId, 'touch.identifier', 'i64') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.x, 'x', 'float') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.y, 'y', 'float') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.dx, 'dx', 'float') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.dy, 'dy', 'float') }}}; if (touch.force !== undefined) { {{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.pressure, 'touch.force', 'float') }}}; } else { // No pressure data, send a digital 0/1 pressure. {{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.pressure, 'event.type == "touchend" ? 0 : 1', 'float') }}}; } break; } case 'unload': { {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; break; } case 'resize': { {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_ResizeEvent.w, 'event.w', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_ResizeEvent.h, 'event.h', 'i32') }}}; break; } case 'joystick_button_up': case 'joystick_button_down': { var state = event.type === 'joystick_button_up' ? 0 : 1; {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.which, 'event.index', 'i8') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.button, 'event.button', 'i8') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.state, 'state', 'i8') }}}; break; } case 'joystick_axis_motion': { {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.which, 'event.index', 'i8') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.axis, 'event.axis', 'i8') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.value, 'SDL.joystickAxisValueConversion(event.value)', 'i32') }}}; break; } case 'focus': { var SDL_WINDOWEVENT_FOCUS_GAINED = 12 /* SDL_WINDOWEVENT_FOCUS_GAINED */; {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.windowID, '0', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.event, 'SDL_WINDOWEVENT_FOCUS_GAINED', 'i8') }}}; break; } case 'blur': { var SDL_WINDOWEVENT_FOCUS_LOST = 13 /* SDL_WINDOWEVENT_FOCUS_LOST */; {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.windowID, '0', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.event, 'SDL_WINDOWEVENT_FOCUS_LOST', 'i8') }}}; break; } case 'visibilitychange': { var SDL_WINDOWEVENT_SHOWN = 1 /* SDL_WINDOWEVENT_SHOWN */; var SDL_WINDOWEVENT_HIDDEN = 2 /* SDL_WINDOWEVENT_HIDDEN */; var visibilityEventID = event.visible ? SDL_WINDOWEVENT_SHOWN : SDL_WINDOWEVENT_HIDDEN; {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.windowID, 0, 'i32') }}}; {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.event, 'visibilityEventID' , 'i8') }}}; break; } default: throw 'Unhandled SDL event: ' + event.type; } }, estimateTextWidth: function(fontData, text) { var h = fontData.size; var fontString = h + 'px ' + fontData.name; var tempCtx = SDL.ttfContext; #if ASSERTIONS assert(tempCtx, 'TTF_Init must have been called'); #endif tempCtx.save(); tempCtx.font = fontString; var ret = tempCtx.measureText(text).width | 0; tempCtx.restore(); return ret; }, // Sound // Channels are a SDL abstraction for allowing multiple sound tracks to be // played at the same time. We don't need to actually implement the mixing // since the browser engine handles that for us. Therefore, in JS we just // maintain a list of channels and return IDs for them to the SDL consumer. allocateChannels: function(num) { // called from Mix_AllocateChannels and init if (SDL.numChannels && SDL.numChannels >= num && num != 0) return; SDL.numChannels = num; SDL.channels = []; for (var i = 0; i < num; i++) { SDL.channels[i] = { audio: null, volume: 1.0 }; } }, setGetVolume: function(info, volume) { if (!info) return 0; var ret = info.volume * 128; // MIX_MAX_VOLUME if (volume != -1) { info.volume = Math.min(Math.max(volume, 0), 128) / 128; if (info.audio) { try { info.audio.volume = info.volume; // For