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/BsGamepad.h"
4#include "Input/BsInput.h"
5#include "Private/Linux/BsLinuxInput.h"
6#include <fcntl.h>
7#include <linux/input.h>
8
9namespace bs
10{
11 /** Contains private data for the Linux Gamepad implementation. */
12 struct Gamepad::Pimpl
13 {
14 GamepadInfo info;
15 INT32 fileHandle;
16 ButtonCode povState;
17 bool hasInputFocus;
18 };
19
20 Gamepad::Gamepad(const String& name, const GamepadInfo& gamepadInfo, Input* owner)
21 : mName(name), mOwner(owner)
22 {
23 m = bs_new<Pimpl>();
24 m->info = gamepadInfo;
25 m->povState = BC_UNASSIGNED;
26 m->hasInputFocus = true;
27
28 String eventPath = "/dev/input/event" + toString(gamepadInfo.eventHandlerIdx);
29 m->fileHandle = open(eventPath.c_str(), O_RDWR | O_NONBLOCK);
30
31 if(m->fileHandle == -1)
32 LOGERR("Failed to open input event file handle for device: " + gamepadInfo.name);
33 }
34
35 Gamepad::~Gamepad()
36 {
37 if(m->fileHandle != -1)
38 close(m->fileHandle);
39
40 bs_delete(m);
41 }
42
43 void Gamepad::capture()
44 {
45 if(m->fileHandle == -1)
46 return;
47
48 struct AxisState
49 {
50 bool moved;
51 INT32 value;
52 };
53
54 AxisState axisState[24];
55 bs_zero_out(axisState);
56
57 input_event events[BUFFER_SIZE_GAMEPAD];
58 while(true)
59 {
60 ssize_t numReadBytes = read(m->fileHandle, &events, sizeof(events));
61 if(numReadBytes < 0)
62 break;
63
64 if(!m->hasInputFocus)
65 continue;
66
67 UINT32 numEvents = numReadBytes / sizeof(input_event);
68 for(UINT32 i = 0; i < numEvents; ++i)
69 {
70 switch(events[i].type)
71 {
72 case EV_KEY:
73 {
74 auto findIter = m->info.buttonMap.find(events[i].code);
75 if(findIter == m->info.buttonMap.end())
76 continue;
77
78 if(events[i].value)
79 mOwner->_notifyButtonPressed(m->info.id, findIter->second, (UINT64)events[i].time.tv_usec);
80 else
81 mOwner->_notifyButtonReleased(m->info.id, findIter->second, (UINT64)events[i].time.tv_usec);
82 }
83 break;
84 case EV_ABS:
85 {
86 // Stick or trigger
87 if(events[i].code <= ABS_BRAKE)
88 {
89 const AxisInfo& axisInfo = m->info.axisMap[events[i].code];
90
91 if(axisInfo.axisIdx >= 24)
92 break;
93
94 axisState[axisInfo.axisIdx].moved = true;
95
96 // Scale range if needed
97 if(axisInfo.min == Gamepad::MIN_AXIS && axisInfo.max != Gamepad::MAX_AXIS )
98 axisState[axisInfo.axisIdx].value = events[i].value;
99 else
100 {
101 float range = (float)(axisInfo.max - axisInfo.min);
102 float normalizedValue = (axisInfo.max - events[i].value) / range;
103
104 range = (float)(Gamepad::MAX_AXIS - Gamepad::MIN_AXIS);
105 axisState[axisInfo.axisIdx].value = Gamepad::MIN_AXIS + (INT32)(normalizedValue * range);
106 }
107 }
108 else if(events[i].code <= ABS_HAT3Y) // POV
109 {
110 // Note: We only support a single POV and report events from all POVs as if they were from the
111 // same source
112 INT32 povIdx = events[i].code - ABS_HAT0X;
113
114 ButtonCode povButton = BC_UNASSIGNED;
115 if((povIdx & 0x1) == 0) // Even, x axis
116 {
117 if(events[i].value == -1)
118 povButton = BC_GAMEPAD_DPAD_LEFT;
119 else if(events[i].value == 1)
120 povButton = BC_GAMEPAD_DPAD_RIGHT;
121 }
122 else // Odd, y axis
123 {
124 if(events[i].value == -1)
125 povButton = BC_GAMEPAD_DPAD_UP;
126 else if(events[i].value == 1)
127 povButton = BC_GAMEPAD_DPAD_DOWN;
128 }
129
130 if(m->povState != povButton)
131 {
132 if(m->povState != BC_UNASSIGNED)
133 mOwner->_notifyButtonReleased(m->info.id, m->povState, (UINT64)events[i].time.tv_usec);
134
135 if(povButton != BC_UNASSIGNED)
136 mOwner->_notifyButtonPressed(m->info.id, povButton, (UINT64)events[i].time.tv_usec);
137
138
139 m->povState = povButton;
140 }
141 }
142 break;
143 }
144 default: break;
145 }
146 }
147 }
148
149 for(UINT32 i = 0; i < 24; i++)
150 {
151 if(axisState[i].moved)
152 mOwner->_notifyAxisMoved(m->info.id, i, axisState[i].value);
153 }
154 }
155
156 void Gamepad::changeCaptureContext(UINT64 windowHandle)
157 {
158 m->hasInputFocus = windowHandle != (UINT64)-1;
159 }
160}
161
162