InputManager.cpp 10.3 KB
Newer Older
1
#include "InputManager.h"
2 3
#include "InputConfig.h"
#include "Window.h"
Aloshi's avatar
Aloshi committed
4 5 6
#include "Log.h"
#include "pugiXML/pugixml.hpp"
#include <boost/filesystem.hpp>
Alec Lofquist's avatar
Alec Lofquist committed
7
#include "platform.h"
Aloshi's avatar
Aloshi committed
8

9 10 11 12 13 14 15 16 17 18 19 20 21
#define KEYBOARD_GUID_STRING "-1"

// SO HEY POTENTIAL POOR SAP WHO IS TRYING TO MAKE SENSE OF ALL THIS (by which I mean my future self)
// There are like four distinct IDs used for joysticks (crazy, right?)
// 1. Device index - this is the "lowest level" identifier, and is just the Nth joystick plugged in to the system (like /dev/js#).
//    It can change even if the device is the same, and is only used to open joysticks (required to receive SDL events).
// 2. SDL_JoystickID - this is an ID for each joystick that is supposed to remain consistent between plugging and unplugging.
//    ES doesn't care if it does, though.
// 3. "Device ID" - this is something I made up and is what InputConfig's getDeviceID() returns.  
//    This is actually just an SDL_JoystickID (also called instance ID), but -1 means "keyboard" instead of "error."
// 4. Joystick GUID - this is some squashed version of joystick vendor, version, and a bunch of other device-specific things.
//    It should remain the same across runs of the program/system restarts/device reordering and is what I use to identify which joystick to load.

Aloshi's avatar
Aloshi committed
22
namespace fs = boost::filesystem;
Aloshi's avatar
Aloshi committed
23

Bim Overbohm's avatar
Bim Overbohm committed
24
InputManager::InputManager(Window* window) : mWindow(window), 
25
	mKeyboardInputConfig(NULL)
26 27 28
{
}

29
InputManager::~InputManager()
30
{
31
	deinit();
32 33
}

34
void InputManager::init()
35
{
36
	if(initialized())
37
		deinit();
38

39
	SDL_InitSubSystem(SDL_INIT_JOYSTICK);
40 41 42 43 44 45 46 47
	SDL_JoystickEventState(SDL_ENABLE);

	// first, open all currently present joysticks
	int numJoysticks = SDL_NumJoysticks();
	for(int i = 0; i < numJoysticks; i++)
	{
		addJoystickByDeviceIndex(i);
	}
48

49 50 51
	mKeyboardInputConfig = new InputConfig(DEVICE_KEYBOARD, "Keyboard", KEYBOARD_GUID_STRING);
	loadInputConfig(mKeyboardInputConfig);
}
52

53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
void InputManager::addJoystickByDeviceIndex(int id)
{
	assert(id >= 0 && id < SDL_NumJoysticks());
	
	// open joystick & add to our list
	SDL_Joystick* joy = SDL_JoystickOpen(id);
	assert(joy);

	// add it to our list so we can close it again later
	SDL_JoystickID joyId = SDL_JoystickInstanceID(joy);
	mJoysticks[joyId] = joy;

	char guid[65];
	SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(joy), guid, 65);

	// create the InputConfig
	mInputConfigs[joyId] = new InputConfig(joyId, SDL_JoystickName(joy), guid);
	if(!loadInputConfig(mInputConfigs[joyId]))
71
	{
72 73 74
		LOG(LogInfo) << "Added unconfigured joystick " << SDL_JoystickName(joy) << " (GUID: " << guid << ", instance ID: " << joyId << ", device index: " << id << ").";
	}else{
		LOG(LogInfo) << "Added known joystick " << SDL_JoystickName(joy) << " (instance ID: " << joyId << ", device index: " << id << ")";
75
	}
76

77 78 79 80 81 82 83 84 85
	// set up the prevAxisValues
	int numAxes = SDL_JoystickNumAxes(joy);
	mPrevAxisValues[joyId] = new int[numAxes];
	std::fill(mPrevAxisValues[joyId], mPrevAxisValues[joyId] + numAxes, 0); //initialize array to 0
}

