1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2 
3 /*
4 	This workaround fixes the windows slow mouse movement problem
5 	(happens on full-screen mode + pressing keys).
6 	The code hacks around the mouse input from DirectInput,
7 	which SDL uses in full-screen mode.
8 	Instead it installs a window message proc and reads input from WM_MOUSEMOVE.
9 	On non-windows, the normal SDL events are used for mouse input
10 
11 	new:
12 	It also workarounds a issue with SDL+windows and hardware cursors
13 	(->it has to block WM_SETCURSOR),
14 	so it is used now always even in window mode!
15 
16 	newer:
17 	SDL_Event struct is used for new input handling.
18 	Several people confirmed its working.
19 */
20 
21 
22 #include "MouseInput.h"
23 #include "InputHandler.h"
24 
25 #include "Game/GlobalUnsynced.h"
26 #include "Game/UI/MouseHandler.h"
27 #include "Rendering/GlobalRendering.h"
28 #include "Rendering/GL/FBO.h"
29 #include "System/maindefines.h"
30 
31 #include <boost/bind.hpp>
32 #include <SDL_events.h>
33 #include <SDL_syswm.h>
34 
35 
36 IMouseInput* mouseInput = NULL;
37 
38 
IMouseInput()39 IMouseInput::IMouseInput()
40 {
41 	inputCon = input.AddHandler(boost::bind(&IMouseInput::HandleSDLMouseEvent, this, _1));
42 }
43 
44 
HandleSDLMouseEvent(const SDL_Event & event)45 bool IMouseInput::HandleSDLMouseEvent(const SDL_Event& event)
46 {
47 	switch (event.type) {
48 		case SDL_MOUSEMOTION: {
49 			mousepos = int2(event.motion.x, event.motion.y);
50 			if (mouse) {
51 				mouse->MouseMove(mousepos.x, mousepos.y, event.motion.xrel, event.motion.yrel);
52 			}
53 		} break;
54 		case SDL_MOUSEBUTTONDOWN: {
55 			mousepos = int2(event.button.x, event.button.y);
56 			if (mouse) {
57 				mouse->MousePress(mousepos.x, mousepos.y, event.button.button);
58 			}
59 		} break;
60 		case SDL_MOUSEBUTTONUP: {
61 			mousepos = int2(event.button.x, event.button.y);
62 			if (mouse) {
63 				mouse->MouseRelease(mousepos.x, mousepos.y, event.button.button);
64 			}
65 		} break;
66 		case SDL_MOUSEWHEEL: {
67 			if (mouse) {
68 				mouse->MouseWheel(event.wheel.y);
69 			}
70 		} break;
71 		case SDL_WINDOWEVENT: {
72 			if (event.window.event == SDL_WINDOWEVENT_LEAVE) {
73 				// mouse left window (set mouse pos internally to window center to prevent endless scrolling)
74 				mousepos.x = globalRendering->viewSizeX / 2;
75 				mousepos.y = globalRendering->viewSizeY / 2;
76 				if (mouse) {
77 					mouse->MouseMove(mousepos.x, mousepos.y, 0, 0);
78 				}
79 			}
80 		} break;
81 	}
82 	return false;
83 }
84 
85 //////////////////////////////////////////////////////////////////////
86 
87 #if defined(WIN32) && !defined (HEADLESS)
88 
89 class CWin32MouseInput : public IMouseInput
90 {
91 public:
92 	static CWin32MouseInput *inst;
93 
94 	LONG_PTR sdl_wndproc;
95 	HWND wnd;
96 	HCURSOR hCursor;
97 
SpringWndProc(HWND wnd,UINT msg,WPARAM wParam,LPARAM lParam)98 	static LRESULT CALLBACK SpringWndProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam)
99 	{
100 		switch (msg) {
101 			case WM_SETCURSOR:
102 			{
103 				if (inst->hCursor!=NULL) {
104 					Uint16 hittest = LOWORD(lParam);
105 					if ( hittest == HTCLIENT ) {
106 						SetCursor(inst->hCursor);
107 						return TRUE;
108 					}
109 				}
110 			} break;
111 		}
112 		return CallWindowProc((WNDPROC)inst->sdl_wndproc, wnd, msg, wParam, lParam);
113 	}
114 
SetWMMouseCursor(void * wmcursor)115 	void SetWMMouseCursor(void* wmcursor)
116 	{
117 		hCursor = (HCURSOR)wmcursor;
118 	}
119 
InstallWndCallback()120 	void InstallWndCallback()
121 	{
122 		SDL_SysWMinfo info;
123 		SDL_VERSION(&info.version);
124 		if(!SDL_GetWindowWMInfo(globalRendering->window, &info))
125 			return;
126 
127 		wnd = info.info.win.window;
128 
129 		LONG_PTR cur_wndproc = GetWindowLongPtr(wnd, GWLP_WNDPROC);
130 		if (cur_wndproc != (LONG_PTR)SpringWndProc) {
131 			sdl_wndproc = GetWindowLongPtr(wnd, GWLP_WNDPROC);
132 			SetWindowLongPtr(wnd,GWLP_WNDPROC,(LONG_PTR)SpringWndProc);
133 		}
134 	}
135 
CWin32MouseInput()136 	CWin32MouseInput()
137 	{
138 		inst = this;
139 		hCursor = NULL;
140 		sdl_wndproc = 0;
141 		wnd = 0;
142 		InstallWndCallback();
143 	}
~CWin32MouseInput()144 	~CWin32MouseInput()
145 	{
146 		// reinstall the SDL window proc
147 		SetWindowLongPtr(wnd, GWLP_WNDPROC, sdl_wndproc);
148 	}
149 };
150 CWin32MouseInput* CWin32MouseInput::inst = NULL;
151 #endif
152 
SetPos(int2 pos)153 void IMouseInput::SetPos(int2 pos)
154 {
155 	if (!globalRendering->active) {
156 		return;
157 	}
158 
159 	if (pos.x == mousepos.x && pos.y == mousepos.y) {
160 		// calling SDL_WarpMouse at 300fps eats ~5% cpu usage, so only update when needed
161 		return;
162 	}
163 
164 	mousepos = pos;
165 
166 	SDL_WarpMouseInWindow(globalRendering->window, pos.x, pos.y);
167 
168 	// SDL_WarpMouse generates SDL_MOUSEMOTION events
169 	// in `middle click scrolling` those SDL generated ones would point into
170 	// the opposite direction the user moved the mouse, and so events would
171 	// cancel each other -> camera wouldn't move at all
172 	// so we need to catch those SDL generated events and delete them
173 
174 	// delete all SDL_MOUSEMOTION in the queue
175 	SDL_PumpEvents();
176 	static SDL_Event events[100];
177 	SDL_PeepEvents(&events[0], 100, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION);
178 }
179 
180 
181 
GetInstance()182 IMouseInput* IMouseInput::GetInstance()
183 {
184 	if (mouseInput == NULL) {
185 #if defined(WIN32) && !defined(HEADLESS)
186 		mouseInput = new CWin32MouseInput;
187 #else
188 		mouseInput = new IMouseInput;
189 #endif
190 	}
191 	return mouseInput;
192 }
193 
FreeInstance(IMouseInput * mouseInp)194 void IMouseInput::FreeInstance(IMouseInput* mouseInp) {
195 	delete mouseInp; mouseInput = NULL;
196 }
197