1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
4 
5   This software is provided 'as-is', without any express or implied
6   warranty.  In no event will the authors be held liable for any damages
7   arising from the use of this software.
8 
9   Permission is granted to anyone to use this software for any purpose,
10   including commercial applications, and to alter it and redistribute it
11   freely, subject to the following restrictions:
12 
13   1. The origin of this software must not be misrepresented; you must not
14      claim that you wrote the original software. If you use this software
15      in a product, an acknowledgment in the product documentation would be
16      appreciated but is not required.
17   2. Altered source versions must be plainly marked as such, and must not be
18      misrepresented as being the original software.
19   3. This notice may not be removed or altered from any source distribution.
20 */
21 
22 
23 #include "../../SDL_internal.h"
24 
25 #if SDL_VIDEO_DRIVER_EMSCRIPTEN
26 
27 #include <emscripten/emscripten.h>
28 #include <emscripten/html5.h>
29 
30 #include "SDL_emscriptenmouse.h"
31 #include "SDL_emscriptenvideo.h"
32 
33 #include "../../events/SDL_mouse_c.h"
34 #include "SDL_assert.h"
35 
36 static SDL_Cursor*
Emscripten_CreateCursorFromString(const char * cursor_str,SDL_bool is_custom)37 Emscripten_CreateCursorFromString(const char* cursor_str, SDL_bool is_custom)
38 {
39     SDL_Cursor* cursor;
40     Emscripten_CursorData *curdata;
41 
42     cursor = SDL_calloc(1, sizeof(SDL_Cursor));
43     if (cursor) {
44         curdata = (Emscripten_CursorData *) SDL_calloc(1, sizeof(*curdata));
45         if (!curdata) {
46             SDL_OutOfMemory();
47             SDL_free(cursor);
48             return NULL;
49         }
50 
51         curdata->system_cursor = cursor_str;
52         curdata->is_custom = is_custom;
53         cursor->driverdata = curdata;
54     }
55     else {
56         SDL_OutOfMemory();
57     }
58 
59     return cursor;
60 }
61 
62 static SDL_Cursor*
Emscripten_CreateDefaultCursor()63 Emscripten_CreateDefaultCursor()
64 {
65     return Emscripten_CreateCursorFromString("default", SDL_FALSE);
66 }
67 
68 static SDL_Cursor*
Emscripten_CreateCursor(SDL_Surface * surface,int hot_x,int hot_y)69 Emscripten_CreateCursor(SDL_Surface* surface, int hot_x, int hot_y)
70 {
71     const char *cursor_url = NULL;
72     SDL_Surface *conv_surf;
73 
74     conv_surf = SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_ABGR8888, 0);
75 
76     if (!conv_surf) {
77         return NULL;
78     }
79 
80     cursor_url = (const char *)EM_ASM_INT({
81         var w = $0;
82         var h = $1;
83         var hot_x = $2;
84         var hot_y = $3;
85         var pixels = $4;
86 
87         var canvas = document.createElement("canvas");
88         canvas.width = w;
89         canvas.height = h;
90 
91         var ctx = canvas.getContext("2d");
92 
93         var image = ctx.createImageData(w, h);
94         var data = image.data;
95         var src = pixels >> 2;
96         var dst = 0;
97         var num;
98         if (typeof CanvasPixelArray !== 'undefined' && data instanceof CanvasPixelArray) {
99             // IE10/IE11: ImageData objects are backed by the deprecated CanvasPixelArray,
100             // not UInt8ClampedArray. These don't have buffers, so we need to revert
101             // to copying a byte at a time. We do the undefined check because modern
102             // browsers do not define CanvasPixelArray anymore.
103             num = data.length;
104             while (dst < num) {
105                 var val = HEAP32[src]; // This is optimized. Instead, we could do {{{ makeGetValue('buffer', 'dst', 'i32') }}};
106                 data[dst  ] = val & 0xff;
107                 data[dst+1] = (val >> 8) & 0xff;
108                 data[dst+2] = (val >> 16) & 0xff;
109                 data[dst+3] = (val >> 24) & 0xff;
110                 src++;
111                 dst += 4;
112             }
113         } else {
114             var data32 = new Int32Array(data.buffer);
115             num = data32.length;
116             data32.set(HEAP32.subarray(src, src + num));
117         }
118 
119         ctx.putImageData(image, 0, 0);
120         var url = hot_x === 0 && hot_y === 0
121             ? "url(" + canvas.toDataURL() + "), auto"
122             : "url(" + canvas.toDataURL() + ") " + hot_x + " " + hot_y + ", auto";
123 
124         var urlBuf = _malloc(url.length + 1);
125         stringToUTF8(url, urlBuf, url.length + 1);
126 
127         return urlBuf;
128     }, surface->w, surface->h, hot_x, hot_y, conv_surf->pixels);
129 
130     SDL_FreeSurface(conv_surf);
131 
132     return Emscripten_CreateCursorFromString(cursor_url, SDL_TRUE);
133 }
134 
135 static SDL_Cursor*
Emscripten_CreateSystemCursor(SDL_SystemCursor id)136 Emscripten_CreateSystemCursor(SDL_SystemCursor id)
137 {
138     const char *cursor_name = NULL;
139 
140     switch(id) {
141         case SDL_SYSTEM_CURSOR_ARROW:
142             cursor_name = "default";
143             break;
144         case SDL_SYSTEM_CURSOR_IBEAM:
145             cursor_name = "text";
146             break;
147         case SDL_SYSTEM_CURSOR_WAIT:
148             cursor_name = "wait";
149             break;
150         case SDL_SYSTEM_CURSOR_CROSSHAIR:
151             cursor_name = "crosshair";
152             break;
153         case SDL_SYSTEM_CURSOR_WAITARROW:
154             cursor_name = "progress";
155             break;
156         case SDL_SYSTEM_CURSOR_SIZENWSE:
157             cursor_name = "nwse-resize";
158             break;
159         case SDL_SYSTEM_CURSOR_SIZENESW:
160             cursor_name = "nesw-resize";
161             break;
162         case SDL_SYSTEM_CURSOR_SIZEWE:
163             cursor_name = "ew-resize";
164             break;
165         case SDL_SYSTEM_CURSOR_SIZENS:
166             cursor_name = "ns-resize";
167             break;
168         case SDL_SYSTEM_CURSOR_SIZEALL:
169             cursor_name = "move";
170             break;
171         case SDL_SYSTEM_CURSOR_NO:
172             cursor_name = "not-allowed";
173             break;
174         case SDL_SYSTEM_CURSOR_HAND:
175             cursor_name = "pointer";
176             break;
177         default:
178             SDL_assert(0);
179             return NULL;
180     }
181 
182     return Emscripten_CreateCursorFromString(cursor_name, SDL_FALSE);
183 }
184 
185 static void
Emscripten_FreeCursor(SDL_Cursor * cursor)186 Emscripten_FreeCursor(SDL_Cursor* cursor)
187 {
188     Emscripten_CursorData *curdata;
189     if (cursor) {
190         curdata = (Emscripten_CursorData *) cursor->driverdata;
191 
192         if (curdata != NULL) {
193             if (curdata->is_custom) {
194                 SDL_free((char *)curdata->system_cursor);
195             }
196             SDL_free(cursor->driverdata);
197         }
198 
199         SDL_free(cursor);
200     }
201 }
202 
203 static int
Emscripten_ShowCursor(SDL_Cursor * cursor)204 Emscripten_ShowCursor(SDL_Cursor* cursor)
205 {
206     Emscripten_CursorData *curdata;
207     if (SDL_GetMouseFocus() != NULL) {
208         if(cursor && cursor->driverdata) {
209             curdata = (Emscripten_CursorData *) cursor->driverdata;
210 
211             if(curdata->system_cursor) {
212                 EM_ASM_INT({
213                     if (Module['canvas']) {
214                         Module['canvas'].style['cursor'] = UTF8ToString($0);
215                     }
216                     return 0;
217                 }, curdata->system_cursor);
218             }
219         }
220         else {
221             EM_ASM(
222                 if (Module['canvas']) {
223                     Module['canvas'].style['cursor'] = 'none';
224                 }
225             );
226         }
227     }
228     return 0;
229 }
230 
231 static void
Emscripten_WarpMouse(SDL_Window * window,int x,int y)232 Emscripten_WarpMouse(SDL_Window* window, int x, int y)
233 {
234     SDL_Unsupported();
235 }
236 
237 static int
Emscripten_SetRelativeMouseMode(SDL_bool enabled)238 Emscripten_SetRelativeMouseMode(SDL_bool enabled)
239 {
240     SDL_Window *window;
241     SDL_WindowData *window_data;
242 
243     /* TODO: pointer lock isn't actually enabled yet */
244     if(enabled) {
245         window = SDL_GetMouseFocus();
246         if (window == NULL) {
247             return -1;
248         }
249 
250         window_data = (SDL_WindowData *) window->driverdata;
251 
252         if(emscripten_request_pointerlock(window_data->canvas_id, 1) >= EMSCRIPTEN_RESULT_SUCCESS) {
253             return 0;
254         }
255     } else {
256         if(emscripten_exit_pointerlock() >= EMSCRIPTEN_RESULT_SUCCESS) {
257             return 0;
258         }
259     }
260     return -1;
261 }
262 
263 void
Emscripten_InitMouse()264 Emscripten_InitMouse()
265 {
266     SDL_Mouse* mouse = SDL_GetMouse();
267 
268     mouse->CreateCursor         = Emscripten_CreateCursor;
269     mouse->ShowCursor           = Emscripten_ShowCursor;
270     mouse->FreeCursor           = Emscripten_FreeCursor;
271     mouse->WarpMouse            = Emscripten_WarpMouse;
272     mouse->CreateSystemCursor   = Emscripten_CreateSystemCursor;
273     mouse->SetRelativeMouseMode = Emscripten_SetRelativeMouseMode;
274 
275     SDL_SetDefaultCursor(Emscripten_CreateDefaultCursor());
276 }
277 
278 void
Emscripten_FiniMouse()279 Emscripten_FiniMouse()
280 {
281 }
282 
283 #endif /* SDL_VIDEO_DRIVER_EMSCRIPTEN */
284 
285 /* vi: set ts=4 sw=4 expandtab: */
286 
287