void InputManager::removeJoystickByJoystickID(SDL_JoystickID joyId)
{
	assert(joyId != -1);
86

87 88 89 90
	// delete old prevAxisValues
	auto axisIt = mPrevAxisValues.find(joyId);
	delete[] axisIt->second;
	mPrevAxisValues.erase(axisIt);
91

92 93 94 95 96 97 98 99 100 101 102 103 104 105
	// delete old InputConfig
	auto it = mInputConfigs.find(joyId);
	delete it->second;
	mInputConfigs.erase(it);

	// close the joystick
	auto joyIt = mJoysticks.find(joyId);
	if(joyIt != mJoysticks.end())
	{
		SDL_JoystickClose(joyIt->second);
		mJoysticks.erase(joyIt);
	}else{
		LOG(LogError) << "Could not find joystick to close (instance ID: " << joyId << ")";
	}
106 107 108 109
}

void InputManager::deinit()
{
110
	if(!initialized())
111
		return;
112

113
	for(auto iter = mJoysticks.begin(); iter != mJoysticks.end(); iter++)
114
	{
115
		SDL_JoystickClose(iter->second);
116 117
	}
	mJoysticks.clear();
118

119 120 121 122 123
	for(auto iter = mInputConfigs.begin(); iter != mInputConfigs.end(); iter++)
	{
		delete iter->second;
	}
	mInputConfigs.clear();
Bim Overbohm's avatar
Bim Overbohm committed
124

125 126 127
	for(auto iter = mPrevAxisValues.begin(); iter != mPrevAxisValues.end(); iter++)
	{
		delete[] iter->second;
128
	}
129
	mPrevAxisValues.clear();
130

131 132 133 134 135 136
	if(mKeyboardInputConfig != NULL)
	{
		delete mKeyboardInputConfig;
		mKeyboardInputConfig = NULL;
	}

137
	SDL_JoystickEventState(SDL_DISABLE);
138
	SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
139
}
140

141 142
int InputManager::getNumJoysticks() { return mJoysticks.size(); }
int InputManager::getButtonCountByDevice(SDL_JoystickID id)
143 144 145 146 147 148
{
	if(id == DEVICE_KEYBOARD)
		return 120; //it's a lot, okay.
	else
		return SDL_JoystickNumButtons(mJoysticks[id]);
}
149

150
InputConfig* InputManager::getInputConfigByDevice(int device)
151 152 153 154 155 156
{
	if(device == DEVICE_KEYBOARD)
		return mKeyboardInputConfig;
	else
		return mInputConfigs[device];
}
157

