1 //
2 // Copyright(C) 1993-1996 Id Software, Inc.
3 // Copyright(C) 2005-2014 Simon Howard
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // DESCRIPTION:
16 // SDL implementation of system-specific input interface.
17 //
18
19
20 #include "SDL.h"
21 #include "SDL_keycode.h"
22
23 #include "doomkeys.h"
24 #include "doomtype.h"
25 #include "d_event.h"
26 #include "i_input.h"
27 #include "m_argv.h"
28 #include "m_config.h"
29
30 static const int scancode_translate_table[] = SCANCODE_TO_KEYS_ARRAY;
31
32 // Lookup table for mapping ASCII characters to their equivalent when
33 // shift is pressed on a US layout keyboard. This is the original table
34 // as found in the Doom sources, comments and all.
35 static const char shiftxform[] =
36 {
37 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
38 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
39 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
40 31, ' ', '!', '"', '#', '$', '%', '&',
41 '"', // shift-'
42 '(', ')', '*', '+',
43 '<', // shift-,
44 '_', // shift--
45 '>', // shift-.
46 '?', // shift-/
47 ')', // shift-0
48 '!', // shift-1
49 '@', // shift-2
50 '#', // shift-3
51 '$', // shift-4
52 '%', // shift-5
53 '^', // shift-6
54 '&', // shift-7
55 '*', // shift-8
56 '(', // shift-9
57 ':',
58 ':', // shift-;
59 '<',
60 '+', // shift-=
61 '>', '?', '@',
62 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
63 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
64 '[', // shift-[
65 '!', // shift-backslash - OH MY GOD DOES WATCOM SUCK
66 ']', // shift-]
67 '"', '_',
68 '\'', // shift-`
69 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
70 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
71 '{', '|', '}', '~', 127
72 };
73
74 // If true, I_StartTextInput() has been called, and we are populating
75 // the data3 field of ev_keydown events.
76 static boolean text_input_enabled = true;
77
78 // Bit mask of mouse button state.
79 static unsigned int mouse_button_state = 0;
80
81 // Disallow mouse and joystick movement to cause forward/backward
82 // motion. Specified with the '-novert' command line parameter.
83 // This is an int to allow saving to config file
84 int novert = 0;
85
86 // If true, keyboard mapping is ignored, like in Vanilla Doom.
87 // The sensible thing to do is to disable this if you have a non-US
88 // keyboard.
89
90 int vanilla_keyboard_mapping = true;
91
92 // Mouse acceleration
93 //
94 // This emulates some of the behavior of DOS mouse drivers by increasing
95 // the speed when the mouse is moved fast.
96 //
97 // The mouse input values are input directly to the game, but when
98 // the values exceed the value of mouse_threshold, they are multiplied
99 // by mouse_acceleration to increase the speed.
100 float mouse_acceleration = 2.0;
101 int mouse_threshold = 10;
102
103 // Translates the SDL key to a value of the type found in doomkeys.h
TranslateKey(SDL_Keysym * sym)104 static int TranslateKey(SDL_Keysym *sym)
105 {
106 int scancode = sym->scancode;
107
108 switch (scancode)
109 {
110 case SDL_SCANCODE_LCTRL:
111 case SDL_SCANCODE_RCTRL:
112 return KEY_RCTRL;
113
114 case SDL_SCANCODE_LSHIFT:
115 case SDL_SCANCODE_RSHIFT:
116 return KEY_RSHIFT;
117
118 case SDL_SCANCODE_LALT:
119 return KEY_LALT;
120
121 case SDL_SCANCODE_RALT:
122 return KEY_RALT;
123
124 default:
125 if (scancode >= 0 && scancode < arrlen(scancode_translate_table))
126 {
127 return scancode_translate_table[scancode];
128 }
129 else
130 {
131 return 0;
132 }
133 }
134 }
135
136 // Get the localized version of the key press. This takes into account the
137 // keyboard layout, but does not apply any changes due to modifiers, (eg.
138 // shift-, alt-, etc.)
GetLocalizedKey(SDL_Keysym * sym)139 static int GetLocalizedKey(SDL_Keysym *sym)
140 {
141 // When using Vanilla mapping, we just base everything off the scancode
142 // and always pretend the user is using a US layout keyboard.
143 if (vanilla_keyboard_mapping)
144 {
145 return TranslateKey(sym);
146 }
147 else
148 {
149 int result = sym->sym;
150
151 if (result < 0 || result >= 128)
152 {
153 result = 0;
154 }
155
156 return result;
157 }
158 }
159
160 // Get the equivalent ASCII (Unicode?) character for a keypress.
GetTypedChar(SDL_Keysym * sym)161 static int GetTypedChar(SDL_Keysym *sym)
162 {
163 // We only return typed characters when entering text, after
164 // I_StartTextInput() has been called. Otherwise we return nothing.
165 if (!text_input_enabled)
166 {
167 return 0;
168 }
169
170 // If we're strictly emulating Vanilla, we should always act like
171 // we're using a US layout keyboard (in ev_keydown, data1=data2).
172 // Otherwise we should use the native key mapping.
173 if (vanilla_keyboard_mapping)
174 {
175 int result = TranslateKey(sym);
176
177 // If shift is held down, apply the original uppercase
178 // translation table used under DOS.
179 if ((SDL_GetModState() & KMOD_SHIFT) != 0
180 && result >= 0 && result < arrlen(shiftxform))
181 {
182 result = shiftxform[result];
183 }
184
185 return result;
186 }
187 else
188 {
189 SDL_Event next_event;
190
191 // Special cases, where we always return a fixed value.
192 switch (sym->sym)
193 {
194 case SDLK_BACKSPACE: return KEY_BACKSPACE;
195 case SDLK_RETURN: return KEY_ENTER;
196 default:
197 break;
198 }
199
200 // The following is a gross hack, but I don't see an easier way
201 // of doing this within the SDL2 API (in SDL1 it was easier).
202 // We want to get the fully transformed input character associated
203 // with this keypress - correct keyboard layout, appropriately
204 // transformed by any modifier keys, etc. So peek ahead in the SDL
205 // event queue and see if the key press is immediately followed by
206 // an SDL_TEXTINPUT event. If it is, it's reasonable to assume the
207 // key press and the text input are connected. Technically the SDL
208 // API does not guarantee anything of the sort, but in practice this
209 // is what happens and I've verified it through manual inspect of
210 // the SDL source code.
211 //
212 // In an ideal world we'd split out ev_keydown into a separate
213 // ev_textinput event, as SDL2 has done. But this doesn't work
214 // (I experimented with the idea), because lots of Doom's code is
215 // based around different responders "eating" events to stop them
216 // being passed on to another responder. If code is listening for
217 // a text input, it cannot block the corresponding keydown events
218 // which can affect other responders.
219 //
220 // So we're stuck with this as a rather fragile alternative.
221
222 if (SDL_PeepEvents(&next_event, 1, SDL_PEEKEVENT,
223 SDL_FIRSTEVENT, SDL_LASTEVENT) == 1
224 && next_event.type == SDL_TEXTINPUT)
225 {
226 // If an SDL_TEXTINPUT event is found, we always assume it
227 // matches the key press. The input text must be a single
228 // ASCII character - if it isn't, it's possible the input
229 // char is a Unicode value instead; better to send a null
230 // character than the unshifted key.
231 if (strlen(next_event.text.text) == 1
232 && (next_event.text.text[0] & 0x80) == 0)
233 {
234 return next_event.text.text[0];
235 }
236 }
237
238 // Failed to find anything :/
239 return 0;
240 }
241 }
242
I_HandleKeyboardEvent(SDL_Event * sdlevent)243 void I_HandleKeyboardEvent(SDL_Event *sdlevent)
244 {
245 // XXX: passing pointers to event for access after this function
246 // has terminated is undefined behaviour
247 event_t event;
248
249 switch (sdlevent->type)
250 {
251 case SDL_KEYDOWN:
252 event.type = ev_keydown;
253 event.data1 = TranslateKey(&sdlevent->key.keysym);
254 event.data2 = GetLocalizedKey(&sdlevent->key.keysym);
255 event.data3 = GetTypedChar(&sdlevent->key.keysym);
256
257 if (event.data1 != 0)
258 {
259 D_PostEvent(&event);
260 }
261 break;
262
263 case SDL_KEYUP:
264 event.type = ev_keyup;
265 event.data1 = TranslateKey(&sdlevent->key.keysym);
266
267 // data2/data3 are initialized to zero for ev_keyup.
268 // For ev_keydown it's the shifted Unicode character
269 // that was typed, but if something wants to detect
270 // key releases it should do so based on data1
271 // (key ID), not the printable char.
272
273 event.data2 = 0;
274 event.data3 = 0;
275
276 if (event.data1 != 0)
277 {
278 D_PostEvent(&event);
279 }
280 break;
281
282 default:
283 break;
284 }
285 }
286
I_StartTextInput(int x1,int y1,int x2,int y2)287 void I_StartTextInput(int x1, int y1, int x2, int y2)
288 {
289 text_input_enabled = true;
290
291 if (!vanilla_keyboard_mapping)
292 {
293 // SDL2-TODO: SDL_SetTextInputRect(...);
294 SDL_StartTextInput();
295 }
296 }
297
I_StopTextInput(void)298 void I_StopTextInput(void)
299 {
300 text_input_enabled = false;
301
302 if (!vanilla_keyboard_mapping)
303 {
304 SDL_StopTextInput();
305 }
306 }
307
UpdateMouseButtonState(unsigned int button,boolean on)308 static void UpdateMouseButtonState(unsigned int button, boolean on)
309 {
310 static event_t event;
311
312 if (button < SDL_BUTTON_LEFT || button > MAX_MOUSE_BUTTONS)
313 {
314 return;
315 }
316
317 // Note: button "0" is left, button "1" is right,
318 // button "2" is middle for Doom. This is different
319 // to how SDL sees things.
320
321 switch (button)
322 {
323 case SDL_BUTTON_LEFT:
324 button = 0;
325 break;
326
327 case SDL_BUTTON_RIGHT:
328 button = 1;
329 break;
330
331 case SDL_BUTTON_MIDDLE:
332 button = 2;
333 break;
334
335 default:
336 // SDL buttons are indexed from 1.
337 --button;
338 break;
339 }
340
341 // Turn bit representing this button on or off.
342
343 if (on)
344 {
345 mouse_button_state |= (1 << button);
346 }
347 else
348 {
349 mouse_button_state &= ~(1 << button);
350 }
351
352 // Post an event with the new button state.
353
354 event.type = ev_mouse;
355 event.data1 = mouse_button_state;
356 event.data2 = event.data3 = 0;
357 D_PostEvent(&event);
358 }
359
MapMouseWheelToButtons(SDL_MouseWheelEvent * wheel)360 static void MapMouseWheelToButtons(SDL_MouseWheelEvent *wheel)
361 {
362 // SDL2 distinguishes button events from mouse wheel events.
363 // We want to treat the mouse wheel as two buttons, as per
364 // SDL1
365 static event_t up, down;
366 int button;
367
368 if (wheel->y <= 0)
369 { // scroll down
370 button = 4;
371 }
372 else
373 { // scroll up
374 button = 3;
375 }
376
377 // post a button down event
378 mouse_button_state |= (1 << button);
379 down.type = ev_mouse;
380 down.data1 = mouse_button_state;
381 down.data2 = down.data3 = 0;
382 D_PostEvent(&down);
383
384 // post a button up event
385 mouse_button_state &= ~(1 << button);
386 up.type = ev_mouse;
387 up.data1 = mouse_button_state;
388 up.data2 = up.data3 = 0;
389 D_PostEvent(&up);
390 }
391
I_HandleMouseEvent(SDL_Event * sdlevent)392 void I_HandleMouseEvent(SDL_Event *sdlevent)
393 {
394 switch (sdlevent->type)
395 {
396 case SDL_MOUSEBUTTONDOWN:
397 UpdateMouseButtonState(sdlevent->button.button, true);
398 break;
399
400 case SDL_MOUSEBUTTONUP:
401 UpdateMouseButtonState(sdlevent->button.button, false);
402 break;
403
404 case SDL_MOUSEWHEEL:
405 MapMouseWheelToButtons(&(sdlevent->wheel));
406 break;
407
408 default:
409 break;
410 }
411 }
412
AccelerateMouse(int val)413 static int AccelerateMouse(int val)
414 {
415 if (val < 0)
416 return -AccelerateMouse(-val);
417
418 if (val > mouse_threshold)
419 {
420 return (int)((val - mouse_threshold) * mouse_acceleration + mouse_threshold);
421 }
422 else
423 {
424 return val;
425 }
426 }
427
428 //
429 // Read the change in mouse state to generate mouse motion events
430 //
431 // This is to combine all mouse movement for a tic into one mouse
432 // motion event.
I_ReadMouse(void)433 void I_ReadMouse(void)
434 {
435 int x, y;
436 event_t ev;
437
438 SDL_GetRelativeMouseState(&x, &y);
439
440 if (x != 0 || y != 0)
441 {
442 ev.type = ev_mouse;
443 ev.data1 = mouse_button_state;
444 ev.data2 = AccelerateMouse(x);
445
446 if (!novert)
447 {
448 ev.data3 = -AccelerateMouse(y);
449 }
450 else
451 {
452 ev.data3 = 0;
453 }
454
455 // XXX: undefined behaviour since event is scoped to
456 // this function
457 D_PostEvent(&ev);
458 }
459 }
460
461 // Bind all variables controlling input options.
I_BindInputVariables(void)462 void I_BindInputVariables(void)
463 {
464 M_BindFloatVariable("mouse_acceleration", &mouse_acceleration);
465 M_BindIntVariable("mouse_threshold", &mouse_threshold);
466 M_BindIntVariable("vanilla_keyboard_mapping", &vanilla_keyboard_mapping);
467 M_BindIntVariable("novert", &novert);
468 }
469