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