Aloshi's avatar
Aloshi committed
158
bool InputManager::parseEvent(const SDL_Event& ev)
159
{
Aloshi's avatar
Aloshi committed
160
	bool causedEvent = false;
161
	switch(ev.type)
162
	{
163 164 165
	case SDL_JOYAXISMOTION:
		//if it switched boundaries
		if((abs(ev.jaxis.value) > DEADZONE) != (abs(mPrevAxisValues[ev.jaxis.which][ev.jaxis.axis]) > DEADZONE))
166
		{
167 168 169 170 171 172 173 174 175 176
			int normValue;
			if(abs(ev.jaxis.value) <= DEADZONE)
				normValue = 0;
			else
				if(ev.jaxis.value > 0)
					normValue = 1;
				else
					normValue = -1;

			mWindow->input(getInputConfigByDevice(ev.jaxis.which), Input(ev.jaxis.which, TYPE_AXIS, ev.jaxis.axis, normValue, false));
Aloshi's avatar
Aloshi committed
177
			causedEvent = true;
178
		}
Aloshi's avatar
Aloshi committed
179

180
		mPrevAxisValues[ev.jaxis.which][ev.jaxis.axis] = ev.jaxis.value;
Aloshi's avatar
Aloshi committed
181
		return causedEvent;
Aloshi's avatar
Aloshi committed
182

183 184 185
	case SDL_JOYBUTTONDOWN:
	case SDL_JOYBUTTONUP:
		mWindow->input(getInputConfigByDevice(ev.jbutton.which), Input(ev.jbutton.which, TYPE_BUTTON, ev.jbutton.button, ev.jbutton.state == SDL_PRESSED, false));
Aloshi's avatar
Aloshi committed
186
		return true;
Aloshi's avatar
Aloshi committed
187

188 189
	case SDL_JOYHATMOTION:
		mWindow->input(getInputConfigByDevice(ev.jhat.which), Input(ev.jhat.which, TYPE_HAT, ev.jhat.hat, ev.jhat.value, false));
Aloshi's avatar
Aloshi committed
190
		return true;
Aloshi's avatar
Aloshi committed
191

192
	case SDL_KEYDOWN:
Aloshi's avatar
Aloshi committed
193 194 195 196 197 198 199 200
		if(ev.key.keysym.sym == SDLK_BACKSPACE && SDL_IsTextInputActive())
		{
			if(mWindow->peekGui() != NULL)
				mWindow->peekGui()->textInput("\b");

			return true;
		}

201
		if(ev.key.repeat)
Aloshi's avatar
Aloshi committed
202 203
			return false;

204 205 206 207 208
		if(ev.key.keysym.sym == SDLK_F4)
		{
			SDL_Event* quit = new SDL_Event();
			quit->type = SDL_QUIT;
			SDL_PushEvent(quit);
Aloshi's avatar
Aloshi committed
209
			return false;
210
		}
Aloshi's avatar
Aloshi committed
211

212
		mWindow->input(getInputConfigByDevice(DEVICE_KEYBOARD), Input(DEVICE_KEYBOARD, TYPE_KEY, ev.key.keysym.sym, 1, false));
Aloshi's avatar
Aloshi committed
213
		return true;
214

215 216
	case SDL_KEYUP:
		mWindow->input(getInputConfigByDevice(DEVICE_KEYBOARD), Input(DEVICE_KEYBOARD, TYPE_KEY, ev.key.keysym.sym, 0, false));
Aloshi's avatar
Aloshi committed
217
		return true;
218

Aloshi's avatar
Aloshi committed
219 220 221 222 223
	case SDL_TEXTINPUT:
		if(mWindow->peekGui() != NULL)
			mWindow->peekGui()->textInput(ev.text.text);
		break;

224
	case SDL_JOYDEVICEADDED:
225
		addJoystickByDeviceIndex(ev.jdevice.which); // ev.jdevice.which is a device index
226
		return true;
227 228 229 230

	case SDL_JOYDEVICEREMOVED:
		removeJoystickByJoystickID(ev.jdevice.which); // ev.jdevice.which is an SDL_JoystickID (instance ID)
		return false;
231
	}
Aloshi's avatar
Aloshi committed
232 233 234 235

	return false;
}

236
bool InputManager::loadInputConfig(InputConfig* config)
Aloshi's avatar
Aloshi committed
237
{
Aloshi's avatar
Aloshi committed
238 239
	std::string path = getConfigPath();
	if(!fs::exists(path))
240 241
		return false;
	
Aloshi's avatar
Aloshi committed
242 243 244 245 246
	pugi::xml_document doc;
	pugi::xml_parse_result res = doc.load_file(path.c_str());

	if(!res)
	{
247 248
		LOG(LogError) << "Error parsing input config: " << res.description();
		return false;
249 250
	}

Aloshi's avatar
Aloshi committed
251
	pugi::xml_node root = doc.child("inputList");
252 253
	if(!root)
		return false;
Aloshi's avatar
Aloshi committed
254

255 256 257
	pugi::xml_node configNode = root.find_child_by_attribute("inputConfig", "deviceGUID", config->getDeviceGUIDString().c_str());
	if(!configNode)
		configNode = root.find_child_by_attribute("inputConfig", "deviceName", config->getDeviceName().c_str());
258 259
	if(!configNode)
		return false;
Aloshi's avatar
Aloshi committed
260

261 262
	config->loadFromXML(configNode);
	return true;
263 264
}

