Support for configuring device to conrtoller id

This change adds support for configuring device to conrtoller id. This
allows for stable controller ids even if USB devices are plugged /
unplugged, system is rebooted, etc.

See documentation for additional context.
This commit is contained in:
Tomer Verona 2016-09-12 18:35:36 -07:00
parent 597626d059
commit 2bd18d5fea
6 changed files with 222 additions and 3 deletions

View File

@ -0,0 +1,81 @@
Stable Controller IDs
===============================
By default, the mapping between devices and controller IDs is not stable. For instance, a gamepad controller may be assigned to "Joy 1" initially, but after a reboot, it may get re-assigned to "Joy 3".
The reason is that MAME enumerates attached devices and assigns controller IDs based on the enumeration order. Factors that can cause controller IDs to change include plugging / unplugging USB devices, changing ports / hubs and even system reboots.
It is quite cumbersome to ensure that controller IDs are always correct.
That's where the "mapdevice" configuration setting comes into the picture. This setting allows you to map a device name to a controller ID, ensuring that the specified device always maps to the same controller ID in MAME.
Usage of mapdevice
------------------
The "mapdevice" xml element is specified under the input xml element in the configuration. It requires two attributes, "device" and "controller".
The "device" attribute specifies the name of the device to match. It may also be a substring of the name. To see the list of available devices, enable verbose output and available devices will then be listed to the console at startup (more on this below).
The "controller" attribute specifies the MAME controller ID. It is made up of a controller class (i.e. "JOYCODE", "GUNCODE", "MOUSECODE") and controller index. For example: "JOYCODE_1".
Example
-------
Here's an example:
| <mameconfig version="10">
| <system name="default">
| <input>
| **<mapdevice device="VID_D209&amp;PID_1601" controller="GUNCODE_1" />**
| **<mapdevice device="VID_D209&amp;PID_1602" controller="GUNCODE_2" />**
| **<mapdevice device="XInput Player 1" controller="JOYCODE_1" />**
| **<mapdevice device="XInput Player 2" controller="JOYCODE_2" />**
|
| <port type="P1_JOYSTICK_UP">
| <newseq type="standard">
| JOYCODE_1_YAXIS_UP_SWITCH OR KEYCODE_8PAD
| </newseq>
| </port>
| ...
|
In the above example, we have four device mappings specified:
The first two mapdevice entries map player 1 and 2 lightguns to Gun 1 and Gun 2, respectively. We use a substring of the full raw device names to match each devices. Note that, since this is XML, we needed to escape the '&' in the name using '&amp;'.
The last two mapdevices entries map player 1 and player 2 gamepad controllers to Joy 1 and Joy 2, respectively. In this case, these are XInput devices.
Listing Available Devices
-------------------------
How did we obtain the device names in the above example? Easy!
We simply set verbose to 1 in mame.ini:
| #
| # CORE DEBUGGING OPTIONS
| #
| **verbose 1**
|
Then, when MAME is started, it will list available devices to the console. For example:
| Input: Adding Gun #0:
| Input: Adding Gun #1:
| Input: Adding Gun #2: HID-compliant mouse (\\?\HID#VID_045E&PID_0053#7&18297dcb&0&0000#{378de44c-56ef-11d1-bc8c-00a0c91405dd})
| Input: Adding Gun #3: HID-compliant mouse (\\?\HID#IrDeviceV2&Col08#2&2818a073&0&0007#{378de44c-56ef-11d1-bc8c-00a0c91405dd})
| Input: Adding Gun #4: HID-compliant mouse (\\?\HID#VID_D209&PID_1602&MI_02#8&389ab7f3&0&0000#{378de44c-56ef-11d1-bc8c-00a0c91405dd})
| Input: Adding Gun #5: HID-compliant mouse (\\?\HID#VID_D209&PID_1601&MI_02#9&375eebb1&0&0000#{378de44c-56ef-11d1-bc8c-00a0c91405dd})
| Input: Adding Gun #6: HID-compliant mouse (\\?\HID#VID_1241&PID_1111#8&198f3adc&0&0000#{378de44c-56ef-11d1-bc8c-00a0c91405dd})
| Skipping DirectInput for XInput compatible joystick Controller (XBOX 360 For Windows).
| Input: Adding Joy #0: ATRAK Device #1
| Skipping DirectInput for XInput compatible joystick Controller (XBOX 360 For Windows).
| Input: Adding Joy #1: ATRAK Device #2
| Input: Adding Joy #2: XInput Player 1
| Input: Adding Joy #3: XInput Player 2
|
Furthermore, when devices are mapped using mapdevice, you'll see that in the verbose logging too, such as:
| Mapped device 'HID-compliant mouse (\\?\HID#VID_D209&PID_1601&MI_02#9&375eebb1&0&0000#{378de44c-56ef-11d1-bc8c-00a0c91405dd})' to Gun #0
| Mapped device 'HID-compliant mouse (\\?\HID#VID_D209&PID_1602&MI_02#8&389ab7f3&0&0000#{378de44c-56ef-11d1-bc8c-00a0c91405dd})' to Gun #1
| Mapped device 'XInput Player 1' to Joy #0
| Mapped device 'XInput Player 2' to Joy #1
|

