1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#include "Input/BsInput.h"
4#include "Private/Linux/BsLinuxInput.h"
5#include "Input/BsMouse.h"
6#include "Input/BsKeyboard.h"
7#include "Input/BsGamepad.h"
8#include <fcntl.h>
9#include <linux/input.h>
10
11namespace bs
12{
13 /** Information about events reported from a specific input event device. */
14 struct EventInfo
15 {
16 Vector<INT32> buttons;
17 Vector<INT32> relAxes;
18 Vector<INT32> absAxes;
19 Vector<INT32> hats;
20 };
21
22 /** Checks is the bit at the specified location in a byte array is set. */
23 bool isBitSet(UINT8 bits[], UINT32 bit)
24 {
25 return ((bits[bit/8] >> (bit%8)) & 1) != 0;
26 }
27
28 /** Returns information about an input event device attached to he provided file handle. */
29 bool getEventInfo(int fileHandle, EventInfo& eventInfo)
30 {
31 UINT8 eventBits[1 + EV_MAX/8];
32 bs_zero_out(eventBits);
33
34 if (ioctl(fileHandle, EVIOCGBIT(0, sizeof(eventBits)), eventBits) == -1)
35 return false;
36
37 for (UINT32 i = 0; i < EV_MAX; i++)
38 {
39 if(isBitSet(eventBits, i))
40 {
41 if(i == EV_ABS)
42 {
43 UINT8 absAxisBits[1 + ABS_MAX/8];
44 bs_zero_out(absAxisBits);
45
46 if (ioctl(fileHandle, EVIOCGBIT(i, sizeof(absAxisBits)), absAxisBits) == -1)
47 {
48 LOGERR("Could not read device absolute axis features.");
49 continue;
50 }
51
52 for (UINT32 j = 0; j < ABS_MAX; j++)
53 {
54 if(isBitSet(absAxisBits, j))
55 {
56 if(j >= ABS_HAT0X && j <= ABS_HAT3Y)
57 eventInfo.hats.push_back(j);
58 else
59 eventInfo.absAxes.push_back(j);
60 }
61 }
62 }
63 else if(i == EV_REL)
64 {
65 UINT8 relAxisBits[1 + REL_MAX/8];
66 bs_zero_out(relAxisBits);
67
68 if (ioctl(fileHandle, EVIOCGBIT(i, sizeof(relAxisBits)), relAxisBits) == -1)
69 {
70 LOGERR("Could not read device relative axis features.");
71 continue;
72 }
73
74 for (UINT32 j = 0; j < REL_MAX; j++)
75 {
76 if(isBitSet(relAxisBits, j))
77 eventInfo.relAxes.push_back(j);
78 }
79 }
80 else if(i == EV_KEY)
81 {
82 UINT8 keyBits[1 + KEY_MAX/8];
83 bs_zero_out(keyBits);
84
85 if (ioctl(fileHandle, EVIOCGBIT(i, sizeof(keyBits)), keyBits) == -1)
86 {
87 LOGERR("Could not read device key features.");
88 continue;
89 }
90
91 for (UINT32 j = 0; j < KEY_MAX; j++)
92 {
93 if(isBitSet(keyBits, j))
94 eventInfo.buttons.push_back(j);
95 }
96 }
97 }
98 }
99
100 return true;
101 }
102
103 /** Converts a Linux button code to Banshee ButtonCode. */
104 ButtonCode gamepadMapCommonButton(INT32 code)
105 {
106 // Note: Assuming XBox controller layout here
107 switch (code)
108 {
109 case BTN_TRIGGER_HAPPY1:
110 return BC_GAMEPAD_DPAD_LEFT;
111 case BTN_TRIGGER_HAPPY2:
112 return BC_GAMEPAD_DPAD_RIGHT;
113 case BTN_TRIGGER_HAPPY3:
114 return BC_GAMEPAD_DPAD_UP;
115 case BTN_TRIGGER_HAPPY4:
116 return BC_GAMEPAD_DPAD_DOWN;
117 case BTN_START:
118 return BC_GAMEPAD_START;
119 case BTN_SELECT:
120 return BC_GAMEPAD_BACK;
121 case BTN_THUMBL:
122 return BC_GAMEPAD_LS;
123 case BTN_THUMBR:
124 return BC_GAMEPAD_RS;
125 case BTN_TL:
126 return BC_GAMEPAD_LB;
127 case BTN_TR:
128 return BC_GAMEPAD_RB;
129 case BTN_A:
130 return BC_GAMEPAD_A;
131 case BTN_B:
132 return BC_GAMEPAD_B;
133 case BTN_X:
134 return BC_GAMEPAD_X;
135 case BTN_Y:
136 return BC_GAMEPAD_Y;
137 }
138
139 return BC_UNASSIGNED;
140 }
141
142 /**
143 * Maps an absolute axis as reported by the Linux system, to a Banshee axis. This will be one of the InputAxis enum
144 * members, or -1 if it cannot be mapped.
145 */
146 INT32 gamepadMapCommonAxis(INT32 axis)
147 {
148 switch(axis)
149 {
150 case ABS_X: return (INT32)InputAxis::LeftStickX;
151 case ABS_Y: return (INT32)InputAxis::LeftStickY;
152 case ABS_RX: return (INT32)InputAxis::RightStickX;
153 case ABS_RY: return (INT32)InputAxis::RightStickY;
154 case ABS_Z: return (INT32)InputAxis::LeftTrigger;
155 case ABS_RZ: return (INT32)InputAxis::RightTrigger;
156 }
157
158 return -1;
159 }
160
161 /**
162 * Returns true if the input event attached to the specified file handle is a gamepad,
163 * and populates the gamepad info structure. Returns false otherwise.
164 */
165 bool parseGamepadInfo(int fileHandle, int eventHandlerIdx, GamepadInfo& info)
166 {
167 EventInfo eventInfo;
168 if(!getEventInfo(fileHandle, eventInfo))
169 return false;
170
171 bool isGamepad = false;
172
173 // Check for gamepad buttons
174 UINT32 unknownButtonIdx = 0;
175 for(auto& entry : eventInfo.buttons)
176 {
177 if((entry >= BTN_JOYSTICK && entry < BTN_GAMEPAD)
178 || (entry >= BTN_GAMEPAD && entry < BTN_DIGI)
179 || (entry >= BTN_WHEEL && entry < KEY_OK))
180 {
181 ButtonCode bc = gamepadMapCommonButton(entry);
182 if(bc == BC_UNASSIGNED)
183 {
184 // Map to unnamed buttons
185 if(unknownButtonIdx < 20)
186 {
187 bc = (ButtonCode)((INT32)BC_GAMEPAD_BTN1 + unknownButtonIdx);
188 info.buttonMap[entry] = bc;
189
190 unknownButtonIdx++;
191 }
192 }
193 else
194 info.buttonMap[entry] = bc;
195
196 isGamepad = true;
197 }
198 }
199
200 if(isGamepad)
201 {
202 info.eventHandlerIdx = eventHandlerIdx;
203
204 // Get device name
205 char name[128];
206 if (ioctl(fileHandle, EVIOCGNAME(sizeof(name)), name) != -1)
207 info.name = String(name);
208 else
209 LOGERR("Could not read device name.");
210
211 // Get axis ranges
212 UINT32 unknownAxisIdx = 0;
213 for(auto& entry : eventInfo.absAxes)
214 {
215 AxisInfo& axisInfo = info.axisMap[entry];
216 axisInfo.min = Gamepad::MIN_AXIS;
217 axisInfo.max = Gamepad::MAX_AXIS;
218
219 input_absinfo absinfo;
220 if (ioctl(fileHandle, EVIOCGABS(entry), &absinfo) == -1)
221 {
222 LOGERR("Could not read absolute axis device features.");
223 continue;
224 }
225
226 axisInfo.min = absinfo.minimum;
227 axisInfo.max = absinfo.maximum;
228
229 axisInfo.axisIdx = gamepadMapCommonAxis(entry);
230 if(axisInfo.axisIdx == -1)
231 {
232 axisInfo.axisIdx = (INT32)InputAxis::Count + unknownAxisIdx;
233 unknownAxisIdx++;
234 }
235 }
236 }
237
238 return isGamepad;
239 }
240
241 void Input::initRawInput()
242 {
243 mPlatformData = bs_new<InputPrivateData>();
244
245 // Scan for valid gamepad devices
246 for(int i = 0; i < 64; ++i )
247 {
248 String eventPath = "/dev/input/event" + toString(i);
249 int file = open(eventPath.c_str(), O_RDONLY |O_NONBLOCK);
250 if(file == -1)
251 {
252 // Note: We're ignoring failures due to permissions. The assumption is that gamepads won't have special
253 // permissions. If this assumption proves wrong, then using udev might be required to read gamepad input.
254 continue;
255 }
256
257 GamepadInfo info;
258 if(parseGamepadInfo(file, i, info))
259 {
260 info.id = (UINT32)mPlatformData->gamepadInfos.size();
261 mPlatformData->gamepadInfos.push_back(info);
262 }
263
264 close(file);
265 }
266
267 mKeyboard = bs_new<Keyboard>("Keyboard", this);
268 mMouse = bs_new<Mouse>("Mouse", this);
269
270 UINT32 numGamepads = getDeviceCount(InputDevice::Gamepad);
271 for (UINT32 i = 0; i < numGamepads; i++)
272 mGamepads.push_back(bs_new<Gamepad>(mPlatformData->gamepadInfos[i].name, mPlatformData->gamepadInfos[i], this));
273 }
274
275 void Input::cleanUpRawInput()
276 {
277 if (mMouse != nullptr)
278 bs_delete(mMouse);
279
280 if (mKeyboard != nullptr)
281 bs_delete(mKeyboard);
282
283 for (auto& gamepad : mGamepads)
284 bs_delete(gamepad);
285
286 bs_delete(mPlatformData);
287 }
288
289 UINT32 Input::getDeviceCount(InputDevice device) const
290 {
291 switch(device)
292 {
293 case InputDevice::Keyboard: return 1;
294 case InputDevice::Mouse: return 1;
295 case InputDevice::Gamepad: return (UINT32)mPlatformData->gamepadInfos.size();
296 case InputDevice::Count: return 0;
297 }
298
299 return 0;
300 }
301}
302
303