265
//used in an "emergency" where no keyboard config could be loaded from the inputmanager config file
266
//allows the user to select to reconfigure in menus if this happens without having to delete es_input.cfg manually
267
void InputManager::loadDefaultKBConfig()
268 269 270
{
	InputConfig* cfg = getInputConfigByDevice(DEVICE_KEYBOARD);

271
	cfg->clear();
272 273 274 275 276 277 278
	cfg->mapInput("up", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_UP, 1, true));
	cfg->mapInput("down", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_DOWN, 1, true));
	cfg->mapInput("left", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_LEFT, 1, true));
	cfg->mapInput("right", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_RIGHT, 1, true));

	cfg->mapInput("a", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_RETURN, 1, true));
	cfg->mapInput("b", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_ESCAPE, 1, true));
279
	cfg->mapInput("start", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_F1, 1, true));
280
	cfg->mapInput("select", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_F2, 1, true));
281

282 283
	cfg->mapInput("pageup", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_RIGHTBRACKET, 1, true));
	cfg->mapInput("pagedown", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_LEFTBRACKET, 1, true));
Aloshi's avatar
Aloshi committed
284 285
}

286
void InputManager::writeDeviceConfig(InputConfig* config)
Aloshi's avatar
Aloshi committed
287
{
288
	assert(initialized());
Aloshi's avatar
Aloshi committed
289 290 291 292 293

	std::string path = getConfigPath();

	pugi::xml_document doc;

294
	if(fs::exists(path))
Aloshi's avatar
Aloshi committed
295
	{
296 297 298 299 300 301 302 303 304 305 306 307 308
		// merge files
		pugi::xml_parse_result result = doc.load_file(path.c_str());
		if(!result)
		{
			LOG(LogError) << "Error parsing input config: " << result.description();
		}else{
			// successfully loaded, delete the old entry if it exists
			pugi::xml_node root = doc.child("inputList");
			if(root)
			{
				pugi::xml_node oldEntry = root.find_child_by_attribute("inputConfig", "deviceGUID", config->getDeviceGUIDString().c_str());
				if(oldEntry)
					root.remove_child(oldEntry);
309 310 311
				oldEntry = root.find_child_by_attribute("inputConfig", "deviceName", config->getDeviceName().c_str());
				if(oldEntry)
					root.remove_child(oldEntry);
312 313
			}
		}
Aloshi's avatar
Aloshi committed
314 315
	}

316 317 318 319 320
	pugi::xml_node root = doc.child("inputList");
	if(!root)
		root = doc.append_child("inputList");

	config->writeToXML(root);
Aloshi's avatar
Aloshi committed
321
	doc.save_file(path.c_str());
322
}
Aloshi's avatar
Aloshi committed
323 324 325

std::string InputManager::getConfigPath()
{
Alec Lofquist's avatar
Alec Lofquist committed
326
	std::string path = getHomePath();
Aloshi's avatar
Aloshi committed
327 328 329
	path += "/.emulationstation/es_input.cfg";
	return path;
}
330 331 332 333 334

bool InputManager::initialized() const
{
	return mKeyboardInputConfig != NULL;
}
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366

int InputManager::getNumConfiguredDevices()
{
	int num = 0;
	for(auto it = mInputConfigs.begin(); it != mInputConfigs.end(); it++)
	{
		if(it->second->isConfigured())
			num++;
	}

	if(mKeyboardInputConfig->isConfigured())
		num++;

	return num;
}

std::string InputManager::getDeviceGUIDString(int deviceId)
{
	if(deviceId == DEVICE_KEYBOARD)
		return KEYBOARD_GUID_STRING;

	auto it = mJoysticks.find(deviceId);
	if(it == mJoysticks.end())
	{
		LOG(LogError) << "getDeviceGUIDString - deviceId " << deviceId << " not found!";
		return "something went horribly wrong";
	}

	char guid[65];
	SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(it->second), guid, 65);
	return std::string(guid);
}