View File

@ -9,4 +9,5 @@ Advanced configuration
shiftertoggle
bgfx
hlsl
glsl
glsl
devicemap

View File

@ -941,6 +941,21 @@ void input_device::apply_steadykey() const
}
}
//-------------------------------------------------
// match_device_name - match device name via
// substring search
//-------------------------------------------------
bool input_device::match_device_name(const char *devicename)
{
std::string devicenameupper(devicename);
std::string nameupper(m_name);
strmakeupper(devicenameupper);
strmakeupper(nameupper);
return std::string::npos == nameupper.find(devicenameupper) ? false : true;
}
//**************************************************************************
@ -1019,6 +1034,29 @@ input_item_class input_class::standard_item_class(input_item_id itemid)
}
//-------------------------------------------------
// remap_device_index - remaps device index by
// mapping oldindex to newindex
//-------------------------------------------------
void input_class::remap_device_index(int oldindex, int newindex)
{
assert(oldindex >= 0 && oldindex < DEVICE_INDEX_MAXIMUM);
assert(newindex >= 0 && newindex < DEVICE_INDEX_MAXIMUM);
// swap indexes in m_device array
m_device[oldindex].swap(m_device[newindex]);
// update device indexes
m_device[oldindex]->set_devindex(oldindex);
m_device[newindex]->set_devindex(newindex);
// update the maximum index found, since newindex may
// exceed current m_maxindex
m_maxindex = std::max(m_maxindex, newindex);
}
//-------------------------------------------------
// frame_callback - per-frame callback for various
// bookkeeping
@ -2058,6 +2096,71 @@ void input_manager::seq_from_tokens(input_seq &seq, const char *string)
}
}
//-------------------------------------------------
// map_device_to_controller - map device to
// controller based on device map table
//-------------------------------------------------
bool input_manager::map_device_to_controller(const devicemap_table_type *devicemap_table)
{
if (nullptr == devicemap_table)
return true;
for (devicemap_table_type::const_iterator it = devicemap_table->begin(); it != devicemap_table->end(); it++)
{
const char *devicename = it->first.c_str();
const char *controllername = it->second.c_str();
// tokenize the controller name into device class and index (i.e. controller name should be of the form "GUNCODE_1")
std::string token[2];
int numtokens;
const char *_token = controllername;
for (numtokens = 0; numtokens < ARRAY_LENGTH(token); )
{
// make a token up to the next underscore
char *score = (char *)strchr(_token, '_');
token[numtokens++].assign(_token, (score == nullptr) ? strlen(_token) : (score - _token));
// if we hit the end, we're done, else advance our pointer
if (score == nullptr)
break;
_token = score + 1;
}
if (2 != numtokens)
return false;
// first token should be the devclass
input_device_class devclass = input_device_class((*devclass_token_table)[strmakeupper(token[0]).c_str()]);
if (devclass == ~input_device_class(0))
return false;
// second token should be the devindex
int devindex = 0;
if (1 != sscanf(token[1].c_str(), "%d", &devindex))
return false;
devindex--;
if (devindex >= DEVICE_INDEX_MAXIMUM)
return false;
// enumerate through devices and look for a match
input_class *input_devclass = m_class[devclass];
for (int devnum = 0; devnum <= input_devclass->maxindex(); devnum++)
{
input_device *device = input_devclass->device(devnum);
if (device != nullptr && device->match_device_name(devicename))
{
// remap devindex
input_devclass->remap_device_index(device->devindex(), devindex);
osd_printf_info("Mapped device '%s' to %s #%d\n", device->name(), (*devclass_string_table)[input_devclass->devclass()], devindex);
break;
}
}
}
return true;
}
//-------------------------------------------------
// set_global_joystick_map - set the joystick map

