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