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 | |
11 | namespace 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 | |