View File

@ -352,6 +352,8 @@ class input_manager;
// callback for getting the value of an item on a device
typedef INT32 (*item_get_state_func)(void *device_internal, void *item_internal);
// controller alias table typedef
typedef std::map<std::string, std::string> devicemap_table_type;
// ======================> joystick_map
@ -562,6 +564,9 @@ public:
bool steadykey_enabled() const { return m_steadykey_enabled; }
bool lightgun_reload_button() const { return m_lightgun_reload_button; }
// setters
void set_devindex(int devindex) { m_devindex = devindex; }
// item management
input_item_id add_item(const char *name, input_item_id itemid, item_get_state_func getstate, void *internal = nullptr);
void set_joystick_map(const joystick_map &map) { m_joymap = map; }
@ -569,6 +574,7 @@ public:
// helpers
INT32 apply_deadzone_and_saturation(INT32 value) const;
void apply_steadykey() const;
bool match_device_name(const char * devicename);
private:
// internal state
@ -616,6 +622,7 @@ public:
// misc helpers
input_item_class standard_item_class(input_item_id itemid);
void remap_device_index(int oldindex, int newindex);
private:
// internal helpers
@ -679,6 +686,7 @@ public:
// misc
bool set_global_joystick_map(const char *mapstring);
bool map_device_to_controller(const devicemap_table_type *devicemap_table = nullptr);
private:
// internal helpers

View File

@ -2904,6 +2904,24 @@ void ioport_manager::load_config(config_type cfg_type, xml_data_node *parentnode
if (cfg_type == config_type::CONFIG_TYPE_CONTROLLER)
load_remap_table(parentnode);
// load device map table, if any
std::unique_ptr<devicemap_table_type> devicemap_table = std::make_unique<devicemap_table_type>();
for (xml_data_node *mapdevice_node = xml_get_sibling(parentnode->child, "mapdevice"); mapdevice_node != nullptr; mapdevice_node = xml_get_sibling(mapdevice_node->next, "mapdevice"))
{
const char *devicename = xml_get_attribute_string(mapdevice_node, "device", nullptr);
const char *controllername = xml_get_attribute_string(mapdevice_node, "controller", nullptr);
if (devicename != nullptr && controllername != nullptr)
{
devicemap_table->insert(std::make_pair(std::string(devicename), std::string(controllername)));
}
}
// map device to controller if we have a device map
if (!devicemap_table->empty())
{
machine().input().map_device_to_controller(devicemap_table.get());
}
// iterate over all the port nodes
for (xml_data_node *portnode = xml_get_sibling(parentnode->child, "port"); portnode; portnode = xml_get_sibling(portnode->next, "port"))
{

View File

@ -572,8 +572,16 @@ protected:
// improve the name and then allocate a device
std::wstring name = rawinput_device_improve_name(tname.get());
// convert name to utf8
std::string utf8_name = utf8_from_wstring(name.c_str());
// convert name to utf8. Preserve raw name as well (if different than improved name) to allow mapping of device to controller.
std::string utf8_name;
if (0 == name.compare(tname.get()))
{
utf8_name = utf8_from_wstring(name.c_str());
}
else
{
utf8_name = util::string_format("%s (%s)", utf8_from_wstring(name.c_str()), utf8_from_wstring(tname.get()));
}
devinfo = devicelist()->create_device<TDevice>(machine, utf8_name.c_str(), *this);