diff options
Diffstat (limited to 'trunk/src/emu/ioport.c')
-rw-r--r-- | trunk/src/emu/ioport.c | 4892 |
1 files changed, 4892 insertions, 0 deletions
diff --git a/trunk/src/emu/ioport.c b/trunk/src/emu/ioport.c new file mode 100644 index 00000000000..7b00ad2cf75 --- /dev/null +++ b/trunk/src/emu/ioport.c @@ -0,0 +1,4892 @@ +/*************************************************************************** + + ioport.c + + Input/output port handling. + + Copyright Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +**************************************************************************** + + Theory of operation + + ------------ + OSD controls + ------------ + + There are three types of controls that the OSD can provide as potential + input devices: digital controls, absolute analog controls, and relative + analog controls. + + Digital controls have only two states: on or off. They are generally + mapped to buttons and digital joystick directions (like a gamepad or a + joystick hat). The OSD layer must return either 0 (off) or 1 (on) for + these types of controls. + + Absolute analog controls are analog in the sense that they return a + range of values depending on how much a given control is moved, but they + are physically bounded. This means that there is a minimum and maximum + limit to how far the control can be moved. They are generally mapped to + analog joystick axes, lightguns, most PC steering wheels, and pedals. + The OSD layer must determine the minimum and maximum range of each + analog device and scale that to a value between -65536 and +65536 + representing the position of the control. -65536 generally refers to + the topmost or leftmost position, while +65536 refers to the bottommost + or rightmost position. Note that pedals are a special case here, the + OSD layer needs to return half axis as full -65536 to + 65536 range. + + Relative analog controls are analog as well, but are not physically + bounded. They can be moved continually in one direction without limit. + They are generally mapped to trackballs and mice. Because they are + unbounded, the OSD layer can only return delta values since the last + read. Because of this, it is difficult to scale appropriately. For + MAME's purposes, when mapping a mouse devices to a relative analog + control, one pixel of movement should correspond to 512 units. Other + analog control types should be scaled to return values of a similar + magnitude. Like absolute analog controls, negative values refer to + upward or leftward movement, while positive values refer to downward + or rightward movement. + + ------------- + Game controls + ------------- + + Similarly, the types of controls used by arcade games fall into the same + three categories: digital, absolute analog, and relative analog. The + tricky part is how to map any arbitrary type of OSD control to an + arbitrary type of game control. + + Digital controls: used for game buttons and standard 4/8-way joysticks, + as well as many other types of game controls. Mapping an OSD digital + control to a game's OSD control is trivial. For OSD analog controls, + the MAME core does not directly support mapping any OSD analog devices + to digital controls. However, the OSD layer is free to enumerate digital + equivalents for analog devices. For example, each analog axis in the + Windows OSD code enumerates to two digital controls, one for the + negative direction (up/left) and one for the position direction + (down/right). When these "digital" inputs are queried, the OSD layer + checks the axis position against the center, adding in a dead zone, + and returns 0 or 1 to indicate its position. + + Absolute analog controls: used for analog joysticks, lightguns, pedals, + and wheel controls. Mapping an OSD absolute analog control to this type + is easy. OSD relative analog controls can be mapped here as well by + accumulating the deltas and bounding the results. OSD digital controls + are mapped to these types of controls in pairs, one for a decrement and + one for an increment, but apart from that, operate the same as the OSD + relative analog controls by accumulating deltas and applying bounds. + The speed of the digital delta is user-configurable per analog input. + In addition, most absolute analog control types have an autocentering + feature that is activated when using the digital increment/decrement + sequences, which returns the control back to the center at a user- + controllable speed if no digital sequences are pressed. + + Relative analog controls: used for trackballs and dial controls. Again, + mapping an OSD relative analog control to this type is straightforward. + OSD absolute analog controls can't map directly to these, but if the OSD + layer provides a digital equivalent for each direction, it can be done. + OSD digital controls map just like they do for absolute analog controls, + except that the accumulated deltas are not bounded, but rather wrap. + +***************************************************************************/ + +#include "emu.h" +#include "emuopts.h" +#include "config.h" +#include "xmlfile.h" +#include "profiler.h" +#include "ui.h" +#include "uiinput.h" +#include "debug/debugcon.h" + +#include <ctype.h> +#include <time.h> + +/* temporary: set this to 1 to enable the originally defined behavior that + a field specified via PORT_MODIFY which intersects a previously-defined + field completely wipes out the previous definition */ +#define INPUT_PORT_OVERRIDE_FULLY_NUKES_PREVIOUS 1 + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +#define DIGITAL_JOYSTICKS_PER_PLAYER 3 + +/* these constants must match the order of the joystick directions in the IPT definition */ +#define JOYDIR_UP 0 +#define JOYDIR_DOWN 1 +#define JOYDIR_LEFT 2 +#define JOYDIR_RIGHT 3 + +#define JOYDIR_UP_BIT (1 << JOYDIR_UP) +#define JOYDIR_DOWN_BIT (1 << JOYDIR_DOWN) +#define JOYDIR_LEFT_BIT (1 << JOYDIR_LEFT) +#define JOYDIR_RIGHT_BIT (1 << JOYDIR_RIGHT) + +#define NUM_SIMUL_KEYS (UCHAR_SHIFT_END - UCHAR_SHIFT_BEGIN + 1) +#define LOG_INPUTX 0 +#define SPACE_COUNT 3 +#define INVALID_CHAR '?' +#define IP_NAME_DEFAULT NULL + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +/* live analog field information */ +typedef struct _analog_field_state analog_field_state; +struct _analog_field_state +{ + analog_field_state * next; /* link to the next analog state for this port */ + const input_field_config * field; /* pointer to the input field referenced */ + + /* adjusted values (right-justified and tweaked) */ + UINT8 shift; /* shift to align final value in the port */ + INT32 adjdefvalue; /* adjusted default value from the config */ + INT32 adjmin; /* adjusted minimum value from the config */ + INT32 adjmax; /* adjusted maximum value from the config */ + + /* live values of configurable parameters */ + INT32 sensitivity; /* current live sensitivity (100=normal) */ + UINT8 reverse; /* current live reverse flag */ + INT32 delta; /* current live delta to apply each frame a digital inc/dec key is pressed */ + INT32 centerdelta; /* current live delta to apply each frame no digital inputs are pressed */ + + /* live analog value tracking */ + INT32 accum; /* accumulated value (including relative adjustments) */ + INT32 previous; /* previous adjusted value */ + INT32 previousanalog; /* previous analog value */ + + /* parameters for modifying live values */ + INT32 minimum; /* minimum adjusted value */ + INT32 maximum; /* maximum adjusted value */ + INT32 center; /* center adjusted value for autocentering */ + INT32 reverse_val; /* value where we subtract from to reverse directions */ + + /* scaling factors */ + INT64 scalepos; /* scale factor to apply to positive adjusted values */ + INT64 scaleneg; /* scale factor to apply to negative adjusted values */ + INT64 keyscalepos; /* scale factor to apply to the key delta field when pos */ + INT64 keyscaleneg; /* scale factor to apply to the key delta field when neg */ + INT64 positionalscale; /* scale factor to divide a joystick into positions */ + + /* misc flags */ + UINT8 absolute; /* is this an absolute or relative input? */ + UINT8 wraps; /* does the control wrap around? */ + UINT8 autocenter; /* autocenter this input? */ + UINT8 single_scale; /* scale joystick differently if default is between min/max */ + UINT8 interpolate; /* should we do linear interpolation for mid-frame reads? */ + UINT8 lastdigital; /* was the last modification caused by a digital form? */ +}; + + +/* shared digital joystick state */ +typedef struct _digital_joystick_state digital_joystick_state; +struct _digital_joystick_state +{ + const input_field_config * field[4]; /* input field for up, down, left, right respectively */ + UINT8 inuse; /* is this joystick used? */ + UINT8 current; /* current value */ + UINT8 current4way; /* current 4-way value */ + UINT8 previous; /* previous value */ +}; + + +/* live device field information */ +typedef struct _device_field_info device_field_info; +struct _device_field_info +{ + device_field_info * next; /* linked list of info for this port */ + const input_field_config * field; /* pointer to the input field referenced */ + device_t * device; /* device */ + UINT8 shift; /* shift to apply to the final result */ + input_port_value oldval; /* last value */ +}; + + +/* internal live state of an input field */ +struct _input_field_state +{ + analog_field_state * analog; /* pointer to live analog data if this is an analog field */ + digital_joystick_state * joystick; /* pointer to digital joystick information */ + input_seq seq[SEQ_TYPE_TOTAL];/* currently configured input sequences */ + input_port_value value; /* current value of this port */ + UINT8 impulse; /* counter for impulse controls */ + UINT8 last; /* were we pressed last time? */ + UINT8 joydir; /* digital joystick direction index */ + char * name; /* overridden name */ +}; + + +/* internal live state of an input port */ +struct _input_port_state +{ + analog_field_state * analoglist; /* pointer to list of analog port info */ + device_field_info * readdevicelist; /* pointer to list of input device info */ + device_field_info * writedevicelist; /* pointer to list of output device info */ + input_port_value defvalue; /* combined default value across the port */ + input_port_value digital; /* current value from all digital inputs */ + input_port_value vblank; /* value of all IPT_VBLANK bits */ + input_port_value outputvalue; /* current value for outputs */ +}; + + +typedef struct _inputx_code inputx_code; +struct _inputx_code +{ + unicode_char ch; + const input_field_config * field[NUM_SIMUL_KEYS]; +}; + +typedef struct _key_buffer key_buffer; +struct _key_buffer +{ + int begin_pos; + int end_pos; + unsigned int status_keydown : 1; + unicode_char buffer[4096]; +}; + +typedef struct _char_info char_info; +struct _char_info +{ + unicode_char ch; + const char *name; + const char *alternate; /* alternative string, in UTF-8 */ +}; + + +/* private input port state */ +struct _input_port_private +{ + /* global state */ + UINT8 safe_to_read; /* clear at start; set after state is loaded */ + + /* types */ + simple_list<input_type_entry> typelist; /* list of live type states */ + input_type_entry * type_to_entry[__ipt_max][MAX_PLAYERS]; /* map from type/player to type state */ + + /* specific special global input states */ + digital_joystick_state joystick_info[MAX_PLAYERS][DIGITAL_JOYSTICKS_PER_PLAYER]; /* joystick states */ + + /* frame time tracking */ + attotime last_frame_time; /* time of the last frame callback */ + attoseconds_t last_delta_nsec; /* nanoseconds that passed since the previous callback */ + + /* playback/record information */ + emu_file * record_file; /* recording file (NULL if not recording) */ + emu_file * playback_file; /* playback file (NULL if not recording) */ + UINT64 playback_accumulated_speed;/* accumulated speed during playback */ + UINT32 playback_accumulated_frames;/* accumulated frames during playback */ + + /* inputx */ + inputx_code *codes; + key_buffer *keybuffer; + emu_timer *inputx_timer; + int (*queue_chars)(running_machine &machine, const unicode_char *text, size_t text_len); + int (*accept_char)(running_machine &machine, unicode_char ch); + int (*charqueue_empty)(running_machine &machine); + attotime current_rate; +}; + + +/*************************************************************************** + MACROS +***************************************************************************/ + +#define APPLY_SENSITIVITY(x,s) (((INT64)(x) * (s)) / 100) +#define APPLY_INVERSE_SENSITIVITY(x,s) (((INT64)(x) * 100) / (s)) + +#define COMPUTE_SCALE(num,den) (((INT64)(num) << 24) / (den)) +#define RECIP_SCALE(s) (((INT64)1 << 48) / (s)) +#define APPLY_SCALE(x,s) (((INT64)(x) * (s)) >> 24) + + + +/*************************************************************************** + GLOBAL VARIABLES +***************************************************************************/ + +/* XML attributes for the different types */ +static const char *const seqtypestrings[] = { "standard", "decrement", "increment" }; + + +static const char_info charinfo[] = +{ + { 0x0008, "Backspace", NULL }, /* Backspace */ + { 0x0009, "Tab", " " }, /* Tab */ + { 0x000c, "Clear", NULL }, /* Clear */ + { 0x000d, "Enter", NULL }, /* Enter */ + { 0x001a, "Esc", NULL }, /* Esc */ + { 0x0020, "Space", " " }, /* Space */ + { 0x0061, NULL, "A" }, /* a */ + { 0x0062, NULL, "B" }, /* b */ + { 0x0063, NULL, "C" }, /* c */ + { 0x0064, NULL, "D" }, /* d */ + { 0x0065, NULL, "E" }, /* e */ + { 0x0066, NULL, "F" }, /* f */ + { 0x0067, NULL, "G" }, /* g */ + { 0x0068, NULL, "H" }, /* h */ + { 0x0069, NULL, "I" }, /* i */ + { 0x006a, NULL, "J" }, /* j */ + { 0x006b, NULL, "K" }, /* k */ + { 0x006c, NULL, "L" }, /* l */ + { 0x006d, NULL, "M" }, /* m */ + { 0x006e, NULL, "N" }, /* n */ + { 0x006f, NULL, "O" }, /* o */ + { 0x0070, NULL, "P" }, /* p */ + { 0x0071, NULL, "Q" }, /* q */ + { 0x0072, NULL, "R" }, /* r */ + { 0x0073, NULL, "S" }, /* s */ + { 0x0074, NULL, "T" }, /* t */ + { 0x0075, NULL, "U" }, /* u */ + { 0x0076, NULL, "V" }, /* v */ + { 0x0077, NULL, "W" }, /* w */ + { 0x0078, NULL, "X" }, /* x */ + { 0x0079, NULL, "Y" }, /* y */ + { 0x007a, NULL, "Z" }, /* z */ + { 0x00a0, NULL, " " }, /* non breaking space */ + { 0x00a1, NULL, "!" }, /* inverted exclaimation mark */ + { 0x00a6, NULL, "|" }, /* broken bar */ + { 0x00a9, NULL, "(c)" }, /* copyright sign */ + { 0x00ab, NULL, "<<" }, /* left pointing double angle */ + { 0x00ae, NULL, "(r)" }, /* registered sign */ + { 0x00bb, NULL, ">>" }, /* right pointing double angle */ + { 0x00bc, NULL, "1/4" }, /* vulgar fraction one quarter */ + { 0x00bd, NULL, "1/2" }, /* vulgar fraction one half */ + { 0x00be, NULL, "3/4" }, /* vulgar fraction three quarters */ + { 0x00bf, NULL, "?" }, /* inverted question mark */ + { 0x00c0, NULL, "A" }, /* 'A' grave */ + { 0x00c1, NULL, "A" }, /* 'A' acute */ + { 0x00c2, NULL, "A" }, /* 'A' circumflex */ + { 0x00c3, NULL, "A" }, /* 'A' tilde */ + { 0x00c4, NULL, "A" }, /* 'A' diaeresis */ + { 0x00c5, NULL, "A" }, /* 'A' ring above */ + { 0x00c6, NULL, "AE" }, /* 'AE' ligature */ + { 0x00c7, NULL, "C" }, /* 'C' cedilla */ + { 0x00c8, NULL, "E" }, /* 'E' grave */ + { 0x00c9, NULL, "E" }, /* 'E' acute */ + { 0x00ca, NULL, "E" }, /* 'E' circumflex */ + { 0x00cb, NULL, "E" }, /* 'E' diaeresis */ + { 0x00cc, NULL, "I" }, /* 'I' grave */ + { 0x00cd, NULL, "I" }, /* 'I' acute */ + { 0x00ce, NULL, "I" }, /* 'I' circumflex */ + { 0x00cf, NULL, "I" }, /* 'I' diaeresis */ + { 0x00d0, NULL, "D" }, /* 'ETH' */ + { 0x00d1, NULL, "N" }, /* 'N' tilde */ + { 0x00d2, NULL, "O" }, /* 'O' grave */ + { 0x00d3, NULL, "O" }, /* 'O' acute */ + { 0x00d4, NULL, "O" }, /* 'O' circumflex */ + { 0x00d5, NULL, "O" }, /* 'O' tilde */ + { 0x00d6, NULL, "O" }, /* 'O' diaeresis */ + { 0x00d7, NULL, "X" }, /* multiplication sign */ + { 0x00d8, NULL, "O" }, /* 'O' stroke */ + { 0x00d9, NULL, "U" }, /* 'U' grave */ + { 0x00da, NULL, "U" }, /* 'U' acute */ + { 0x00db, NULL, "U" }, /* 'U' circumflex */ + { 0x00dc, NULL, "U" }, /* 'U' diaeresis */ + { 0x00dd, NULL, "Y" }, /* 'Y' acute */ + { 0x00df, NULL, "SS" }, /* sharp S */ + { 0x00e0, NULL, "a" }, /* 'a' grave */ + { 0x00e1, NULL, "a" }, /* 'a' acute */ + { 0x00e2, NULL, "a" }, /* 'a' circumflex */ + { 0x00e3, NULL, "a" }, /* 'a' tilde */ + { 0x00e4, NULL, "a" }, /* 'a' diaeresis */ + { 0x00e5, NULL, "a" }, /* 'a' ring above */ + { 0x00e6, NULL, "ae" }, /* 'ae' ligature */ + { 0x00e7, NULL, "c" }, /* 'c' cedilla */ + { 0x00e8, NULL, "e" }, /* 'e' grave */ + { 0x00e9, NULL, "e" }, /* 'e' acute */ + { 0x00ea, NULL, "e" }, /* 'e' circumflex */ + { 0x00eb, NULL, "e" }, /* 'e' diaeresis */ + { 0x00ec, NULL, "i" }, /* 'i' grave */ + { 0x00ed, NULL, "i" }, /* 'i' acute */ + { 0x00ee, NULL, "i" }, /* 'i' circumflex */ + { 0x00ef, NULL, "i" }, /* 'i' diaeresis */ + { 0x00f0, NULL, "d" }, /* 'eth' */ + { 0x00f1, NULL, "n" }, /* 'n' tilde */ + { 0x00f2, NULL, "o" }, /* 'o' grave */ + { 0x00f3, NULL, "o" }, /* 'o' acute */ + { 0x00f4, NULL, "o" }, /* 'o' circumflex */ + { 0x00f5, NULL, "o" }, /* 'o' tilde */ + { 0x00f6, NULL, "o" }, /* 'o' diaeresis */ + { 0x00f8, NULL, "o" }, /* 'o' stroke */ + { 0x00f9, NULL, "u" }, /* 'u' grave */ + { 0x00fa, NULL, "u" }, /* 'u' acute */ + { 0x00fb, NULL, "u" }, /* 'u' circumflex */ + { 0x00fc, NULL, "u" }, /* 'u' diaeresis */ + { 0x00fd, NULL, "y" }, /* 'y' acute */ + { 0x00ff, NULL, "y" }, /* 'y' diaeresis */ + { 0x2010, NULL, "-" }, /* hyphen */ + { 0x2011, NULL, "-" }, /* non-breaking hyphen */ + { 0x2012, NULL, "-" }, /* figure dash */ + { 0x2013, NULL, "-" }, /* en dash */ + { 0x2014, NULL, "-" }, /* em dash */ + { 0x2015, NULL, "-" }, /* horizontal dash */ + { 0x2018, NULL, "\'" }, /* left single quotation mark */ + { 0x2019, NULL, "\'" }, /* right single quotation mark */ + { 0x201a, NULL, "\'" }, /* single low quotation mark */ + { 0x201b, NULL, "\'" }, /* single high reversed quotation mark */ + { 0x201c, NULL, "\"" }, /* left double quotation mark */ + { 0x201d, NULL, "\"" }, /* right double quotation mark */ + { 0x201e, NULL, "\"" }, /* double low quotation mark */ + { 0x201f, NULL, "\"" }, /* double high reversed quotation mark */ + { 0x2024, NULL, "." }, /* one dot leader */ + { 0x2025, NULL, ".." }, /* two dot leader */ + { 0x2026, NULL, "..." }, /* horizontal ellipsis */ + { 0x2047, NULL, "??" }, /* double question mark */ + { 0x2048, NULL, "?!" }, /* question exclamation mark */ + { 0x2049, NULL, "!?" }, /* exclamation question mark */ + { 0xff01, NULL, "!" }, /* fullwidth exclamation point */ + { 0xff02, NULL, "\"" }, /* fullwidth quotation mark */ + { 0xff03, NULL, "#" }, /* fullwidth number sign */ + { 0xff04, NULL, "$" }, /* fullwidth dollar sign */ + { 0xff05, NULL, "%" }, /* fullwidth percent sign */ + { 0xff06, NULL, "&" }, /* fullwidth ampersand */ + { 0xff07, NULL, "\'" }, /* fullwidth apostrophe */ + { 0xff08, NULL, "(" }, /* fullwidth left parenthesis */ + { 0xff09, NULL, ")" }, /* fullwidth right parenthesis */ + { 0xff0a, NULL, "*" }, /* fullwidth asterisk */ + { 0xff0b, NULL, "+" }, /* fullwidth plus */ + { 0xff0c, NULL, "," }, /* fullwidth comma */ + { 0xff0d, NULL, "-" }, /* fullwidth minus */ + { 0xff0e, NULL, "." }, /* fullwidth period */ + { 0xff0f, NULL, "/" }, /* fullwidth slash */ + { 0xff10, NULL, "0" }, /* fullwidth zero */ + { 0xff11, NULL, "1" }, /* fullwidth one */ + { 0xff12, NULL, "2" }, /* fullwidth two */ + { 0xff13, NULL, "3" }, /* fullwidth three */ + { 0xff14, NULL, "4" }, /* fullwidth four */ + { 0xff15, NULL, "5" }, /* fullwidth five */ + { 0xff16, NULL, "6" }, /* fullwidth six */ + { 0xff17, NULL, "7" }, /* fullwidth seven */ + { 0xff18, NULL, "8" }, /* fullwidth eight */ + { 0xff19, NULL, "9" }, /* fullwidth nine */ + { 0xff1a, NULL, ":" }, /* fullwidth colon */ + { 0xff1b, NULL, ";" }, /* fullwidth semicolon */ + { 0xff1c, NULL, "<" }, /* fullwidth less than sign */ + { 0xff1d, NULL, "=" }, /* fullwidth equals sign */ + { 0xff1e, NULL, ">" }, /* fullwidth greater than sign */ + { 0xff1f, NULL, "?" }, /* fullwidth question mark */ + { 0xff20, NULL, "@" }, /* fullwidth at sign */ + { 0xff21, NULL, "A" }, /* fullwidth 'A' */ + { 0xff22, NULL, "B" }, /* fullwidth 'B' */ + { 0xff23, NULL, "C" }, /* fullwidth 'C' */ + { 0xff24, NULL, "D" }, /* fullwidth 'D' */ + { 0xff25, NULL, "E" }, /* fullwidth 'E' */ + { 0xff26, NULL, "F" }, /* fullwidth 'F' */ + { 0xff27, NULL, "G" }, /* fullwidth 'G' */ + { 0xff28, NULL, "H" }, /* fullwidth 'H' */ + { 0xff29, NULL, "I" }, /* fullwidth 'I' */ + { 0xff2a, NULL, "J" }, /* fullwidth 'J' */ + { 0xff2b, NULL, "K" }, /* fullwidth 'K' */ + { 0xff2c, NULL, "L" }, /* fullwidth 'L' */ + { 0xff2d, NULL, "M" }, /* fullwidth 'M' */ + { 0xff2e, NULL, "N" }, /* fullwidth 'N' */ + { 0xff2f, NULL, "O" }, /* fullwidth 'O' */ + { 0xff30, NULL, "P" }, /* fullwidth 'P' */ + { 0xff31, NULL, "Q" }, /* fullwidth 'Q' */ + { 0xff32, NULL, "R" }, /* fullwidth 'R' */ + { 0xff33, NULL, "S" }, /* fullwidth 'S' */ + { 0xff34, NULL, "T" }, /* fullwidth 'T' */ + { 0xff35, NULL, "U" }, /* fullwidth 'U' */ + { 0xff36, NULL, "V" }, /* fullwidth 'V' */ + { 0xff37, NULL, "W" }, /* fullwidth 'W' */ + { 0xff38, NULL, "X" }, /* fullwidth 'X' */ + { 0xff39, NULL, "Y" }, /* fullwidth 'Y' */ + { 0xff3a, NULL, "Z" }, /* fullwidth 'Z' */ + { 0xff3b, NULL, "[" }, /* fullwidth left bracket */ + { 0xff3c, NULL, "\\" }, /* fullwidth backslash */ + { 0xff3d, NULL, "]" }, /* fullwidth right bracket */ + { 0xff3e, NULL, "^" }, /* fullwidth caret */ + { 0xff3f, NULL, "_" }, /* fullwidth underscore */ + { 0xff40, NULL, "`" }, /* fullwidth backquote */ + { 0xff41, NULL, "a" }, /* fullwidth 'a' */ + { 0xff42, NULL, "b" }, /* fullwidth 'b' */ + { 0xff43, NULL, "c" }, /* fullwidth 'c' */ + { 0xff44, NULL, "d" }, /* fullwidth 'd' */ + { 0xff45, NULL, "e" }, /* fullwidth 'e' */ + { 0xff46, NULL, "f" }, /* fullwidth 'f' */ + { 0xff47, NULL, "g" }, /* fullwidth 'g' */ + { 0xff48, NULL, "h" }, /* fullwidth 'h' */ + { 0xff49, NULL, "i" }, /* fullwidth 'i' */ + { 0xff4a, NULL, "j" }, /* fullwidth 'j' */ + { 0xff4b, NULL, "k" }, /* fullwidth 'k' */ + { 0xff4c, NULL, "l" }, /* fullwidth 'l' */ + { 0xff4d, NULL, "m" }, /* fullwidth 'm' */ + { 0xff4e, NULL, "n" }, /* fullwidth 'n' */ + { 0xff4f, NULL, "o" }, /* fullwidth 'o' */ + { 0xff50, NULL, "p" }, /* fullwidth 'p' */ + { 0xff51, NULL, "q" }, /* fullwidth 'q' */ + { 0xff52, NULL, "r" }, /* fullwidth 'r' */ + { 0xff53, NULL, "s" }, /* fullwidth 's' */ + { 0xff54, NULL, "t" }, /* fullwidth 't' */ + { 0xff55, NULL, "u" }, /* fullwidth 'u' */ + { 0xff56, NULL, "v" }, /* fullwidth 'v' */ + { 0xff57, NULL, "w" }, /* fullwidth 'w' */ + { 0xff58, NULL, "x" }, /* fullwidth 'x' */ + { 0xff59, NULL, "y" }, /* fullwidth 'y' */ + { 0xff5a, NULL, "z" }, /* fullwidth 'z' */ + { 0xff5b, NULL, "{" }, /* fullwidth left brace */ + { 0xff5c, NULL, "|" }, /* fullwidth vertical bar */ + { 0xff5d, NULL, "}" }, /* fullwidth right brace */ + { 0xff5e, NULL, "~" }, /* fullwidth tilde */ + { 0xff5f, NULL, "((" }, /* fullwidth double left parenthesis */ + { 0xff60, NULL, "))" }, /* fullwidth double right parenthesis */ + { 0xffe0, NULL, "\xC2\xA2" }, /* fullwidth cent sign */ + { 0xffe1, NULL, "\xC2\xA3" }, /* fullwidth pound sign */ + { 0xffe4, NULL, "\xC2\xA4" }, /* fullwidth broken bar */ + { 0xffe5, NULL, "\xC2\xA5" }, /* fullwidth yen sign */ + { 0xffe6, NULL, "\xE2\x82\xA9" }, /* fullwidth won sign */ + { 0xffe9, NULL, "\xE2\x86\x90" }, /* fullwidth left arrow */ + { 0xffea, NULL, "\xE2\x86\x91" }, /* fullwidth up arrow */ + { 0xffeb, NULL, "\xE2\x86\x92" }, /* fullwidth right arrow */ + { 0xffec, NULL, "\xE2\x86\x93" }, /* fullwidth down arrow */ + { 0xffed, NULL, "\xE2\x96\xAA" }, /* fullwidth solid box */ + { 0xffee, NULL, "\xE2\x97\xA6" }, /* fullwidth open circle */ + { UCHAR_SHIFT_1, "Shift", NULL }, /* Shift key */ + { UCHAR_SHIFT_2, "Ctrl", NULL }, /* Ctrl key */ + { UCHAR_MAMEKEY(F1), "F1", NULL }, /* F1 function key */ + { UCHAR_MAMEKEY(F2), "F2", NULL }, /* F2 function key */ + { UCHAR_MAMEKEY(F3), "F3", NULL }, /* F3 function key */ + { UCHAR_MAMEKEY(F4), "F4", NULL }, /* F4 function key */ + { UCHAR_MAMEKEY(F5), "F5", NULL }, /* F5 function key */ + { UCHAR_MAMEKEY(F6), "F6", NULL }, /* F6 function key */ + { UCHAR_MAMEKEY(F7), "F7", NULL }, /* F7 function key */ + { UCHAR_MAMEKEY(F8), "F8", NULL }, /* F8 function key */ + { UCHAR_MAMEKEY(F9), "F9", NULL }, /* F9 function key */ + { UCHAR_MAMEKEY(F10), "F10", NULL }, /* F10 function key */ + { UCHAR_MAMEKEY(F11), "F11", NULL }, /* F11 function key */ + { UCHAR_MAMEKEY(F12), "F12", NULL }, /* F12 function key */ + { UCHAR_MAMEKEY(F13), "F13", NULL }, /* F13 function key */ + { UCHAR_MAMEKEY(F14), "F14", NULL }, /* F14 function key */ + { UCHAR_MAMEKEY(F15), "F15", NULL }, /* F15 function key */ + { UCHAR_MAMEKEY(ESC), "Esc", "\033" }, /* Esc key */ + { UCHAR_MAMEKEY(INSERT), "Insert", NULL }, /* Insert key */ + { UCHAR_MAMEKEY(DEL), "Delete", "\010" }, /* Delete key */ + { UCHAR_MAMEKEY(HOME), "Home", "\014" }, /* Home key */ + { UCHAR_MAMEKEY(END), "End", NULL }, /* End key */ + { UCHAR_MAMEKEY(PGUP), "Page Up", NULL }, /* Page Up key */ + { UCHAR_MAMEKEY(PGDN), "Page Down", NULL }, /* Page Down key */ + { UCHAR_MAMEKEY(LEFT), "Cursor Left", NULL }, /* Cursor Left */ + { UCHAR_MAMEKEY(RIGHT), "Cursor Right", NULL }, /* Cursor Right */ + { UCHAR_MAMEKEY(UP), "Cursor Up", NULL }, /* Cursor Up */ + { UCHAR_MAMEKEY(DOWN), "Cursor Down", NULL }, /* Cursor Down */ + { UCHAR_MAMEKEY(0_PAD), "Keypad 0", NULL }, /* 0 on the numeric keypad */ + { UCHAR_MAMEKEY(1_PAD), "Keypad 1", NULL }, /* 1 on the numeric keypad */ + { UCHAR_MAMEKEY(2_PAD), "Keypad 2", NULL }, /* 2 on the numeric keypad */ + { UCHAR_MAMEKEY(3_PAD), "Keypad 3", NULL }, /* 3 on the numeric keypad */ + { UCHAR_MAMEKEY(4_PAD), "Keypad 4", NULL }, /* 4 on the numeric keypad */ + { UCHAR_MAMEKEY(5_PAD), "Keypad 5", NULL }, /* 5 on the numeric keypad */ + { UCHAR_MAMEKEY(6_PAD), "Keypad 6", NULL }, /* 6 on the numeric keypad */ + { UCHAR_MAMEKEY(7_PAD), "Keypad 7", NULL }, /* 7 on the numeric keypad */ + { UCHAR_MAMEKEY(8_PAD), "Keypad 8", NULL }, /* 8 on the numeric keypad */ + { UCHAR_MAMEKEY(9_PAD), "Keypad 9", NULL }, /* 9 on the numeric keypad */ + { UCHAR_MAMEKEY(SLASH_PAD), "Keypad /", NULL }, /* / on the numeric keypad */ + { UCHAR_MAMEKEY(ASTERISK), "Keypad *", NULL }, /* * on the numeric keypad */ + { UCHAR_MAMEKEY(MINUS_PAD), "Keypad -", NULL }, /* - on the numeric Keypad */ + { UCHAR_MAMEKEY(PLUS_PAD), "Keypad +", NULL }, /* + on the numeric Keypad */ + { UCHAR_MAMEKEY(DEL_PAD), "Keypad .", NULL }, /* . on the numeric keypad */ + { UCHAR_MAMEKEY(ENTER_PAD), "Keypad Enter", NULL }, /* Enter on the numeric keypad */ + { UCHAR_MAMEKEY(PRTSCR), "Print Screen", NULL }, /* Print Screen key */ + { UCHAR_MAMEKEY(PAUSE), "Pause", NULL }, /* Pause key */ + { UCHAR_MAMEKEY(LSHIFT), "Left Shift", NULL }, /* Left Shift key */ + { UCHAR_MAMEKEY(RSHIFT), "Right Shift", NULL }, /* Right Shift key */ + { UCHAR_MAMEKEY(LCONTROL), "Left Ctrl", NULL }, /* Left Control key */ + { UCHAR_MAMEKEY(RCONTROL), "Right Ctrl", NULL }, /* Right Control key */ + { UCHAR_MAMEKEY(LALT), "Left Alt", NULL }, /* Left Alt key */ + { UCHAR_MAMEKEY(RALT), "Right Alt", NULL }, /* Right Alt key */ + { UCHAR_MAMEKEY(SCRLOCK), "Scroll Lock", NULL }, /* Scroll Lock key */ + { UCHAR_MAMEKEY(NUMLOCK), "Num Lock", NULL }, /* Num Lock key */ + { UCHAR_MAMEKEY(CAPSLOCK), "Caps Lock", NULL }, /* Caps Lock key */ + { UCHAR_MAMEKEY(LWIN), "Left Win", NULL }, /* Left Win key */ + { UCHAR_MAMEKEY(RWIN), "Right Win", NULL }, /* Right Win key */ + { UCHAR_MAMEKEY(MENU), "Menu", NULL }, /* Menu key */ + { UCHAR_MAMEKEY(CANCEL), "Break", NULL } /* Break/Pause key */ +}; + +static TIMER_CALLBACK(inputx_timerproc); + + +/* Debugging commands and handlers. */ +static void execute_input(running_machine &machine, int ref, int params, const char *param[]); +static void execute_dumpkbd(running_machine &machine, int ref, int params, const char *param[]); + +/*************************************************************************** + COMMON SHARED STRINGS +***************************************************************************/ + +static const struct +{ + UINT32 id; + const char *string; +} input_port_default_strings[] = +{ + { INPUT_STRING_Off, "Off" }, + { INPUT_STRING_On, "On" }, + { INPUT_STRING_No, "No" }, + { INPUT_STRING_Yes, "Yes" }, + { INPUT_STRING_Lives, "Lives" }, + { INPUT_STRING_Bonus_Life, "Bonus Life" }, + { INPUT_STRING_Difficulty, "Difficulty" }, + { INPUT_STRING_Demo_Sounds, "Demo Sounds" }, + { INPUT_STRING_Coinage, "Coinage" }, + { INPUT_STRING_Coin_A, "Coin A" }, + { INPUT_STRING_Coin_B, "Coin B" }, + { INPUT_STRING_9C_1C, "9 Coins/1 Credit" }, + { INPUT_STRING_8C_1C, "8 Coins/1 Credit" }, + { INPUT_STRING_7C_1C, "7 Coins/1 Credit" }, + { INPUT_STRING_6C_1C, "6 Coins/1 Credit" }, + { INPUT_STRING_5C_1C, "5 Coins/1 Credit" }, + { INPUT_STRING_4C_1C, "4 Coins/1 Credit" }, + { INPUT_STRING_3C_1C, "3 Coins/1 Credit" }, + { INPUT_STRING_8C_3C, "8 Coins/3 Credits" }, + { INPUT_STRING_4C_2C, "4 Coins/2 Credits" }, + { INPUT_STRING_2C_1C, "2 Coins/1 Credit" }, + { INPUT_STRING_5C_3C, "5 Coins/3 Credits" }, + { INPUT_STRING_3C_2C, "3 Coins/2 Credits" }, + { INPUT_STRING_4C_3C, "4 Coins/3 Credits" }, + { INPUT_STRING_4C_4C, "4 Coins/4 Credits" }, + { INPUT_STRING_3C_3C, "3 Coins/3 Credits" }, + { INPUT_STRING_2C_2C, "2 Coins/2 Credits" }, + { INPUT_STRING_1C_1C, "1 Coin/1 Credit" }, + { INPUT_STRING_4C_5C, "4 Coins/5 Credits" }, + { INPUT_STRING_3C_4C, "3 Coins/4 Credits" }, + { INPUT_STRING_2C_3C, "2 Coins/3 Credits" }, + { INPUT_STRING_4C_7C, "4 Coins/7 Credits" }, + { INPUT_STRING_2C_4C, "2 Coins/4 Credits" }, + { INPUT_STRING_1C_2C, "1 Coin/2 Credits" }, + { INPUT_STRING_2C_5C, "2 Coins/5 Credits" }, + { INPUT_STRING_2C_6C, "2 Coins/6 Credits" }, + { INPUT_STRING_1C_3C, "1 Coin/3 Credits" }, + { INPUT_STRING_2C_7C, "2 Coins/7 Credits" }, + { INPUT_STRING_2C_8C, "2 Coins/8 Credits" }, + { INPUT_STRING_1C_4C, "1 Coin/4 Credits" }, + { INPUT_STRING_1C_5C, "1 Coin/5 Credits" }, + { INPUT_STRING_1C_6C, "1 Coin/6 Credits" }, + { INPUT_STRING_1C_7C, "1 Coin/7 Credits" }, + { INPUT_STRING_1C_8C, "1 Coin/8 Credits" }, + { INPUT_STRING_1C_9C, "1 Coin/9 Credits" }, + { INPUT_STRING_Free_Play, "Free Play" }, + { INPUT_STRING_Cabinet, "Cabinet" }, + { INPUT_STRING_Upright, "Upright" }, + { INPUT_STRING_Cocktail, "Cocktail" }, + { INPUT_STRING_Flip_Screen, "Flip Screen" }, + { INPUT_STRING_Service_Mode, "Service Mode" }, + { INPUT_STRING_Pause, "Pause" }, + { INPUT_STRING_Test, "Test" }, + { INPUT_STRING_Tilt, "Tilt" }, + { INPUT_STRING_Version, "Version" }, + { INPUT_STRING_Region, "Region" }, + { INPUT_STRING_International, "International" }, + { INPUT_STRING_Japan, "Japan" }, + { INPUT_STRING_USA, "USA" }, + { INPUT_STRING_Europe, "Europe" }, + { INPUT_STRING_Asia, "Asia" }, + { INPUT_STRING_World, "World" }, + { INPUT_STRING_Hispanic, "Hispanic" }, + { INPUT_STRING_Language, "Language" }, + { INPUT_STRING_English, "English" }, + { INPUT_STRING_Japanese, "Japanese" }, + { INPUT_STRING_German, "German" }, + { INPUT_STRING_French, "French" }, + { INPUT_STRING_Italian, "Italian" }, + { INPUT_STRING_Spanish, "Spanish" }, + { INPUT_STRING_Very_Easy, "Very Easy" }, + { INPUT_STRING_Easiest, "Easiest" }, + { INPUT_STRING_Easier, "Easier" }, + { INPUT_STRING_Easy, "Easy" }, + { INPUT_STRING_Medium_Easy, "Medium Easy" }, + { INPUT_STRING_Normal, "Normal" }, + { INPUT_STRING_Medium, "Medium" }, + { INPUT_STRING_Medium_Hard, "Medium Hard" }, + { INPUT_STRING_Hard, "Hard" }, + { INPUT_STRING_Harder, "Harder" }, + { INPUT_STRING_Hardest, "Hardest" }, + { INPUT_STRING_Very_Hard, "Very Hard" }, + { INPUT_STRING_Medium_Difficult, "Medium Difficult" }, + { INPUT_STRING_Difficult, "Difficult" }, + { INPUT_STRING_More_Difficult, "More Difficult" }, + { INPUT_STRING_Most_Difficult, "Most Difficult" }, + { INPUT_STRING_Very_Difficult, "Very Difficult" }, + { INPUT_STRING_Very_Low, "Very Low" }, + { INPUT_STRING_Low, "Low" }, + { INPUT_STRING_High, "High" }, + { INPUT_STRING_Higher, "Higher" }, + { INPUT_STRING_Highest, "Highest" }, + { INPUT_STRING_Very_High, "Very High" }, + { INPUT_STRING_Players, "Players" }, + { INPUT_STRING_Controls, "Controls" }, + { INPUT_STRING_Dual, "Dual" }, + { INPUT_STRING_Single, "Single" }, + { INPUT_STRING_Game_Time, "Game Time" }, + { INPUT_STRING_Continue_Price, "Continue Price" }, + { INPUT_STRING_Controller, "Controller" }, + { INPUT_STRING_Light_Gun, "Light Gun" }, + { INPUT_STRING_Joystick, "Joystick" }, + { INPUT_STRING_Trackball, "Trackball" }, + { INPUT_STRING_Continues, "Continues" }, + { INPUT_STRING_Allow_Continue, "Allow Continue" }, + { INPUT_STRING_Level_Select, "Level Select" }, + { INPUT_STRING_Infinite, "Infinite" }, + { INPUT_STRING_Stereo, "Stereo" }, + { INPUT_STRING_Mono, "Mono" }, + { INPUT_STRING_Unused, "Unused" }, + { INPUT_STRING_Unknown, "Unknown" }, + { INPUT_STRING_Standard, "Standard" }, + { INPUT_STRING_Reverse, "Reverse" }, + { INPUT_STRING_Alternate, "Alternate" }, + { INPUT_STRING_None, "None" } +}; + + + +/*************************************************************************** + BUILT-IN CORE MAPPINGS +***************************************************************************/ + +#include "inpttype.h" + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + +/* core system management */ +static void input_port_exit(running_machine &machine); + +/* port reading */ +static INT32 apply_analog_settings(INT32 current, analog_field_state *analog); + +/* initialization helpers */ +static void init_port_types(running_machine &machine); +static void init_port_state(running_machine &machine); +static void init_autoselect_devices(running_machine &machine, int type1, int type2, int type3, const char *option, const char *ananame); +static device_field_info *init_field_device_info(const input_field_config *field,const char *device_name); +static analog_field_state *init_field_analog_state(const input_field_config *field); + +/* once-per-frame updates */ +static void frame_update_callback(running_machine &machine); +static void frame_update(running_machine &machine); +static void frame_update_digital_joysticks(running_machine &machine); +static void frame_update_analog_field(running_machine &machine, analog_field_state *analog); +static int frame_get_digital_field_state(const input_field_config *field, int mouse_down); + +/* tokenization helpers */ +static int token_to_input_field_type(running_machine &machine, const char *string, int *player); +static const char *input_field_type_to_token(running_machine &machine, int type, int player); +static int token_to_seq_type(const char *string); + +/* settings load */ +static void load_config_callback(running_machine &machine, int config_type, xml_data_node *parentnode); +static void load_remap_table(running_machine &machine, xml_data_node *parentnode); +static int load_default_config(running_machine &machine, xml_data_node *portnode, int type, int player, const input_seq *newseq); +static int load_game_config(running_machine &machine, xml_data_node *portnode, int type, int player, const input_seq *newseq); + +/* settings save */ +static void save_config_callback(running_machine &machine, int config_type, xml_data_node *parentnode); +static void save_sequence(running_machine &machine, xml_data_node *parentnode, int type, int porttype, const input_seq &seq); +static int save_this_input_field_type(int type); +static void save_default_inputs(running_machine &machine, xml_data_node *parentnode); +static void save_game_inputs(running_machine &machine, xml_data_node *parentnode); + +/* input playback */ +static time_t playback_init(running_machine &machine); +static void playback_end(running_machine &machine, const char *message); +static void playback_frame(running_machine &machine, attotime curtime); +static void playback_port(const input_port_config *port); + +/* input recording */ +static void record_init(running_machine &machine); +static void record_end(running_machine &machine, const char *message); +static void record_frame(running_machine &machine, attotime curtime); +static void record_port(const input_port_config *port); + + + +/*************************************************************************** + INLINE FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + apply_analog_min_max - clamp the given input + value to the appropriate min/max for the + analog control +-------------------------------------------------*/ + +INLINE INT32 apply_analog_min_max(const analog_field_state *analog, INT32 value) +{ + /* take the analog minimum and maximum values and apply the inverse of the */ + /* sensitivity so that we can clamp against them before applying sensitivity */ + INT32 adjmin = APPLY_INVERSE_SENSITIVITY(analog->minimum, analog->sensitivity); + INT32 adjmax = APPLY_INVERSE_SENSITIVITY(analog->maximum, analog->sensitivity); + + /* for absolute devices, clamp to the bounds absolutely */ + if (!analog->wraps) + { + if (value > adjmax) + value = adjmax; + else if (value < adjmin) + value = adjmin; + } + + /* for relative devices, wrap around when we go past the edge */ + else + { + INT32 adj1 = APPLY_INVERSE_SENSITIVITY(INPUT_RELATIVE_PER_PIXEL, analog->sensitivity); + INT32 range = adjmax - adjmin + adj1; + /* rolls to other end when 1 position past end. */ + adjmax += adj1; + adjmin -= adj1; + + while (value >= adjmax) + { + value -= range;; + } + while (value <= adjmin) + { + value += range;; + } + } + + return value; +} + + +/*------------------------------------------------- + get_port_tag - return a guaranteed tag for + a port +-------------------------------------------------*/ + +INLINE const char *get_port_tag(const input_port_config *port, char *tempbuffer) +{ + return port->tag(); +} + + +/*------------------------------------------------- + condition_equal - TRUE if two conditions are + equivalent +-------------------------------------------------*/ + +INLINE int condition_equal(const input_condition *cond1, const input_condition *cond2) +{ + return (cond1->mask == cond2->mask && cond1->value == cond2->value && cond1->condition == cond2->condition && strcmp(cond1->tag, cond2->tag) == 0); +} + + + +/*************************************************************************** + CORE SYSTEM MANAGEMENT +***************************************************************************/ + +/*------------------------------------------------- + input_port_init - initialize the input port + system +-------------------------------------------------*/ + +time_t input_port_init(running_machine &machine) +{ + //input_port_private *portdata; + time_t basetime; + + /* allocate memory for our data structure */ + machine.input_port_data = auto_alloc_clear(machine, input_port_private); + //portdata = machine.input_port_data; + + /* add an exit callback and a frame callback */ + machine.add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(FUNC(input_port_exit), &machine)); + machine.add_notifier(MACHINE_NOTIFY_FRAME, machine_notify_delegate(FUNC(frame_update_callback), &machine)); + + /* initialize the default port info from the OSD */ + init_port_types(machine); + + /* if we have a token list, proceed */ + for (device_t *device = machine.devicelist().first(); device != NULL; device = device->next()) + { + astring errors; + input_port_list_init(*device, machine.m_portlist, errors); + if (errors) + mame_printf_error("Input port errors:\n%s", errors.cstr()); + } + + init_port_state(machine); + /* register callbacks for when we load configurations */ + config_register(machine, "input", config_saveload_delegate(FUNC(load_config_callback), &machine), config_saveload_delegate(FUNC(save_config_callback), &machine)); + + /* open playback and record files if specified */ + basetime = playback_init(machine); + record_init(machine); + + return basetime; +} + + +/*------------------------------------------------- + input_port_exit - exit callback to ensure + we clean up and close our files +-------------------------------------------------*/ + +static void input_port_exit(running_machine &machine) +{ + /* close any playback or recording files */ + playback_end(machine, NULL); + record_end(machine, NULL); +} + + + +/*************************************************************************** + PORT CONFIGURATIONS +***************************************************************************/ + +/*------------------------------------------------- + input_port_list_init - initialize an input + port list structure and allocate ports + according to the given tokens +-------------------------------------------------*/ + +void input_port_list_init(device_t &device, ioport_list &portlist, astring &errorbuf) +{ + /* no constructor, no list */ + ioport_constructor constructor = device.input_ports(); + if (constructor == NULL) + return; + + /* reset error buffer */ + errorbuf.reset(); + + /* detokenize into the list */ + (*constructor)(device, portlist, errorbuf); + + // collapse fields and sort the list + for (input_port_config *port = portlist.first(); port != NULL; port = port->next()) + port->collapse_fields(errorbuf); +} + + +/*------------------------------------------------- + input_field_by_tag_and_mask - return a pointer + to the first field that intersects the given + mask on the tagged port +-------------------------------------------------*/ + +const input_field_config *input_field_by_tag_and_mask(const ioport_list &portlist, const char *tag, input_port_value mask) +{ + const input_port_config *port = portlist.find(tag); + + /* if we got the port, look for the field */ + if (port != NULL) + for (const input_field_config *field = port->first_field(); field != NULL; field = field->next()) + if ((field->mask & mask) != 0) + return field; + + return NULL; +} + + + +/*************************************************************************** + ACCESSORS FOR INPUT FIELDS +***************************************************************************/ + +/*------------------------------------------------- + input_field_name - return the field name for + a given input field +-------------------------------------------------*/ + +const char *input_field_name(const input_field_config *field) +{ + /* if we have a non-default name, use that */ + if ((field->state != NULL) && (field->state->name != NULL)) + return field->state->name; + if (field->name != NULL) + return field->name; + + /* otherwise, return the name associated with the type */ + return input_type_name(field->machine(), field->type, field->player); +} + + +/*------------------------------------------------- + input_field_seq - return the input sequence + for the given input field +-------------------------------------------------*/ + +const input_seq &input_field_seq(const input_field_config *field, input_seq_type seqtype) +{ + /* if the field is disabled, return no key */ + if (field->flags & FIELD_FLAG_UNUSED) + return input_seq::empty_seq; + + /* select either the live or config state depending on whether we have live state */ + const input_seq &portseq = (field->state == NULL) ? field->seq[seqtype] : field->state->seq[seqtype]; + + /* if the portseq is the special default code, return the expanded default value */ + if (portseq.is_default()) + return input_type_seq(field->machine(), field->type, field->player, seqtype); + + /* otherwise, return the sequence as-is */ + return portseq; +} + + +/*------------------------------------------------- + input_field_get_user_settings - return the current + settings for the given input field +-------------------------------------------------*/ + +void input_field_get_user_settings(const input_field_config *field, input_field_user_settings *settings) +{ + int seqtype; + + /* zap the entire structure */ + memset(settings, 0, sizeof(*settings)); + + /* copy the basics */ + for (seqtype = 0; seqtype < ARRAY_LENGTH(settings->seq); seqtype++) + settings->seq[seqtype] = field->state->seq[seqtype]; + + /* if there's a list of settings or we're an adjuster, copy the current value */ + if (field->settinglist().count() != 0 || field->type == IPT_ADJUSTER) + settings->value = field->state->value; + + /* if there's analog data, extract the analog settings */ + if (field->state->analog != NULL) + { + settings->sensitivity = field->state->analog->sensitivity; + settings->delta = field->state->analog->delta; + settings->centerdelta = field->state->analog->centerdelta; + settings->reverse = field->state->analog->reverse; + } +} + + +/*------------------------------------------------- + input_field_set_user_settings - modify the current + settings for the given input field +-------------------------------------------------*/ + +void input_field_set_user_settings(const input_field_config *field, const input_field_user_settings *settings) +{ + int seqtype; + + /* copy the basics */ + for (seqtype = 0; seqtype < ARRAY_LENGTH(settings->seq); seqtype++) + { + const input_seq &defseq = input_type_seq(field->machine(), field->type, field->player, (input_seq_type)seqtype); + if (defseq == settings->seq[seqtype]) + field->state->seq[seqtype].set_default(); + else + field->state->seq[seqtype] = settings->seq[seqtype]; + } + + /* if there's a list of settings or we're an adjuster, copy the current value */ + if (field->settinglist().count() != 0 || field->type == IPT_ADJUSTER) + field->state->value = settings->value; + + /* if there's analog data, extract the analog settings */ + if (field->state->analog != NULL) + { + field->state->analog->sensitivity = settings->sensitivity; + field->state->analog->delta = settings->delta; + field->state->analog->centerdelta = settings->centerdelta; + field->state->analog->reverse = settings->reverse; + } +} + + +/*------------------------------------------------- + input_field_setting_name - return the expanded + setting name for a field +-------------------------------------------------*/ + +const char *input_field_setting_name(const input_field_config *field) +{ + const input_setting_config *setting; + + /* only makes sense if we have settings */ + assert(field->settinglist().count() != 0); + + /* scan the list of settings looking for a match on the current value */ + for (setting = field->settinglist().first(); setting != NULL; setting = setting->next()) + if (input_condition_true(field->machine(), &setting->condition, field->port().owner())) + if (setting->value == field->state->value) + return setting->name; + + return "INVALID"; +} + + +/*------------------------------------------------- + input_field_has_previous_setting - return TRUE + if the given field has a "previous" setting +-------------------------------------------------*/ + +int input_field_has_previous_setting(const input_field_config *field) +{ + const input_setting_config *setting; + + /* only makes sense if we have settings */ + assert(field->settinglist().count() != 0); + + /* scan the list of settings looking for a match on the current value */ + for (setting = field->settinglist().first(); setting != NULL; setting = setting->next()) + if (input_condition_true(field->machine(), &setting->condition, field->port().owner())) + return (setting->value != field->state->value); + + return FALSE; +} + + +/*------------------------------------------------- + input_field_select_previous_setting - select + the previous item for a DIP switch or + configuration field +-------------------------------------------------*/ + +void input_field_select_previous_setting(const input_field_config *field) +{ + const input_setting_config *setting, *prevsetting; + int found_match = FALSE; + + /* only makes sense if we have settings */ + assert(field->settinglist().count() != 0); + + /* scan the list of settings looking for a match on the current value */ + prevsetting = NULL; + for (setting = field->settinglist().first(); setting != NULL; setting = setting->next()) + if (input_condition_true(field->machine(), &setting->condition, field->port().owner())) + { + if (setting->value == field->state->value) + { + found_match = TRUE; + if (prevsetting != NULL) + break; + } + prevsetting = setting; + } + + /* if we didn't find a matching value, select the first */ + if (!found_match) + { + for (prevsetting = field->settinglist().first(); prevsetting != NULL; prevsetting = prevsetting->next()) + if (input_condition_true(field->machine(), &prevsetting->condition, field->port().owner())) + break; + } + + /* update the value to the previous one */ + if (prevsetting != NULL) + field->state->value = prevsetting->value; +} + + +/*------------------------------------------------- + input_field_has_next_setting - return TRUE + if the given field has a "next" setting +-------------------------------------------------*/ + +int input_field_has_next_setting(const input_field_config *field) +{ + const input_setting_config *setting; + int found = FALSE; + + /* only makes sense if we have settings */ + assert(field->settinglist().count() != 0); + + /* scan the list of settings looking for a match on the current value */ + for (setting = field->settinglist().first(); setting != NULL; setting = setting->next()) + if (input_condition_true(field->machine(), &setting->condition, field->port().owner())) + { + if (found) + return TRUE; + if (setting->value == field->state->value) + found = TRUE; + } + + return FALSE; +} + + +/*------------------------------------------------- + input_field_select_next_setting - select the + next item for a DIP switch or + configuration field +-------------------------------------------------*/ + +void input_field_select_next_setting(const input_field_config *field) +{ + const input_setting_config *setting, *nextsetting; + + /* only makes sense if we have settings */ + assert(field->settinglist().count() != 0); + + /* scan the list of settings looking for a match on the current value */ + nextsetting = NULL; + for (setting = field->settinglist().first(); setting != NULL; setting = setting->next()) + if (input_condition_true(field->machine(), &setting->condition, field->port().owner())) + if (setting->value == field->state->value) + break; + + /* if we found one, scan forward for the next valid one */ + if (setting != NULL) + for (nextsetting = setting->next(); nextsetting != NULL; nextsetting = nextsetting->next()) + if (input_condition_true(field->machine(), &nextsetting->condition, field->port().owner())) + break; + + /* if we hit the end, search from the beginning */ + if (nextsetting == NULL) + for (nextsetting = field->settinglist().first(); nextsetting != NULL; nextsetting = nextsetting->next()) + if (input_condition_true(field->machine(), &nextsetting->condition, field->port().owner())) + break; + + /* update the value to the previous one */ + if (nextsetting != NULL) + field->state->value = nextsetting->value; +} + + + +/*************************************************************************** + ACCESSORS FOR INPUT TYPES +***************************************************************************/ + +/*------------------------------------------------- + input_type_is_analog - return TRUE if + the given type represents an analog control +-------------------------------------------------*/ + +int input_type_is_analog(int type) +{ + return (type >= __ipt_analog_start && type <= __ipt_analog_end); +} + + +/*------------------------------------------------- + input_type_name - return the name + for the given type/player +-------------------------------------------------*/ + +const char *input_type_name(running_machine &machine, int type, int player) +{ + /* if we have a machine, use the live state and quick lookup */ + input_port_private *portdata = machine.input_port_data; + input_type_entry *entry = portdata->type_to_entry[type][player]; + if (entry != NULL) + return entry->name; + + /* if we find nothing, return an invalid group */ + return "???"; +} + + +/*------------------------------------------------- + input_type_group - return the group + for the given type/player +-------------------------------------------------*/ + +int input_type_group(running_machine &machine, int type, int player) +{ + input_port_private *portdata = machine.input_port_data; + input_type_entry *entry = portdata->type_to_entry[type][player]; + if (entry != NULL) + return entry->group; + + /* if we find nothing, return an invalid group */ + return IPG_INVALID; +} + + +/*------------------------------------------------- + input_type_seq - return the input + sequence for the given type/player +-------------------------------------------------*/ + +const input_seq &input_type_seq(running_machine &machine, int type, int player, input_seq_type seqtype) +{ + assert(type >= 0 && type < __ipt_max); + assert(player >= 0 && player < MAX_PLAYERS); + + /* if we have a machine, use the live state and quick lookup */ + input_port_private *portdata = machine.input_port_data; + input_type_entry *entry = portdata->type_to_entry[type][player]; + if (entry != NULL) + return entry->seq[seqtype]; + + /* if we find nothing, return an empty sequence */ + return input_seq::empty_seq; +} + + +/*------------------------------------------------- + input_type_set_seq - change the input + sequence for the given type/player +-------------------------------------------------*/ + +void input_type_set_seq(running_machine &machine, int type, int player, input_seq_type seqtype, const input_seq *newseq) +{ + input_port_private *portdata = machine.input_port_data; + input_type_entry *entry = portdata->type_to_entry[type][player]; + if (entry != NULL) + entry->seq[seqtype] = *newseq; +} + + +/*------------------------------------------------- + input_type_pressed - return TRUE if + the sequence for the given input type/player + is pressed +-------------------------------------------------*/ + +int input_type_pressed(running_machine &machine, int type, int player) +{ + return machine.input().seq_pressed(input_type_seq(machine, type, player, SEQ_TYPE_STANDARD)); +} + + +/*------------------------------------------------- + input_type_list - return the list of types +-------------------------------------------------*/ + +const simple_list<input_type_entry> &input_type_list(running_machine &machine) +{ + input_port_private *portdata = machine.input_port_data; + return portdata->typelist; +} + + + +/*************************************************************************** + PORT CHECKING +***************************************************************************/ + + +/*------------------------------------------------- + input_port_exists - return whether an input + port exists +-------------------------------------------------*/ + +bool input_port_exists(running_machine &machine, const char *tag) +{ + return machine.port(tag) != 0; +} + + +/*------------------------------------------------- + input_port_active - return a bitmask of which + bits of an input port are active (i.e. not + unused or unknown) +-------------------------------------------------*/ + +input_port_value input_port_active(running_machine &machine, const char *tag) +{ + const input_port_config *port = machine.port(tag); + if (port == NULL) + fatalerror("Unable to locate input port '%s'", tag); + return port->active; +} + + +/*------------------------------------------------- + input_port_active_safe - return a bitmask of + which bits of an input port are active (i.e. + not unused or unknown), or a default value if + the port does not exist +-------------------------------------------------*/ + +input_port_value input_port_active_safe(running_machine &machine, const char *tag, input_port_value defvalue) +{ + const input_port_config *port = machine.port(tag); + return port == NULL ? defvalue : port->active; +} + + + +/*************************************************************************** + PORT READING +***************************************************************************/ + +/*------------------------------------------------- + input_port_read_direct - return the value of + an input port +-------------------------------------------------*/ + +input_port_value input_port_read_direct(const input_port_config *port) +{ + input_port_private *portdata = port->machine().input_port_data; + analog_field_state *analog; + device_field_info *device_field; + input_port_value result; + + assert_always(portdata->safe_to_read, "Input ports cannot be read at init time!"); + + /* start with the digital */ + result = port->state->digital; + + /* update read values */ + for (device_field = port->state->readdevicelist; device_field != NULL; device_field = device_field->next) + if (input_condition_true(port->machine(), &device_field->field->condition, port->owner())) + { + /* replace the bits with bits from the device */ + input_port_value newval = device_field->field->read(*device_field->field, device_field->field->read_param); + device_field->oldval = newval; + result = (result & ~device_field->field->mask) | ((newval << device_field->shift) & device_field->field->mask); + } + + /* update VBLANK bits */ + if (port->state->vblank != 0) + { + if (port->machine().primary_screen->vblank()) + result |= port->state->vblank; + else + result &= ~port->state->vblank; + } + + /* apply active high/low state to digital, read, and VBLANK inputs */ + result ^= port->state->defvalue; + + /* merge in analog portions */ + for (analog = port->state->analoglist; analog != NULL; analog = analog->next) + if (input_condition_true(port->machine(), &analog->field->condition, port->owner())) + { + /* start with the raw value */ + INT32 value = analog->accum; + + /* interpolate if appropriate and if time has passed since the last update */ + if (analog->interpolate && !(analog->field->flags & ANALOG_FLAG_RESET) && portdata->last_delta_nsec != 0) + { + attoseconds_t nsec_since_last = (port->machine().time() - portdata->last_frame_time).as_attoseconds() / ATTOSECONDS_PER_NANOSECOND; + value = analog->previous + ((INT64)(analog->accum - analog->previous) * nsec_since_last / portdata->last_delta_nsec); + } + + /* apply standard analog settings */ + value = apply_analog_settings(value, analog); + + /* remap the value if needed */ + if (analog->field->remap_table != NULL) + value = analog->field->remap_table[value]; + + /* invert bits if needed */ + if (analog->field->flags & ANALOG_FLAG_INVERT) + value = ~value; + + /* insert into the port */ + result = (result & ~analog->field->mask) | ((value << analog->shift) & analog->field->mask); + } + + return result; +} + + +/*------------------------------------------------- + input_port_read - return the value of + an input port specified by tag +-------------------------------------------------*/ + +input_port_value input_port_read(running_machine &machine, const char *tag) +{ + const input_port_config *port = machine.port(tag); + if (port == NULL) + fatalerror("Unable to locate input port '%s'", tag); + return input_port_read_direct(port); +} + + +/*------------------------------------------------- + input_port_read - return the value of + a device input port specified by tag +-------------------------------------------------*/ + +input_port_value input_port_read(device_t *device, const char *tag) +{ + astring tempstring; + const input_port_config *port = device->machine().port(device->subtag(tempstring, tag)); + if (port == NULL) + fatalerror("Unable to locate input port '%s'", tag); + return input_port_read_direct(port); +} + + +/*------------------------------------------------- + input_port_read_safe - return the value of + an input port specified by tag, or a default + value if the port does not exist +-------------------------------------------------*/ + +input_port_value input_port_read_safe(running_machine &machine, const char *tag, UINT32 defvalue) +{ + const input_port_config *port = machine.port(tag); + return (port == NULL) ? defvalue : input_port_read_direct(port); +} + + +/*------------------------------------------------- + input_port_read_crosshair - return the + extracted crosshair values for the given + player +-------------------------------------------------*/ + +int input_port_get_crosshair_position(running_machine &machine, int player, float *x, float *y) +{ + const input_port_config *port; + const input_field_config *field; + int gotx = FALSE, goty = FALSE; + + /* read all the lightgun values */ + for (port = machine.m_portlist.first(); port != NULL; port = port->next()) + for (field = port->first_field(); field != NULL; field = field->next()) + if (field->player == player && field->crossaxis != CROSSHAIR_AXIS_NONE) + if (input_condition_true(machine, &field->condition, port->owner())) + { + analog_field_state *analog = field->state->analog; + INT32 rawvalue = apply_analog_settings(analog->accum, analog) & (analog->field->mask >> analog->shift); + float value = (float)(rawvalue - field->state->analog->adjmin) / (float)(field->state->analog->adjmax - field->state->analog->adjmin); + + /* apply the scale and offset */ + if (field->crossscale < 0) + value = -(1.0 - value) * field->crossscale; + else + value *= field->crossscale; + value += field->crossoffset; + + /* apply custom mapping if necessary */ + if (!field->crossmapper.isnull()) + value = field->crossmapper(*field, value); + + /* handle X axis */ + if (field->crossaxis == CROSSHAIR_AXIS_X) + { + *x = value; + gotx = TRUE; + if (field->crossaltaxis != 0) + { + *y = field->crossaltaxis; + goty = TRUE; + } + } + + /* handle Y axis */ + else + { + *y = value; + goty = TRUE; + if (field->crossaltaxis != 0) + { + *x = field->crossaltaxis; + gotx = TRUE; + } + } + + /* if we got both, stop */ + if (gotx && goty) + break; + } + + return (gotx && goty); +} + + +/*------------------------------------------------- + input_port_update_defaults - force an update + to the input port values based on current + conditions +-------------------------------------------------*/ + +void input_port_update_defaults(running_machine &machine) +{ + int loopnum; + + /* two passes to catch conditionals properly */ + for (loopnum = 0; loopnum < 2; loopnum++) + { + const input_port_config *port; + + /* loop over all input ports */ + for (port = machine.m_portlist.first(); port != NULL; port = port->next()) + { + const input_field_config *field; + + /* only clear on the first pass */ + if (loopnum == 0) + port->state->defvalue = 0; + + /* first compute the default value for the entire port */ + for (field = port->first_field(); field != NULL; field = field->next()) + if (input_condition_true(machine, &field->condition, port->owner())) + port->state->defvalue = (port->state->defvalue & ~field->mask) | (field->state->value & field->mask); + } + } +} + + +/*------------------------------------------------- + apply_analog_settings - return the value of + an input port +-------------------------------------------------*/ + +static INT32 apply_analog_settings(INT32 value, analog_field_state *analog) +{ + /* apply the min/max and then the sensitivity */ + value = apply_analog_min_max(analog, value); + value = APPLY_SENSITIVITY(value, analog->sensitivity); + + /* apply reversal if needed */ + if (analog->reverse) + value = analog->reverse_val - value; + else if (analog->single_scale) + /* it's a pedal or the default value is equal to min/max */ + /* so we need to adjust the center to the minimum */ + value -= INPUT_ABSOLUTE_MIN; + + /* map differently for positive and negative values */ + if (value >= 0) + value = APPLY_SCALE(value, analog->scalepos); + else + value = APPLY_SCALE(value, analog->scaleneg); + value += analog->adjdefvalue; + + return value; +} + + + +/*************************************************************************** + PORT WRITING +***************************************************************************/ + +/*------------------------------------------------- + input_port_write_direct - write a value + to a port +-------------------------------------------------*/ + +void input_port_write_direct(const input_port_config *port, input_port_value data, input_port_value mem_mask) +{ + /* call device line write handlers */ + device_field_info *device_field; + + COMBINE_DATA(&port->state->outputvalue); + + for (device_field = port->state->writedevicelist; device_field; device_field = device_field->next) + if (device_field->field->type == IPT_OUTPUT && input_condition_true(port->machine(), &device_field->field->condition, port->owner())) + { + input_port_value newval = ( (port->state->outputvalue ^ device_field->field->defvalue ) & device_field->field->mask) >> device_field->shift; + + /* if the bits have write, call the handler */ + if (device_field->oldval != newval) + { + device_field->field->write(*device_field->field, device_field->field->write_param, device_field->oldval, newval); + + device_field->oldval = newval; + } + } +} + + +/*------------------------------------------------- + input_port_write - write a value to a + port specified by tag +-------------------------------------------------*/ + +void input_port_write(running_machine &machine, const char *tag, input_port_value value, input_port_value mask) +{ + const input_port_config *port = machine.port(tag); + if (port == NULL) + fatalerror("Unable to locate input port '%s'", tag); + input_port_write_direct(port, value, mask); +} + + +/*------------------------------------------------- + input_port_write_safe - write a value to + a port, ignore if the port does not exist +-------------------------------------------------*/ + +void input_port_write_safe(running_machine &machine, const char *tag, input_port_value value, input_port_value mask) +{ + const input_port_config *port = machine.port(tag); + if (port != NULL) + input_port_write_direct(port, value, mask); +} + + + +/*************************************************************************** + MISC HELPER FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + input_condition_true - return the TRUE + if the given condition attached is true +-------------------------------------------------*/ + +int input_condition_true(running_machine &machine, const input_condition *condition,device_t &owner) +{ + input_port_value condvalue; + + /* always condition is always true */ + if (condition->condition == PORTCOND_ALWAYS) + return TRUE; + + /* otherwise, read the referenced port */ + astring conditiontag; + owner.subtag(conditiontag, condition->tag); + condvalue = input_port_read(machine, conditiontag.cstr()); + + /* based on the condition encoded, determine truth */ + switch (condition->condition) + { + case PORTCOND_EQUALS: + return ((condvalue & condition->mask) == condition->value); + + case PORTCOND_NOTEQUALS: + return ((condvalue & condition->mask) != condition->value); + + case PORTCOND_GREATERTHAN: + return ((condvalue & condition->mask) > condition->value); + + case PORTCOND_NOTGREATERTHAN: + return ((condvalue & condition->mask) <= condition->value); + + case PORTCOND_LESSTHAN: + return ((condvalue & condition->mask) < condition->value); + + case PORTCOND_NOTLESSTHAN: + return ((condvalue & condition->mask) >= condition->value); + } + return TRUE; +} + + +/*------------------------------------------------- + input_port_string_from_token - convert an + input_port_token to a default string +-------------------------------------------------*/ + +const char *input_port_string_from_token(const char *string) +{ + int index; + + /* 0 is an invalid index */ + if (string == NULL) + return NULL; + + /* if the index is greater than the count, assume it to be a pointer */ + if (FPTR(string) >= INPUT_STRING_COUNT) + return string; + + /* otherwise, scan the list for a matching string and return it */ + for (index = 0; index < ARRAY_LENGTH(input_port_default_strings); index++) + if (input_port_default_strings[index].id == FPTR(string)) + return input_port_default_strings[index].string; + return "(Unknown Default)"; +} + + + +/*************************************************************************** + INITIALIZATION HELPERS +***************************************************************************/ + +/*------------------------------------------------- + init_port_types - initialize the default + type list +-------------------------------------------------*/ + +static void init_port_types(running_machine &machine) +{ + input_port_private *portdata = machine.input_port_data; + + /* convert the array into a list of type states that can be modified */ + construct_core_types(portdata->typelist); + + /* ask the OSD to customize the list */ + machine.osd().customize_input_type_list(portdata->typelist); + + /* now iterate over the OSD-modified types */ + for (input_type_entry *curtype = portdata->typelist.first(); curtype != NULL; curtype = curtype->next()) + { + /* first copy all the OSD-updated sequences into our current state */ + for (int seqtype = 0; seqtype < ARRAY_LENGTH(curtype->seq); seqtype++) + curtype->seq[seqtype] = curtype->defseq[seqtype]; + + /* also make a lookup table mapping type/player to the appropriate type list entry */ + portdata->type_to_entry[curtype->type][curtype->player] = curtype; + } +} + + +/*------------------------------------------------- + get_keyboard_code - accesses a particular + keyboard code +-------------------------------------------------*/ + +static unicode_char get_keyboard_code(const input_field_config *field, int i) +{ + unicode_char ch = field->chars[i]; + + /* special hack to allow for PORT_CODE('\xA3') */ + if ((ch >= 0xFFFFFF80) && (ch <= 0xFFFFFFFF)) + ch &= 0xFF; + return ch; +} + + +/*************************************************************************** + MISCELLANEOUS +***************************************************************************/ + +/*------------------------------------------------- + find_charinfo - looks up information about a + particular character +-------------------------------------------------*/ + +static const char_info *find_charinfo(unicode_char target_char) +{ + int low = 0; + int high = ARRAY_LENGTH(charinfo); + int i; + unicode_char ch; + + /* perform a simple binary search to find the proper alternate */ + while(high > low) + { + i = (high + low) / 2; + ch = charinfo[i].ch; + if (ch < target_char) + low = i + 1; + else if (ch > target_char) + high = i; + else + return &charinfo[i]; + } + return NULL; +} + +/*------------------------------------------------- + inputx_key_name - returns the name of a + specific key +-------------------------------------------------*/ + +static const char *inputx_key_name(unicode_char ch) +{ + static char buf[UTF8_CHAR_MAX + 1]; + const char_info *ci; + const char *result; + int pos; + + ci = find_charinfo(ch); + result = ci ? ci->name : NULL; + + if (ci && ci->name) + { + result = ci->name; + } + else + { + if ((ch > 0x7F) || isprint(ch)) + { + pos = utf8_from_uchar(buf, ARRAY_LENGTH(buf), ch); + buf[pos] = '\0'; + result = buf; + } + else + result = "???"; + } + return result; +} + +/*------------------------------------------------- + get_keyboard_key_name - builds the name of + a key based on natural keyboard characters +-------------------------------------------------*/ + +static astring *get_keyboard_key_name(const input_field_config *field) +{ + astring *result = astring_alloc(); + int i; + unicode_char ch; + + + /* loop through each character on the field*/ + for (i = 0; i < ARRAY_LENGTH(field->chars) && (field->chars[i] != '\0'); i++) + { + ch = get_keyboard_code(field, i); + astring_printf(result, "%s%-*s ", astring_c(result), MAX(SPACE_COUNT - 1, 0), inputx_key_name(ch)); + } + + /* trim extra spaces */ + astring_trimspace(result); + + /* special case */ + if (astring_len(result) == 0) + astring_cpyc(result, "Unnamed Key"); + + return result; +} + +/*------------------------------------------------- + init_port_state - initialize the live port + states based on the tokens +-------------------------------------------------*/ + +inline const char *get_device_tag(const device_t &device, const char *tag, astring &finaltag) +{ + if (strcmp(tag, DEVICE_SELF) == 0) + finaltag.cpy(device.tag()); + else if (strcmp(tag, DEVICE_SELF_OWNER) == 0) + { + assert(device.owner() != NULL); + finaltag.cpy(device.owner()->tag()); + } + else + device.subtag(finaltag, tag); + return finaltag; +} + +static void init_port_state(running_machine &machine) +{ + const char *joystick_map_default = machine.options().joystick_map(); + input_port_private *portdata = machine.input_port_data; + input_field_config *field; + input_port_config *port; + + /* allocate live structures to mirror the configuration */ + for (port = machine.m_portlist.first(); port != NULL; port = port->next()) + { + analog_field_state **analogstatetail; + device_field_info **readdevicetail; + device_field_info **writedevicetail; + input_port_state *portstate; + + /* allocate a new input_port_info structure */ + portstate = auto_alloc_clear(machine, input_port_state); + ((input_port_config *)port)->state = portstate; + + /* start with tail pointers to all the data */ + analogstatetail = &portstate->analoglist; + readdevicetail = &portstate->readdevicelist; + writedevicetail = &portstate->writedevicelist; + + /* iterate over fields */ + for (field = port->first_field(); field != NULL; field = field->next()) + { + input_field_state *fieldstate; + int seqtype; + + /* allocate a new input_field_info structure */ + fieldstate = auto_alloc_clear(machine, input_field_state); + ((input_field_config *)field)->state = fieldstate; + + /* fill in the basic values */ + for (seqtype = 0; seqtype < ARRAY_LENGTH(fieldstate->seq); seqtype++) + fieldstate->seq[seqtype] = field->seq[seqtype]; + fieldstate->value = field->defvalue; + + /* if this is an analog field, allocate memory for the analog data */ + if (field->type >= __ipt_analog_start && field->type <= __ipt_analog_end) + { + *analogstatetail = fieldstate->analog = init_field_analog_state(field); + analogstatetail = &(*analogstatetail)->next; + } + + /* if this is a digital joystick field, make a note of it */ + if (field->type >= __ipt_digital_joystick_start && field->type <= __ipt_digital_joystick_end) + { + fieldstate->joystick = &portdata->joystick_info[field->player][(field->type - __ipt_digital_joystick_start) / 4]; + fieldstate->joydir = (field->type - __ipt_digital_joystick_start) % 4; + fieldstate->joystick->field[fieldstate->joydir] = field; + fieldstate->joystick->inuse = TRUE; + } + + /* if this entry has device input, allocate memory for the tracking structure */ + astring devicetag; + if (!field->read.isnull()) + { + *readdevicetail = init_field_device_info(field, get_device_tag(port->owner(), field->read_device, devicetag)); + field->read.late_bind(*(*readdevicetail)->device); + readdevicetail = &(*readdevicetail)->next; + } + + /* if this entry has device output, allocate memory for the tracking structure */ + if (!field->write.isnull()) + { + *writedevicetail = init_field_device_info(field, get_device_tag(port->owner(), field->write_device, devicetag)); + field->write.late_bind(*(*writedevicetail)->device); + writedevicetail = &(*writedevicetail)->next; + } + + /* if this entry has device output, allocate memory for the tracking structure */ + if (!field->crossmapper.isnull()) + { + device_t *device = machine.device(get_device_tag(port->owner(), field->crossmapper_device, devicetag)); + field->crossmapper.late_bind(*device); + } + + /* Name keyboard key names */ + if ((field->type == IPT_KEYBOARD || field->type == IPT_KEYPAD) && (field->name == NULL)) + { + astring *name = get_keyboard_key_name(field); + if (name != NULL) + { + field->state->name = auto_strdup(machine, astring_c(name)); + astring_free(name); + } + } + } + } + + /* handle autoselection of devices */ + init_autoselect_devices(machine, IPT_PADDLE, IPT_PADDLE_V, 0, OPTION_PADDLE_DEVICE, "paddle"); + init_autoselect_devices(machine, IPT_AD_STICK_X, IPT_AD_STICK_Y, IPT_AD_STICK_Z, OPTION_ADSTICK_DEVICE, "analog joystick"); + init_autoselect_devices(machine, IPT_LIGHTGUN_X, IPT_LIGHTGUN_Y, 0, OPTION_LIGHTGUN_DEVICE, "lightgun"); + init_autoselect_devices(machine, IPT_PEDAL, IPT_PEDAL2, IPT_PEDAL3, OPTION_PEDAL_DEVICE, "pedal"); + init_autoselect_devices(machine, IPT_DIAL, IPT_DIAL_V, 0, OPTION_DIAL_DEVICE, "dial"); + init_autoselect_devices(machine, IPT_TRACKBALL_X, IPT_TRACKBALL_Y, 0, OPTION_TRACKBALL_DEVICE, "trackball"); + init_autoselect_devices(machine, IPT_POSITIONAL, IPT_POSITIONAL_V, 0, OPTION_POSITIONAL_DEVICE, "positional"); + init_autoselect_devices(machine, IPT_MOUSE_X, IPT_MOUSE_Y, 0, OPTION_MOUSE_DEVICE, "mouse"); + + /* look for 4-way joysticks and change the default map if we find any */ + if (joystick_map_default[0] == 0 || strcmp(joystick_map_default, "auto") == 0) + for (port = machine.m_portlist.first(); port != NULL; port = port->next()) + for (field = port->first_field(); field != NULL; field = field->next()) + if (field->state->joystick != NULL && field->way == 4) + { + machine.input().set_global_joystick_map((field->flags & FIELD_FLAG_ROTATED) ? joystick_map_4way_diagonal : joystick_map_4way_sticky); + break; + } +} + + +/*------------------------------------------------- + init_autoselect_devices - autoselect a single + device based on the input port list passed + in and the corresponding option +-------------------------------------------------*/ + +static void init_autoselect_devices(running_machine &machine, int type1, int type2, int type3, const char *option, const char *ananame) +{ + const ioport_list &portlist = machine.m_portlist; + const char *stemp = machine.options().value(option); + input_device_class autoenable = DEVICE_CLASS_KEYBOARD; + const char *autostring = "keyboard"; + const input_field_config *field; + const input_port_config *port; + + /* if nothing specified, ignore the option */ + if (stemp[0] == 0) + return; + + /* extract valid strings */ + if (strcmp(stemp, "mouse") == 0) + { + autoenable = DEVICE_CLASS_MOUSE; + autostring = "mouse"; + } + else if (strcmp(stemp, "joystick") == 0) + { + autoenable = DEVICE_CLASS_JOYSTICK; + autostring = "joystick"; + } + else if (strcmp(stemp, "lightgun") == 0) + { + autoenable = DEVICE_CLASS_LIGHTGUN; + autostring = "lightgun"; + } + else if (strcmp(stemp, "none") == 0) + { + /* nothing specified */ + return; + } + else if (strcmp(stemp, "keyboard") != 0) + mame_printf_error("Invalid %s value %s; reverting to keyboard\n", option, stemp); + + /* only scan the list if we haven't already enabled this class of control */ + if (portlist.first() != NULL && !machine.input().device_class(autoenable).enabled()) + for (port = portlist.first(); port != NULL; port = port->next()) + for (field = port->first_field(); field != NULL; field = field->next()) + + /* if this port type is in use, apply the autoselect criteria */ + if ((type1 != 0 && field->type == type1) || + (type2 != 0 && field->type == type2) || + (type3 != 0 && field->type == type3)) + { + mame_printf_verbose("Input: Autoenabling %s due to presence of a %s\n", autostring, ananame); + machine.input().device_class(autoenable).enable(); + break; + } +} + + +/*------------------------------------------------- + init_field_device_info - allocate and populate + information about a device callback +-------------------------------------------------*/ + +static device_field_info *init_field_device_info(const input_field_config *field, const char *device_name) +{ + device_field_info *info; + input_port_value mask; + + /* allocate memory */ + info = auto_alloc_clear(field->machine(), device_field_info); + + /* fill in the data */ + info->field = field; + for (mask = field->mask; !(mask & 1); mask >>= 1) + info->shift++; + + info->device = (device_name != NULL) ? field->machine().device(device_name) : &field->port().owner(); + + info->oldval = field->defvalue >> info->shift; + return info; +} + + +/*------------------------------------------------- + init_field_analog_state - allocate and populate + information about an analog port +-------------------------------------------------*/ + +static analog_field_state *init_field_analog_state(const input_field_config *field) +{ + analog_field_state *state; + input_port_value mask; + + /* allocate memory */ + state = auto_alloc_clear(field->machine(), analog_field_state); + + /* compute the shift amount and number of bits */ + for (mask = field->mask; !(mask & 1); mask >>= 1) + state->shift++; + + /* initialize core data */ + state->field = field; + state->adjdefvalue = (field->defvalue & field->mask) >> state->shift; + state->adjmin = (field->min & field->mask) >> state->shift; + state->adjmax = (field->max & field->mask) >> state->shift; + state->sensitivity = field->sensitivity; + state->reverse = ((field->flags & ANALOG_FLAG_REVERSE) != 0); + state->delta = field->delta; + state->centerdelta = field->centerdelta; + state->minimum = INPUT_ABSOLUTE_MIN; + state->maximum = INPUT_ABSOLUTE_MAX; + + /* set basic parameters based on the configured type */ + switch (field->type) + { + /* pedals start at and autocenter to the min range */ + case IPT_PEDAL: + case IPT_PEDAL2: + case IPT_PEDAL3: + state->center = INPUT_ABSOLUTE_MIN; + state->accum = APPLY_INVERSE_SENSITIVITY(state->center, state->sensitivity); + state->absolute = TRUE; + state->autocenter = TRUE; + state->interpolate = TRUE; + break; + + /* paddles and analog joysticks are absolute and autocenter */ + case IPT_AD_STICK_X: + case IPT_AD_STICK_Y: + case IPT_AD_STICK_Z: + case IPT_PADDLE: + case IPT_PADDLE_V: + state->absolute = TRUE; + state->autocenter = TRUE; + state->interpolate = TRUE; + break; + + /* lightguns are absolute as well, but don't autocenter and don't interpolate their values */ + case IPT_LIGHTGUN_X: + case IPT_LIGHTGUN_Y: + state->absolute = TRUE; + state->autocenter = FALSE; + state->interpolate = FALSE; + break; + + /* dials, mice and trackballs are relative devices */ + /* these have fixed "min" and "max" values based on how many bits are in the port */ + /* in addition, we set the wrap around min/max values to 512 * the min/max values */ + /* this takes into account the mapping that one mouse unit ~= 512 analog units */ + case IPT_DIAL: + case IPT_DIAL_V: + case IPT_MOUSE_X: + case IPT_MOUSE_Y: + case IPT_TRACKBALL_X: + case IPT_TRACKBALL_Y: + state->absolute = FALSE; + state->wraps = TRUE; + state->interpolate = TRUE; + break; + + /* positional devices are abolute, but can also wrap like relative devices */ + /* set each position to be 512 units */ + case IPT_POSITIONAL: + case IPT_POSITIONAL_V: + state->positionalscale = COMPUTE_SCALE(field->max, INPUT_ABSOLUTE_MAX - INPUT_ABSOLUTE_MIN); + state->adjmin = 0; + state->adjmax = field->max - 1; + state->wraps = ((field->flags & ANALOG_FLAG_WRAPS) != 0); + state->autocenter = !state->wraps; + break; + + default: + fatalerror("Unknown analog port type -- don't know if it is absolute or not"); + break; + } + + /* further processing for absolute controls */ + if (state->absolute) + { + /* if the default value is pegged at the min or max, use a single scale value for the whole axis */ + state->single_scale = (state->adjdefvalue == state->adjmin) || (state->adjdefvalue == state->adjmax); + + /* if not "single scale", compute separate scales for each side of the default */ + if (!state->single_scale) + { + /* unsigned */ + state->scalepos = COMPUTE_SCALE(state->adjmax - state->adjdefvalue, INPUT_ABSOLUTE_MAX - 0); + state->scaleneg = COMPUTE_SCALE(state->adjdefvalue - state->adjmin, 0 - INPUT_ABSOLUTE_MIN); + + if (state->adjmin > state->adjmax) + state->scaleneg = -state->scaleneg; + + /* reverse point is at center */ + state->reverse_val = 0; + } + else + { + /* single axis that increases from default */ + state->scalepos = COMPUTE_SCALE(state->adjmax - state->adjmin, INPUT_ABSOLUTE_MAX - INPUT_ABSOLUTE_MIN); + + /* move from default */ + if (state->adjdefvalue == state->adjmax) + state->scalepos = -state->scalepos; + + /* make the scaling the same for easier coding when we need to scale */ + state->scaleneg = state->scalepos; + + /* reverse point is at max */ + state->reverse_val = state->maximum; + } + } + + /* relative and positional controls all map directly with a 512x scale factor */ + else + { + /* The relative code is set up to allow specifing PORT_MINMAX and default values. */ + /* The validity checks are purposely set up to not allow you to use anything other */ + /* a default of 0 and PORT_MINMAX(0,mask). This is in case the need arises to use */ + /* this feature in the future. Keeping the code in does not hurt anything. */ + if (state->adjmin > state->adjmax) + /* adjust for signed */ + state->adjmin = -state->adjmin; + + state->minimum = (state->adjmin - state->adjdefvalue) * INPUT_RELATIVE_PER_PIXEL; + state->maximum = (state->adjmax - state->adjdefvalue) * INPUT_RELATIVE_PER_PIXEL; + + /* make the scaling the same for easier coding when we need to scale */ + state->scaleneg = state->scalepos = COMPUTE_SCALE(1, INPUT_RELATIVE_PER_PIXEL); + + if (field->flags & ANALOG_FLAG_RESET) + /* delta values reverse from center */ + state->reverse_val = 0; + else + { + /* positional controls reverse from their max range */ + state->reverse_val = state->maximum + state->minimum; + + /* relative controls reverse from 1 past their max range */ + if (state->positionalscale == 0) + state->reverse_val += INPUT_RELATIVE_PER_PIXEL; + } + } + + /* compute scale for keypresses */ + state->keyscalepos = RECIP_SCALE(state->scalepos); + state->keyscaleneg = RECIP_SCALE(state->scaleneg); + + return state; +} + + + +/*************************************************************************** + ONCE-PER-FRAME UPDATES +***************************************************************************/ + +/*------------------------------------------------- + frame_update_callback - system-wide callback to + update the input ports once per frame, but + only if we are not paused +-------------------------------------------------*/ + +static void frame_update_callback(running_machine &machine) +{ + /* if we're paused, don't do anything */ + if (machine.paused()) + return; + + /* otherwise, use the common code */ + frame_update(machine); +} + +static key_buffer *get_buffer(running_machine &machine) +{ + input_port_private *portdata = machine.input_port_data; + assert(inputx_can_post(machine)); + return (key_buffer *)portdata->keybuffer; +} + + + +static const inputx_code *find_code(inputx_code *codes, unicode_char ch) +{ + int i; + + assert(codes); + for (i = 0; codes[i].ch; i++) + { + if (codes[i].ch == ch) + return &codes[i]; + } + return NULL; +} + +/*------------------------------------------------- + input_port_update_hook - hook function + called from core to allow for natural keyboard +-------------------------------------------------*/ + +static void input_port_update_hook(running_machine &machine, const input_port_config *port, input_port_value *digital) +{ + input_port_private *portdata = machine.input_port_data; + const key_buffer *keybuf; + const inputx_code *code; + unicode_char ch; + int i; + UINT32 value; + + if (inputx_can_post(machine)) + { + keybuf = get_buffer(machine); + + /* is the key down right now? */ + if (keybuf && keybuf->status_keydown && (keybuf->begin_pos != keybuf->end_pos)) + { + /* identify the character that is down right now, and its component codes */ + ch = keybuf->buffer[keybuf->begin_pos]; + code = find_code(portdata->codes, ch); + + /* loop through this character's component codes */ + if (code != NULL) + { + for (i = 0; i < ARRAY_LENGTH(code->field) && (code->field[i] != NULL); i++) + { + if (&code->field[i]->port() == port) + { + value = code->field[i]->mask; + *digital |= value; + } + } + } + } + } +} + + +/*------------------------------------------------- + frame_update - core logic for per-frame input + port updating +-------------------------------------------------*/ + +static void frame_update(running_machine &machine) +{ + input_port_private *portdata = machine.input_port_data; + const input_field_config *mouse_field = NULL; + int ui_visible = ui_is_menu_active(); + attotime curtime = machine.time(); + const input_port_config *port; + render_target *mouse_target; + INT32 mouse_target_x; + INT32 mouse_target_y; + int mouse_button; + +g_profiler.start(PROFILER_INPUT); + + /* record/playback information about the current frame */ + playback_frame(machine, curtime); + record_frame(machine, curtime); + + /* track the duration of the previous frame */ + portdata->last_delta_nsec = (curtime - portdata->last_frame_time).as_attoseconds() / ATTOSECONDS_PER_NANOSECOND; + portdata->last_frame_time = curtime; + + /* update the digital joysticks */ + frame_update_digital_joysticks(machine); + + /* compute default values for all the ports */ + input_port_update_defaults(machine); + + /* perform the mouse hit test */ + mouse_target = ui_input_find_mouse(machine, &mouse_target_x, &mouse_target_y, &mouse_button); + if (mouse_button && mouse_target) + { + const char *tag = NULL; + input_port_value mask; + float x, y; + if (mouse_target->map_point_input(mouse_target_x, mouse_target_y, tag, mask, x, y)) + mouse_field = input_field_by_tag_and_mask(machine.m_portlist, tag, mask); + } + + /* loop over all input ports */ + for (port = machine.m_portlist.first(); port != NULL; port = port->next()) + { + const input_field_config *field; + device_field_info *device_field; + input_port_value newvalue; + + /* start with 0 values for the digital and VBLANK bits */ + port->state->digital = 0; + port->state->vblank = 0; + + /* now loop back and modify based on the inputs */ + for (field = port->first_field(); field != NULL; field = field->next()) + if (input_condition_true(port->machine(), &field->condition, port->owner())) + { + /* accumulate VBLANK bits */ + if (field->type == IPT_VBLANK) + port->state->vblank ^= field->mask; + + /* handle analog inputs */ + else if (field->state->analog != NULL) + frame_update_analog_field(machine, field->state->analog); + + /* handle non-analog types, but only when the UI isn't visible */ + else if (!ui_visible && frame_get_digital_field_state(field, field == mouse_field)) + port->state->digital |= field->mask; + } + + /* hook for MESS's natural keyboard support */ + input_port_update_hook(machine, port, &port->state->digital); + + /* handle playback/record */ + playback_port(port); + record_port(port); + + /* call device line write handlers */ + newvalue = input_port_read_direct(port); + for (device_field = port->state->writedevicelist; device_field; device_field = device_field->next) + if (device_field->field->type != IPT_OUTPUT && input_condition_true(port->machine(), &device_field->field->condition, port->owner())) + { + input_port_value newval = (newvalue & device_field->field->mask) >> device_field->shift; + + /* if the bits have write, call the handler */ + if (device_field->oldval != newval) + { + device_field->field->write(*device_field->field, device_field->field->write_param, device_field->oldval, newval); + + device_field->oldval = newval; + } + } + } + +g_profiler.stop(); +} + + +/*------------------------------------------------- + frame_update_digital_joysticks - update the + state of digital joysticks prior to + accumulating the results in a port +-------------------------------------------------*/ + +static void frame_update_digital_joysticks(running_machine &machine) +{ + input_port_private *portdata = machine.input_port_data; + int player, joyindex; + + /* loop over all the joysticks */ + for (player = 0; player < MAX_PLAYERS; player++) + for (joyindex = 0; joyindex < DIGITAL_JOYSTICKS_PER_PLAYER; joyindex++) + { + digital_joystick_state *joystick = &portdata->joystick_info[player][joyindex]; + if (joystick->inuse) + { + joystick->previous = joystick->current; + joystick->current = 0; + + /* read all the associated ports */ + if (joystick->field[JOYDIR_UP] != NULL && machine.input().seq_pressed(input_field_seq(joystick->field[JOYDIR_UP], SEQ_TYPE_STANDARD))) + joystick->current |= JOYDIR_UP_BIT; + if (joystick->field[JOYDIR_DOWN] != NULL && machine.input().seq_pressed(input_field_seq(joystick->field[JOYDIR_DOWN], SEQ_TYPE_STANDARD))) + joystick->current |= JOYDIR_DOWN_BIT; + if (joystick->field[JOYDIR_LEFT] != NULL && machine.input().seq_pressed(input_field_seq(joystick->field[JOYDIR_LEFT], SEQ_TYPE_STANDARD))) + joystick->current |= JOYDIR_LEFT_BIT; + if (joystick->field[JOYDIR_RIGHT] != NULL && machine.input().seq_pressed(input_field_seq(joystick->field[JOYDIR_RIGHT], SEQ_TYPE_STANDARD))) + joystick->current |= JOYDIR_RIGHT_BIT; + + /* lock out opposing directions (left + right or up + down) */ + if ((joystick->current & (JOYDIR_UP_BIT | JOYDIR_DOWN_BIT)) == (JOYDIR_UP_BIT | JOYDIR_DOWN_BIT)) + joystick->current &= ~(JOYDIR_UP_BIT | JOYDIR_DOWN_BIT); + if ((joystick->current & (JOYDIR_LEFT_BIT | JOYDIR_RIGHT_BIT)) == (JOYDIR_LEFT_BIT | JOYDIR_RIGHT_BIT)) + joystick->current &= ~(JOYDIR_LEFT_BIT | JOYDIR_RIGHT_BIT); + + /* only update 4-way case if joystick has moved */ + if (joystick->current != joystick->previous) + { + joystick->current4way = joystick->current; + + /* + If joystick is pointing at a diagonal, acknowledge that the player moved + the joystick by favoring a direction change. This minimizes frustration + when using a keyboard for input, and maximizes responsiveness. + + For example, if you are holding "left" then switch to "up" (where both left + and up are briefly pressed at the same time), we'll transition immediately + to "up." + + Zero any switches that didn't change from the previous to current state. + */ + if ((joystick->current4way & (JOYDIR_UP_BIT | JOYDIR_DOWN_BIT)) && + (joystick->current4way & (JOYDIR_LEFT_BIT | JOYDIR_RIGHT_BIT))) + { + joystick->current4way ^= joystick->current4way & joystick->previous; + } + + /* + If we are still pointing at a diagonal, we are in an indeterminant state. + + This could happen if the player moved the joystick from the idle position directly + to a diagonal, or from one diagonal directly to an extreme diagonal. + + The chances of this happening with a keyboard are slim, but we still need to + constrain this case. + + For now, just resolve randomly. + */ + if ((joystick->current4way & (JOYDIR_UP_BIT | JOYDIR_DOWN_BIT)) && + (joystick->current4way & (JOYDIR_LEFT_BIT | JOYDIR_RIGHT_BIT))) + { + if (machine.rand() & 1) + joystick->current4way &= ~(JOYDIR_LEFT_BIT | JOYDIR_RIGHT_BIT); + else + joystick->current4way &= ~(JOYDIR_UP_BIT | JOYDIR_DOWN_BIT); + } + } + } + } +} + + +/*------------------------------------------------- + frame_update_analog_field - update the + internals of a single analog field +-------------------------------------------------*/ + +static void frame_update_analog_field(running_machine &machine, analog_field_state *analog) +{ + input_item_class itemclass; + int keypressed = FALSE; + INT64 keyscale; + INT32 rawvalue; + INT32 delta = 0; + + /* clamp the previous value to the min/max range and remember it */ + analog->previous = analog->accum = apply_analog_min_max(analog, analog->accum); + + /* get the new raw analog value and its type */ + rawvalue = machine.input().seq_axis_value(input_field_seq(analog->field, SEQ_TYPE_STANDARD), itemclass); + + /* if we got an absolute input, it overrides everything else */ + if (itemclass == ITEM_CLASS_ABSOLUTE) + { + if (analog->previousanalog != rawvalue) + { + /* only update if analog value changed */ + analog->previousanalog = rawvalue; + + /* apply the inverse of the sensitivity to the raw value so that */ + /* it will still cover the full min->max range requested after */ + /* we apply the sensitivity adjustment */ + if (analog->absolute || (analog->field->flags & ANALOG_FLAG_RESET)) + { + /* if port is absolute, then just return the absolute data supplied */ + analog->accum = APPLY_INVERSE_SENSITIVITY(rawvalue, analog->sensitivity); + } + else if (analog->positionalscale != 0) + { + /* if port is positional, we will take the full analog control and divide it */ + /* into positions, that way as the control is moved full scale, */ + /* it moves through all the positions */ + rawvalue = APPLY_SCALE(rawvalue - INPUT_ABSOLUTE_MIN, analog->positionalscale) * INPUT_RELATIVE_PER_PIXEL + analog->minimum; + + /* clamp the high value so it does not roll over */ + rawvalue = MIN(rawvalue, analog->maximum); + analog->accum = APPLY_INVERSE_SENSITIVITY(rawvalue, analog->sensitivity); + } + else + /* if port is relative, we use the value to simulate the speed of relative movement */ + /* sensitivity adjustment is allowed for this mode */ + analog->accum += rawvalue; + + analog->lastdigital = 0; + /* do not bother with other control types if the analog data is changing */ + return; + } + else + { + /* we still have to update fake relative from joystick control */ + if (!analog->absolute && analog->positionalscale == 0) + analog->accum += rawvalue; + } + } + + /* if we got it from a relative device, use that as the starting delta */ + /* also note that the last input was not a digital one */ + if (itemclass == ITEM_CLASS_RELATIVE && rawvalue != 0) + { + delta = rawvalue; + analog->lastdigital = 0; + } + + keyscale = (analog->accum >= 0) ? analog->keyscalepos : analog->keyscaleneg; + + /* if the decrement code sequence is pressed, add the key delta to */ + /* the accumulated delta; also note that the last input was a digital one */ + if (machine.input().seq_pressed(input_field_seq(analog->field, SEQ_TYPE_DECREMENT))) + { + keypressed = TRUE; + if (analog->delta != 0) + delta -= APPLY_SCALE(analog->delta, keyscale); + else if (!analog->lastdigital) + /* decrement only once when first pressed */ + delta -= APPLY_SCALE(1, keyscale); + analog->lastdigital = TRUE; + } + + /* same for the increment code sequence */ + if (machine.input().seq_pressed(input_field_seq(analog->field, SEQ_TYPE_INCREMENT))) + { + keypressed = TRUE; + if (analog->delta) + delta += APPLY_SCALE(analog->delta, keyscale); + else if (!analog->lastdigital) + /* increment only once when first pressed */ + delta += APPLY_SCALE(1, keyscale); + analog->lastdigital = TRUE; + } + + /* if resetting is requested, clear the accumulated position to 0 before */ + /* applying the deltas so that we only return this frame's delta */ + /* note that centering only works for relative controls */ + /* no need to check if absolute here because it is checked by the validity tests */ + if (analog->field->flags & ANALOG_FLAG_RESET) + analog->accum = 0; + + /* apply the delta to the accumulated value */ + analog->accum += delta; + + /* if our last movement was due to a digital input, and if this control */ + /* type autocenters, and if neither the increment nor the decrement seq */ + /* was pressed, apply autocentering */ + if (analog->autocenter) + { + INT32 center = APPLY_INVERSE_SENSITIVITY(analog->center, analog->sensitivity); + if (analog->lastdigital && !keypressed) + { + /* autocenter from positive values */ + if (analog->accum >= center) + { + analog->accum -= APPLY_SCALE(analog->centerdelta, analog->keyscalepos); + if (analog->accum < center) + { + analog->accum = center; + analog->lastdigital = FALSE; + } + } + + /* autocenter from negative values */ + else + { + analog->accum += APPLY_SCALE(analog->centerdelta, analog->keyscaleneg); + if (analog->accum > center) + { + analog->accum = center; + analog->lastdigital = FALSE; + } + } + } + } + else if (!keypressed) + analog->lastdigital = FALSE; +} + + +/*------------------------------------------------- + frame_get_digital_field_state - get the state + of a digital field +-------------------------------------------------*/ + +static int frame_get_digital_field_state(const input_field_config *field, int mouse_down) +{ + int curstate = mouse_down || field->machine().input().seq_pressed(input_field_seq(field, SEQ_TYPE_STANDARD)); + int changed = FALSE; + + /* if the state changed, look for switch down/switch up */ + if (curstate != field->state->last) + { + field->state->last = curstate; + changed = TRUE; + } + + if (field->type == IPT_KEYBOARD && ui_get_use_natural_keyboard(field->machine())) + return FALSE; + + /* if this is a switch-down event, handle impulse and toggle */ + if (changed && curstate) + { + /* impluse controls: reset the impulse counter */ + if (field->impulse != 0 && field->state->impulse == 0) + field->state->impulse = field->impulse; + + /* toggle controls: flip the toggle state or advance to the next setting */ + if (field->flags & FIELD_FLAG_TOGGLE) + { + if (field->settinglist().count() == 0) + field->state->value ^= field->mask; + else + input_field_select_next_setting(field); + } + } + + /* update the current state with the impulse state */ + if (field->impulse != 0) + { + if (field->state->impulse != 0) + { + field->state->impulse--; + curstate = TRUE; + } + else + curstate = FALSE; + } + + /* for toggle switches, the current value is folded into the port's default value */ + /* so we always return FALSE here */ + if (field->flags & FIELD_FLAG_TOGGLE) + curstate = FALSE; + + /* additional logic to restrict digital joysticks */ + if (curstate && !mouse_down && field->state->joystick != NULL && field->way != 16) + { + UINT8 mask = (field->way == 4) ? field->state->joystick->current4way : field->state->joystick->current; + if (!(mask & (1 << field->state->joydir))) + curstate = FALSE; + } + + /* skip locked-out coin inputs */ + if (curstate && field->type >= IPT_COIN1 && field->type <= IPT_COIN12 && coin_lockout_get_state(field->machine(), field->type - IPT_COIN1) && field->machine().options().coin_lockout()) + { + int verbose = field->machine().options().verbose(); +#ifdef MAME_DEBUG + verbose = 1; +#endif + if (verbose) + ui_popup_time(3, "Coinlock disabled %s.", input_field_name(field)); + return FALSE; + } + return curstate; +} + + + +/*************************************************************************** + PORT CONFIGURATION HELPERS +***************************************************************************/ + +/*------------------------------------------------- + port_default_value - updates default value + of port settings according to device settings +-------------------------------------------------*/ + +UINT32 port_default_value(const char *fulltag, UINT32 mask, UINT32 defval, device_t &owner) +{ + astring tempstring; + const input_device_default *def = NULL; + def = owner.input_ports_defaults(); + if (def!=NULL) { + while (def->tag!=NULL) { + if ((strcmp(fulltag,owner.subtag(tempstring,def->tag))==0) && (def->mask == mask)) { + return def->defvalue; + } + def++; + } + } + return defval; +} + + +/*------------------------------------------------- + input_port_config - constructor for an + I/O port configuration object +-------------------------------------------------*/ + +input_port_config::input_port_config(device_t &owner, const char *tag) + : state(NULL), + active(0), + m_next(NULL), + m_owner(owner), + m_tag(tag), + m_modcount(0) +{ +} + + +running_machine &input_port_config::machine() const +{ + return m_owner.machine(); +} + + +/*------------------------------------------------- + field_config_alloc - allocate a new input + port field config +-------------------------------------------------*/ + +input_field_config::input_field_config(input_port_config &port, int _type, input_port_value _defvalue, input_port_value _maskbits, const char *_name) + : mask(_maskbits), + defvalue(_defvalue & _maskbits), + type(_type), + player(0), + flags(0), + impulse(0), + name(_name), + read_param(NULL), + read_device(DEVICE_SELF), + write_param(NULL), + write_device(DEVICE_SELF), + min(0), + max(_maskbits), + sensitivity(0), + delta(0), + centerdelta(0), + crossaxis(0), + crossscale(0), + crossoffset(0), + crossaltaxis(0), + crossmapper_device(DEVICE_SELF), + full_turn_count(0), + remap_table(NULL), + way(0), + state(NULL), + m_next(NULL), + m_port(port), + m_modcount(port.modcount()) +{ + memset(&condition, 0, sizeof(condition)); + for (int seqtype = 0; seqtype < ARRAY_LENGTH(seq); seqtype++) + seq[seqtype].set_default(); + chars[0] = chars[1] = chars[2] = (unicode_char) 0; +} + + +input_setting_config::input_setting_config(input_field_config &field, input_port_value _value, const char *_name) + : value(_value), + name(_name), + m_field(field), + m_next(NULL) +{ + memset(&condition, 0, sizeof(condition)); +} + +input_field_diplocation::input_field_diplocation(const char *string, UINT8 _swnum, bool _invert) + : swname(string), + swnum(_swnum), + invert(_invert) +{ +} + +/*------------------------------------------------- + field_config_insert - insert an allocated + input port field config, replacing any + intersecting fields already present and + inserting at the correct sorted location +-------------------------------------------------*/ + +void input_port_config::collapse_fields(astring &errorbuf) +{ + input_field_config *list = m_fieldlist.detach_all(); + input_port_value maskbits = 0; + int lastmodcount = -1; + while (list != NULL) + { + if (list->modcount() != lastmodcount) + { + lastmodcount = list->modcount(); + maskbits = 0; + } + input_field_config *current = list; + list = list->next(); + field_config_insert(*current, maskbits, errorbuf); + } +} + +void field_config_insert(input_field_config &newfield, input_port_value &disallowedbits, astring &errorbuf) +{ + input_port_value lowbit; + + /* verify against the disallowed bits, but only if we are condition-free */ + if (newfield.condition.condition == PORTCOND_ALWAYS) + { + if ((newfield.mask & disallowedbits) != 0) + errorbuf.catprintf("INPUT_TOKEN_FIELD specifies duplicate port bits (port=%s mask=%X)\n", newfield.port().tag(), newfield.mask); + disallowedbits |= newfield.mask; + } + + /* first modify/nuke any entries that intersect our maskbits */ + input_field_config *nextfield; + for (input_field_config *field = newfield.port().fieldlist().first(); field != NULL; field = nextfield) + { + nextfield = field->next(); + if ((field->mask & newfield.mask) != 0 && (newfield.condition.condition == PORTCOND_ALWAYS || + field->condition.condition == PORTCOND_ALWAYS || + condition_equal(&field->condition, &newfield.condition))) + { + /* reduce the mask of the field we found */ + field->mask &= ~newfield.mask; + + /* if the new entry fully overrides the previous one, we nuke */ + if (INPUT_PORT_OVERRIDE_FULLY_NUKES_PREVIOUS || field->mask == 0) + newfield.port().fieldlist().remove(*field); + } + } + + /* make a mask of just the low bit */ + lowbit = (newfield.mask ^ (newfield.mask - 1)) & newfield.mask; + + /* scan forward to find where to insert ourselves */ + input_field_config *field; + for (field = newfield.port().fieldlist().first(); field != NULL; field = field->next()) + if (field->mask > lowbit) + break; + + /* insert it into the list */ + newfield.port().fieldlist().insert_before(newfield, field); +} + + +/*------------------------------------------------- + diplocation_expand - expand a string-based + DIP location into a linked list of + descriptions +-------------------------------------------------*/ + +void diplocation_list_alloc(input_field_config &field, const char *location, astring &errorbuf) +{ + /* if nothing present, bail */ + if (location == NULL) + return; + + field.diploclist().reset(); + + /* parse the string */ + astring name; // Don't move this variable inside the loop, lastname's lifetime depends on it being outside + const char *lastname = NULL; + const char *curentry = location; + int entries = 0; + while (*curentry != 0) + { + /* find the end of this entry */ + const char *comma = strchr(curentry, ','); + if (comma == NULL) + comma = curentry + strlen(curentry); + + /* extract it to tempbuf */ + astring tempstr; + tempstr.cpy(curentry, comma - curentry); + + /* first extract the switch name if present */ + const char *number = tempstr; + const char *colon = strchr(tempstr, ':'); + + /* allocate and copy the name if it is present */ + if (colon != NULL) + { + lastname = name.cpy(number, colon - number); + number = colon + 1; + } + + /* otherwise, just copy the last name */ + else + { + if (lastname == NULL) + { + errorbuf.catprintf("Switch location '%s' missing switch name!\n", location); + lastname = (char *)"UNK"; + } + name.cpy(lastname); + } + + /* if the number is preceded by a '!' it's active high */ + bool invert = false; + if (*number == '!') + { + invert = true; + number++; + } + + /* now scan the switch number */ + int swnum = -1; + if (sscanf(number, "%d", &swnum) != 1) + errorbuf.catprintf("Switch location '%s' has invalid format!\n", location); + + /* allocate a new entry */ + field.diploclist().append(*global_alloc(input_field_diplocation(name, swnum, invert))); + entries++; + + /* advance to the next item */ + curentry = comma; + if (*curentry != 0) + curentry++; + } + + /* then verify the number of bits in the mask matches */ + input_port_value temp; + int bits; + for (bits = 0, temp = field.mask; temp != 0 && bits < 32; bits++) + temp &= temp - 1; + if (bits != entries) + errorbuf.catprintf("Switch location '%s' does not describe enough bits for mask %X\n", location, field.mask); +} + + + +/*************************************************************************** + TOKENIZATION HELPERS +***************************************************************************/ + +/*------------------------------------------------- + token_to_input_field_type - convert a string + token to an input field type and player +-------------------------------------------------*/ + +static int token_to_input_field_type(running_machine &machine, const char *string, int *player) +{ + input_port_private *portdata = machine.input_port_data; + int ipnum; + + /* check for our failsafe case first */ + if (sscanf(string, "TYPE_OTHER(%d,%d)", &ipnum, player) == 2) + return ipnum; + + /* find the token in the list */ + for (input_type_entry *entry = portdata->typelist.first(); entry != NULL; entry = entry->next()) + if (entry->token != NULL && !strcmp(entry->token, string)) + { + *player = entry->player; + return entry->type; + } + + /* if we fail, return IPT_UNKNOWN */ + *player = 0; + return IPT_UNKNOWN; +} + + +/*------------------------------------------------- + input_field_type_to_token - convert an input + field type and player to a string token +-------------------------------------------------*/ + +static const char *input_field_type_to_token(running_machine &machine, int type, int player) +{ + input_port_private *portdata = machine.input_port_data; + static char tempbuf[32]; + + /* look up the port and return the token */ + input_type_entry *entry = portdata->type_to_entry[type][player]; + if (entry != NULL) + return entry->token; + + /* if that fails, carry on */ + sprintf(tempbuf, "TYPE_OTHER(%d,%d)", type, player); + return tempbuf; +} + + +/*------------------------------------------------- + token_to_seq_type - convert a string to + a sequence type +-------------------------------------------------*/ + +static int token_to_seq_type(const char *string) +{ + int seqindex; + + /* look up the string in the table of possible sequence types and return the index */ + for (seqindex = 0; seqindex < ARRAY_LENGTH(seqtypestrings); seqindex++) + if (!mame_stricmp(string, seqtypestrings[seqindex])) + return seqindex; + + return -1; +} + + + +/*************************************************************************** + SETTINGS LOAD +***************************************************************************/ + +/*------------------------------------------------- + load_config_callback - callback to extract + configuration data from the XML nodes +-------------------------------------------------*/ + +static void load_config_callback(running_machine &machine, int config_type, xml_data_node *parentnode) +{ + input_port_private *portdata = machine.input_port_data; + xml_data_node *portnode; + int seqtype; + + /* in the completion phase, we finish the initialization with the final ports */ + if (config_type == CONFIG_TYPE_FINAL) + { + portdata->safe_to_read = TRUE; + frame_update(machine); + } + + /* early exit if no data to parse */ + if (parentnode == NULL) + return; + + /* iterate over all the remap nodes for controller configs only */ + if (config_type == CONFIG_TYPE_CONTROLLER) + load_remap_table(machine, parentnode); + + /* iterate over all the port nodes */ + for (portnode = xml_get_sibling(parentnode->child, "port"); portnode; portnode = xml_get_sibling(portnode->next, "port")) + { + input_seq newseq[SEQ_TYPE_TOTAL], tempseq; + xml_data_node *seqnode; + int type, player; + + /* get the basic port info from the attributes */ + type = token_to_input_field_type(machine, xml_get_attribute_string(portnode, "type", ""), &player); + + /* initialize sequences to invalid defaults */ + for (seqtype = 0; seqtype < ARRAY_LENGTH(newseq); seqtype++) + newseq[seqtype].set(INPUT_CODE_INVALID); + + /* loop over new sequences */ + for (seqnode = xml_get_sibling(portnode->child, "newseq"); seqnode; seqnode = xml_get_sibling(seqnode->next, "newseq")) + { + /* with a valid type, parse out the new sequence */ + seqtype = token_to_seq_type(xml_get_attribute_string(seqnode, "type", "")); + if (seqtype != -1 && seqnode->value != NULL) + { + if (strcmp(seqnode->value, "NONE") == 0) + newseq[seqtype].set(); + else + machine.input().seq_from_tokens(newseq[seqtype], seqnode->value); + } + } + + /* if we're loading default ports, apply to the defaults */ + if (config_type != CONFIG_TYPE_GAME) + load_default_config(machine, portnode, type, player, newseq); + else + load_game_config(machine, portnode, type, player, newseq); + } + + /* after applying the controller config, push that back into the backup, since that is */ + /* what we will diff against */ + if (config_type == CONFIG_TYPE_CONTROLLER) + for (input_type_entry *entry = portdata->typelist.first(); entry != NULL; entry = entry->next()) + for (seqtype = 0; seqtype < ARRAY_LENGTH(entry->seq); seqtype++) + entry->defseq[seqtype] = entry->seq[seqtype]; +} + + +/*------------------------------------------------- + load_remap_table - extract and apply the + global remapping table +-------------------------------------------------*/ + +static void load_remap_table(running_machine &machine, xml_data_node *parentnode) +{ + input_port_private *portdata = machine.input_port_data; + input_code *oldtable, *newtable; + xml_data_node *remapnode; + int count; + + /* count items first so we can allocate */ + count = 0; + for (remapnode = xml_get_sibling(parentnode->child, "remap"); remapnode != NULL; remapnode = xml_get_sibling(remapnode->next, "remap")) + count++; + + /* if we have some, deal with them */ + if (count > 0) + { + int remapnum; + + /* allocate tables */ + oldtable = global_alloc_array(input_code, count); + newtable = global_alloc_array(input_code, count); + + /* build up the remap table */ + count = 0; + for (remapnode = xml_get_sibling(parentnode->child, "remap"); remapnode != NULL; remapnode = xml_get_sibling(remapnode->next, "remap")) + { + input_code origcode = machine.input().code_from_token(xml_get_attribute_string(remapnode, "origcode", "")); + input_code newcode = machine.input().code_from_token(xml_get_attribute_string(remapnode, "newcode", "")); + if (origcode != INPUT_CODE_INVALID && newcode != INPUT_CODE_INVALID) + { + oldtable[count] = origcode; + newtable[count] = newcode; + count++; + } + } + + /* loop over the remapping table, operating only if something was specified */ + for (remapnum = 0; remapnum < count; remapnum++) + { + input_code oldcode = oldtable[remapnum]; + input_code newcode = newtable[remapnum]; + + /* loop over all default ports, remapping the requested keys */ + for (input_type_entry *entry = portdata->typelist.first(); entry != NULL; entry = entry->next()) + { + /* remap anything in the default sequences */ + for (int seqtype = 0; seqtype < ARRAY_LENGTH(entry->seq); seqtype++) + entry->seq[seqtype].replace(oldcode, newcode); + } + } + + /* release the tables */ + global_free(oldtable); + global_free(newtable); + } +} + + +/*------------------------------------------------- + load_default_config - apply configuration + data to the default mappings +-------------------------------------------------*/ + +static int load_default_config(running_machine &machine, xml_data_node *portnode, int type, int player, const input_seq *newseq) +{ + input_port_private *portdata = machine.input_port_data; + + /* find a matching port in the list */ + for (input_type_entry *entry = portdata->typelist.first(); entry != NULL; entry = entry->next()) + if (entry->type == type && entry->player == player) + { + for (int seqtype = 0; seqtype < ARRAY_LENGTH(entry->seq); seqtype++) + if (newseq[seqtype][0] != INPUT_CODE_INVALID) + entry->seq[seqtype] = newseq[seqtype]; + return TRUE; + } + + return FALSE; +} + + +/*------------------------------------------------- + load_game_config - apply configuration + data to the current set of input ports +-------------------------------------------------*/ + +static int load_game_config(running_machine &machine, xml_data_node *portnode, int type, int player, const input_seq *newseq) +{ + input_port_value mask, defvalue; + const input_field_config *field; + const input_port_config *port; + char tempbuffer[20]; + const char *tag; + + /* read the mask, index, and defvalue attributes */ + tag = xml_get_attribute_string(portnode, "tag", NULL); + mask = xml_get_attribute_int(portnode, "mask", 0); + defvalue = xml_get_attribute_int(portnode, "defvalue", 0); + + /* find the port we want; if no tag, search them all */ + for (port = machine.m_portlist.first(); port != NULL; port = port->next()) + if (tag == NULL || strcmp(get_port_tag(port, tempbuffer), tag) == 0) + for (field = port->first_field(); field != NULL; field = field->next()) + + /* find the matching mask and defvalue */ + if (field->type == type && field->player == player && + field->mask == mask && (field->defvalue & mask) == (defvalue & mask)) + { + const char *revstring; + int seqtype; + + /* if a sequence was specified, copy it in */ + for (seqtype = 0; seqtype < ARRAY_LENGTH(field->state->seq); seqtype++) + if (newseq[seqtype][0] != INPUT_CODE_INVALID) + field->state->seq[seqtype] = newseq[seqtype]; + + /* for non-analog fields, fetch the value */ + if (field->state->analog == NULL) + field->state->value = xml_get_attribute_int(portnode, "value", field->defvalue); + + /* for analog fields, fetch configurable analog attributes */ + else + { + /* get base attributes */ + field->state->analog->delta = xml_get_attribute_int(portnode, "keydelta", field->delta); + field->state->analog->centerdelta = xml_get_attribute_int(portnode, "centerdelta", field->centerdelta); + field->state->analog->sensitivity = xml_get_attribute_int(portnode, "sensitivity", field->sensitivity); + + /* fetch yes/no for reverse setting */ + revstring = xml_get_attribute_string(portnode, "reverse", NULL); + if (revstring != NULL) + field->state->analog->reverse = (strcmp(revstring, "yes") == 0); + } + return TRUE; + } + + return FALSE; +} + + + +/*************************************************************************** + SETTINGS SAVE +***************************************************************************/ + +/*------------------------------------------------- + save_config_callback - config callback for + saving input port configuration +-------------------------------------------------*/ + +static void save_config_callback(running_machine &machine, int config_type, xml_data_node *parentnode) +{ + /* if no parentnode, ignore */ + if (parentnode == NULL) + return; + + /* default ports save differently */ + if (config_type == CONFIG_TYPE_DEFAULT) + save_default_inputs(machine, parentnode); + else + save_game_inputs(machine, parentnode); +} + + +/*------------------------------------------------- + save_sequence - add a node for an input + sequence +-------------------------------------------------*/ + +static void save_sequence(running_machine &machine, xml_data_node *parentnode, int type, int porttype, const input_seq &seq) +{ + astring seqstring; + xml_data_node *seqnode; + + /* get the string for the sequence */ + if (seq.length() == 0) + seqstring.cpy("NONE"); + else + machine.input().seq_to_tokens(seqstring, seq); + + /* add the new node */ + seqnode = xml_add_child(parentnode, "newseq", seqstring); + if (seqnode != NULL) + xml_set_attribute(seqnode, "type", seqtypestrings[type]); +} + + +/*------------------------------------------------- + save_this_input_field_type - determine if the given + port type is worth saving +-------------------------------------------------*/ + +static int save_this_input_field_type(int type) +{ + switch (type) + { + case IPT_UNUSED: + case IPT_END: + case IPT_PORT: + case IPT_VBLANK: + case IPT_UNKNOWN: + return FALSE; + } + return TRUE; +} + + +/*------------------------------------------------- + save_default_inputs - add nodes for any default + mappings that have changed +-------------------------------------------------*/ + +static void save_default_inputs(running_machine &machine, xml_data_node *parentnode) +{ + input_port_private *portdata = machine.input_port_data; + input_type_entry *entry; + + /* iterate over ports */ + for (entry = portdata->typelist.first(); entry != NULL; entry = entry->next()) + { + /* only save if this port is a type we save */ + if (save_this_input_field_type(entry->type)) + { + int seqtype; + + /* see if any of the sequences have changed */ + for (seqtype = 0; seqtype < ARRAY_LENGTH(entry->seq); seqtype++) + if (entry->seq[seqtype] != entry->defseq[seqtype]) + break; + + /* if so, we need to add a node */ + if (seqtype < ARRAY_LENGTH(entry->seq)) + { + /* add a new port node */ + xml_data_node *portnode = xml_add_child(parentnode, "port", NULL); + if (portnode != NULL) + { + /* add the port information and attributes */ + xml_set_attribute(portnode, "type", input_field_type_to_token(machine, entry->type, entry->player)); + + /* add only the sequences that have changed from the defaults */ + for (seqtype = 0; seqtype < ARRAY_LENGTH(entry->seq); seqtype++) + if (entry->seq[seqtype] != entry->defseq[seqtype]) + save_sequence(machine, portnode, seqtype, entry->type, entry->seq[seqtype]); + } + } + } + } +} + + +/*------------------------------------------------- + save_game_inputs - add nodes for any game + mappings that have changed +-------------------------------------------------*/ + +static void save_game_inputs(running_machine &machine, xml_data_node *parentnode) +{ + const input_field_config *field; + const input_port_config *port; + + /* iterate over ports */ + for (port = machine.m_portlist.first(); port != NULL; port = port->next()) + for (field = port->first_field(); field != NULL; field = field->next()) + if (save_this_input_field_type(field->type)) + { + bool changed = false; + int seqtype; + + /* determine if we changed */ + for (seqtype = 0; seqtype < ARRAY_LENGTH(field->state->seq); seqtype++) + changed |= (field->state->seq[seqtype] != field->seq[seqtype]); + + /* non-analog changes */ + if (field->state->analog == NULL) + changed |= ((field->state->value & field->mask) != (field->defvalue & field->mask)); + + /* analog changes */ + else + { + changed |= (field->state->analog->delta != field->delta); + changed |= (field->state->analog->centerdelta != field->centerdelta); + changed |= (field->state->analog->sensitivity != field->sensitivity); + changed |= (field->state->analog->reverse != ((field->flags & ANALOG_FLAG_REVERSE) != 0)); + } + + /* if we did change, add a new node */ + if (changed) + { + /* add a new port node */ + xml_data_node *portnode = xml_add_child(parentnode, "port", NULL); + if (portnode != NULL) + { + char tempbuffer[20]; + + /* add the identifying information and attributes */ + xml_set_attribute(portnode, "tag", get_port_tag(port, tempbuffer)); + xml_set_attribute(portnode, "type", input_field_type_to_token(machine, field->type, field->player)); + xml_set_attribute_int(portnode, "mask", field->mask); + xml_set_attribute_int(portnode, "defvalue", field->defvalue & field->mask); + + /* add sequences if changed */ + for (seqtype = 0; seqtype < ARRAY_LENGTH(field->state->seq); seqtype++) + if (field->state->seq[seqtype] != field->seq[seqtype]) + save_sequence(machine, portnode, seqtype, field->type, field->state->seq[seqtype]); + + /* write out non-analog changes */ + if (field->state->analog == NULL) + { + if ((field->state->value & field->mask) != (field->defvalue & field->mask)) + xml_set_attribute_int(portnode, "value", field->state->value & field->mask); + } + + /* write out analog changes */ + else + { + if (field->state->analog->delta != field->delta) + xml_set_attribute_int(portnode, "keydelta", field->state->analog->delta); + if (field->state->analog->centerdelta != field->centerdelta) + xml_set_attribute_int(portnode, "centerdelta", field->state->analog->centerdelta); + if (field->state->analog->sensitivity != field->sensitivity) + xml_set_attribute_int(portnode, "sensitivity", field->state->analog->sensitivity); + if (field->state->analog->reverse != ((field->flags & ANALOG_FLAG_REVERSE) != 0)) + xml_set_attribute(portnode, "reverse", field->state->analog->reverse ? "yes" : "no"); + } + } + } + } +} + + + +/*************************************************************************** + INPUT PLAYBACK +***************************************************************************/ + +/*------------------------------------------------- + playback_read_uint8 - read an 8-bit value + from the playback file +-------------------------------------------------*/ + +static UINT8 playback_read_uint8(running_machine &machine) +{ + input_port_private *portdata = machine.input_port_data; + UINT8 result; + + /* protect against NULL handles if previous reads fail */ + if (portdata->playback_file == NULL) + return 0; + + /* read the value; if we fail, end playback */ + if (portdata->playback_file->read(&result, sizeof(result)) != sizeof(result)) + { + playback_end(machine, "End of file"); + return 0; + } + + /* return the appropriate value */ + return result; +} + + +/*------------------------------------------------- + playback_read_uint32 - read a 32-bit value + from the playback file +-------------------------------------------------*/ + +static UINT32 playback_read_uint32(running_machine &machine) +{ + input_port_private *portdata = machine.input_port_data; + UINT32 result; + + /* protect against NULL handles if previous reads fail */ + if (portdata->playback_file == NULL) + return 0; + + /* read the value; if we fail, end playback */ + if (portdata->playback_file->read(&result, sizeof(result)) != sizeof(result)) + { + playback_end(machine, "End of file"); + return 0; + } + + /* return the appropriate value */ + return LITTLE_ENDIANIZE_INT32(result); +} + + +/*------------------------------------------------- + playback_read_uint64 - read a 64-bit value + from the playback file +-------------------------------------------------*/ + +static UINT64 playback_read_uint64(running_machine &machine) +{ + input_port_private *portdata = machine.input_port_data; + UINT64 result; + + /* protect against NULL handles if previous reads fail */ + if (portdata->playback_file == NULL) + return 0; + + /* read the value; if we fail, end playback */ + if (portdata->playback_file->read(&result, sizeof(result)) != sizeof(result)) + { + playback_end(machine, "End of file"); + return 0; + } + + /* return the appropriate value */ + return LITTLE_ENDIANIZE_INT64(result); +} + + +/*------------------------------------------------- + playback_init - initialize INP playback +-------------------------------------------------*/ + +static time_t playback_init(running_machine &machine) +{ + const char *filename = machine.options().playback(); + input_port_private *portdata = machine.input_port_data; + UINT8 header[INP_HEADER_SIZE]; + time_t basetime; + + /* if no file, nothing to do */ + if (filename[0] == 0) + return 0; + + /* open the playback file */ + portdata->playback_file = auto_alloc(machine, emu_file(machine.options().input_directory(), OPEN_FLAG_READ)); + file_error filerr = portdata->playback_file->open(filename); + assert_always(filerr == FILERR_NONE, "Failed to open file for playback"); + + /* read the header and verify that it is a modern version; if not, print an error */ + if (portdata->playback_file->read(header, sizeof(header)) != sizeof(header)) + fatalerror("Input file is corrupt or invalid (missing header)"); + if (memcmp(header, "MAMEINP\0", 8) != 0) + fatalerror("Input file invalid or in an older, unsupported format"); + if (header[0x10] != INP_HEADER_MAJVERSION) + fatalerror("Input file format version mismatch"); + + /* output info to console */ + mame_printf_info("Input file: %s\n", filename); + mame_printf_info("INP version %d.%d\n", header[0x10], header[0x11]); + basetime = header[0x08] | (header[0x09] << 8) | (header[0x0a] << 16) | (header[0x0b] << 24) | + ((UINT64)header[0x0c] << 32) | ((UINT64)header[0x0d] << 40) | ((UINT64)header[0x0e] << 48) | ((UINT64)header[0x0f] << 56); + mame_printf_info("Created %s", ctime(&basetime)); + mame_printf_info("Recorded using %s\n", header + 0x20); + + /* verify the header against the current game */ + if (memcmp(machine.system().name, header + 0x14, strlen(machine.system().name) + 1) != 0) + mame_printf_info("Input file is for " GAMENOUN " '%s', not for current " GAMENOUN " '%s'\n", header + 0x14, machine.system().name); + + /* enable compression */ + portdata->playback_file->compress(FCOMPRESS_MEDIUM); + + return basetime; +} + + +/*------------------------------------------------- + playback_end - end INP playback +-------------------------------------------------*/ + +static void playback_end(running_machine &machine, const char *message) +{ + input_port_private *portdata = machine.input_port_data; + + /* only applies if we have a live file */ + if (portdata->playback_file != NULL) + { + /* close the file */ + auto_free(machine, portdata->playback_file); + portdata->playback_file = NULL; + + /* pop a message */ + if (message != NULL) + popmessage("Playback Ended\nReason: %s", message); + + /* display speed stats */ + portdata->playback_accumulated_speed /= portdata->playback_accumulated_frames; + mame_printf_info("Total playback frames: %d\n", (UINT32)portdata->playback_accumulated_frames); + mame_printf_info("Average recorded speed: %d%%\n", (UINT32)((portdata->playback_accumulated_speed * 200 + 1) >> 21)); + } +} + + +/*------------------------------------------------- + playback_frame - start of frame callback for + playback +-------------------------------------------------*/ + +static void playback_frame(running_machine &machine, attotime curtime) +{ + input_port_private *portdata = machine.input_port_data; + + /* if playing back, fetch the information and verify */ + if (portdata->playback_file != NULL) + { + attotime readtime; + + /* first the absolute time */ + readtime.seconds = playback_read_uint32(machine); + readtime.attoseconds = playback_read_uint64(machine); + if (readtime != curtime) + playback_end(machine, "Out of sync"); + + /* then the speed */ + portdata->playback_accumulated_speed += playback_read_uint32(machine); + portdata->playback_accumulated_frames++; + } +} + + +/*------------------------------------------------- + playback_port - per-port callback for playback +-------------------------------------------------*/ + +static void playback_port(const input_port_config *port) +{ + input_port_private *portdata = port->machine().input_port_data; + + /* if playing back, fetch information about this port */ + if (portdata->playback_file != NULL) + { + analog_field_state *analog; + + /* read the default value and the digital state */ + port->state->defvalue = playback_read_uint32(port->machine()); + port->state->digital = playback_read_uint32(port->machine()); + + /* loop over analog ports and save their data */ + for (analog = port->state->analoglist; analog != NULL; analog = analog->next) + { + /* read current and previous values */ + analog->accum = playback_read_uint32(port->machine()); + analog->previous = playback_read_uint32(port->machine()); + + /* read configuration information */ + analog->sensitivity = playback_read_uint32(port->machine()); + analog->reverse = playback_read_uint8(port->machine()); + } + } +} + + + +/*************************************************************************** + INPUT RECORDING +***************************************************************************/ + +/*------------------------------------------------- + record_write_uint8 - write an 8-bit value + to the record file +-------------------------------------------------*/ + +static void record_write_uint8(running_machine &machine, UINT8 data) +{ + input_port_private *portdata = machine.input_port_data; + UINT8 result = data; + + /* protect against NULL handles if previous reads fail */ + if (portdata->record_file == NULL) + return; + + /* read the value; if we fail, end playback */ + if (portdata->record_file->write(&result, sizeof(result)) != sizeof(result)) + record_end(machine, "Out of space"); +} + + +/*------------------------------------------------- + record_write_uint32 - write a 32-bit value + to the record file +-------------------------------------------------*/ + +static void record_write_uint32(running_machine &machine, UINT32 data) +{ + input_port_private *portdata = machine.input_port_data; + UINT32 result = LITTLE_ENDIANIZE_INT32(data); + + /* protect against NULL handles if previous reads fail */ + if (portdata->record_file == NULL) + return; + + /* read the value; if we fail, end playback */ + if (portdata->record_file->write(&result, sizeof(result)) != sizeof(result)) + record_end(machine, "Out of space"); +} + + +/*------------------------------------------------- + record_write_uint64 - write a 64-bit value + to the record file +-------------------------------------------------*/ + +static void record_write_uint64(running_machine &machine, UINT64 data) +{ + input_port_private *portdata = machine.input_port_data; + UINT64 result = LITTLE_ENDIANIZE_INT64(data); + + /* protect against NULL handles if previous reads fail */ + if (portdata->record_file == NULL) + return; + + /* read the value; if we fail, end playback */ + if (portdata->record_file->write(&result, sizeof(result)) != sizeof(result)) + record_end(machine, "Out of space"); +} + + +/*------------------------------------------------- + record_init - initialize INP recording +-------------------------------------------------*/ + +static void record_init(running_machine &machine) +{ + const char *filename = machine.options().record(); + input_port_private *portdata = machine.input_port_data; + UINT8 header[INP_HEADER_SIZE]; + system_time systime; + + /* if no file, nothing to do */ + if (filename[0] == 0) + return; + + /* open the record file */ + portdata->record_file = auto_alloc(machine, emu_file(machine.options().input_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS)); + file_error filerr = portdata->record_file->open(filename); + assert_always(filerr == FILERR_NONE, "Failed to open file for recording"); + + /* get the base time */ + machine.base_datetime(systime); + + /* fill in the header */ + memset(header, 0, sizeof(header)); + memcpy(header, "MAMEINP\0", 8); + header[0x08] = systime.time >> 0; + header[0x09] = systime.time >> 8; + header[0x0a] = systime.time >> 16; + header[0x0b] = systime.time >> 24; + header[0x0c] = systime.time >> 32; + header[0x0d] = systime.time >> 40; + header[0x0e] = systime.time >> 48; + header[0x0f] = systime.time >> 56; + header[0x10] = INP_HEADER_MAJVERSION; + header[0x11] = INP_HEADER_MINVERSION; + strcpy((char *)header + 0x14, machine.system().name); + sprintf((char *)header + 0x20, APPNAME " %s", build_version); + + /* write it */ + portdata->record_file->write(header, sizeof(header)); + + /* enable compression */ + portdata->record_file->compress(FCOMPRESS_MEDIUM); +} + + +/*------------------------------------------------- + record_end - end INP recording +-------------------------------------------------*/ + +static void record_end(running_machine &machine, const char *message) +{ + input_port_private *portdata = machine.input_port_data; + + /* only applies if we have a live file */ + if (portdata->record_file != NULL) + { + /* close the file */ + auto_free(machine, portdata->record_file); + portdata->record_file = NULL; + + /* pop a message */ + if (message != NULL) + popmessage("Recording Ended\nReason: %s", message); + } +} + + +/*------------------------------------------------- + record_frame - start of frame callback for + recording +-------------------------------------------------*/ + +static void record_frame(running_machine &machine, attotime curtime) +{ + input_port_private *portdata = machine.input_port_data; + + /* if recording, record information about the current frame */ + if (portdata->record_file != NULL) + { + /* first the absolute time */ + record_write_uint32(machine, curtime.seconds); + record_write_uint64(machine, curtime.attoseconds); + + /* then the current speed */ + record_write_uint32(machine, machine.video().speed_percent() * (double)(1 << 20)); + } +} + + +/*------------------------------------------------- + record_port - per-port callback for record +-------------------------------------------------*/ + +static void record_port(const input_port_config *port) +{ + input_port_private *portdata = port->machine().input_port_data; + + /* if recording, store information about this port */ + if (portdata->record_file != NULL) + { + analog_field_state *analog; + + /* store the default value and digital state */ + record_write_uint32(port->machine(), port->state->defvalue); + record_write_uint32(port->machine(), port->state->digital); + + /* loop over analog ports and save their data */ + for (analog = port->state->analoglist; analog != NULL; analog = analog->next) + { + /* store current and previous values */ + record_write_uint32(port->machine(), analog->accum); + record_write_uint32(port->machine(), analog->previous); + + /* store configuration information */ + record_write_uint32(port->machine(), analog->sensitivity); + record_write_uint8(port->machine(), analog->reverse); + } + } +} + +int input_machine_has_keyboard(running_machine &machine) +{ + int have_keyboard = FALSE; + const input_field_config *field; + const input_port_config *port; + for (port = machine.m_portlist.first(); port != NULL; port = port->next()) + { + for (field = port->first_field(); field != NULL; field = field->next()) + { + // if we are at init, check IPT_KEYBOARD for inputx_init + if (!port->machine().input_port_data->safe_to_read && field->type == IPT_KEYBOARD) + { + have_keyboard = TRUE; + break; + } + + // else, check if there is a keyboard and if such a keyboard is enabled + if (field->type == IPT_KEYBOARD && input_condition_true(field->machine(), &field->condition, field->port().owner())) + { + have_keyboard = TRUE; + break; + } + } + } + return have_keyboard; +} + +/*************************************************************************** + CODE ASSEMBLING +***************************************************************************/ + +/*------------------------------------------------- + code_point_string - obtain a string representation of a + given code; used for logging and debugging +-------------------------------------------------*/ + +static const char *code_point_string(running_machine &machine, unicode_char ch) +{ + static char buf[16]; + const char *result = buf; + + switch(ch) + { + /* check some magic values */ + case '\0': strcpy(buf, "\\0"); break; + case '\r': strcpy(buf, "\\r"); break; + case '\n': strcpy(buf, "\\n"); break; + case '\t': strcpy(buf, "\\t"); break; + + default: + if ((ch >= 32) && (ch < 128)) + { + /* seven bit ASCII is easy */ + buf[0] = (char) ch; + buf[1] = '\0'; + } + else if (ch >= UCHAR_MAMEKEY_BEGIN) + { + /* try to obtain a codename with code_name(); this can result in an empty string */ + input_code code(DEVICE_CLASS_KEYBOARD, 0, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, input_item_id(ch - UCHAR_MAMEKEY_BEGIN)); + astring tempstr; + snprintf(buf, ARRAY_LENGTH(buf), "%s", machine.input().code_name(tempstr, code)); + } + else + { + /* empty string; resolve later */ + buf[0] = '\0'; + } + + /* did we fail to resolve? if so, we have a last resort */ + if (buf[0] == '\0') + snprintf(buf, ARRAY_LENGTH(buf), "U+%04X", (unsigned) ch); + break; + } + return result; +} + + +/*------------------------------------------------- + scan_keys - scans through input ports and + sets up natural keyboard input mapping +-------------------------------------------------*/ + +static int scan_keys(running_machine &machine, const input_port_config *portconfig, inputx_code *codes, const input_port_config * *ports, const input_field_config * *shift_ports, int keys, int shift) +{ + int code_count = 0; + const input_port_config *port; + const input_field_config *field; + unicode_char code; + + assert(keys < NUM_SIMUL_KEYS); + + for (port = portconfig; port != NULL; port = port->next()) + { + for (field = port->first_field(); field != NULL; field = field->next()) + { + if (field->type == IPT_KEYBOARD) + { + code = get_keyboard_code(field, shift); + if (code != 0) + { + /* is this a shifter key? */ + if ((code >= UCHAR_SHIFT_BEGIN) && (code <= UCHAR_SHIFT_END)) + { + shift_ports[keys] = field; + code_count += scan_keys(machine, + portconfig, + codes ? &codes[code_count] : NULL, + ports, + shift_ports, + keys+1, + code - UCHAR_SHIFT_1 + 1); + } + else + { + /* not a shifter key; record normally */ + if (codes) + { + /* if we have a destination, record the codes used here */ + memcpy((void *) codes[code_count].field, shift_ports, sizeof(shift_ports[0]) * keys); + codes[code_count].ch = code; + codes[code_count].field[keys] = field; + } + + /* increment the count */ + code_count++; + + if (LOG_INPUTX) + logerror("inputx: code=%i (%s) port=%p field->name='%s'\n", (int) code, code_point_string(machine, code), port, field->name); + } + } + } + } + } + return code_count; +} + + + +/*------------------------------------------------- + build_codes - given an input port table, create + a input code table useful for mapping unicode + chars +-------------------------------------------------*/ + +static inputx_code *build_codes(running_machine &machine, const input_port_config *portconfig) +{ + inputx_code *codes = NULL; + const input_port_config *ports[NUM_SIMUL_KEYS]; + const input_field_config *fields[NUM_SIMUL_KEYS]; + int code_count; + + /* first count the number of codes */ + code_count = scan_keys(machine, portconfig, NULL, ports, fields, 0, 0); + if (code_count > 0) + { + /* allocate the codes */ + codes = auto_alloc_array_clear(machine, inputx_code, code_count + 1); + + /* and populate them */ + scan_keys(machine, portconfig, codes, ports, fields, 0, 0); + } + return codes; +} + + + +/*************************************************************************** + VALIDITY CHECKS +***************************************************************************/ + +/*------------------------------------------------- + validate_natural_keyboard_statics - + validates natural keyboard static data +-------------------------------------------------*/ + +int validate_natural_keyboard_statics(void) +{ + int i; + int error = FALSE; + unicode_char last_char = 0; + const char_info *ci; + + /* check to make sure that charinfo is in order */ + for (i = 0; i < ARRAY_LENGTH(charinfo); i++) + { + if (last_char >= charinfo[i].ch) + { + mame_printf_error("inputx: charinfo is out of order; 0x%08x should be higher than 0x%08x\n", charinfo[i].ch, last_char); + error = TRUE; + } + last_char = charinfo[i].ch; + } + + /* check to make sure that I can look up everything on alternate_charmap */ + for (i = 0; i < ARRAY_LENGTH(charinfo); i++) + { + ci = find_charinfo(charinfo[i].ch); + if (ci != &charinfo[i]) + { + mame_printf_error("inputx: expected find_charinfo(0x%08x) to work properly\n", charinfo[i].ch); + error = TRUE; + } + } + return error; +} + + + +/*************************************************************************** + CORE IMPLEMENTATION +***************************************************************************/ + +static void clear_keybuffer(running_machine &machine) +{ + input_port_private *portdata = machine.input_port_data; + portdata->keybuffer = NULL; + portdata->queue_chars = NULL; + portdata->codes = NULL; +} + + + +static void setup_keybuffer(running_machine &machine) +{ + input_port_private *portdata = machine.input_port_data; + portdata->inputx_timer = machine.scheduler().timer_alloc(FUNC(inputx_timerproc)); + portdata->keybuffer = auto_alloc_clear(machine, key_buffer); + machine.add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(FUNC(clear_keybuffer), &machine)); +} + + + +void inputx_init(running_machine &machine) +{ + input_port_private *portdata = machine.input_port_data; + portdata->codes = NULL; + portdata->inputx_timer = NULL; + portdata->queue_chars = NULL; + portdata->accept_char = NULL; + portdata->charqueue_empty = NULL; + portdata->keybuffer = NULL; + + if (machine.debug_flags & DEBUG_FLAG_ENABLED) + { + debug_console_register_command(machine, "input", CMDFLAG_NONE, 0, 1, 1, execute_input); + debug_console_register_command(machine, "dumpkbd", CMDFLAG_NONE, 0, 0, 1, execute_dumpkbd); + } + + /* posting keys directly only makes sense for a computer */ + if (input_machine_has_keyboard(machine)) + { + portdata->codes = build_codes(machine, machine.m_portlist.first()); + setup_keybuffer(machine); + } +} + + + +void inputx_setup_natural_keyboard( + running_machine &machine, + int (*queue_chars)(running_machine &machine, const unicode_char *text, size_t text_len), + int (*accept_char)(running_machine &machine, unicode_char ch), + int (*charqueue_empty)(running_machine &machine)) +{ + input_port_private *portdata = machine.input_port_data; + portdata->queue_chars = queue_chars; + portdata->accept_char = accept_char; + portdata->charqueue_empty = charqueue_empty; +} + +int inputx_can_post(running_machine &machine) +{ + input_port_private *portdata = machine.input_port_data; + return portdata->queue_chars || portdata->codes; +} + + +static int can_post_key_directly(running_machine &machine, unicode_char ch) +{ + input_port_private *portdata = machine.input_port_data; + int rc = FALSE; + const inputx_code *code; + + if (portdata->queue_chars) + { + rc = portdata->accept_char ? (*portdata->accept_char)(machine, ch) : TRUE; + } + else + { + code = find_code(portdata->codes, ch); + if (code) + rc = code->field[0] != NULL; + } + return rc; +} + + + +static int can_post_key_alternate(running_machine &machine, unicode_char ch) +{ + const char *s; + const char_info *ci; + unicode_char uchar; + int rc; + + ci = find_charinfo(ch); + s = ci ? ci->alternate : NULL; + if (!s) + return 0; + + while(*s) + { + rc = uchar_from_utf8(&uchar, s, strlen(s)); + if (rc <= 0) + return 0; + if (!can_post_key_directly(machine, uchar)) + return 0; + s += rc; + } + return 1; +} + +static attotime choose_delay(input_port_private *portdata, unicode_char ch) +{ + if (portdata->current_rate != attotime::zero) + return portdata->current_rate; + + attotime delay = attotime::zero; + if (portdata->queue_chars) + { + /* systems with queue_chars can afford a much smaller delay */ + delay = attotime::from_msec(10); + } + else + { + switch(ch) { + case '\r': + delay = attotime::from_msec(200); + break; + + default: + delay = attotime::from_msec(50); + break; + } + } + return delay; +} + + + +static void internal_post_key(running_machine &machine, unicode_char ch) +{ + input_port_private *portdata = machine.input_port_data; + key_buffer *keybuf; + + keybuf = get_buffer(machine); + + /* need to start up the timer? */ + if (keybuf->begin_pos == keybuf->end_pos) + { + portdata->inputx_timer->adjust(choose_delay(portdata, ch)); + keybuf->status_keydown = 0; + } + + keybuf->buffer[keybuf->end_pos++] = ch; + keybuf->end_pos %= ARRAY_LENGTH(keybuf->buffer); +} + + + +static int buffer_full(running_machine &machine) +{ + key_buffer *keybuf; + keybuf = get_buffer(machine); + return ((keybuf->end_pos + 1) % ARRAY_LENGTH(keybuf->buffer)) == keybuf->begin_pos; +} + + + +static void inputx_postn_rate(running_machine &machine, const unicode_char *text, size_t text_len, attotime rate) +{ + input_port_private *portdata = machine.input_port_data; + int last_cr = 0; + unicode_char ch; + const char *s; + const char_info *ci; + const inputx_code *code; + + portdata->current_rate = rate; + + if (inputx_can_post(machine)) + { + while((text_len > 0) && !buffer_full(machine)) + { + ch = *(text++); + text_len--; + + /* change all eolns to '\r' */ + if ((ch != '\n') || !last_cr) + { + if (ch == '\n') + ch = '\r'; + else + last_cr = (ch == '\r'); + + if (LOG_INPUTX) + { + code = find_code(portdata->codes, ch); + logerror("inputx_postn(): code=%i (%s) field->name='%s'\n", (int) ch, code_point_string(machine, ch), (code && code->field[0]) ? code->field[0]->name : "<null>"); + } + + if (can_post_key_directly(machine, ch)) + { + /* we can post this key in the queue directly */ + internal_post_key(machine, ch); + } + else if (can_post_key_alternate(machine, ch)) + { + /* we can post this key with an alternate representation */ + ci = find_charinfo(ch); + assert(ci && ci->alternate); + s = ci->alternate; + while(*s) + { + s += uchar_from_utf8(&ch, s, strlen(s)); + internal_post_key(machine, ch); + } + } + } + else + { + last_cr = 0; + } + } + } +} + + + +static TIMER_CALLBACK(inputx_timerproc) +{ + input_port_private *portdata = machine.input_port_data; + key_buffer *keybuf; + attotime delay; + + keybuf = get_buffer(machine); + + if (portdata->queue_chars) + { + /* the driver has a queue_chars handler */ + while((keybuf->begin_pos != keybuf->end_pos) && (*portdata->queue_chars)(machine, &keybuf->buffer[keybuf->begin_pos], 1)) + { + keybuf->begin_pos++; + keybuf->begin_pos %= ARRAY_LENGTH(keybuf->buffer); + + if (portdata->current_rate != attotime::zero) + break; + } + } + else + { + /* the driver does not have a queue_chars handler */ + if (keybuf->status_keydown) + { + keybuf->status_keydown = FALSE; + keybuf->begin_pos++; + keybuf->begin_pos %= ARRAY_LENGTH(keybuf->buffer); + } + else + { + keybuf->status_keydown = TRUE; + } + } + + /* need to make sure timerproc is called again if buffer not empty */ + if (keybuf->begin_pos != keybuf->end_pos) + { + delay = choose_delay(portdata, keybuf->buffer[keybuf->begin_pos]); + portdata->inputx_timer->adjust(delay); + } +} + +int inputx_is_posting(running_machine &machine) +{ + input_port_private *portdata = machine.input_port_data; + const key_buffer *keybuf; + keybuf = get_buffer(machine); + return (keybuf->begin_pos != keybuf->end_pos) || (portdata->charqueue_empty && !(*portdata->charqueue_empty)(machine)); +} + +/*************************************************************************** + + Coded input + +***************************************************************************/ +static void inputx_postc_rate(running_machine &machine, unicode_char ch, attotime rate); + +static void inputx_postn_coded_rate(running_machine &machine, const char *text, size_t text_len, attotime rate) +{ + size_t i, j, key_len, increment; + unicode_char ch; + + static const struct + { + const char *key; + unicode_char code; + } codes[] = + { + { "BACKSPACE", 8 }, + { "BS", 8 }, + { "BKSP", 8 }, + { "DEL", UCHAR_MAMEKEY(DEL) }, + { "DELETE", UCHAR_MAMEKEY(DEL) }, + { "END", UCHAR_MAMEKEY(END) }, + { "ENTER", 13 }, + { "ESC", '\033' }, + { "HOME", UCHAR_MAMEKEY(HOME) }, + { "INS", UCHAR_MAMEKEY(INSERT) }, + { "INSERT", UCHAR_MAMEKEY(INSERT) }, + { "PGDN", UCHAR_MAMEKEY(PGDN) }, + { "PGUP", UCHAR_MAMEKEY(PGUP) }, + { "SPACE", 32 }, + { "TAB", 9 }, + { "F1", UCHAR_MAMEKEY(F1) }, + { "F2", UCHAR_MAMEKEY(F2) }, + { "F3", UCHAR_MAMEKEY(F3) }, + { "F4", UCHAR_MAMEKEY(F4) }, + { "F5", UCHAR_MAMEKEY(F5) }, + { "F6", UCHAR_MAMEKEY(F6) }, + { "F7", UCHAR_MAMEKEY(F7) }, + { "F8", UCHAR_MAMEKEY(F8) }, + { "F9", UCHAR_MAMEKEY(F9) }, + { "F10", UCHAR_MAMEKEY(F10) }, + { "F11", UCHAR_MAMEKEY(F11) }, + { "F12", UCHAR_MAMEKEY(F12) }, + { "QUOTE", '\"' } + }; + + i = 0; + while(i < text_len) + { + ch = text[i]; + increment = 1; + + if (ch == '{') + { + for (j = 0; j < ARRAY_LENGTH(codes); j++) + { + key_len = strlen(codes[j].key); + if (i + key_len + 2 <= text_len) + { + if (!memcmp(codes[j].key, &text[i + 1], key_len) && (text[i + key_len + 1] == '}')) + { + ch = codes[j].code; + increment = key_len + 2; + } + } + } + } + + if (ch) + inputx_postc_rate(machine, ch, rate); + i += increment; + } +} + + + +/*************************************************************************** + + Alternative calls + +***************************************************************************/ + +static void inputx_postc_rate(running_machine &machine, unicode_char ch, attotime rate) +{ + inputx_postn_rate(machine, &ch, 1, rate); +} + +void inputx_postc(running_machine &machine, unicode_char ch) +{ + inputx_postc_rate(machine, ch, attotime::zero); +} + +static void inputx_postn_utf8_rate(running_machine &machine, const char *text, size_t text_len, attotime rate) +{ + size_t len = 0; + unicode_char buf[256]; + unicode_char c; + int rc; + + while(text_len > 0) + { + if (len == ARRAY_LENGTH(buf)) + { + inputx_postn_rate(machine, buf, len, attotime::zero); + len = 0; + } + + rc = uchar_from_utf8(&c, text, text_len); + if (rc < 0) + { + rc = 1; + c = INVALID_CHAR; + } + text += rc; + text_len -= rc; + buf[len++] = c; + } + inputx_postn_rate(machine, buf, len, rate); +} + +void inputx_post_utf8(running_machine &machine, const char *text) +{ + inputx_postn_utf8_rate(machine, text, strlen(text), attotime::zero); +} + +void inputx_post_utf8_rate(running_machine &machine, const char *text, attotime rate) +{ + inputx_postn_utf8_rate(machine, text, strlen(text), rate); +} + +/*************************************************************************** + + Other stuff + + This stuff is here more out of convienience than anything else +***************************************************************************/ + +int input_classify_port(const input_field_config *field) +{ + int result; + + switch(field->type) + { + case IPT_JOYSTICK_UP: + case IPT_JOYSTICK_DOWN: + case IPT_JOYSTICK_LEFT: + case IPT_JOYSTICK_RIGHT: + case IPT_JOYSTICKLEFT_UP: + case IPT_JOYSTICKLEFT_DOWN: + case IPT_JOYSTICKLEFT_LEFT: + case IPT_JOYSTICKLEFT_RIGHT: + case IPT_JOYSTICKRIGHT_UP: + case IPT_JOYSTICKRIGHT_DOWN: + case IPT_JOYSTICKRIGHT_LEFT: + case IPT_JOYSTICKRIGHT_RIGHT: + case IPT_BUTTON1: + case IPT_BUTTON2: + case IPT_BUTTON3: + case IPT_BUTTON4: + case IPT_BUTTON5: + case IPT_BUTTON6: + case IPT_BUTTON7: + case IPT_BUTTON8: + case IPT_BUTTON9: + case IPT_BUTTON10: + case IPT_AD_STICK_X: + case IPT_AD_STICK_Y: + case IPT_AD_STICK_Z: + case IPT_TRACKBALL_X: + case IPT_TRACKBALL_Y: + case IPT_LIGHTGUN_X: + case IPT_LIGHTGUN_Y: + case IPT_MOUSE_X: + case IPT_MOUSE_Y: + case IPT_START: + case IPT_SELECT: + result = INPUT_CLASS_CONTROLLER; + break; + + case IPT_KEYPAD: + case IPT_KEYBOARD: + result = INPUT_CLASS_KEYBOARD; + break; + + case IPT_CONFIG: + result = INPUT_CLASS_CONFIG; + break; + + case IPT_DIPSWITCH: + result = INPUT_CLASS_DIPSWITCH; + break; + + case 0: + if (field->name && (field->name != (const char *) -1)) + result = INPUT_CLASS_MISC; + else + result = INPUT_CLASS_INTERNAL; + break; + + default: + result = INPUT_CLASS_INTERNAL; + break; + } + return result; +} + + + +int input_player_number(const input_field_config *port) +{ + return port->player; +} + + + +/*------------------------------------------------- + input_has_input_class - checks to see if a + particular input class is present +-------------------------------------------------*/ + +int input_has_input_class(running_machine &machine, int inputclass) +{ + const input_port_config *port; + const input_field_config *field; + + for (port = machine.m_portlist.first(); port != NULL; port = port->next()) + { + for (field = port->first_field(); field != NULL; field = field->next()) + { + if (input_classify_port(field) == inputclass) + return TRUE; + } + } + return FALSE; +} + + + +/*------------------------------------------------- + input_count_players - counts the number of + active players +-------------------------------------------------*/ + +int input_count_players(running_machine &machine) +{ + const input_port_config *port; + const input_field_config *field; + int joystick_count; + + joystick_count = 0; + for (port = machine.m_portlist.first(); port != NULL; port = port->next()) + { + for (field = port->first_field(); field != NULL; field = field->next()) + { + if (input_classify_port(field) == INPUT_CLASS_CONTROLLER) + { + if (joystick_count <= field->player + 1) + joystick_count = field->player + 1; + } + } + } + return joystick_count; +} + + + +/*************************************************************************** + DEBUGGER SUPPORT +***************************************************************************/ + +/*------------------------------------------------- + execute_input - debugger command to enter + natural keyboard input +-------------------------------------------------*/ + +static void execute_input(running_machine &machine, int ref, int params, const char *param[]) +{ + inputx_postn_coded_rate(machine, param[0], strlen(param[0]), attotime::zero); +} + + + +/*------------------------------------------------- + execute_dumpkbd - debugger command to natural + keyboard codes +-------------------------------------------------*/ + +static void execute_dumpkbd(running_machine &machine, int ref, int params, const char *param[]) +{ + inputx_code *codes = machine.input_port_data->codes; + const char *filename; + FILE *file = NULL; + const inputx_code *code; + char buffer[512]; + size_t pos; + int i, j; + size_t left_column_width = 24; + + /* was there a file specified? */ + filename = (params > 0) ? param[0] : NULL; + if (filename != NULL) + { + /* if so, open it */ + file = fopen(filename, "w"); + if (file == NULL) + { + debug_console_printf(machine, "Cannot open \"%s\"\n", filename); + return; + } + } + + if ((codes != NULL) && (codes[0].ch != 0)) + { + /* loop through all codes */ + for (i = 0; codes[i].ch; i++) + { + code = &codes[i]; + pos = 0; + + /* describe the character code */ + pos += snprintf(&buffer[pos], ARRAY_LENGTH(buffer) - pos, "%08X (%s) ", + code->ch, + code_point_string(machine, code->ch)); + + /* pad with spaces */ + while(pos < left_column_width) + buffer[pos++] = ' '; + buffer[pos] = '\0'; + + /* identify the keys used */ + for (j = 0; j < ARRAY_LENGTH(code->field) && (code->field[j] != NULL); j++) + { + pos += snprintf(&buffer[pos], ARRAY_LENGTH(buffer) - pos, "%s'%s'", + (j > 0) ? ", " : "", + code->field[j]->name); + } + + /* and output it as appropriate */ + if (file != NULL) + fprintf(file, "%s\n", buffer); + else + debug_console_printf(machine, "%s\n", buffer); + } + } + else + { + debug_console_printf(machine, "No natural keyboard support\n"); + } + + /* cleanup */ + if (file != NULL) + fclose(file); + +} + + + +input_port_config *ioconfig_alloc_port(ioport_list &portlist, device_t &device, const char *tag) +{ + astring fulltag; + device.subtag(fulltag, tag); + return &portlist.append(fulltag, *global_alloc(input_port_config(device, fulltag))); +} + +input_port_config *ioconfig_modify_port(ioport_list &portlist, device_t &device, const char *tag) +{ + astring fulltag; + device.subtag(fulltag, tag); + input_port_config *port = portlist.find(fulltag.cstr()); + if (port == NULL) + throw emu_fatalerror("Requested to modify nonexistent port '%s'", fulltag.cstr()); + port->bump_modcount(); + return port; +} + +input_field_config *ioconfig_alloc_field(input_port_config &port, int type, input_port_value defval, input_port_value mask, const char *name) +{ + if (&port == NULL) + throw emu_fatalerror("INPUT_TOKEN_FIELD encountered with no active port (mask=%X defval=%X)\n", mask, defval); \ + if (type != IPT_UNKNOWN && type != IPT_UNUSED) + port.active |= mask; + if (type == IPT_DIPSWITCH || type == IPT_CONFIG) + defval = port_default_value(port.tag(), mask, defval, port.owner()); + return &port.fieldlist().append(*global_alloc(input_field_config(port, type, defval, mask, input_port_string_from_token(name)))); +} + +input_field_config *ioconfig_alloc_onoff(input_port_config &port, const char *name, input_port_value defval, input_port_value mask, const char *diplocation, astring &errorbuf) +{ + input_field_config *curfield = ioconfig_alloc_field(port, IPT_DIPSWITCH, defval, mask, name); + if (name == DEF_STR(Service_Mode)) + { + curfield->flags |= FIELD_FLAG_TOGGLE; + curfield->seq[SEQ_TYPE_STANDARD].set(KEYCODE_F2); + } + if (diplocation != NULL) + diplocation_list_alloc(*curfield, diplocation, errorbuf); + ioconfig_alloc_setting(*curfield, defval & mask, DEF_STR(Off)); + ioconfig_alloc_setting(*curfield, ~defval & mask, DEF_STR(On)); + return curfield; +} + +input_setting_config *ioconfig_alloc_setting(input_field_config &field, input_port_value value, const char *name) +{ + return &field.settinglist().append(*global_alloc(input_setting_config(field, value, input_port_string_from_token(name)))); +} + +void ioconfig_field_add_char(input_field_config &field, unicode_char ch, astring &errorbuf) +{ + for (int index = 0; index < ARRAY_LENGTH(field.chars); index++) + if (field.chars[index] == 0) + { + field.chars[index] = ch; + break; + } +} + +void ioconfig_add_code(input_field_config &field, int which, input_code code) +{ + field.seq[which] |= code; +} + + + +input_type_entry::input_type_entry(UINT32 _type, ioport_group _group, int _player, const char *_token, const char *_name, input_seq standard) + : type(_type), + group(_group), + player(_player), + token(_token), + name(_name), + m_next(NULL) +{ + defseq[SEQ_TYPE_STANDARD] = seq[SEQ_TYPE_STANDARD] = standard; +} + +input_type_entry::input_type_entry(UINT32 _type, ioport_group _group, int _player, const char *_token, const char *_name, input_seq standard, input_seq decrement, input_seq increment) + : type(_type), + group(_group), + player(_player), + token(_token), + name(_name), + m_next(NULL) +{ + defseq[SEQ_TYPE_STANDARD] = seq[SEQ_TYPE_STANDARD] = standard; + defseq[SEQ_TYPE_INCREMENT] = seq[SEQ_TYPE_INCREMENT] = increment; + defseq[SEQ_TYPE_DECREMENT] = seq[SEQ_TYPE_DECREMENT] = decrement; +} |