1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2021 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 #include "../../SDL_internal.h"
22 
23 #if SDL_VIDEO_DRIVER_WINDOWS
24 
25 #include "SDL_windowsvideo.h"
26 
27 #include "../../events/SDL_mouse_c.h"
28 
29 
30 DWORD SDL_last_warp_time = 0;
31 HCURSOR SDL_cursor = NULL;
32 static SDL_Cursor *SDL_blank_cursor = NULL;
33 
34 static int rawInputEnableCount = 0;
35 
36 static int
ToggleRawInput(SDL_bool enabled)37 ToggleRawInput(SDL_bool enabled)
38 {
39     RAWINPUTDEVICE rawMouse = { 0x01, 0x02, 0, NULL }; /* Mouse: UsagePage = 1, Usage = 2 */
40 
41     if (enabled) {
42         rawInputEnableCount++;
43         if (rawInputEnableCount > 1) {
44             return 0;  /* already done. */
45         }
46     } else {
47         if (rawInputEnableCount == 0) {
48             return 0;  /* already done. */
49         }
50         rawInputEnableCount--;
51         if (rawInputEnableCount > 0) {
52             return 0;  /* not time to disable yet */
53         }
54     }
55 
56     if (!enabled) {
57         rawMouse.dwFlags |= RIDEV_REMOVE;
58     }
59 
60     /* (Un)register raw input for mice */
61     if (RegisterRawInputDevices(&rawMouse, 1, sizeof(RAWINPUTDEVICE)) == FALSE) {
62         /* Reset the enable count, otherwise subsequent enable calls will
63            believe raw input is enabled */
64         rawInputEnableCount = 0;
65 
66         /* Only return an error when registering. If we unregister and fail,
67            then it's probably that we unregistered twice. That's OK. */
68         if (enabled) {
69             return SDL_Unsupported();
70         }
71     }
72     return 0;
73 }
74 
75 
76 static SDL_Cursor *
WIN_CreateDefaultCursor()77 WIN_CreateDefaultCursor()
78 {
79     SDL_Cursor *cursor;
80 
81     cursor = SDL_calloc(1, sizeof(*cursor));
82     if (cursor) {
83         cursor->driverdata = LoadCursor(NULL, IDC_ARROW);
84     } else {
85         SDL_OutOfMemory();
86     }
87 
88     return cursor;
89 }
90 
91 static SDL_Cursor *
WIN_CreateCursor(SDL_Surface * surface,int hot_x,int hot_y)92 WIN_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y)
93 {
94     /* msdn says cursor mask has to be padded out to word alignment. Not sure
95         if that means machine word or WORD, but this handles either case. */
96     const size_t pad = (sizeof (size_t) * 8);  /* 32 or 64, or whatever. */
97     SDL_Cursor *cursor;
98     HICON hicon;
99     HICON hcursor;
100     HDC hdc;
101     BITMAPV4HEADER bmh;
102     LPVOID pixels;
103     LPVOID maskbits;
104     size_t maskbitslen;
105     SDL_bool isstack;
106     ICONINFO ii;
107 
108     SDL_zero(bmh);
109     bmh.bV4Size = sizeof(bmh);
110     bmh.bV4Width = surface->w;
111     bmh.bV4Height = -surface->h; /* Invert the image */
112     bmh.bV4Planes = 1;
113     bmh.bV4BitCount = 32;
114     bmh.bV4V4Compression = BI_BITFIELDS;
115     bmh.bV4AlphaMask = 0xFF000000;
116     bmh.bV4RedMask   = 0x00FF0000;
117     bmh.bV4GreenMask = 0x0000FF00;
118     bmh.bV4BlueMask  = 0x000000FF;
119 
120     maskbitslen = ((surface->w + (pad - (surface->w % pad))) / 8) * surface->h;
121     maskbits = SDL_small_alloc(Uint8, maskbitslen, &isstack);
122     if (maskbits == NULL) {
123         SDL_OutOfMemory();
124         return NULL;
125     }
126 
127     /* AND the cursor against full bits: no change. We already have alpha. */
128     SDL_memset(maskbits, 0xFF, maskbitslen);
129 
130     hdc = GetDC(NULL);
131     SDL_zero(ii);
132     ii.fIcon = FALSE;
133     ii.xHotspot = (DWORD)hot_x;
134     ii.yHotspot = (DWORD)hot_y;
135     ii.hbmColor = CreateDIBSection(hdc, (BITMAPINFO*)&bmh, DIB_RGB_COLORS, &pixels, NULL, 0);
136     ii.hbmMask = CreateBitmap(surface->w, surface->h, 1, 1, maskbits);
137     ReleaseDC(NULL, hdc);
138     SDL_small_free(maskbits, isstack);
139 
140     SDL_assert(surface->format->format == SDL_PIXELFORMAT_ARGB8888);
141     SDL_assert(surface->pitch == surface->w * 4);
142     SDL_memcpy(pixels, surface->pixels, surface->h * surface->pitch);
143 
144     hicon = CreateIconIndirect(&ii);
145 
146     DeleteObject(ii.hbmColor);
147     DeleteObject(ii.hbmMask);
148 
149     if (!hicon) {
150         WIN_SetError("CreateIconIndirect()");
151         return NULL;
152     }
153 
154     /* The cursor returned by CreateIconIndirect does not respect system cursor size
155         preference, use CopyImage to duplicate the cursor with desired sizes */
156     hcursor = CopyImage(hicon, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE);
157     DestroyIcon(hicon);
158 
159     if (!hcursor) {
160         WIN_SetError("CopyImage()");
161         return NULL;
162     }
163 
164     cursor = SDL_calloc(1, sizeof(*cursor));
165     if (cursor) {
166         cursor->driverdata = hcursor;
167     } else {
168         DestroyIcon(hcursor);
169         SDL_OutOfMemory();
170     }
171 
172     return cursor;
173 }
174 
175 static SDL_Cursor *
WIN_CreateBlankCursor()176 WIN_CreateBlankCursor()
177 {
178     SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormat(0, 32, 32, 32, SDL_PIXELFORMAT_ARGB8888);
179     if (surface) {
180         return WIN_CreateCursor(surface, 0, 0);
181     }
182     return NULL;
183 }
184 
185 static SDL_Cursor *
WIN_CreateSystemCursor(SDL_SystemCursor id)186 WIN_CreateSystemCursor(SDL_SystemCursor id)
187 {
188     SDL_Cursor *cursor;
189     LPCTSTR name;
190 
191     switch(id)
192     {
193     default:
194         SDL_assert(0);
195         return NULL;
196     case SDL_SYSTEM_CURSOR_ARROW:     name = IDC_ARROW; break;
197     case SDL_SYSTEM_CURSOR_IBEAM:     name = IDC_IBEAM; break;
198     case SDL_SYSTEM_CURSOR_WAIT:      name = IDC_WAIT; break;
199     case SDL_SYSTEM_CURSOR_CROSSHAIR: name = IDC_CROSS; break;
200     case SDL_SYSTEM_CURSOR_WAITARROW: name = IDC_WAIT; break;
201     case SDL_SYSTEM_CURSOR_SIZENWSE:  name = IDC_SIZENWSE; break;
202     case SDL_SYSTEM_CURSOR_SIZENESW:  name = IDC_SIZENESW; break;
203     case SDL_SYSTEM_CURSOR_SIZEWE:    name = IDC_SIZEWE; break;
204     case SDL_SYSTEM_CURSOR_SIZENS:    name = IDC_SIZENS; break;
205     case SDL_SYSTEM_CURSOR_SIZEALL:   name = IDC_SIZEALL; break;
206     case SDL_SYSTEM_CURSOR_NO:        name = IDC_NO; break;
207     case SDL_SYSTEM_CURSOR_HAND:      name = IDC_HAND; break;
208     }
209 
210     cursor = SDL_calloc(1, sizeof(*cursor));
211     if (cursor) {
212         HICON hicon;
213 
214         hicon = LoadCursor(NULL, name);
215 
216         cursor->driverdata = hicon;
217     } else {
218         SDL_OutOfMemory();
219     }
220 
221     return cursor;
222 }
223 
224 static void
WIN_FreeCursor(SDL_Cursor * cursor)225 WIN_FreeCursor(SDL_Cursor * cursor)
226 {
227     HICON hicon = (HICON)cursor->driverdata;
228 
229     DestroyIcon(hicon);
230     SDL_free(cursor);
231 }
232 
233 static int
WIN_ShowCursor(SDL_Cursor * cursor)234 WIN_ShowCursor(SDL_Cursor * cursor)
235 {
236     if (!cursor) {
237         cursor = SDL_blank_cursor;
238     }
239     if (cursor) {
240         SDL_cursor = (HCURSOR)cursor->driverdata;
241     } else {
242         SDL_cursor = NULL;
243     }
244     if (SDL_GetMouseFocus() != NULL) {
245         SetCursor(SDL_cursor);
246     }
247     return 0;
248 }
249 
250 void
WIN_SetCursorPos(int x,int y)251 WIN_SetCursorPos(int x, int y)
252 {
253     /* We need to jitter the value because otherwise Windows will occasionally inexplicably ignore the SetCursorPos() or SendInput() */
254     SetCursorPos(x, y);
255     SetCursorPos(x+1, y);
256     SetCursorPos(x, y);
257 
258     /* Flush any mouse motion prior to or associated with this warp */
259     SDL_last_warp_time = GetTickCount();
260     if (!SDL_last_warp_time) {
261         SDL_last_warp_time = 1;
262     }
263 }
264 
265 static void
WIN_WarpMouse(SDL_Window * window,int x,int y)266 WIN_WarpMouse(SDL_Window * window, int x, int y)
267 {
268     SDL_WindowData *data = (SDL_WindowData *)window->driverdata;
269     HWND hwnd = data->hwnd;
270     POINT pt;
271 
272     /* Don't warp the mouse while we're doing a modal interaction */
273     if (data->in_title_click || data->focus_click_pending) {
274         return;
275     }
276 
277     pt.x = x;
278     pt.y = y;
279     ClientToScreen(hwnd, &pt);
280     WIN_SetCursorPos(pt.x, pt.y);
281 
282     /* Send the exact mouse motion associated with this warp */
283     SDL_SendMouseMotion(window, SDL_GetMouse()->mouseID, 0, x, y);
284 }
285 
286 static int
WIN_WarpMouseGlobal(int x,int y)287 WIN_WarpMouseGlobal(int x, int y)
288 {
289     POINT pt;
290 
291     pt.x = x;
292     pt.y = y;
293     SetCursorPos(pt.x, pt.y);
294     return 0;
295 }
296 
297 static int
WIN_SetRelativeMouseMode(SDL_bool enabled)298 WIN_SetRelativeMouseMode(SDL_bool enabled)
299 {
300     return ToggleRawInput(enabled);
301 }
302 
303 static int
WIN_CaptureMouse(SDL_Window * window)304 WIN_CaptureMouse(SDL_Window *window)
305 {
306     if (window) {
307         SDL_WindowData *data = (SDL_WindowData *)window->driverdata;
308         SetCapture(data->hwnd);
309     } else {
310         SDL_Window *focus_window = SDL_GetMouseFocus();
311 
312         if (focus_window) {
313             SDL_WindowData *data = (SDL_WindowData *)focus_window->driverdata;
314             if (!data->mouse_tracked) {
315                 SDL_SetMouseFocus(NULL);
316             }
317         }
318         ReleaseCapture();
319     }
320 
321     return 0;
322 }
323 
324 static Uint32
WIN_GetGlobalMouseState(int * x,int * y)325 WIN_GetGlobalMouseState(int *x, int *y)
326 {
327     Uint32 retval = 0;
328     POINT pt = { 0, 0 };
329     SDL_bool swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0;
330 
331     GetCursorPos(&pt);
332     *x = (int) pt.x;
333     *y = (int) pt.y;
334 
335     retval |= GetAsyncKeyState(!swapButtons ? VK_LBUTTON : VK_RBUTTON) & 0x8000 ? SDL_BUTTON_LMASK : 0;
336     retval |= GetAsyncKeyState(!swapButtons ? VK_RBUTTON : VK_LBUTTON) & 0x8000 ? SDL_BUTTON_RMASK : 0;
337     retval |= GetAsyncKeyState(VK_MBUTTON) & 0x8000 ? SDL_BUTTON_MMASK : 0;
338     retval |= GetAsyncKeyState(VK_XBUTTON1) & 0x8000 ? SDL_BUTTON_X1MASK : 0;
339     retval |= GetAsyncKeyState(VK_XBUTTON2) & 0x8000 ? SDL_BUTTON_X2MASK : 0;
340 
341     return retval;
342 }
343 
344 void
WIN_InitMouse(_THIS)345 WIN_InitMouse(_THIS)
346 {
347     SDL_Mouse *mouse = SDL_GetMouse();
348 
349     mouse->CreateCursor = WIN_CreateCursor;
350     mouse->CreateSystemCursor = WIN_CreateSystemCursor;
351     mouse->ShowCursor = WIN_ShowCursor;
352     mouse->FreeCursor = WIN_FreeCursor;
353     mouse->WarpMouse = WIN_WarpMouse;
354     mouse->WarpMouseGlobal = WIN_WarpMouseGlobal;
355     mouse->SetRelativeMouseMode = WIN_SetRelativeMouseMode;
356     mouse->CaptureMouse = WIN_CaptureMouse;
357     mouse->GetGlobalMouseState = WIN_GetGlobalMouseState;
358 
359     SDL_SetDefaultCursor(WIN_CreateDefaultCursor());
360 
361     SDL_blank_cursor = WIN_CreateBlankCursor();
362 }
363 
364 void
WIN_QuitMouse(_THIS)365 WIN_QuitMouse(_THIS)
366 {
367     if (rawInputEnableCount) {  /* force RAWINPUT off here. */
368         rawInputEnableCount = 1;
369         ToggleRawInput(SDL_FALSE);
370     }
371 
372     if (SDL_blank_cursor) {
373         SDL_FreeCursor(SDL_blank_cursor);
374     }
375 }
376 
377 #endif /* SDL_VIDEO_DRIVER_WINDOWS */
378 
379 /* vi: set ts=4 sw=4 expandtab: */
380