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