Files
Main_MiSTer/input.cpp
That One Seong e484d703f7 input: Add support for OpenFIRE lightguns (#964)
* Add OpenFIRE lightguns to guns whitelist

* Add preliminary OpenFIRE support

Guns are detected as light guns based on static VID and OpenFIRE name prefix, as the PID can be any number of different values depending on the user.

Because OpenFIRE guns can be polled through serial devices, a basic (read: hacky) heuristic is used to poll for serial port paths and send a signal to compatible guns to enable their MiSTer compatible mode.

* Tabbbbbs

* Directly open and write to file rather than rely on system echo
2025-02-05 21:07:13 +08:00

6002 lines
151 KiB
C++

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/poll.h>
#include <sys/sysinfo.h>
#include <dirent.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/input.h>
#include <linux/uinput.h>
#include <sys/time.h>
#include <sys/types.h>
#include <stdarg.h>
#include <math.h>
#include "input.h"
#include "user_io.h"
#include "menu.h"
#include "hardware.h"
#include "cfg.h"
#include "fpga_io.h"
#include "osd.h"
#include "video.h"
#include "audio.h"
#include "joymapping.h"
#include "support.h"
#include "profiling.h"
#include "gamecontroller_db.h"
#include "str_util.h"
#define NUMDEV 30
#define NUMPLAYERS 6
#define UINPUT_NAME "MiSTer virtual input"
char joy_bnames[NUMBUTTONS][32] = {};
int joy_bcount = 0;
static struct pollfd pool[NUMDEV + 3];
static int ev2amiga[] =
{
NONE, //0 KEY_RESERVED
0x45, //1 KEY_ESC
0x01, //2 KEY_1
0x02, //3 KEY_2
0x03, //4 KEY_3
0x04, //5 KEY_4
0x05, //6 KEY_5
0x06, //7 KEY_6
0x07, //8 KEY_7
0x08, //9 KEY_8
0x09, //10 KEY_9
0x0a, //11 KEY_0
0x0b, //12 KEY_MINUS
0x0c, //13 KEY_EQUAL
0x41, //14 KEY_BACKSPACE
0x42, //15 KEY_TAB
0x10, //16 KEY_Q
0x11, //17 KEY_W
0x12, //18 KEY_E
0x13, //19 KEY_R
0x14, //20 KEY_T
0x15, //21 KEY_Y
0x16, //22 KEY_U
0x17, //23 KEY_I
0x18, //24 KEY_O
0x19, //25 KEY_P
0x1a, //26 KEY_LEFTBRACE
0x1b, //27 KEY_RIGHTBRACE
0x44, //28 KEY_ENTER
0x63, //29 KEY_LEFTCTRL
0x20, //30 KEY_A
0x21, //31 KEY_S
0x22, //32 KEY_D
0x23, //33 KEY_F
0x24, //34 KEY_G
0x25, //35 KEY_H
0x26, //36 KEY_J
0x27, //37 KEY_K
0x28, //38 KEY_L
0x29, //39 KEY_SEMICOLON
0x2a, //40 KEY_APOSTROPHE
0x00, //41 KEY_GRAVE
0x60, //42 KEY_LEFTSHIFT
0x0d, //43 KEY_BACKSLASH
0x31, //44 KEY_Z
0x32, //45 KEY_X
0x33, //46 KEY_C
0x34, //47 KEY_V
0x35, //48 KEY_B
0x36, //49 KEY_N
0x37, //50 KEY_M
0x38, //51 KEY_COMMA
0x39, //52 KEY_DOT
0x3a, //53 KEY_SLASH
0x61, //54 KEY_RIGHTSHIFT
0x5d, //55 KEY_KPASTERISK
0x64, //56 KEY_LEFTALT
0x40, //57 KEY_SPACE
0x62 | CAPS_TOGGLE, //58 KEY_CAPSLOCK
0x50, //59 KEY_F1
0x51, //60 KEY_F2
0x52, //61 KEY_F3
0x53, //62 KEY_F4
0x54, //63 KEY_F5
0x55, //64 KEY_F6
0x56, //65 KEY_F7
0x57, //66 KEY_F8
0x58, //67 KEY_F9
0x59, //68 KEY_F10
NONE, //69 KEY_NUMLOCK
NONE, //70 KEY_SCROLLLOCK
0x3d, //71 KEY_KP7
0x3e, //72 KEY_KP8
0x3f, //73 KEY_KP9
0x4a, //74 KEY_KPMINUS
0x2d, //75 KEY_KP4
0x2e, //76 KEY_KP5
0x2f, //77 KEY_KP6
0x5e, //78 KEY_KPPLUS
0x1d, //79 KEY_KP1
0x1e, //80 KEY_KP2
0x1f, //81 KEY_KP3
0x0f, //82 KEY_KP0
0x3c, //83 KEY_KPDOT
NONE, //84 ???
NONE, //85 KEY_ZENKAKU
0x30, //86 KEY_102ND, '<' on most keyboards
0x5f, //87 KEY_F11
NONE, //88 KEY_F12
NONE, //89 KEY_RO
NONE, //90 KEY_KATAKANA
NONE, //91 KEY_HIRAGANA
NONE, //92 KEY_HENKAN
NONE, //93 KEY_KATAKANA
NONE, //94 KEY_MUHENKAN
NONE, //95 KEY_KPJPCOMMA
0x43, //96 KEY_KPENTER
0x63, //97 KEY_RIGHTCTRL
0x5c, //98 KEY_KPSLASH
NONE, //99 KEY_SYSRQ
0x65, //100 KEY_RIGHTALT
NONE, //101 KEY_LINEFEED
0x6a, //102 KEY_HOME
0x4c, //103 KEY_UP
0x5b, //104 KEY_PAGEUP
0x4f, //105 KEY_LEFT
0x4e, //106 KEY_RIGHT
NONE, //107 KEY_END
0x4d, //108 KEY_DOWN
0x5a, //109 KEY_PAGEDOWN
0x0d, //110 KEY_INSERT
0x46, //111 KEY_DELETE
NONE, //112 KEY_MACRO
NONE, //113 KEY_MUTE
NONE, //114 KEY_VOLUMEDOWN
NONE, //115 KEY_VOLUMEUP
NONE, //116 KEY_POWER
NONE, //117 KEY_KPEQUAL
NONE, //118 KEY_KPPLUSMINUS
NONE, //119 KEY_PAUSE
NONE, //120 KEY_SCALE
NONE, //121 KEY_KPCOMMA
NONE, //122 KEY_HANGEUL
NONE, //123 KEY_HANJA
NONE, //124 KEY_YEN
0x66, //125 KEY_LEFTMETA
0x67, //126 KEY_RIGHTMETA
NONE, //127 KEY_COMPOSE
NONE, //128 KEY_STOP
NONE, //129 KEY_AGAIN
NONE, //130 KEY_PROPS
NONE, //131 KEY_UNDO
NONE, //132 KEY_FRONT
NONE, //133 KEY_COPY
NONE, //134 KEY_OPEN
NONE, //135 KEY_PASTE
NONE, //136 KEY_FIND
NONE, //137 KEY_CUT
NONE, //138 KEY_HELP
NONE, //139 KEY_MENU
NONE, //140 KEY_CALC
NONE, //141 KEY_SETUP
NONE, //142 KEY_SLEEP
NONE, //143 KEY_WAKEUP
NONE, //144 KEY_FILE
NONE, //145 KEY_SENDFILE
NONE, //146 KEY_DELETEFILE
NONE, //147 KEY_XFER
NONE, //148 KEY_PROG1
NONE, //149 KEY_PROG2
NONE, //150 KEY_WWW
NONE, //151 KEY_MSDOS
NONE, //152 KEY_SCREENLOCK
NONE, //153 KEY_DIRECTION
NONE, //154 KEY_CYCLEWINDOWS
NONE, //155 KEY_MAIL
NONE, //156 KEY_BOOKMARKS
NONE, //157 KEY_COMPUTER
NONE, //158 KEY_BACK
NONE, //159 KEY_FORWARD
NONE, //160 KEY_CLOSECD
NONE, //161 KEY_EJECTCD
NONE, //162 KEY_EJECTCLOSECD
NONE, //163 KEY_NEXTSONG
NONE, //164 KEY_PLAYPAUSE
NONE, //165 KEY_PREVIOUSSONG
NONE, //166 KEY_STOPCD
NONE, //167 KEY_RECORD
NONE, //168 KEY_REWIND
NONE, //169 KEY_PHONE
NONE, //170 KEY_ISO
NONE, //171 KEY_CONFIG
NONE, //172 KEY_HOMEPAGE
NONE, //173 KEY_REFRESH
NONE, //174 KEY_EXIT
NONE, //175 KEY_MOVE
NONE, //176 KEY_EDIT
NONE, //177 KEY_SCROLLUP
NONE, //178 KEY_SCROLLDOWN
NONE, //179 KEY_KPLEFTPAREN
NONE, //180 KEY_KPRIGHTPAREN
NONE, //181 KEY_NEW
NONE, //182 KEY_REDO
0x5a, //183 KEY_F13
0x5b, //184 KEY_F14
NONE, //185 KEY_F15
0x5f, //186 KEY_F16
NONE, //187 KEY_F17
NONE, //188 KEY_F18
NONE, //189 KEY_F19
NONE, //190 KEY_F20
NONE, //191 KEY_F21
NONE, //192 KEY_F22
NONE, //193 KEY_F23
0x2b, //194 # on German keyboard, was 0x63 (CTRL on Amiga), 194 KEY_F24
NONE, //195 ???
NONE, //196 ???
NONE, //197 ???
NONE, //198 ???
NONE, //199 ???
NONE, //200 KEY_PLAYCD
NONE, //201 KEY_PAUSECD
NONE, //202 KEY_PROG3
NONE, //203 KEY_PROG4
NONE, //204 KEY_DASHBOARD
NONE, //205 KEY_SUSPEND
NONE, //206 KEY_CLOSE
NONE, //207 KEY_PLAY
NONE, //208 KEY_FASTFORWARD
NONE, //209 KEY_BASSBOOST
NONE, //210 KEY_PRINT
NONE, //211 KEY_HP
NONE, //212 KEY_CAMERA
NONE, //213 KEY_SOUND
NONE, //214 KEY_QUESTION
NONE, //215 KEY_EMAIL
NONE, //216 KEY_CHAT
NONE, //217 KEY_SEARCH
NONE, //218 KEY_CONNECT
NONE, //219 KEY_FINANCE
NONE, //220 KEY_SPORT
NONE, //221 KEY_SHOP
NONE, //222 KEY_ALTERASE
NONE, //223 KEY_CANCEL
NONE, //224 KEY_BRIGHT_DOWN
NONE, //225 KEY_BRIGHT_UP
NONE, //226 KEY_MEDIA
NONE, //227 KEY_SWITCHVIDEO
NONE, //228 KEY_DILLUMTOGGLE
NONE, //229 KEY_DILLUMDOWN
NONE, //230 KEY_DILLUMUP
NONE, //231 KEY_SEND
NONE, //232 KEY_REPLY
NONE, //233 KEY_FORWARDMAIL
NONE, //234 KEY_SAVE
NONE, //235 KEY_DOCUMENTS
NONE, //236 KEY_BATTERY
NONE, //237 KEY_BLUETOOTH
NONE, //238 KEY_WLAN
NONE, //239 KEY_UWB
NONE, //240 KEY_UNKNOWN
NONE, //241 KEY_VIDEO_NEXT
NONE, //242 KEY_VIDEO_PREV
NONE, //243 KEY_BRIGHT_CYCLE
NONE, //244 KEY_BRIGHT_AUTO
NONE, //245 KEY_DISPLAY_OFF
NONE, //246 KEY_WWAN
NONE, //247 KEY_RFKILL
NONE, //248 KEY_MICMUTE
NONE, //249 ???
NONE, //250 ???
NONE, //251 ???
NONE, //252 ???
NONE, //253 ???
NONE, //254 ???
NONE //255 ???
};
static const int ev2ps2[] =
{
NONE, //0 KEY_RESERVED
0x76, //1 KEY_ESC
0x16, //2 KEY_1
0x1e, //3 KEY_2
0x26, //4 KEY_3
0x25, //5 KEY_4
0x2e, //6 KEY_5
0x36, //7 KEY_6
0x3d, //8 KEY_7
0x3e, //9 KEY_8
0x46, //10 KEY_9
0x45, //11 KEY_0
0x4e, //12 KEY_MINUS
0x55, //13 KEY_EQUAL
0x66, //14 KEY_BACKSPACE
0x0d, //15 KEY_TAB
0x15, //16 KEY_Q
0x1d, //17 KEY_W
0x24, //18 KEY_E
0x2d, //19 KEY_R
0x2c, //20 KEY_T
0x35, //21 KEY_Y
0x3c, //22 KEY_U
0x43, //23 KEY_I
0x44, //24 KEY_O
0x4d, //25 KEY_P
0x54, //26 KEY_LEFTBRACE
0x5b, //27 KEY_RIGHTBRACE
0x5a, //28 KEY_ENTER
LCTRL | 0x14, //29 KEY_LEFTCTRL
0x1c, //30 KEY_A
0x1b, //31 KEY_S
0x23, //32 KEY_D
0x2b, //33 KEY_F
0x34, //34 KEY_G
0x33, //35 KEY_H
0x3b, //36 KEY_J
0x42, //37 KEY_K
0x4b, //38 KEY_L
0x4c, //39 KEY_SEMICOLON
0x52, //40 KEY_APOSTROPHE
0x0e, //41 KEY_GRAVE
LSHIFT | 0x12, //42 KEY_LEFTSHIFT
0x5d, //43 KEY_BACKSLASH
0x1a, //44 KEY_Z
0x22, //45 KEY_X
0x21, //46 KEY_C
0x2a, //47 KEY_V
0x32, //48 KEY_B
0x31, //49 KEY_N
0x3a, //50 KEY_M
0x41, //51 KEY_COMMA
0x49, //52 KEY_DOT
0x4a, //53 KEY_SLASH
RSHIFT | 0x59, //54 KEY_RIGHTSHIFT
0x7c, //55 KEY_KPASTERISK
LALT | 0x11, //56 KEY_LEFTALT
0x29, //57 KEY_SPACE
0x58, //58 KEY_CAPSLOCK
0x05, //59 KEY_F1
0x06, //60 KEY_F2
0x04, //61 KEY_F3
0x0c, //62 KEY_F4
0x03, //63 KEY_F5
0x0b, //64 KEY_F6
0x83, //65 KEY_F7
0x0a, //66 KEY_F8
0x01, //67 KEY_F9
0x09, //68 KEY_F10
EMU_SWITCH_2 | 0x77, //69 KEY_NUMLOCK
EMU_SWITCH_1 | 0x7E, //70 KEY_SCROLLLOCK
0x6c, //71 KEY_KP7
0x75, //72 KEY_KP8
0x7d, //73 KEY_KP9
0x7b, //74 KEY_KPMINUS
0x6b, //75 KEY_KP4
0x73, //76 KEY_KP5
0x74, //77 KEY_KP6
0x79, //78 KEY_KPPLUS
0x69, //79 KEY_KP1
0x72, //80 KEY_KP2
0x7a, //81 KEY_KP3
0x70, //82 KEY_KP0
0x71, //83 KEY_KPDOT
NONE, //84 ???
0x0e, //85 KEY_ZENKAKU
0x61, //86 KEY_102ND
0x78, //87 KEY_F11
0x07, //88 KEY_F12
0x13, //89 KEY_RO
0x13, //90 KEY_KATAKANA
0x13, //91 KEY_HIRAGANA
0x64, //92 KEY_HENKAN
0x13, //93 KEY_KATAKANA
0x67, //94 KEY_MUHENKAN
NONE, //95 KEY_KPJPCOMMA
EXT | 0x5a, //96 KEY_KPENTER
RCTRL | EXT | 0x14, //97 KEY_RIGHTCTRL
EXT | 0x4a, //98 KEY_KPSLASH
0xE2, //99 KEY_SYSRQ
RALT | EXT | 0x11, //100 KEY_RIGHTALT
NONE, //101 KEY_LINEFEED
EXT | 0x6c, //102 KEY_HOME
EXT | 0x75, //103 KEY_UP
EXT | 0x7d, //104 KEY_PAGEUP
EXT | 0x6b, //105 KEY_LEFT
EXT | 0x74, //106 KEY_RIGHT
EXT | 0x69, //107 KEY_END
EXT | 0x72, //108 KEY_DOWN
EXT | 0x7a, //109 KEY_PAGEDOWN
EXT | 0x70, //110 KEY_INSERT
EXT | 0x71, //111 KEY_DELETE
NONE, //112 KEY_MACRO
NONE, //113 KEY_MUTE
NONE, //114 KEY_VOLUMEDOWN
NONE, //115 KEY_VOLUMEUP
NONE, //116 KEY_POWER
NONE, //117 KEY_KPEQUAL
NONE, //118 KEY_KPPLUSMINUS
0xE1, //119 KEY_PAUSE
NONE, //120 KEY_SCALE
NONE, //121 KEY_KPCOMMA
NONE, //122 KEY_HANGEUL
NONE, //123 KEY_HANJA
0x6a, //124 KEY_YEN
LGUI | EXT | 0x1f, //125 KEY_LEFTMETA
RGUI | EXT | 0x27, //126 KEY_RIGHTMETA
EXT | 0x2f, //127 KEY_COMPOSE
NONE, //128 KEY_STOP
NONE, //129 KEY_AGAIN
NONE, //130 KEY_PROPS
NONE, //131 KEY_UNDO
NONE, //132 KEY_FRONT
NONE, //133 KEY_COPY
NONE, //134 KEY_OPEN
NONE, //135 KEY_PASTE
NONE, //136 KEY_FIND
NONE, //137 KEY_CUT
NONE, //138 KEY_HELP
NONE, //139 KEY_MENU
NONE, //140 KEY_CALC
NONE, //141 KEY_SETUP
NONE, //142 KEY_SLEEP
NONE, //143 KEY_WAKEUP
NONE, //144 KEY_FILE
NONE, //145 KEY_SENDFILE
NONE, //146 KEY_DELETEFILE
NONE, //147 KEY_XFER
NONE, //148 KEY_PROG1
NONE, //149 KEY_PROG2
NONE, //150 KEY_WWW
NONE, //151 KEY_MSDOS
NONE, //152 KEY_SCREENLOCK
NONE, //153 KEY_DIRECTION
NONE, //154 KEY_CYCLEWINDOWS
NONE, //155 KEY_MAIL
NONE, //156 KEY_BOOKMARKS
NONE, //157 KEY_COMPUTER
NONE, //158 KEY_BACK
NONE, //159 KEY_FORWARD
NONE, //160 KEY_CLOSECD
NONE, //161 KEY_EJECTCD
NONE, //162 KEY_EJECTCLOSECD
NONE, //163 KEY_NEXTSONG
NONE, //164 KEY_PLAYPAUSE
NONE, //165 KEY_PREVIOUSSONG
NONE, //166 KEY_STOPCD
NONE, //167 KEY_RECORD
NONE, //168 KEY_REWIND
NONE, //169 KEY_PHONE
NONE, //170 KEY_ISO
NONE, //171 KEY_CONFIG
NONE, //172 KEY_HOMEPAGE
NONE, //173 KEY_REFRESH
NONE, //174 KEY_EXIT
NONE, //175 KEY_MOVE
NONE, //176 KEY_EDIT
NONE, //177 KEY_SCROLLUP
NONE, //178 KEY_SCROLLDOWN
NONE, //179 KEY_KPLEFTPAREN
NONE, //180 KEY_KPRIGHTPAREN
NONE, //181 KEY_NEW
NONE, //182 KEY_REDO
NONE, //183 KEY_F13
NONE, //184 KEY_F14
NONE, //185 KEY_F15
NONE, //186 KEY_F16
EMU_SWITCH_1 | 1, //187 KEY_F17
EMU_SWITCH_1 | 2, //188 KEY_F18
EMU_SWITCH_1 | 3, //189 KEY_F19
EMU_SWITCH_1 | 4, //190 KEY_F20
NONE, //191 KEY_F21
NONE, //192 KEY_F22
NONE, //193 KEY_F23
0x5D, //194 U-mlaut on DE mapped to backslash
NONE, //195 ???
NONE, //196 ???
NONE, //197 ???
NONE, //198 ???
NONE, //199 ???
NONE, //200 KEY_PLAYCD
NONE, //201 KEY_PAUSECD
NONE, //202 KEY_PROG3
NONE, //203 KEY_PROG4
NONE, //204 KEY_DASHBOARD
NONE, //205 KEY_SUSPEND
NONE, //206 KEY_CLOSE
NONE, //207 KEY_PLAY
NONE, //208 KEY_FASTFORWARD
NONE, //209 KEY_BASSBOOST
NONE, //210 KEY_PRINT
NONE, //211 KEY_HP
NONE, //212 KEY_CAMERA
NONE, //213 KEY_SOUND
NONE, //214 KEY_QUESTION
NONE, //215 KEY_EMAIL
NONE, //216 KEY_CHAT
NONE, //217 KEY_SEARCH
NONE, //218 KEY_CONNECT
NONE, //219 KEY_FINANCE
NONE, //220 KEY_SPORT
NONE, //221 KEY_SHOP
NONE, //222 KEY_ALTERASE
NONE, //223 KEY_CANCEL
NONE, //224 KEY_BRIGHT_DOWN
NONE, //225 KEY_BRIGHT_UP
NONE, //226 KEY_MEDIA
NONE, //227 KEY_SWITCHVIDEO
NONE, //228 KEY_DILLUMTOGGLE
NONE, //229 KEY_DILLUMDOWN
NONE, //230 KEY_DILLUMUP
NONE, //231 KEY_SEND
NONE, //232 KEY_REPLY
NONE, //233 KEY_FORWARDMAIL
NONE, //234 KEY_SAVE
NONE, //235 KEY_DOCUMENTS
NONE, //236 KEY_BATTERY
NONE, //237 KEY_BLUETOOTH
NONE, //238 KEY_WLAN
NONE, //239 KEY_UWB
NONE, //240 KEY_UNKNOWN
NONE, //241 KEY_VIDEO_NEXT
NONE, //242 KEY_VIDEO_PREV
NONE, //243 KEY_BRIGHT_CYCLE
NONE, //244 KEY_BRIGHT_AUTO
NONE, //245 KEY_DISPLAY_OFF
NONE, //246 KEY_WWAN
NONE, //247 KEY_RFKILL
NONE, //248 KEY_MICMUTE
NONE, //249 ???
NONE, //250 ???
NONE, //251 ???
NONE, //252 ???
NONE, //253 ???
NONE, //254 ???
NONE //255 ???
};
static const int ev2ps2_set1[] =
{
NONE, //0 KEY_RESERVED
0x01, //1 KEY_ESC
0x02, //2 KEY_1
0x03, //3 KEY_2
0x04, //4 KEY_3
0x05, //5 KEY_4
0x06, //6 KEY_5
0x07, //7 KEY_6
0x08, //8 KEY_7
0x09, //9 KEY_8
0x0a, //10 KEY_9
0x0b, //11 KEY_0
0x0c, //12 KEY_MINUS
0x0d, //13 KEY_EQUAL
0x0e, //14 KEY_BACKSPACE
0x0f, //15 KEY_TAB
0x10, //16 KEY_Q
0x11, //17 KEY_W
0x12, //18 KEY_E
0x13, //19 KEY_R
0x14, //20 KEY_T
0x15, //21 KEY_Y
0x16, //22 KEY_U
0x17, //23 KEY_I
0x18, //24 KEY_O
0x19, //25 KEY_P
0x1a, //26 KEY_LEFTBRACE
0x1b, //27 KEY_RIGHTBRACE
0x1c, //28 KEY_ENTER
LCTRL | 0x1d, //29 KEY_LEFTCTRL
0x1e, //30 KEY_A
0x1f, //31 KEY_S
0x20, //32 KEY_D
0x21, //33 KEY_F
0x22, //34 KEY_G
0x23, //35 KEY_H
0x24, //36 KEY_J
0x25, //37 KEY_K
0x26, //38 KEY_L
0x27, //39 KEY_SEMICOLON ;
0x28, //40 KEY_APOSTROPHE
0x29, //41 KEY_GRAVE
LSHIFT | 0x2a, //42 KEY_LEFTSHIFT
0x2b, //43 KEY_BACKSLASH
0x2c, //44 KEY_Z
0x2d, //45 KEY_X
0x2e, //46 KEY_C
0x2f, //47 KEY_V
0x30, //48 KEY_B
0x31, //49 KEY_N
0x32, //50 KEY_M
0x33, //51 KEY_COMMA
0x34, //52 KEY_DOT
0x35, //53 KEY_SLASH
RSHIFT | 0x36, //54 KEY_RIGHTSHIFT
0x37, //55 KEY_KPASTERISK
LALT | 0x38, //56 KEY_LEFTALT
0x39, //57 KEY_SPACE
0x3a, //58 KEY_CAPSLOCK
0x3b, //59 KEY_F1
0x3c, //60 KEY_F2
0x3d, //61 KEY_F3
0x3e, //62 KEY_F4
0x3f, //63 KEY_F5
0x40, //64 KEY_F6
0x41, //65 KEY_F7
0x42, //66 KEY_F8
0x43, //67 KEY_F9
0x44, //68 KEY_F10
EMU_SWITCH_2 | 0x45, //69 KEY_NUMLOCK
EMU_SWITCH_1 | 0x46, //70 KEY_SCROLLLOCK
0x47, //71 KEY_KP7
0x48, //72 KEY_KP8
0x49, //73 KEY_KP9
0x4a, //74 KEY_KPMINUS
0x4b, //75 KEY_KP4
0x4c, //76 KEY_KP5
0x4d, //77 KEY_KP6
0x4e, //78 KEY_KPPLUS
0x4f, //79 KEY_KP1
0x50, //80 KEY_KP2
0x51, //81 KEY_KP3
0x52, //82 KEY_KP0
0x53, //83 KEY_KPDOT
NONE, //84 ???
NONE, //85 KEY_ZENKAKU
0x56, //86 KEY_102ND
0x57, //87 KEY_F11
0x58, //88 KEY_F12
NONE, //89 KEY_RO
NONE, //90 KEY_KATAKANA
NONE, //91 KEY_HIRAGANA
NONE, //92 KEY_HENKAN
NONE, //93 KEY_KATAKANA
NONE, //94 KEY_MUHENKAN
NONE, //95 KEY_KPJPCOMMA
EXT | 0x1c, //96 KEY_KPENTER
RCTRL | EXT | 0x1d, //97 KEY_RIGHTCTRL
EXT | 0x35, //98 KEY_KPSLASH
0xE2, //99 KEY_SYSRQ
RALT | EXT | 0x38, //100 KEY_RIGHTALT
NONE, //101 KEY_LINEFEED
EXT | 0x47, //102 KEY_HOME
EXT | 0x48, //103 KEY_UP
EXT | 0x49, //104 KEY_PAGEUP
EXT | 0x4b, //105 KEY_LEFT
EXT | 0x4d, //106 KEY_RIGHT
EXT | 0x4f, //107 KEY_END
EXT | 0x50, //108 KEY_DOWN
EXT | 0x51, //109 KEY_PAGEDOWN
EXT | 0x52, //110 KEY_INSERT
EXT | 0x53, //111 KEY_DELETE
NONE, //112 KEY_MACRO
NONE, //113 KEY_MUTE
NONE, //114 KEY_VOLUMEDOWN
NONE, //115 KEY_VOLUMEUP
NONE, //116 KEY_POWER
NONE, //117 KEY_KPEQUAL
NONE, //118 KEY_KPPLUSMINUS
0xE1, //119 KEY_PAUSE
NONE, //120 KEY_SCALE
NONE, //121 KEY_KPCOMMA
NONE, //122 KEY_HANGEUL
NONE, //123 KEY_HANJA
NONE, //124 KEY_YEN
LGUI | EXT | 0x5B, //125 KEY_LEFTMETA
RGUI | EXT | 0x5C, //126 KEY_RIGHTMETA
NONE, //127 KEY_COMPOSE
NONE, //128 KEY_STOP
NONE, //129 KEY_AGAIN
NONE, //130 KEY_PROPS
NONE, //131 KEY_UNDO
NONE, //132 KEY_FRONT
NONE, //133 KEY_COPY
NONE, //134 KEY_OPEN
NONE, //135 KEY_PASTE
NONE, //136 KEY_FIND
NONE, //137 KEY_CUT
NONE, //138 KEY_HELP
NONE, //139 KEY_MENU
NONE, //140 KEY_CALC
NONE, //141 KEY_SETUP
NONE, //142 KEY_SLEEP
NONE, //143 KEY_WAKEUP
NONE, //144 KEY_FILE
NONE, //145 KEY_SENDFILE
NONE, //146 KEY_DELETEFILE
NONE, //147 KEY_XFER
NONE, //148 KEY_PROG1
NONE, //149 KEY_PROG2
NONE, //150 KEY_WWW
NONE, //151 KEY_MSDOS
NONE, //152 KEY_SCREENLOCK
NONE, //153 KEY_DIRECTION
NONE, //154 KEY_CYCLEWINDOWS
NONE, //155 KEY_MAIL
NONE, //156 KEY_BOOKMARKS
NONE, //157 KEY_COMPUTER
NONE, //158 KEY_BACK
NONE, //159 KEY_FORWARD
NONE, //160 KEY_CLOSECD
NONE, //161 KEY_EJECTCD
NONE, //162 KEY_EJECTCLOSECD
NONE, //163 KEY_NEXTSONG
NONE, //164 KEY_PLAYPAUSE
NONE, //165 KEY_PREVIOUSSONG
NONE, //166 KEY_STOPCD
NONE, //167 KEY_RECORD
NONE, //168 KEY_REWIND
NONE, //169 KEY_PHONE
NONE, //170 KEY_ISO
NONE, //171 KEY_CONFIG
NONE, //172 KEY_HOMEPAGE
NONE, //173 KEY_REFRESH
NONE, //174 KEY_EXIT
NONE, //175 KEY_MOVE
NONE, //176 KEY_EDIT
NONE, //177 KEY_SCROLLUP
NONE, //178 KEY_SCROLLDOWN
NONE, //179 KEY_KPLEFTPAREN
NONE, //180 KEY_KPRIGHTPAREN
NONE, //181 KEY_NEW
NONE, //182 KEY_REDO
NONE, //183 KEY_F13
NONE, //184 KEY_F14
NONE, //185 KEY_F15
NONE, //186 KEY_F16
EMU_SWITCH_1 | 1, //187 KEY_F17
EMU_SWITCH_1 | 2, //188 KEY_F18
EMU_SWITCH_1 | 3, //189 KEY_F19
EMU_SWITCH_1 | 4, //190 KEY_F20
NONE, //191 KEY_F21
NONE, //192 KEY_F22
NONE, //193 KEY_F23
0x2B, //194 U-mlaut on DE mapped to backslash
NONE, //195 ???
NONE, //196 ???
NONE, //197 ???
NONE, //198 ???
NONE, //199 ???
NONE, //200 KEY_PLAYCD
NONE, //201 KEY_PAUSECD
NONE, //202 KEY_PROG3
NONE, //203 KEY_PROG4
NONE, //204 KEY_DASHBOARD
NONE, //205 KEY_SUSPEND
NONE, //206 KEY_CLOSE
NONE, //207 KEY_PLAY
NONE, //208 KEY_FASTFORWARD
NONE, //209 KEY_BASSBOOST
NONE, //210 KEY_PRINT
NONE, //211 KEY_HP
NONE, //212 KEY_CAMERA
NONE, //213 KEY_SOUND
NONE, //214 KEY_QUESTION
NONE, //215 KEY_EMAIL
NONE, //216 KEY_CHAT
NONE, //217 KEY_SEARCH
NONE, //218 KEY_CONNECT
NONE, //219 KEY_FINANCE
NONE, //220 KEY_SPORT
NONE, //221 KEY_SHOP
NONE, //222 KEY_ALTERASE
NONE, //223 KEY_CANCEL
NONE, //224 KEY_BRIGHT_DOWN
NONE, //225 KEY_BRIGHT_UP
NONE, //226 KEY_MEDIA
NONE, //227 KEY_SWITCHVIDEO
NONE, //228 KEY_DILLUMTOGGLE
NONE, //229 KEY_DILLUMDOWN
NONE, //230 KEY_DILLUMUP
NONE, //231 KEY_SEND
NONE, //232 KEY_REPLY
NONE, //233 KEY_FORWARDMAIL
NONE, //234 KEY_SAVE
NONE, //235 KEY_DOCUMENTS
NONE, //236 KEY_BATTERY
NONE, //237 KEY_BLUETOOTH
NONE, //238 KEY_WLAN
NONE, //239 KEY_UWB
NONE, //240 KEY_UNKNOWN
NONE, //241 KEY_VIDEO_NEXT
NONE, //242 KEY_VIDEO_PREV
NONE, //243 KEY_BRIGHT_CYCLE
NONE, //244 KEY_BRIGHT_AUTO
NONE, //245 KEY_DISPLAY_OFF
NONE, //246 KEY_WWAN
NONE, //247 KEY_RFKILL
NONE, //248 KEY_MICMUTE
NONE, //249 ???
NONE, //250 ???
NONE, //251 ???
NONE, //252 ???
NONE, //253 ???
NONE, //254 ???
NONE //255 ???
};
static int ev2archie[] =
{
NONE, //0 KEY_RESERVED
0x00, //1 KEY_ESC
0x11, //2 KEY_1
0x12, //3 KEY_2
0x13, //4 KEY_3
0x14, //5 KEY_4
0x15, //6 KEY_5
0x16, //7 KEY_6
0x17, //8 KEY_7
0x18, //9 KEY_8
0x19, //10 KEY_9
0x1a, //11 KEY_0
0x1b, //12 KEY_MINUS
0x1c, //13 KEY_EQUAL
0x1e, //14 KEY_BACKSPACE
0x26, //15 KEY_TAB
0x27, //16 KEY_Q
0x28, //17 KEY_W
0x29, //18 KEY_E
0x2a, //19 KEY_R
0x2b, //20 KEY_T
0x2c, //21 KEY_Y
0x2d, //22 KEY_U
0x2e, //23 KEY_I
0x2f, //24 KEY_O
0x30, //25 KEY_P
0x31, //26 KEY_LEFTBRACE
0x32, //27 KEY_RIGHTBRACE
0x47, //28 KEY_ENTER
0x3b, //29 KEY_LEFTCTRL
0x3c, //30 KEY_A
0x3d, //31 KEY_S
0x3e, //32 KEY_D
0x3f, //33 KEY_F
0x40, //34 KEY_G
0x41, //35 KEY_H
0x42, //36 KEY_J
0x43, //37 KEY_K
0x44, //38 KEY_L
0x45, //39 KEY_SEMICOLON
0x46, //40 KEY_APOSTROPHE
0x10, //41 KEY_GRAVE
0x4c, //42 KEY_LEFTSHIFT
0x33, //43 KEY_BACKSLASH
0x4e, //44 KEY_Z
0x4f, //45 KEY_X
0x50, //46 KEY_C
0x51, //47 KEY_V
0x52, //48 KEY_B
0x53, //49 KEY_N
0x54, //50 KEY_M
0x55, //51 KEY_COMMA
0x56, //52 KEY_DOT
0x57, //53 KEY_SLASH
0x58, //54 KEY_RIGHTSHIFT
0x24, //55 KEY_KPASTERISK
0x5e, //56 KEY_LEFTALT
0x5f, //57 KEY_SPACE
0x5d, //58 KEY_CAPSLOCK
0x01, //59 KEY_F1
0x02, //60 KEY_F2
0x03, //61 KEY_F3
0x04, //62 KEY_F4
0x05, //63 KEY_F5
0x06, //64 KEY_F6
0x07, //65 KEY_F7
0x08, //66 KEY_F8
0x09, //67 KEY_F9
0x0a, //68 KEY_F10
0x22, //69 KEY_NUMLOCK
NONE, //70 KEY_SCROLLLOCK
0x37, //71 KEY_KP7
0x38, //72 KEY_KP8
0x39, //73 KEY_KP9
0x3a, //74 KEY_KPMINUS
0x48, //75 KEY_KP4
0x49, //76 KEY_KP5
0x4a, //77 KEY_KP6
0x4b, //78 KEY_KPPLUS
0x5a, //79 KEY_KP1
0x5b, //80 KEY_KP2
0x5c, //81 KEY_KP3
0x65, //82 KEY_KP0
0x66, //83 KEY_KPDOT
NONE, //84 ???
NONE, //85 KEY_ZENKAKU
NONE, //86 KEY_102ND
0x0b, //87 KEY_F11
0x0c, //88 KEY_F12
NONE, //89 KEY_RO
NONE, //90 KEY_KATAKANA
NONE, //91 KEY_HIRAGANA
NONE, //92 KEY_HENKAN
NONE, //93 KEY_KATAKANA
NONE, //94 KEY_MUHENKAN
NONE, //95 KEY_KPJPCOMMA
0x67, //96 KEY_KPENTER
0x61, //97 KEY_RIGHTCTRL
0x23, //98 KEY_KPSLASH
0x0D, //99 KEY_SYSRQ
0x60, //100 KEY_RIGHTALT
NONE, //101 KEY_LINEFEED
0x20, //102 KEY_HOME
0x59, //103 KEY_UP
0x21, //104 KEY_PAGEUP
0x62, //105 KEY_LEFT
0x64, //106 KEY_RIGHT
0x35, //107 KEY_END
0x63, //108 KEY_DOWN
0x36, //109 KEY_PAGEDOWN
0x1f, //110 KEY_INSERT
0x34, //111 KEY_DELETE
NONE, //112 KEY_MACRO
NONE, //113 KEY_MUTE
NONE, //114 KEY_VOLUMEDOWN
NONE, //115 KEY_VOLUMEUP
NONE, //116 KEY_POWER
NONE, //117 KEY_KPEQUAL
NONE, //118 KEY_KPPLUSMINUS
0x0f, //119 KEY_PAUSE
NONE, //120 KEY_SCALE
NONE, //121 KEY_KPCOMMA
NONE, //122 KEY_HANGEUL
NONE, //123 KEY_HANJA
NONE, //124 KEY_YEN
NONE, //125 KEY_LEFTMETA
NONE, //126 KEY_RIGHTMETA
0x71, //127 KEY_COMPOSE
NONE, //128 KEY_STOP
NONE, //129 KEY_AGAIN
NONE, //130 KEY_PROPS
NONE, //131 KEY_UNDO
NONE, //132 KEY_FRONT
NONE, //133 KEY_COPY
NONE, //134 KEY_OPEN
NONE, //135 KEY_PASTE
NONE, //136 KEY_FIND
NONE, //137 KEY_CUT
NONE, //138 KEY_HELP
NONE, //139 KEY_MENU
NONE, //140 KEY_CALC
NONE, //141 KEY_SETUP
NONE, //142 KEY_SLEEP
NONE, //143 KEY_WAKEUP
NONE, //144 KEY_FILE
NONE, //145 KEY_SENDFILE
NONE, //146 KEY_DELETEFILE
NONE, //147 KEY_XFER
NONE, //148 KEY_PROG1
NONE, //149 KEY_PROG2
NONE, //150 KEY_WWW
NONE, //151 KEY_MSDOS
NONE, //152 KEY_SCREENLOCK
NONE, //153 KEY_DIRECTION
NONE, //154 KEY_CYCLEWINDOWS
NONE, //155 KEY_MAIL
NONE, //156 KEY_BOOKMARKS
NONE, //157 KEY_COMPUTER
NONE, //158 KEY_BACK
NONE, //159 KEY_FORWARD
NONE, //160 KEY_CLOSECD
NONE, //161 KEY_EJECTCD
NONE, //162 KEY_EJECTCLOSECD
NONE, //163 KEY_NEXTSONG
NONE, //164 KEY_PLAYPAUSE
NONE, //165 KEY_PREVIOUSSONG
NONE, //166 KEY_STOPCD
NONE, //167 KEY_RECORD
NONE, //168 KEY_REWIND
NONE, //169 KEY_PHONE
NONE, //170 KEY_ISO
NONE, //171 KEY_CONFIG
NONE, //172 KEY_HOMEPAGE
NONE, //173 KEY_REFRESH
NONE, //174 KEY_EXIT
NONE, //175 KEY_MOVE
NONE, //176 KEY_EDIT
NONE, //177 KEY_SCROLLUP
NONE, //178 KEY_SCROLLDOWN
NONE, //179 KEY_KPLEFTPAREN
NONE, //180 KEY_KPRIGHTPAREN
NONE, //181 KEY_NEW
NONE, //182 KEY_REDO
NONE, //183 KEY_F13
NONE, //184 KEY_F14
NONE, //185 KEY_F15
NONE, //186 KEY_F16
NONE, //187 KEY_F17
NONE, //188 KEY_F18
NONE, //189 KEY_F19
NONE, //190 KEY_F20
NONE, //191 KEY_F21
NONE, //192 KEY_F22
NONE, //193 KEY_F23
NONE, //194 KEY_F24
NONE, //195 ???
NONE, //196 ???
NONE, //197 ???
NONE, //198 ???
NONE, //199 ???
NONE, //200 KEY_PLAYCD
NONE, //201 KEY_PAUSECD
NONE, //202 KEY_PROG3
NONE, //203 KEY_PROG4
NONE, //204 KEY_DASHBOARD
NONE, //205 KEY_SUSPEND
NONE, //206 KEY_CLOSE
NONE, //207 KEY_PLAY
NONE, //208 KEY_FASTFORWARD
NONE, //209 KEY_BASSBOOST
NONE, //210 KEY_PRINT
NONE, //211 KEY_HP
NONE, //212 KEY_CAMERA
NONE, //213 KEY_SOUND
NONE, //214 KEY_QUESTION
NONE, //215 KEY_EMAIL
NONE, //216 KEY_CHAT
NONE, //217 KEY_SEARCH
NONE, //218 KEY_CONNECT
NONE, //219 KEY_FINANCE
NONE, //220 KEY_SPORT
NONE, //221 KEY_SHOP
NONE, //222 KEY_ALTERASE
NONE, //223 KEY_CANCEL
NONE, //224 KEY_BRIGHT_DOWN
NONE, //225 KEY_BRIGHT_UP
NONE, //226 KEY_MEDIA
NONE, //227 KEY_SWITCHVIDEO
NONE, //228 KEY_DILLUMTOGGLE
NONE, //229 KEY_DILLUMDOWN
NONE, //230 KEY_DILLUMUP
NONE, //231 KEY_SEND
NONE, //232 KEY_REPLY
NONE, //233 KEY_FORWARDMAIL
NONE, //234 KEY_SAVE
NONE, //235 KEY_DOCUMENTS
NONE, //236 KEY_BATTERY
NONE, //237 KEY_BLUETOOTH
NONE, //238 KEY_WLAN
NONE, //239 KEY_UWB
NONE, //240 KEY_UNKNOWN
NONE, //241 KEY_VIDEO_NEXT
NONE, //242 KEY_VIDEO_PREV
NONE, //243 KEY_BRIGHT_CYCLE
NONE, //244 KEY_BRIGHT_AUTO
NONE, //245 KEY_DISPLAY_OFF
NONE, //246 KEY_WWAN
NONE, //247 KEY_RFKILL
NONE, //248 KEY_MICMUTE
NONE, //249 ???
NONE, //250 ???
NONE, //251 ???
NONE, //252 ???
NONE, //253 ???
NONE, //254 ???
NONE //255 ???
};
uint8_t ps2_kbd_scan_set = 2;
uint32_t get_ps2_code(uint16_t key)
{
if (key > 255) return NONE;
return (ps2_kbd_scan_set == 1) ? ev2ps2_set1[key] : ev2ps2[key];
}
uint32_t get_amiga_code(uint16_t key)
{
if (key > 255) return NONE;
return ev2amiga[key];
}
uint32_t get_archie_code(uint16_t key)
{
if (key > 255) return NONE;
return ev2archie[key];
}
static uint32_t modifier = 0;
uint32_t get_key_mod()
{
return modifier & MODMASK;
}
enum QUIRK
{
QUIRK_NONE = 0,
QUIRK_WIIMOTE,
QUIRK_DS3,
QUIRK_DS4,
QUIRK_DS4TOUCH,
QUIRK_MADCATZ360,
QUIRK_PDSP,
QUIRK_PDSP_ARCADE,
QUIRK_JAMMA,
QUIRK_JAMMA2,
QUIRK_MSSP,
QUIRK_TOUCHGUN,
QUIRK_VCS,
QUIRK_JOYCON,
QUIRK_LIGHTGUN_CRT,
QUIRK_LIGHTGUN,
QUIRK_LIGHTGUN_MOUSE,
QUIRK_WHEEL,
};
typedef struct
{
uint16_t bustype, vid, pid, version;
char idstr[256];
char mod;
uint8_t led;
uint8_t mouse;
uint8_t axis_edge[256];
int8_t axis_pos[256];
uint8_t num;
uint8_t has_map;
uint32_t map[NUMBUTTONS];
int map_shown;
uint8_t osd_combo;
uint8_t has_mmap;
uint32_t mmap[NUMBUTTONS];
uint8_t has_jkmap;
uint16_t jkmap[1024];
int stick_l[2];
int stick_r[2];
uint8_t has_kbdmap;
uint8_t kbdmap[256];
int32_t guncal[4];
int accx, accy;
int startx, starty;
int lastx, lasty;
int quirk;
int misc_flags;
int paddle_val;
int spinner_prev;
int spinner_acc;
int spinner_prediv;
int spinner_dir;
int spinner_accept;
int old_btn;
int ds_mouse_emu;
int lightgun_req;
int lightgun;
int has_rumble;
int rumble_en;
uint16_t last_rumble;
ff_effect rumble_effect;
int8_t wh_steer;
int8_t wh_accel;
int8_t wh_brake;
int8_t wh_clutch;
int8_t wh_combo;
int8_t wh_pedal_invert;
int timeout;
char mac[64];
int bind;
uint32_t unique_hash;
char devname[32];
char id[80];
char name[128];
char sysfs[512];
int ss_range[2];
int max_cardinal[2];
float max_range[2];
uint32_t deadzone;
} devInput;
static devInput input[NUMDEV] = {};
static devInput player_pad[NUMPLAYERS] = {};
static devInput player_pdsp[NUMPLAYERS] = {};
#define JOYCON_COMBO(dev) (input[(dev)].misc_flags & (1 << 31))
#define JOYCON_LEFT(dev) (input[(dev)].misc_flags & (1 << 30))
#define JOYCON_RIGHT(dev) (input[(dev)].misc_flags & (1 << 29))
#define JOYCON_REQ(dev) ((input[(dev)].misc_flags & 7) == 7)
#define JOYCON_COMBINED(dev) (input[(dev)].quirk == QUIRK_JOYCON && JOYCON_COMBO((dev)))
#define BTN_NUM (sizeof(devInput::map) / sizeof(devInput::map[0]))
int mfd = -1;
int mwd = -1;
static int set_watch()
{
mwd = -1;
mfd = inotify_init1(IN_CLOEXEC);
if (mfd < 0)
{
printf("ERR: inotify_init");
return -1;
}
mwd = inotify_add_watch(mfd, "/dev/input", IN_MODIFY | IN_CREATE | IN_DELETE);
if (mwd < 0)
{
printf("ERR: inotify_add_watch");
return -1;
}
return mfd;
}
#define EVENT_SIZE ( sizeof (struct inotify_event) )
#define BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) )
static int check_devs()
{
int result = 0;
int length, i = 0;
char buffer[BUF_LEN];
length = read(mfd, buffer, BUF_LEN);
if (length < 0)
{
printf("ERR: read\n");
return 0;
}
while (i<length)
{
struct inotify_event *event = (struct inotify_event *) &buffer[i];
if (event->len)
{
if (event->mask & IN_CREATE)
{
result = 1;
if (event->mask & IN_ISDIR)
{
printf("The directory %s was created.\n", event->name);
}
else
{
printf("The file %s was created.\n", event->name);
}
}
else if (event->mask & IN_DELETE)
{
result = 1;
if (event->mask & IN_ISDIR)
{
printf("The directory %s was deleted.\n", event->name);
}
else
{
printf("The file %s was deleted.\n", event->name);
}
}
/*
else if ( event->mask & IN_MODIFY )
{
result = 1;
if ( event->mask & IN_ISDIR )
{
printf( "The directory %s was modified.\n", event->name );
}
else
{
printf( "The file %s was modified.\n", event->name );
}
}
*/
}
i += EVENT_SIZE + event->len;
}
return result;
}
static void INThandler(int code)
{
(void)code;
printf("\nExiting...\n");
if (mwd >= 0) inotify_rm_watch(mfd, mwd);
if (mfd >= 0) close(mfd);
exit(0);
}
#define test_bit(bit, array) (array [bit / 8] & (1 << (bit % 8)))
static char has_led(int fd)
{
unsigned char evtype_b[(EV_MAX + 7) / 8];
if (fd<0) return 0;
memset(&evtype_b, 0, sizeof(evtype_b));
if (ioctl(fd, EVIOCGBIT(0, sizeof(evtype_b)), evtype_b) < 0)
{
printf("ERR: evdev ioctl.\n");
return 0;
}
return test_bit(EV_LED, evtype_b) ? 1 : 0;
}
static char leds_state = 0;
void set_kbdled(int mask, int state)
{
leds_state = state ? leds_state | (mask&HID_LED_MASK) : leds_state & ~(mask&HID_LED_MASK);
}
int get_kbdled(int mask)
{
return (leds_state & (mask&HID_LED_MASK)) ? 1 : 0;
}
int toggle_kbdled(int mask)
{
int state = !get_kbdled(mask);
set_kbdled(mask, state);
return state;
}
static int sysled_is_enabled = 1;
void sysled_enable(int en)
{
sysled_is_enabled = en;
}
#define JOYMAP_DIR "inputs/"
static int load_map(const char *name, void *pBuffer, int size)
{
char path[256] = { JOYMAP_DIR };
strcat(path, name);
int ret = FileLoadConfig(path, pBuffer, size);
if (!ret) return FileLoadConfig(name, pBuffer, size);
return ret;
}
static void delete_map(const char *name)
{
char path[256] = { JOYMAP_DIR };
strcat(path, name);
FileDeleteConfig(name);
FileDeleteConfig(path);
}
static int save_map(const char *name, void *pBuffer, int size)
{
char path[256] = { JOYMAP_DIR };
strcat(path, name);
FileDeleteConfig(name);
return FileSaveConfig(path, pBuffer, size);
}
static int mapping = 0;
static int mapping_button;
static int mapping_dev = -1;
static int mapping_type;
static int mapping_count;
static int mapping_clear;
static int mapping_finish;
static int mapping_set;
static int mapping_current_key = 0;
static int mapping_current_dev = -1;
static uint32_t tmp_axis[4];
static int tmp_axis_n = 0;
static int grabbed = 1;
void start_map_setting(int cnt, int set)
{
mapping_current_key = 0;
mapping_current_dev = -1;
mapping_button = 0;
mapping = 1;
mapping_set = set;
if (!mapping_set)
{
mapping_dev = -1;
mapping_type = (cnt < 0) ? 3 : cnt ? 1 : 2;
}
mapping_count = cnt;
mapping_clear = 0;
mapping_finish = 0;
tmp_axis_n = 0;
if (mapping_type <= 1 && is_menu()) mapping_button = -6;
memset(tmp_axis, 0, sizeof(tmp_axis));
//un-stick the enter key
user_io_kbd(KEY_ENTER, 0);
}
int get_map_set()
{
return mapping_set;
}
int get_map_button()
{
return mapping_button;
}
int get_map_type()
{
return mapping_type;
}
int get_map_clear()
{
return mapping_clear;
}
int get_map_finish()
{
return mapping_finish;
}
static uint32_t osd_timer = 0;
int get_map_cancel()
{
return (mapping && !is_menu() && osd_timer && CheckTimer(osd_timer));
}
static char *get_unique_mapping(int dev, int force_unique = 0)
{
uint32_t vidpid = (input[dev].vid << 16) | input[dev].pid;
static char str[128];
for (uint i = 0; i < cfg.controller_unique_mapping[0]; i++)
{
if (!cfg.controller_unique_mapping[i + 1]) break;
if (force_unique || cfg.controller_unique_mapping[i + 1] == 1 || cfg.controller_unique_mapping[i + 1] == vidpid)
{
sprintfz(str, "%s_%08x", input[dev].idstr, input[dev].unique_hash);
return str;
}
}
sprintfz(str, "%s", input[dev].idstr);
return str;
}
static char *get_map_name(int dev, int def)
{
static char name[1024];
char *id = get_unique_mapping(dev);
if (def || is_menu()) sprintfz(name, "input_%s%s_v3.map", id, input[dev].mod ? "_m" : "");
else sprintfz(name, "%s_input_%s%s_v3.map", user_io_get_core_name(), id, input[dev].mod ? "_m" : "");
return name;
}
static char *get_jkmap_name(int dev)
{
static char name[1024];
char *id = get_unique_mapping(dev);
sprintfz(name, "%s_input_%s_jk.map", user_io_get_core_name(), id);
return name;
}
static char *get_kbdmap_name(int dev)
{
static char name[128];
char *id = get_unique_mapping(dev);
sprintfz(name, "kbd_%s.map", id);
return name;
}
void finish_map_setting(int dismiss)
{
mapping = 0;
if (mapping_dev<0) return;
if (mapping_type == 2)
{
input[mapping_dev].has_kbdmap = 0;
if (dismiss) FileDeleteConfig(get_kbdmap_name(mapping_dev));
else FileSaveConfig(get_kbdmap_name(mapping_dev), &input[mapping_dev].kbdmap, sizeof(input[mapping_dev].kbdmap));
}
else if (mapping_type == 3)
{
if (dismiss) memset(input[mapping_dev].jkmap, 0, sizeof(input[mapping_dev].jkmap));
save_map(get_jkmap_name(mapping_dev), &input[mapping_dev].jkmap, sizeof(input[mapping_dev].jkmap));
}
else
{
for (int i = 0; i < NUMDEV; i++)
{
input[i].has_map = 0;
input[i].has_mmap = 0;
}
if (!dismiss) save_map(get_map_name(mapping_dev, 0), &input[mapping_dev].map, sizeof(input[mapping_dev].map));
if (dismiss == 2) delete_map(get_map_name(mapping_dev, 0));
}
}
void input_lightgun_save(int idx, int32_t *cal)
{
static char name[128];
sprintf(name, "%s_gun_cal_%04x_%04x_v2.cfg", user_io_get_core_name(), input[idx].vid, input[idx].pid);
FileSaveConfig(name, cal, 4 * sizeof(int32_t));
memcpy(input[idx].guncal, cal, sizeof(input[idx].guncal));
}
static void input_lightgun_load(int idx)
{
static char name[128];
sprintf(name, "%s_gun_cal_%04x_%04x_v2.cfg", user_io_get_core_name(), input[idx].vid, input[idx].pid);
FileLoadConfig(name, input[idx].guncal, 4 * sizeof(int32_t));
}
int input_has_lightgun()
{
for (int i = 0; i < NUMDEV; i++)
{
if (input[i].quirk == QUIRK_WIIMOTE) return 1;
if (input[i].quirk == QUIRK_TOUCHGUN) return 1;
if (input[i].quirk == QUIRK_LIGHTGUN) return 1;
if (input[i].quirk == QUIRK_LIGHTGUN_CRT) return 1;
if (input[i].quirk == QUIRK_LIGHTGUN_MOUSE) return 1;
}
return 0;
}
uint16_t get_map_vid()
{
return (mapping && mapping_dev >= 0) ? input[mapping_dev].vid : 0;
}
uint16_t get_map_pid()
{
return (mapping && mapping_dev >= 0) ? input[mapping_dev].pid : 0;
}
int has_default_map()
{
return (mapping_dev >= 0) ? (input[mapping_dev].has_mmap == 1) : 0;
}
static char kr_fn_table[] =
{
KEY_KPSLASH, KEY_PAUSE,
KEY_KPASTERISK, KEY_PRINT,
KEY_LEFT, KEY_HOME,
KEY_RIGHT, KEY_END,
KEY_UP, KEY_PAGEUP,
KEY_DOWN, KEY_PAGEDOWN,
KEY_F1, KEY_F11,
KEY_F2, KEY_F12,
KEY_F3, KEY_F17, // EMU_MOUSE
KEY_F4, KEY_F18, // EMU_JOY0
KEY_F5, KEY_F19, // EMU_JOY1
KEY_F6, KEY_F20, // EMU_NONE
//Emulate keypad for A600
KEY_1, KEY_KP1,
KEY_2, KEY_KP2,
KEY_3, KEY_KP3,
KEY_4, KEY_KP4,
KEY_5, KEY_KP5,
KEY_6, KEY_KP6,
KEY_7, KEY_KP7,
KEY_8, KEY_KP8,
KEY_9, KEY_KP9,
KEY_0, KEY_KP0,
KEY_MINUS, KEY_KPMINUS,
KEY_EQUAL, KEY_KPPLUS,
KEY_BACKSLASH, KEY_KPASTERISK,
KEY_LEFTBRACE, KEY_F13, //KP(
KEY_RIGHTBRACE, KEY_F14, //KP)
KEY_DOT, KEY_KPDOT,
KEY_ENTER, KEY_KPENTER
};
static int keyrah_trans(int key, int press)
{
static int fn = 0;
if (key == KEY_NUMLOCK) return KEY_F13; // numlock -> f13
if (key == KEY_SCROLLLOCK) return KEY_F14; // scrlock -> f14
if (key == KEY_INSERT) return KEY_F16; // insert -> f16. workaround!
if (key == KEY_102ND)
{
if (!press && fn == 1) menu_key_set(KEY_MENU);
fn = press ? 1 : 0;
return 0;
}
else if (fn)
{
fn |= 2;
for (uint32_t n = 0; n<(sizeof(kr_fn_table) / (2 * sizeof(kr_fn_table[0]))); n++)
{
if ((key&255) == kr_fn_table[n * 2]) return kr_fn_table[(n * 2) + 1];
}
}
return key;
}
static void input_cb(struct input_event *ev, struct input_absinfo *absinfo, int dev);
static int kbd_toggle = 0;
static uint64_t joy[NUMPLAYERS] = {}; // 0-31 primary mappings, 32-64 alternate
static uint64_t autofire[NUMPLAYERS] = {}; // 0-31 primary mappings, 32-64 alternate
static uint32_t autofirecodes[NUMPLAYERS][BTN_NUM] = {};
static int af_delay[NUMPLAYERS] = {};
static uint32_t crtgun_timeout[NUMDEV] = {};
static unsigned char mouse_btn = 0; //emulated mouse
static unsigned char mice_btn = 0;
static int mouse_req = 0;
static int mouse_x = 0;
static int mouse_y = 0;
static int mouse_w = 0;
static int mouse_emu = 0;
static int kbd_mouse_emu = 0;
static int mouse_sniper = 0;
static int mouse_emu_x = 0;
static int mouse_emu_y = 0;
static uint32_t mouse_timer = 0;
#define BTN_TGL 100
#define BTN_OSD 101
#define AF_MIN 16
#define AF_MAX 512
#define AF_STEP 8
static int uinp_fd = -1;
static int input_uinp_setup()
{
if (uinp_fd <= 0)
{
struct uinput_user_dev uinp;
if (!(uinp_fd = open("/dev/uinput", O_WRONLY | O_NDELAY | O_CLOEXEC)))
{
printf("Unable to open /dev/uinput\n");
uinp_fd = -1;
return 0;
}
memset(&uinp, 0, sizeof(uinp));
strncpy(uinp.name, UINPUT_NAME, UINPUT_MAX_NAME_SIZE);
uinp.id.version = 4;
uinp.id.bustype = BUS_USB;
ioctl(uinp_fd, UI_SET_EVBIT, EV_KEY);
for (int i = 0; i < 256; i++) ioctl(uinp_fd, UI_SET_KEYBIT, i);
write(uinp_fd, &uinp, sizeof(uinp));
if (ioctl(uinp_fd, UI_DEV_CREATE))
{
printf("Unable to create UINPUT device.");
close(uinp_fd);
uinp_fd = -1;
return 0;
}
}
return 1;
}
void input_uinp_destroy()
{
if (uinp_fd > 0)
{
ioctl(uinp_fd, UI_DEV_DESTROY);
close(uinp_fd);
uinp_fd = -1;
}
}
static unsigned long uinp_repeat = 0;
static struct input_event uinp_ev;
static void uinp_send_key(uint16_t key, int press)
{
if (uinp_fd > 0)
{
if (!uinp_ev.value && press)
{
uinp_repeat = GetTimer(REPEATDELAY);
}
memset(&uinp_ev, 0, sizeof(uinp_ev));
gettimeofday(&uinp_ev.time, NULL);
uinp_ev.type = EV_KEY;
uinp_ev.code = key;
uinp_ev.value = press;
write(uinp_fd, &uinp_ev, sizeof(uinp_ev));
static struct input_event ev;
ev.time = uinp_ev.time;
ev.type = EV_SYN;
ev.code = SYN_REPORT;
ev.value = 0;
write(uinp_fd, &ev, sizeof(ev));
}
}
static void uinp_check_key()
{
if (uinp_fd > 0)
{
if (!grabbed)
{
if (uinp_ev.value && CheckTimer(uinp_repeat))
{
uinp_repeat = GetTimer(REPEATRATE);
uinp_send_key(uinp_ev.code, 2);
}
}
else
{
if (uinp_ev.value)
{
uinp_send_key(uinp_ev.code, 0);
}
}
}
}
static void mouse_cb(int16_t x = 0, int16_t y = 0, int16_t w = 0)
{
if (grabbed)
{
mouse_x += x;
mouse_y += y;
mouse_w += w;
mouse_req |= 1;
}
}
static void mouse_btn_req()
{
if (grabbed) mouse_req |= 2;
}
static inline void joy_clamp(int* value, const int min, const int max)
{
if (*value < min) {
*value = min;
}
else if (*value > max) {
*value = max;
}
}
static inline float boxradf(const float angle)
{
return 1.0f / fmaxf(fabsf(sinf(angle)), fabsf(cosf(angle)));
}
static void joy_apply_deadzone(int* x, int* y, const devInput* dev, const int stick) {
// Don't be fancy with such a small deadzone.
if (dev->deadzone <= 2)
{
if (dev->deadzone && (abs((*x > *y) == (*x > -*y) ? *x : *y) <= dev->deadzone))
*x = *y = 0;
return;
}
const float radius = hypotf(*x, *y);
if (radius <= (float)dev->deadzone)
{
*x = *y = 0;
return;
}
const float angle = atan2f(*y, *x);
const float box_radius = boxradf(angle);
/* A measure of how "cardinal" the angle is,
i.e closeness to [0, 90, 180, 270] degrees (0.0 - 1.0). */
const float cardinality = (1.4142136f - box_radius) * 2.4142136f;
// Expected range for the given angle.
const float max_cardinal = dev->max_cardinal[stick] > (2.0f * dev->deadzone) ? dev->max_cardinal[stick] : 127.0f;
const float max_diagonal = dev->max_range[stick] > (2.0f * dev->deadzone) ? dev->max_range[stick] : 127.0f;
const float range = cardinality * max_cardinal + (1.0f - cardinality) * max_diagonal;
const float weight = 1.0f - fmaxf(range - radius, .0f) / (range - dev->deadzone);
const float adjusted_radius = fminf(weight * range, max_cardinal * box_radius);
/* Don't ever return a larger magnitude than that was given.
The whole point of this function is to subtract some magnitude, not add. */
if (adjusted_radius > radius) return;
*x = nearbyintf(adjusted_radius * cosf(angle));
*y = nearbyintf(adjusted_radius * sinf(angle));
// Just to be sure.
const int min_range = is_psx() ? -128 : -127;
joy_clamp(x, min_range, INT8_MAX);
joy_clamp(y, min_range, INT8_MAX);
}
static uint32_t osdbtn = 0;
static void joy_digital(int jnum, uint64_t mask, uint32_t code, char press, int bnum, int dont_save = 0)
{
static char str[128];
static uint32_t lastcode[NUMPLAYERS];
static uint64_t lastmask[NUMPLAYERS];
int num = jnum - 1;
if (num < NUMPLAYERS)
{
if (jnum)
{
if (bnum != BTN_OSD && bnum != BTN_TGL)
{
if (!dont_save)
{
if (press)
{
lastcode[num] = code;
lastmask[num] = mask;
}
else
{
lastcode[num] = 0;
lastmask[num] = 0;
}
}
}
else
{
if (!user_io_osd_is_visible() && press && !cfg.disable_autofire)
{
if (lastcode[num] && lastmask[num])
{
int found = 0;
int zero = -1;
for (uint i = 0; i < BTN_NUM; i++)
{
if (!autofirecodes[num][i]) zero = i;
if (autofirecodes[num][i] == lastcode[num])
{
found = 1;
autofirecodes[num][i] = 0;
break;
}
}
if (!found && zero >= 0) autofirecodes[num][zero] = lastcode[num];
autofire[num] = !found ? autofire[num] | lastmask[num] : autofire[num] & ~lastmask[num];
if (hasAPI1_5())
{
if (!found) sprintf(str, "Auto fire: %dms (%uhz)", af_delay[num] * 2, 1000 / (af_delay[num] * 2));
else sprintf(str, "Auto fire: OFF");
Info(str);
}
else InfoMessage((!found) ? "\n\n Auto fire\n ON" :
"\n\n Auto fire\n OFF");
return;
}
else if (lastmask[num] & 0xF)
{
if (lastmask[num] & 9)
{
af_delay[num] += AF_STEP << ((lastmask[num] & 1) ? 1 : 0);
if (af_delay[num] > AF_MAX) af_delay[num] = AF_MAX;
}
else
{
af_delay[num] -= AF_STEP << ((lastmask[num] & 2) ? 1 : 0);
if (af_delay[num] < AF_MIN) af_delay[num] = AF_MIN;
}
static char str[256];
if (hasAPI1_5())
{
sprintf(str, "Auto fire period: %dms (%uhz)", af_delay[num] * 2, 1000 / (af_delay[num] * 2));
Info(str);
}
else
{
sprintf(str, "\n\n Auto fire period\n %dms(%uhz)", af_delay[num] * 2, 1000 / (af_delay[num] * 2));
InfoMessage(str);
}
return;
}
}
}
}
if (bnum == BTN_TGL)
{
if(press) kbd_toggle = !kbd_toggle;
return;
}
if (!user_io_osd_is_visible() && (bnum == BTN_OSD) && (mouse_emu & 1))
{
if (press)
{
mouse_sniper = 0;
mouse_timer = 0;
mouse_btn = 0;
mouse_emu_x = 0;
mouse_emu_y = 0;
mouse_cb();
mouse_btn_req();
mouse_emu ^= 2;
if (hasAPI1_5()) Info((mouse_emu & 2) ? "Mouse mode ON" : "Mouse mode OFF");
else InfoMessage((mouse_emu & 2) ? "\n\n Mouse mode lock\n ON" :
"\n\n Mouse mode lock\n OFF");
}
return;
}
// clear OSD button state if not in the OSD. this avoids problems where buttons are still held
// on OSD exit and causes combinations to match when partial buttons are pressed.
if (!user_io_osd_is_visible()) osdbtn = 0;
if (user_io_osd_is_visible() || (bnum == BTN_OSD))
{
mask &= ~JOY_BTN3;
if (press)
{
osdbtn |= mask;
if (mask & (JOY_BTN1 | JOY_BTN2)) {
if ((osdbtn & (JOY_BTN1 | JOY_BTN2)) == (JOY_BTN1 | JOY_BTN2))
{
osdbtn |= JOY_BTN3;
mask = JOY_BTN3;
}
}
}
else
{
int old_osdbtn = osdbtn;
osdbtn &= ~mask;
if (mask & (JOY_BTN1 | JOY_BTN2)) {
if ((old_osdbtn & (JOY_BTN1 | JOY_BTN2 | JOY_BTN3)) == (JOY_BTN1 | JOY_BTN2 | JOY_BTN3))
{
mask = JOY_BTN3;
}
else if (old_osdbtn & JOY_BTN3)
{
if (!(osdbtn & (JOY_BTN1 | JOY_BTN2))) osdbtn &= ~JOY_BTN3;
mask = 0;
}
}
if((mask & JOY_BTN2) && !(old_osdbtn & JOY_BTN2)) mask = 0;
}
memset(joy, 0, sizeof(joy));
struct input_event ev;
ev.type = EV_KEY;
ev.value = press;
int cfg_switch = menu_allow_cfg_switch() && (osdbtn & JOY_BTN2) && press;
switch (mask)
{
case JOY_RIGHT:
if (cfg_switch)
{
user_io_set_ini(0);
osdbtn = 0;
return;
}
ev.code = KEY_RIGHT;
break;
case JOY_LEFT:
if (cfg_switch)
{
user_io_set_ini(1);
osdbtn = 0;
return;
}
ev.code = KEY_LEFT;
break;
case JOY_UP:
if (cfg_switch)
{
user_io_set_ini(2);
osdbtn = 0;
return;
}
ev.code = KEY_UP;
break;
case JOY_DOWN:
if (cfg_switch)
{
user_io_set_ini(3);
osdbtn = 0;
return;
}
ev.code = KEY_DOWN;
break;
case JOY_BTN1:
ev.code = KEY_ENTER;
break;
case JOY_BTN2:
ev.code = KEY_BACK;
break;
case JOY_BTN3:
ev.code = KEY_BACKSPACE;
break;
case JOY_BTN4:
ev.code = KEY_TAB;
break;
case JOY_L:
ev.code = KEY_MINUS;
break;
case JOY_R:
ev.code = KEY_EQUAL;
break;
case JOY_R2:
ev.code = KEY_GRAVE;
break;
default:
ev.code = (bnum == BTN_OSD) ? KEY_MENU : 0;
}
input_cb(&ev, 0, 0);
}
else if (video_fb_state())
{
switch (mask)
{
case JOY_RIGHT:
uinp_send_key(KEY_RIGHT, press);
break;
case JOY_LEFT:
uinp_send_key(KEY_LEFT, press);
break;
case JOY_UP:
uinp_send_key(KEY_UP, press);
break;
case JOY_DOWN:
uinp_send_key(KEY_DOWN, press);
break;
case JOY_BTN1:
uinp_send_key(KEY_ENTER, press);
break;
case JOY_BTN2:
uinp_send_key(KEY_ESC, press);
break;
case JOY_BTN3:
uinp_send_key(KEY_SPACE, press);
break;
case JOY_BTN4:
uinp_send_key(KEY_TAB, press);
break;
case JOY_L:
uinp_send_key(KEY_PAGEUP, press);
break;
case JOY_R:
uinp_send_key(KEY_PAGEDOWN, press);
break;
}
}
else if(jnum)
{
if (press) joy[num] |= mask;
else joy[num] &= ~mask;
//user_io_digital_joystick(num, joy[num]);
if (code)
{
int found = 0;
for (uint i = 0; i < BTN_NUM; i++) if (autofirecodes[num][i] == code) found = 1;
if (found) autofire[num] = press ? autofire[num] | mask : autofire[num] & ~mask;
}
}
}
}
static bool joy_dir_is_diagonal(const int x, const int y)
{
static const float JOY_DIAG_THRESHOLD = .85f;
return
((x == 0) || (y == 0)) ? false :
((x == y) || (x == -y)) ? true :
abs((x > y) == (x > -y) ? (float)y / x : (float)x / y) >= JOY_DIAG_THRESHOLD;
}
static void joy_analog(int dev, int axis, int offset, int stick = 0)
{
int num = input[dev].num;
static int pos[2][NUMPLAYERS][2] = {};
if (grabbed && num > 0 && --num < NUMPLAYERS)
{
pos[stick][num][axis] = offset;
int x = pos[stick][num][0], y = pos[stick][num][1];
if (joy_dir_is_diagonal(x, y))
{
// Update maximum observed diag
// Use sum of squares and only calc sqrt() when necessary
const int ss_range_curr = x * x + y * y;
if ((ss_range_curr > input[dev].ss_range[stick]))
{
input[dev].ss_range[stick] = ss_range_curr;
input[dev].max_range[stick] = sqrtf(ss_range_curr);
}
}
// Update maximum observed cardinal distance
const int c_dist = abs((x > y) == (x > -y) ? x : y);
if (c_dist > input[dev].max_cardinal[stick])
{
input[dev].max_cardinal[stick] = c_dist;
}
joy_apply_deadzone(&x, &y, &input[dev], stick);
if (is_n64())
{
// Emulate N64 joystick range and shape for regular -127-+127 controllers
n64_joy_emu(x, y, &x, &y, input[dev].max_cardinal[stick], input[dev].max_range[stick]);
stick_swap(num, stick, &num, &stick);
}
if (stick)
{
user_io_r_analog_joystick(num, (char)x, (char)y);
}
else
{
user_io_l_analog_joystick(num, (char)x, (char)y);
}
}
}
static char* get_led_path(int dev, int add_id = 1)
{
static char path[1024];
if (!input[dev].sysfs[0]) return NULL;
sprintf(path, "/sys%s", input[dev].sysfs);
char *p = strstr(path, "/input/");
if (p)
{
*p = 0;
char *id = strrchr(path, '/');
strcpy(p, "/leds");
if (add_id && id) strncat(p, id, p - id);
return path;
}
return NULL;
}
static int set_led(char *base, const char *led, int brightness)
{
static char path[1024];
snprintf(path, sizeof(path), "%s%s/brightness", base, led);
FILE* f = fopen(path, "w");
if (f)
{
fprintf(f, "%d", brightness);
fclose(f);
return 1;
}
return 0;
}
static int get_led(char *base, const char *led)
{
static char path[1024];
snprintf(path, sizeof(path), "%s%s/brightness", base, led);
FILE* f = fopen(path, "r");
if (f)
{
int res = 0;
fscanf(f, "%d", &res);
fclose(f);
return res;
}
return 0;
}
static void update_num_hw(int dev, int num)
{
char *led_path;
if (num > 7) num = 7;
if (input[dev].quirk == QUIRK_DS4 || input[dev].quirk == QUIRK_DS4TOUCH)
{
led_path = get_led_path(dev);
if (led_path)
{
if (set_led(led_path, ":player_id", (num > 5) ? 0 : num))
{
//duslsense
set_led(led_path, ":blue", (num == 0) ? 128 : 64);
set_led(led_path, ":green", (num == 0) ? 128 : 64);
set_led(led_path, ":red", (num == 0) ? 128 : 0);
}
else
{
//dualshock4
static const uint8_t color_code[8][3] =
{
{ 0x30, 0x30, 0x30 }, // White
{ 0x00, 0x00, 0x40 }, // Blue
{ 0x40, 0x00, 0x00 }, // Red
{ 0x00, 0x40, 0x00 }, // Green
{ 0x20, 0x00, 0x20 }, // Pink
{ 0x40, 0x10, 0x00 }, // Orange
{ 0x00, 0x20, 0x20 }, // Teal
{ 0x00, 0x00, 0x00 } // none
};
set_led(led_path, ":blue", color_code[num][2]);
set_led(led_path, ":green", color_code[num][1]);
set_led(led_path, ":red", color_code[num][0]);
}
}
}
else if (input[dev].quirk == QUIRK_DS3)
{
led_path = get_led_path(dev);
if (led_path)
{
set_led(led_path, "::sony1", (num == 0 || num == 1 || num == 5));
set_led(led_path, "::sony2", (num == 0 || num == 2 || num == 6));
set_led(led_path, "::sony3", (num == 0 || num == 3));
set_led(led_path, "::sony4", (num == 0 || num == 4 || num == 5 || num == 6));
}
}
else if (input[dev].quirk == QUIRK_WIIMOTE)
{
led_path = get_led_path(dev);
if (led_path)
{
set_led(led_path, ":blue:p0", (num == 0 || num == 1 || num == 5));
set_led(led_path, ":blue:p1", (num == 0 || num == 2 || num == 6));
set_led(led_path, ":blue:p2", (num == 0 || num == 3));
set_led(led_path, ":blue:p3", (num == 0 || num == 4 || num == 5 || num == 6));
}
}
else if (input[dev].vid == 0x057e && ((input[dev].pid & 0xFF00) == 0x2000))
{
// nintendo switch controllers
int repeat = 1;
while (1)
{
led_path = get_led_path(dev);
if (led_path)
{
set_led(led_path, ":home", num ? 1 : 15);
set_led(led_path, ":player1", (num == 0 || num == 1 || num == 5));
set_led(led_path, ":player2", (num == 0 || num == 2 || num == 6));
set_led(led_path, ":player3", (num == 0 || num == 3));
set_led(led_path, ":player4", (num == 0 || num == 4 || num == 5 || num == 6));
}
if (repeat && JOYCON_COMBINED(dev)) dev = input[dev].bind; else break;
repeat = 0;
}
}
}
void reset_players()
{
for (int i = 0; i < NUMDEV; i++)
{
input[i].num = 0;
input[i].map_shown = 0;
update_num_hw(i, 0);
}
memset(player_pad, 0, sizeof(player_pad));
memset(player_pdsp, 0, sizeof(player_pdsp));
}
static void store_player(int num, int dev)
{
devInput *player = (input[dev].quirk == QUIRK_PDSP || input[dev].quirk == QUIRK_MSSP) ? player_pdsp : player_pad;
// remove possible old assignment
for (int i = 1; i < NUMPLAYERS; i++) if (!strcmp(player[i].id, input[dev].id)) player[i].id[0] = 0;
if(num && num < NUMPLAYERS) memcpy(&player[num], &input[dev], sizeof(devInput));
update_num_hw(dev, num);
}
static void restore_player(int dev)
{
// do not restore bound devices
if (dev != input[dev].bind && !(JOYCON_COMBINED(dev) && JOYCON_LEFT(dev))) return;
devInput *player = (input[dev].quirk == QUIRK_PDSP || input[dev].quirk == QUIRK_MSSP) ? player_pdsp : player_pad;
for (int k = 1; k < NUMPLAYERS; k++)
{
if (strlen(player[k].id) && !strcmp(player[k].id, input[dev].id))
{
printf("restore player %d to %s (%s)\n", k, input[dev].devname, input[dev].id);
input[dev].num = k;
input[dev].map_shown = player[k].map_shown;
if (JOYCON_COMBINED(dev))
{
input[input[dev].bind].num = k;
input[input[dev].bind].map_shown = player[k].map_shown;
}
memcpy(input[dev].jkmap, player[k].jkmap, sizeof(input[dev].jkmap));
input[dev].lightgun = player[k].lightgun;
break;
}
}
update_num_hw(dev, input[dev].num);
}
// Analog joystick dead zone
static void setup_deadzone(struct input_event* ev, int dev)
{
// Lightgun/wheel has no dead zone
if (ev->type != EV_ABS || (ev->code <= 1 && (input[dev].lightgun || input[dev].quirk == QUIRK_WHEEL)))
{
input[dev].deadzone = 0U;
}
// Dual Shock 3/4
else if (input[dev].quirk == QUIRK_DS3 || input[dev].quirk == QUIRK_DS4)
{
input[dev].deadzone = 10U;
}
// Default dead zone
else
{
input[dev].deadzone = 2U;
}
char cfg_format[32];
char cfg_uid[sizeof(*cfg.controller_deadzone)];
snprintf(cfg_format, sizeof(cfg_format), "%%%u[^ \t,]%%*[ \t,]%%u%%n", (size_t)(sizeof(cfg_uid) - 1));
const char* dev_uid = get_unique_mapping(dev, 1);
for (size_t i = 0; i < sizeof(cfg.controller_deadzone) / sizeof(*cfg.controller_deadzone); i++)
{
const char* cfg_line = cfg.controller_deadzone[i];
if (!cfg_line || !strlen(cfg_line)) break;
uint32_t cfg_vidpid, cfg_deadzone;
size_t scan_pos;
char vp;
if ((sscanf(cfg_line, cfg_format, cfg_uid, &cfg_deadzone, &scan_pos) < 2) ||
(scan_pos != strlen(cfg_line))) continue;
if ((
sscanf(cfg_uid, "0%*[Xx]%08x%n", &cfg_vidpid, &scan_pos) ||
sscanf(cfg_uid, "%08x%n", &cfg_vidpid, &scan_pos)) &&
(scan_pos == strlen(cfg_uid)))
{
const uint32_t vidpid = (input[dev].vid << 16) | input[dev].pid;
if (vidpid != cfg_vidpid) continue;
}
else if ((
(sscanf(cfg_uid, "%[VvPp]%*[Ii]%*[Dd]:0%*[Xx]%04x%n", &vp, &cfg_vidpid, &scan_pos) == 2) ||
(sscanf(cfg_uid, "%[VvPp]%*[Ii]%*[Dd]:%04x%n", &vp, &cfg_vidpid, &scan_pos) == 2)) &&
(scan_pos == strlen(cfg_uid)))
{
if (vp == 'V' || vp == 'v')
{
if (input[dev].vid != cfg_vidpid) continue;
}
else
{
if (input[dev].pid != cfg_vidpid) continue;
}
}
else if (
!strcasestr(input[dev].id, cfg_uid) &&
!strcasestr(input[dev].sysfs, cfg_uid) &&
!strcasestr(dev_uid, cfg_uid))
{
continue;
}
if (cfg_deadzone > 64) cfg_deadzone = 64;
printf("Analog device %s was given a dead zone of %u\n", input[dev].id, cfg_deadzone);
input[dev].deadzone = cfg_deadzone;
break;
}
}
void unflag_players()
{
for (int k = 1; k < NUMPLAYERS; k++)
{
int found = 0;
for (int i = 0; i < NUMDEV; i++) if (strlen(player_pad[k].id) && !strcmp(player_pad[k].id, input[i].id)) found = 1;
if (!found) player_pad[k].map_shown = 0;
}
for (int k = 1; k < NUMPLAYERS; k++)
{
int found = 0;
for (int i = 0; i < NUMDEV; i++) if (strlen(player_pdsp[k].id) && !strcmp(player_pdsp[k].id, input[i].id)) found = 1;
if (!found) player_pdsp[k].map_shown = 0;
}
}
static uint16_t def_mmap[] = {
0x0321, 0x0000, 0x0320, 0x0000, 0x0323, 0x0000, 0x0322, 0x0000,
0x0131, 0x0000, 0x0130, 0x0000, 0x0133, 0x0000, 0x0134, 0x0000,
0x0136, 0x0000, 0x0137, 0x0000, 0x013A, 0x0000, 0x013B, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x013C, 0x0000, 0x013C, 0x0000, 0x0131, 0x0130,
0x0000, 0x0002, 0x0001, 0x0002, 0x0003, 0x0002, 0x0004, 0x0002,
0x0000, 0x0002, 0x0001, 0x0002, 0x0000, 0x0000, 0x0000, 0x0000
};
static void assign_player(int dev, int num, int force = 0)
{
input[dev].num = num;
if (JOYCON_COMBINED(dev)) input[input[dev].bind].num = num;
store_player(num, dev);
printf("Device %s %sassigned to player %d\n", input[dev].id, force ? "forcebly " : "", input[dev].num);
}
static void input_cb(struct input_event *ev, struct input_absinfo *absinfo, int dev)
{
if (ev->type != EV_KEY && ev->type != EV_ABS && ev->type != EV_REL) return;
if (ev->type == EV_KEY && (!ev->code || ev->code == KEY_UNKNOWN)) return;
static uint16_t last_axis = 0;
int sub_dev = dev;
//check if device is a part of multifunctional device
if (!JOYCON_COMBINED(dev) && input[dev].bind >= 0) dev = input[dev].bind;
if (ev->type == EV_KEY)
{
if (input[dev].timeout > 0) input[dev].timeout = cfg.bt_auto_disconnect * 10;
//mouse
//if the lightgun mouse quirk is set then don't pass these button presses to the mouse system
//let them get handled and mapped like regular buttons
if (ev->code >= BTN_MOUSE && ev->code < BTN_JOYSTICK && input[dev].quirk != QUIRK_LIGHTGUN_MOUSE)
{
if (ev->value <= 1)
{
int mask = 1 << (ev->code - BTN_MOUSE);
if (input[dev].ds_mouse_emu && mask == 1) mask = 2;
mice_btn = (ev->value) ? (mice_btn | mask) : (mice_btn & ~mask);
mouse_btn_req();
}
return;
}
}
if (ev->type == EV_KEY && ev->code < 256 && !(mapping && mapping_type == 2))
{
if (!input[dev].has_kbdmap)
{
if (!FileLoadConfig(get_kbdmap_name(dev), &input[dev].kbdmap, sizeof(input[dev].kbdmap)))
{
memset(input[dev].kbdmap, 0, sizeof(input[dev].kbdmap));
}
input[dev].has_kbdmap = 1;
}
if (input[dev].kbdmap[ev->code]) ev->code = input[dev].kbdmap[ev->code];
}
static int key_mapped = 0;
int map_skip = (ev->type == EV_KEY && mapping && ((ev->code == KEY_SPACE && mapping_type == 1) || ev->code == KEY_ALTERASE) && (mapping_dev >= 0 || mapping_button<0));
int cancel = (ev->type == EV_KEY && ev->code == KEY_ESC && !(mapping && mapping_type == 3 && mapping_button));
int enter = (ev->type == EV_KEY && ev->code == KEY_ENTER && !(mapping && mapping_type == 3 && mapping_button));
int origcode = ev->code;
if (!input[dev].has_mmap)
{
if (input[dev].quirk == QUIRK_TOUCHGUN)
{
memset(input[dev].mmap, 0, sizeof(input[dev].mmap));
input[dev].mmap[SYS_AXIS_MX] = -1;
input[dev].mmap[SYS_AXIS_MY] = -1;
input[dev].mmap[SYS_AXIS_X] = -1;
input[dev].mmap[SYS_AXIS_Y] = -1;
}
else if (input[dev].quirk != QUIRK_PDSP && input[dev].quirk != QUIRK_MSSP)
{
if (!load_map(get_map_name(dev, 1), &input[dev].mmap, sizeof(input[dev].mmap)))
{
if (!gcdb_map_for_controller(input[sub_dev].bustype, input[sub_dev].vid, input[sub_dev].pid, input[sub_dev].version, pool[sub_dev].fd, input[dev].mmap))
{
memset(input[dev].mmap, 0, sizeof(input[dev].mmap));
memcpy(input[dev].mmap, def_mmap, sizeof(def_mmap));
//input[dev].has_mmap++;
}
} else {
gcdb_show_string_for_ctrl_map(input[sub_dev].bustype, input[sub_dev].vid, input[sub_dev].pid, input[sub_dev].version, pool[sub_dev].fd, input[sub_dev].name, input[dev].mmap);
}
if (!input[dev].mmap[SYS_BTN_OSD_KTGL + 2]) input[dev].mmap[SYS_BTN_OSD_KTGL + 2] = input[dev].mmap[SYS_BTN_OSD_KTGL + 1];
if (input[dev].quirk == QUIRK_WHEEL)
{
input[dev].mmap[SYS_AXIS_MX] = -1;
input[dev].mmap[SYS_AXIS_MY] = -1;
input[dev].mmap[SYS_AXIS_X] = -1;
input[dev].mmap[SYS_AXIS_Y] = -1;
input[dev].mmap[SYS_AXIS1_X] = -1;
input[dev].mmap[SYS_AXIS1_Y] = -1;
input[dev].mmap[SYS_AXIS2_X] = -1;
input[dev].mmap[SYS_AXIS2_Y] = -1;
}
else
{
if (input[dev].mmap[SYS_AXIS_X] == input[dev].mmap[SYS_AXIS1_X])
{
input[dev].stick_l[0] = SYS_AXIS1_X;
if ((input[dev].mmap[SYS_AXIS2_X] >> 16) == 2) input[dev].stick_r[0] = SYS_AXIS2_X;
}
if (input[dev].mmap[SYS_AXIS_Y] == input[dev].mmap[SYS_AXIS1_Y])
{
input[dev].stick_l[1] = SYS_AXIS1_Y;
if ((input[dev].mmap[SYS_AXIS2_Y] >> 16) == 2) input[dev].stick_r[1] = SYS_AXIS2_Y;
}
if (input[dev].mmap[SYS_AXIS_X] == input[dev].mmap[SYS_AXIS2_X])
{
input[dev].stick_l[0] = SYS_AXIS2_X;
if ((input[dev].mmap[SYS_AXIS1_X] >> 16) == 2) input[dev].stick_r[0] = SYS_AXIS1_X;
}
if (input[dev].mmap[SYS_AXIS_Y] == input[dev].mmap[SYS_AXIS2_Y])
{
input[dev].stick_l[1] = SYS_AXIS2_Y;
if ((input[dev].mmap[SYS_AXIS1_Y] >> 16) == 2) input[dev].stick_r[1] = SYS_AXIS1_Y;
}
}
}
input[dev].has_mmap++;
}
if (!input[dev].has_map)
{
if (input[dev].quirk == QUIRK_PDSP || input[dev].quirk == QUIRK_MSSP)
{
memset(input[dev].map, 0, sizeof(input[dev].map));
input[dev].map[map_paddle_btn()] = 0x120;
}
else if (!load_map(get_map_name(dev, 0), &input[dev].map, sizeof(input[dev].map)))
{
memset(input[dev].map, 0, sizeof(input[dev].map));
if (!is_menu())
{
if (input[dev].has_mmap == 1)
{
// not defined try to guess the mapping
map_joystick(input[dev].map, input[dev].mmap);
}
else
{
input[dev].has_map++;
}
}
input[dev].has_map++;
}
input[dev].has_map++;
}
if (!input[dev].has_jkmap)
{
if (!load_map(get_jkmap_name(dev), &input[dev].jkmap, sizeof(input[dev].jkmap)))
{
memset(input[dev].jkmap, 0, sizeof(input[dev].jkmap));
}
input[dev].has_jkmap = 1;
}
if (!input[dev].num)
{
bool assign_btn = ((input[dev].quirk == QUIRK_PDSP || input[dev].quirk == QUIRK_MSSP) && (ev->type == EV_REL || ev->type == EV_KEY));
assign_btn |= (input[dev].quirk == QUIRK_LIGHTGUN_MOUSE && ev->type == EV_KEY && ev->value == 1 && ev->code == BTN_MOUSE);
if (!assign_btn && ev->type == EV_KEY && ev->value >= 1 && ev->code >= 256)
{
for (int i = SYS_BTN_RIGHT; i <= SYS_BTN_START; i++)
{
if (ev->code == input[dev].mmap[i]) assign_btn = 1;
}
}
if (assign_btn)
{
for (uint8_t i = 0; i < (sizeof(cfg.player_controller) / sizeof(cfg.player_controller[0])); i++)
{
for (int n = 0; n < 8; n++)
{
if (!cfg.player_controller[i][n][0]) break;
if (strcasestr(input[dev].id, cfg.player_controller[i][n]))
{
assign_player(dev, i + 1, 1);
break;
}
if (strcasestr(input[dev].sysfs, cfg.player_controller[i][n]))
{
assign_player(dev, i + 1, 1);
break;
}
if (strcasestr(get_unique_mapping(dev, 1), cfg.player_controller[i][n]))
{
assign_player(dev, i + 1, 1);
break;
}
}
}
if (!input[dev].num)
{
for (uint8_t num = 1; num < NUMDEV + 1; num++)
{
int found = 0;
for (int i = 0; i < NUMDEV; i++)
{
if (input[i].quirk != QUIRK_TOUCHGUN)
{
// paddles/spinners overlay on top of other gamepad
if (!((input[dev].quirk == QUIRK_PDSP || input[dev].quirk == QUIRK_MSSP) ^ (input[i].quirk == QUIRK_PDSP || input[i].quirk == QUIRK_MSSP)))
{
found = (input[i].num == num);
if (found) break;
}
}
}
if (!found)
{
assign_player(dev, num);
break;
}
}
}
}
}
if (!input[dev].map_shown && input[dev].num)
{
input[dev].map_shown = 1;
if (JOYCON_COMBINED(dev)) input[input[dev].bind].map_shown = 1;
store_player(input[dev].num, dev);
if (cfg.controller_info)
{
if (input[dev].quirk == QUIRK_PDSP || input[dev].quirk == QUIRK_MSSP)
{
char str[32];
sprintf(str, "P%d paddle/spinner", input[dev].num);
Info(str, cfg.controller_info * 1000);
}
else
{
map_joystick_show(input[dev].map, input[dev].mmap, input[dev].num);
}
}
}
int old_combo = input[dev].osd_combo;
if (ev->type == EV_KEY)
{
if (ev->code == input[dev].mmap[SYS_BTN_OSD_KTGL + 2])
{
if (ev->value) input[dev].osd_combo |= 2;
else input[dev].osd_combo &= ~2;
}
if (ev->code == input[dev].mmap[SYS_BTN_OSD_KTGL + 1])
{
if (ev->value) input[dev].osd_combo |= 1;
else input[dev].osd_combo &= ~1;
}
}
int osd_event = 0;
if (old_combo != 3 && input[dev].osd_combo == 3)
{
osd_event = 1;
if (mapping && !is_menu()) osd_timer = GetTimer(1000);
}
else if (old_combo == 3 && input[dev].osd_combo != 3)
{
osd_event = 2;
if (mapping && !is_menu())
{
if (CheckTimer(osd_timer))
{
cancel = 1;
ev->code = KEY_ESC;
ev->value = 0;
}
else
{
map_skip = 1;
ev->value = 1;
}
}
osd_timer = 0;
}
if (mapping && mapping_type == 3)
{
if (map_skip)
{
mapping_finish = 1;
ev->value = 0;
}
osd_event = 0;
}
//mapping
if (mapping && (mapping_dev >= 0 || ev->value)
&& !((mapping_type < 2 || !mapping_button) && (cancel || enter))
&& input[dev].quirk != QUIRK_PDSP
&& input[dev].quirk != QUIRK_MSSP)
{
int idx = 0;
osdbtn = 0;
if (is_menu())
{
spi_uio_cmd(UIO_KEYBOARD); //ping the Menu core to wakeup
osd_event = 0;
}
// paddle axis - skip from mapping
if ((ev->type == EV_ABS || ev->type == EV_REL) && (ev->code == 7 || ev->code == 8) && input[dev].quirk != QUIRK_WHEEL) return;
// protection against joysticks generating 2 codes per button
if (ev->type == EV_KEY && !(is_menu() && mapping < 2 && mapping_button == SYS_BTN_OSD_KTGL) && !map_skip)
{
if (!mapping_current_key)
{
if (ev->value == 1)
{
mapping_current_key = ev->code;
mapping_current_dev = dev;
}
else return;
}
else
{
if (ev->value == 0 && mapping_current_key == ev->code && mapping_current_dev == dev)
{
mapping_current_key = 0;
}
else return;
}
}
if (map_skip) mapping_current_key = 0;
if (ev->type == EV_KEY && mapping_button>=0 && !osd_event)
{
if (mapping_type == 2) // keyboard remap
{
if (ev->code < 256)
{
if (!mapping_button)
{
if (ev->value == 1)
{
if (mapping_dev < 0)
{
mapping_dev = dev;
mapping_button = 0;
}
if (!mapping_button) mapping_button = ev->code;
mapping_current_dev = mapping_dev;
}
}
else
{
if (ev->value == 0 && mapping_dev >= 0 && mapping_button != ev->code)
{
input[mapping_dev].kbdmap[mapping_button] = ev->code;
mapping_button = 0;
}
}
}
return;
}
else if (mapping_type == 3) // button remap
{
if (input[dev].mmap[SYS_BTN_OSD_KTGL] == ev->code ||
input[dev].mmap[SYS_BTN_OSD_KTGL + 1] == ev->code ||
input[dev].mmap[SYS_BTN_OSD_KTGL + 2] == ev->code) return;
if (ev->value == 1 && !mapping_button)
{
if (mapping_dev < 0) mapping_dev = dev;
if (mapping_dev == dev && ev->code < 1024) mapping_button = ev->code;
mapping_current_dev = mapping_dev;
}
if (mapping_dev >= 0 && (ev->code < 256 || mapping_dev == dev) && mapping_button && mapping_button != ev->code)
{
if (ev->value == 1)
{
// Technically it's hard to map the key to button as keyboards
// are all the same while joysticks are personalized and numbered.
input[mapping_dev].jkmap[mapping_button] = ev->code;
mapping_current_dev = dev;
}
if (ev->value == 0) mapping_button = 0;
}
return;
}
else
{
int clear = (ev->code == KEY_F12 || ev->code == KEY_MENU || ev->code == KEY_HOMEPAGE) && !is_menu();
if (ev->value == 1 && mapping_dev < 0 && !clear)
{
mapping_dev = dev;
mapping_type = (ev->code >= 256) ? 1 : 0;
key_mapped = 0;
memset(input[mapping_dev].map, 0, sizeof(input[mapping_dev].map));
}
mapping_clear = 0;
if (mapping_dev >= 0 && !map_skip && (mapping_dev == dev || clear) && mapping_button < (is_menu() ? (mapping_type ? SYS_BTN_CNT_ESC + 1 : SYS_BTN_OSD_KTGL + 1) : mapping_count))
{
if (ev->value == 1 && !key_mapped)
{
if (is_menu())
{
if (mapping_dev == dev && !(!mapping_button && last_axis && ((ev->code == last_axis) || (ev->code == last_axis + 1))))
{
if (!mapping_button) memset(input[dev].map, 0, sizeof(input[dev].map));
input[dev].osd_combo = 0;
int found = 0;
if (mapping_button < SYS_BTN_CNT_OK)
{
for (int i = (mapping_button >= BUTTON_DPAD_COUNT) ? BUTTON_DPAD_COUNT : 0; i < mapping_button; i++) if (input[dev].map[i] == ev->code) found = 1;
}
if (!found || (mapping_button == SYS_BTN_OSD_KTGL && mapping_type))
{
if (mapping_button == SYS_BTN_CNT_OK) input[dev].map[SYS_BTN_MENU_FUNC] = ev->code & 0xFFFF;
else if (mapping_button == SYS_BTN_CNT_ESC) input[dev].map[SYS_BTN_MENU_FUNC] = (ev->code << 16) | input[dev].map[SYS_BTN_MENU_FUNC];
else if (mapping_button == SYS_BTN_OSD_KTGL)
{
input[dev].map[SYS_BTN_OSD_KTGL + mapping_type] = ev->code;
input[dev].map[SYS_BTN_OSD_KTGL + 2] = input[dev].map[SYS_BTN_OSD_KTGL + 1];
mapping_current_key = 0; // allow 2 buttons to be pressed
}
else input[dev].map[mapping_button] = ev->code;
key_mapped = ev->code;
//check if analog stick has been used for mouse
if (mapping_button == BUTTON_DPAD_COUNT + 1 || mapping_button == BUTTON_DPAD_COUNT + 3)
{
if (input[dev].map[mapping_button] >= KEY_EMU &&
input[dev].map[mapping_button - 1] >= KEY_EMU &&
(input[dev].map[mapping_button - 1] - input[dev].map[mapping_button] == 1) && // same axis
absinfo)
{
input[dev].map[SYS_AXIS_MX + (mapping_button - (BUTTON_DPAD_COUNT + 1)) / 2] = ((input[dev].map[mapping_button] - KEY_EMU) / 2) | 0x20000;
}
}
}
}
}
else
{
if (clear)
{
memset(input[mapping_dev].map, 0, sizeof(input[mapping_dev].map));
mapping_button = 0;
mapping_clear = 1;
}
else
{
if (!mapping_button)
{
for (uint i = 0; i < sizeof(input[0].map) / sizeof(input[0].map[0]); i++)
{
input[dev].map[i] &= mapping_set ? 0x0000FFFF : 0xFFFF0000;
}
}
int found = 0;
for (int i = 0; i < mapping_button; i++)
{
if (mapping_set && (input[dev].map[i] >> 16) == ev->code) found = 1;
if (!mapping_set && (input[dev].map[i] & 0xFFFF) == ev->code) found = 1;
}
if (!found)
{
if (mapping_set) input[dev].map[mapping_button] = (input[dev].map[mapping_button] & 0xFFFF) | (ev->code << 16);
else input[dev].map[mapping_button] = (input[dev].map[mapping_button] & 0xFFFF0000) | ev->code;
key_mapped = ev->code;
}
}
}
}
//combo for osd button
if (ev->value == 1 && key_mapped && key_mapped != ev->code && is_menu() && mapping_button == SYS_BTN_OSD_KTGL && mapping_type)
{
input[dev].map[SYS_BTN_OSD_KTGL + 2] = ev->code;
printf("Set combo: %x + %x\n", input[dev].map[SYS_BTN_OSD_KTGL + 1], input[dev].map[SYS_BTN_OSD_KTGL + 2]);
}
else if(mapping_dev == dev && ev->value == 0 && key_mapped == ev->code)
{
mapping_button++;
key_mapped = 0;
}
if(!ev->value && mapping_dev == dev && ((ev->code == last_axis) || (ev->code == last_axis+1)))
{
last_axis = 0;
}
}
}
}
else if (is_menu())
{
//Define min-0-max analogs
switch (mapping_button)
{
case 23: idx = SYS_AXIS_X; break;
case 24: idx = SYS_AXIS_Y; break;
case -4: idx = SYS_AXIS1_X; break;
case -3: idx = SYS_AXIS1_Y; break;
case -2: idx = SYS_AXIS2_X; break;
case -1: idx = SYS_AXIS2_Y; break;
}
if (mapping_dev == dev || (mapping_dev < 0 && mapping_button < 0))
{
int max = 0; // , min = 0;
if (ev->type == EV_ABS)
{
int threshold = (absinfo->maximum - absinfo->minimum) / 5;
max = (ev->value >= (absinfo->maximum - threshold));
//min = (ev->value <= (absinfo->minimum + threshold));
//printf("threshold=%d, min=%d, max=%d\n", threshold, min, max);
}
//check DPAD horz
if (mapping_button == -6)
{
last_axis = 0;
if (ev->type == EV_ABS && max)
{
if (mapping_dev < 0) mapping_dev = dev;
mapping_type = 1;
if (absinfo->maximum > 2)
{
tmp_axis[tmp_axis_n++] = ev->code | 0x20000;
mapping_button++;
}
else
{
//Standard DPAD event
mapping_button += 2;
}
}
else if (ev->type == EV_KEY && ev->value == 1)
{
//DPAD uses simple button events
if (!map_skip)
{
mapping_button += 2;
if (mapping_dev < 0) mapping_dev = dev;
if (ev->code < 256)
{
// keyboard, skip stick 1/2
mapping_button += 4;
mapping_type = 0;
}
}
}
}
//check DPAD vert
else if (mapping_button == -5)
{
if (ev->type == EV_ABS && max && absinfo->maximum > 1 && ev->code != (tmp_axis[0] & 0xFFFF))
{
tmp_axis[tmp_axis_n++] = ev->code | 0x20000;
mapping_button++;
}
}
//Sticks
else if (ev->type == EV_ABS && idx)
{
if (mapping_dev < 0) mapping_dev = dev;
if (idx && max && absinfo->maximum > 2)
{
if (mapping_button < 0)
{
int found = 0;
for (int i = 0; i < tmp_axis_n; i++) if (ev->code == (tmp_axis[i] & 0xFFFF)) found = 1;
if (!found)
{
mapping_type = 1;
tmp_axis[tmp_axis_n++] = ev->code | 0x20000;
//if (min) tmp_axis[idx - AXIS1_X] |= 0x10000;
mapping_button++;
if (tmp_axis_n >= 4) mapping_button = 0;
last_axis = KEY_EMU + (ev->code << 1);
}
}
else
{
if (idx == SYS_AXIS_X || ev->code != (input[dev].map[idx - 1] & 0xFFFF))
{
input[dev].map[idx] = ev->code | 0x20000;
//if (min) input[dev].map[idx] |= 0x10000;
mapping_button++;
}
}
}
}
}
}
while (mapping_type <= 1 && mapping_button < mapping_count)
{
if (map_skip)
{
if (map_skip == 2 || ev->value == 1)
{
if (mapping_dev >= 0)
{
if (idx) input[mapping_dev].map[idx] = 0;
else if (mapping_button > 0)
{
if (!is_menu()) input[mapping_dev].map[mapping_button] &= mapping_set ? 0x0000FFFF : 0xFFFF0000;
}
}
last_axis = 0;
mapping_button++;
if (mapping_button < 0 && (mapping_button & 1)) mapping_button++;
}
}
map_skip = 0;
if (mapping_button >= 4 && !is_menu() && !strcmp(joy_bnames[mapping_button - 4], "-")) map_skip = 2;
if (!map_skip) break;
}
if (is_menu() && mapping_type <= 1 && mapping_dev >= 0)
{
memcpy(&input[mapping_dev].mmap[SYS_AXIS1_X], tmp_axis, sizeof(tmp_axis));
memcpy(&input[mapping_dev].map[SYS_AXIS1_X], tmp_axis, sizeof(tmp_axis));
}
}
else
{
key_mapped = 0;
switch (ev->type)
{
case EV_KEY:
if (ev->code < 1024 && input[dev].jkmap[ev->code] && !user_io_osd_is_visible()) ev->code = input[dev].jkmap[ev->code];
//joystick buttons, digital directions
if (ev->code >= 256)
{
if (input[dev].lightgun_req && !user_io_osd_is_visible())
{
if (osd_event == 1)
{
input[dev].lightgun = !input[dev].lightgun;
Info(input[dev].lightgun ? "Light Gun mode is ON" : "Light Gun mode is OFF");
}
}
else
{
if (osd_event == 1) joy_digital(input[dev].num, 0, 0, 1, BTN_OSD);
if (osd_event == 2) joy_digital(input[dev].num, 0, 0, 0, BTN_OSD);
}
if (user_io_osd_is_visible() || video_fb_state())
{
if (ev->value <= 1)
{
if ((input[dev].mmap[SYS_BTN_MENU_FUNC] & 0xFFFF) ?
(ev->code == (input[dev].mmap[SYS_BTN_MENU_FUNC] & 0xFFFF)) :
(ev->code == input[dev].mmap[SYS_BTN_A]))
{
joy_digital(0, JOY_BTN1, 0, ev->value, 0);
return;
}
if ((input[dev].mmap[SYS_BTN_MENU_FUNC] >> 16) ?
(ev->code == (input[dev].mmap[SYS_BTN_MENU_FUNC] >> 16)) :
(ev->code == input[dev].mmap[SYS_BTN_B]))
{
joy_digital(0, JOY_BTN2, 0, ev->value, 0);
return;
}
if (ev->code == input[dev].mmap[SYS_BTN_X])
{
joy_digital(0, JOY_BTN4, 0, ev->value, 0);
return;
}
if (ev->code == input[dev].mmap[SYS_BTN_Y])
{
joy_digital(0, JOY_BTN3, 0, ev->value, 0);
return;
}
if (ev->code == input[dev].mmap[SYS_BTN_L])
{
joy_digital(0, JOY_L, 0, ev->value, 0);
return;
}
if (ev->code == input[dev].mmap[SYS_BTN_R])
{
joy_digital(0, JOY_R, 0, ev->value, 0);
return;
}
if (ev->code == input[dev].mmap[SYS_BTN_START])
{
joy_digital(0, JOY_L2, 0, ev->value, 0);
return;
}
if (ev->code == input[dev].mmap[SYS_BTN_SELECT])
{
joy_digital(0, JOY_R2, 0, ev->value, 0);
return;
}
for (int i = 0; i < SYS_BTN_A; i++)
{
if (ev->code == input[dev].mmap[i])
{
joy_digital(0, 1 << i, 0, ev->value, i);
return;
}
}
for (int i = SYS_MS_RIGHT; i <= SYS_MS_UP; i++)
{
if (ev->code == input[dev].mmap[i])
{
int n = i - SYS_MS_RIGHT;
joy_digital(0, 1 << n, 0, ev->value, n);
return;
}
}
if (input[dev].quirk != QUIRK_WHEEL)
{
if (input[dev].mmap[SYS_AXIS_X])
{
uint16_t key = KEY_EMU + ((uint16_t)input[dev].mmap[SYS_AXIS_X] * 2);
if (ev->code == (key + 1)) joy_digital(0, 1 << 0, 0, ev->value, 0);
if (ev->code == key) joy_digital(0, 1 << 1, 0, ev->value, 1);
}
if (input[dev].mmap[SYS_AXIS_Y])
{
uint16_t key = KEY_EMU + ((uint16_t)input[dev].mmap[SYS_AXIS_Y] * 2);
if (ev->code == (key + 1)) joy_digital(0, 1 << 2, 0, ev->value, 2);
if (ev->code == key) joy_digital(0, 1 << 3, 0, ev->value, 3);
}
}
}
}
else
{
if (mouse_emu)
{
int use_analog = (input[dev].mmap[SYS_AXIS_MX] || input[dev].mmap[SYS_AXIS_MY]);
for (int i = (use_analog ? SYS_MS_BTN_L : SYS_MS_RIGHT); i <= SYS_MS_BTN_M; i++)
{
if (ev->code == input[dev].mmap[i])
{
switch (i)
{
case SYS_MS_RIGHT:
mouse_emu_x = ev->value ? 10 : 0;
break;
case SYS_MS_LEFT:
mouse_emu_x = ev->value ? -10 : 0;
break;
case SYS_MS_DOWN:
mouse_emu_y = ev->value ? 10 : 0;
break;
case SYS_MS_UP:
mouse_emu_y = ev->value ? -10 : 0;
break;
default:
mouse_btn = ev->value ? mouse_btn | 1 << (i - SYS_MS_BTN_L) : mouse_btn & ~(1 << (i - SYS_MS_BTN_L));
mouse_btn_req();
break;
}
return;
}
}
}
if (input[dev].has_map >= 2)
{
if (input[dev].has_map == 3) Info("This joystick is not defined");
input[dev].has_map = 1;
}
for (uint i = 0; i < BTN_NUM; i++)
{
uint64_t mask = 0;
if (ev->code == (input[dev].map[i] & 0xFFFF)) mask = (uint64_t)1 << i;
else if (ev->code == (input[dev].map[i] >> 16)) mask = (uint64_t)1 << (i + 32); // 1 is uint32_t. i spent hours realizing this.
if (mask) {
if (i <= 3 && origcode == ev->code) origcode = 0; // prevent autofire for original dpad
if (ev->value <=1) joy_digital(input[dev].num, mask, origcode, ev->value, i, (ev->code == input[dev].mmap[SYS_BTN_OSD_KTGL + 1] || ev->code == input[dev].mmap[SYS_BTN_OSD_KTGL + 2]));
// support 2 simultaneous functions for 1 button if defined in 2 sets. No return.
}
}
if (ev->code == input[dev].mmap[SYS_MS_BTN_EMU] && (ev->value <= 1) && ((!(mouse_emu & 1)) ^ (!ev->value)))
{
mouse_emu = ev->value ? mouse_emu | 1 : mouse_emu & ~1;
if (input[sub_dev].quirk == QUIRK_DS4) input[dev].ds_mouse_emu = mouse_emu & 1;
if (mouse_emu & 2)
{
mouse_sniper = ev->value;
}
else
{
mouse_timer = 0;
mouse_btn = 0;
mouse_emu_x = 0;
mouse_emu_y = 0;
mouse_cb();
mouse_btn_req();
}
}
}
return;
}
// keyboard
else
{
// replace MENU key by RGUI to allow using Right Amiga on reduced keyboards
// (it also disables the use of Menu for OSD)
if (cfg.key_menu_as_rgui && ev->code == KEY_COMPOSE) ev->code = KEY_RIGHTMETA;
//Keyrah v2: USB\VID_18D8&PID_0002\A600/A1200_MULTIMEDIA_EXTENSION_VERSION
int keyrah = (cfg.keyrah_mode && (((((uint32_t)input[dev].vid) << 16) | input[dev].pid) == cfg.keyrah_mode));
if (keyrah) ev->code = keyrah_trans(ev->code, ev->value);
uint32_t ps2code = get_ps2_code(ev->code);
if (ev->value) modifier |= ps2code;
else modifier &= ~ps2code;
uint16_t reset_m = (modifier & MODMASK) >> 8;
if (ev->code == 111) reset_m |= 0x100;
user_io_check_reset(reset_m, (keyrah && !cfg.reset_combo) ? 1 : cfg.reset_combo);
if(!user_io_osd_is_visible() && ((user_io_get_kbdemu() == EMU_JOY0) || (user_io_get_kbdemu() == EMU_JOY1)) && !video_fb_state())
{
if (!kbd_toggle)
{
for (uint i = 0; i < BTN_NUM; i++)
{
if (ev->code == (uint16_t)input[dev].map[i])
{
if (i <= 3 && origcode == ev->code) origcode = 0; // prevent autofire for original dpad
if (ev->value <= 1) joy_digital((user_io_get_kbdemu() == EMU_JOY0) ? 1 : 2, 1 << i, origcode, ev->value, i);
return;
}
}
}
if (ev->code == input[dev].mmap[SYS_BTN_OSD_KTGL])
{
if (ev->value <= 1) joy_digital((user_io_get_kbdemu() == EMU_JOY0) ? 1 : 2, 0, 0, ev->value, BTN_TGL);
return;
}
}
else
{
kbd_toggle = 0;
}
if (!user_io_osd_is_visible() && (user_io_get_kbdemu() == EMU_MOUSE))
{
if (kbd_mouse_emu)
{
for (int i = SYS_MS_RIGHT; i <= SYS_MS_BTN_M; i++)
{
if (ev->code == input[dev].mmap[i])
{
switch (i)
{
case SYS_MS_RIGHT:
mouse_emu_x = ev->value ? 10 : 0;
break;
case SYS_MS_LEFT:
mouse_emu_x = ev->value ? -10 : 0;
break;
case SYS_MS_DOWN:
mouse_emu_y = ev->value ? 10 : 0;
break;
case SYS_MS_UP:
mouse_emu_y = ev->value ? -10 : 0;
break;
default:
mouse_btn = ev->value ? mouse_btn | 1 << (i - SYS_MS_BTN_L) : mouse_btn & ~(1 << (i - SYS_MS_BTN_L));
mouse_btn_req();
break;
}
return;
}
}
if (ev->code == input[dev].mmap[SYS_MS_BTN_EMU])
{
if (ev->value <= 1) mouse_sniper = ev->value;
return;
}
}
if (ev->code == input[dev].mmap[SYS_BTN_OSD_KTGL])
{
if (ev->value == 1)
{
kbd_mouse_emu = !kbd_mouse_emu;
printf("kbd_mouse_emu = %d\n", kbd_mouse_emu);
mouse_timer = 0;
mouse_btn = 0;
mouse_emu_x = 0;
mouse_emu_y = 0;
mouse_cb();
mouse_btn_req();
}
return;
}
}
if (ev->code == KEY_HOMEPAGE) ev->code = KEY_MENU;
user_io_kbd(ev->code, ev->value);
return;
}
break;
//analog joystick
case EV_ABS:
if (!user_io_osd_is_visible())
{
int value = ev->value;
if (ev->value < absinfo->minimum) value = absinfo->minimum;
else if (ev->value > absinfo->maximum) value = absinfo->maximum;
if (ev->code == 8 && input[dev].quirk != QUIRK_WHEEL)
{
if (input[dev].num && input[dev].num <= NUMPLAYERS)
{
value -= absinfo->minimum;
value = (value * 255) / (absinfo->maximum - absinfo->minimum);
user_io_l_analog_joystick(((input[dev].num - 1) << 4) | 0xF, value, 0);
}
break;
}
int hrange = (absinfo->maximum - absinfo->minimum) / 2;
// normalize to -range/2...+range/2
value -= (absinfo->minimum + absinfo->maximum) / 2;
int range = is_psx() ? 128 : 127;
value = (value * range) / hrange;
// final check to eliminate additive error
if (value < -range) value = -range;
else if (value > 127) value = 127;
if (input[sub_dev].axis_pos[ev->code & 0xFF] == (int8_t)value) break;
input[sub_dev].axis_pos[ev->code & 0xFF] = (int8_t)value;
if (ev->code == (input[dev].mmap[SYS_AXIS_MX] & 0xFFFF) && mouse_emu)
{
mouse_emu_x = 0;
if (value < -1 || value > 1) mouse_emu_x = value;
mouse_emu_x /= 12;
return;
}
else if (ev->code == (input[dev].mmap[SYS_AXIS_MY] & 0xFFFF) && mouse_emu)
{
mouse_emu_y = 0;
if (value < -1 || value > 1) mouse_emu_y = value;
mouse_emu_y /= 12;
return;
}
else
{
// skip if joystick is undefined.
if (!input[dev].num) break;
if (input[dev].quirk == QUIRK_WHEEL)
{
int wh_value = ((127 * (ev->value - absinfo->minimum)) / (absinfo->maximum - absinfo->minimum)) - 127;
if (input[dev].wh_pedal_invert > 0) {
// invert pedal values range for wheel setups that require it
wh_value = ~(wh_value + 127);
}
// steering wheel passes full range, pedals are standardised in +127 to 0 to -127 range
if (ev->code == input[dev].wh_steer)
{
joy_analog(dev, 0, value, 0);
}
else if (ev->code == input[dev].wh_accel)
{
joy_analog(dev, 1, wh_value, 0);
}
else if (ev->code == input[dev].wh_brake)
{
joy_analog(dev, 1, wh_value, 1);
}
else if (ev->code == input[dev].wh_clutch)
{
joy_analog(dev, 0, wh_value, 1);
}
else if (ev->code == input[dev].wh_combo)
{
// if accel and brake pedal use a shared axis then map negative to accel and positive to brake
if (value < -1) joy_analog(dev, 1, value, 0);
else if (value > 1) joy_analog(dev, 1, -value, 1);
else
{
joy_analog(dev, 1, 0, 0);
joy_analog(dev, 1, 0, 0);
}
}
}
else if (ev->code == 0 && input[dev].lightgun)
{
joy_analog(dev, 0, value);
}
else if (ev->code == 1 && input[dev].lightgun)
{
joy_analog(dev, 1, value);
}
else
{
int offset = (value < -1 || value > 1) ? value : 0;
if (input[dev].stick_l[0] && ev->code == (uint16_t)input[dev].mmap[input[dev].stick_l[0]])
{
joy_analog(dev, 0, offset, 0);
}
else if (input[dev].stick_l[1] && ev->code == (uint16_t)input[dev].mmap[input[dev].stick_l[1]])
{
joy_analog(dev, 1, offset, 0);
}
else if (input[dev].stick_r[0] && ev->code == (uint16_t)input[dev].mmap[input[dev].stick_r[0]])
{
joy_analog(dev, 0, offset, 1);
}
else if (input[dev].stick_r[1] && ev->code == (uint16_t)input[dev].mmap[input[dev].stick_r[1]])
{
joy_analog(dev, 1, offset, 1);
}
}
}
}
break;
// spinner
case EV_REL:
if (!user_io_osd_is_visible() && ev->code == 7)
{
if (input[dev].num && input[dev].num <= NUMPLAYERS)
{
int value = ev->value;
if (ev->value < -128) value = -128;
else if (ev->value > 127) value = 127;
user_io_l_analog_joystick(((input[dev].num - 1) << 4) | 0x8F, value, 0);
}
}
break;
}
}
}
void send_map_cmd(int key)
{
if (mapping && mapping_dev >= 0)
{
input_event ev;
ev.type = EV_KEY;
ev.code = key;
ev.value = 1;
input_cb(&ev, 0, mapping_dev);
}
}
#define CMD_FIFO "/dev/MiSTer_cmd"
#define LED_MONITOR "/sys/class/leds/hps_led0/brightness_hw_changed"
// add sequential suffixes for non-merged devices
void make_unique(uint16_t vid, uint16_t pid, int type)
{
int cnt = 0;
int lastmin = -1;
int min;
printf("make_unique(%04X,%04X,%d)\n", vid, pid, type);
while(1)
{
int idx = -1;
min = INT32_MAX;
for (int i = 0; i < NUMDEV; i++)
{
if ((!type && (input[i].vid == vid)) ||
(type > 0 && (input[i].vid == vid) && (input[i].pid == pid)) ||
(type < 0 && (input[i].vid == vid) && (input[i].pid != pid)))
{
int num = -1;
const char *n = strstr(input[i].devname, "/event");
if (n) num = strtoul(n + 6, NULL, 10);
if (num >= 0 && num < min && num > lastmin)
{
min = num;
idx = i;
}
}
}
if (idx < 0) break;
lastmin = min;
sprintf(input[idx].id + strlen(input[idx].id), "/%d", cnt++);
}
}
void mergedevs()
{
for (int i = 0; i < NUMDEV; i++)
{
memset(input[i].id, 0, sizeof(input[i].id));
}
FILE *f = fopen("/proc/bus/input/devices", "r");
if (!f)
{
printf("Failed to open /proc/bus/input/devices\n");
return;
}
static char str[1024];
char phys[64] = {};
char uniq[64] = {};
char id[64] = {};
static char sysfs[512] = {};
while (fgets(str, sizeof(str), f))
{
int len = strlen(str);
while (len && str[len - 1] == '\n') str[--len] = 0;
if (!len)
{
phys[0] = 0;
uniq[0] = 0;
}
else
{
if (!strncmp("P: Phys", str, 7)) snprintf(phys, sizeof(phys), "%s", strchr(str, '=') + 1);
if (!strncmp("U: Uniq", str, 7)) snprintf(uniq, sizeof(uniq), "%s", strchr(str, '=') + 1);
if (!strncmp("S: Sysfs", str, 8)) snprintf(sysfs, sizeof(sysfs), "%s", strchr(str, '=') + 1);
if (!strncmp("H: ", str, 3))
{
if (strlen(phys) && strlen(uniq)) snprintf(id, sizeof(id), "%s/%s", phys, uniq);
else if (strlen(phys)) strcpy(id, phys);
else strcpy(id, uniq);
char *handlers = strchr(str, '=');
if (handlers && id[0])
{
handlers++;
for (int i = 0; i < NUMDEV; i++)
{
if (pool[i].fd >= 0)
{
char *dev = strrchr(input[i].devname, '/');
if (dev)
{
char idsp[32];
strcpy(idsp, dev + 1);
strcat(idsp, " ");
if (strstr(handlers, idsp))
{
strcpy(input[i].id, id);
strcpy(input[i].sysfs, sysfs);
strcpy(input[i].mac, uniq);
input[i].unique_hash = str_hash(input[i].id);
input[i].unique_hash = str_hash(input[i].mac, input[i].unique_hash);
input[i].timeout = (strlen(uniq) && strstr(sysfs, "bluetooth")) ? (cfg.bt_auto_disconnect * 10) : 0;
}
}
}
}
}
}
}
}
fclose(f);
//Bypass merging of specified 2 port/player controllers
make_unique(0x289B, 0x0057, -1); // Raphnet
make_unique(0x0E8F, 0x3013, 1); // Mayflash SNES controller 2 port adapter
make_unique(0x16C0, 0x05E1, 1); // XinMo XM-10 2 player USB Encoder
make_unique(0x045E, 0x02A1, 1); // Xbox 360 wireless receiver
make_unique(0x8282, 0x3201, 1); // Irken Labs JAMMA Expander / Mojo Retro Adapter
make_unique(0x1209, 0xFACA, 1); // ControllaBLE
make_unique(0x16D0, 0x127E, 1); // Reflex Adapt to USB
make_unique(0x1209, 0x595A, 1); // RetroZord adapter
if (cfg.no_merge_vid)
{
make_unique(cfg.no_merge_vid, cfg.no_merge_pid, (cfg.no_merge_pid ? 1 : 0));
}
for (int i = 0; i < (int)cfg.no_merge_vidpid[0]; i++) make_unique(cfg.no_merge_vidpid[i + 1] >> 16, (uint16_t)(cfg.no_merge_vidpid[i + 1]), 1);
// merge multifunctional devices by id
for (int i = 0; i < NUMDEV; i++)
{
input[i].bind = i;
if (input[i].id[0] && !input[i].mouse)
{
for (int j = 0; j < i; j++)
{
if (!strcmp(input[i].id, input[j].id))
{
input[i].bind = j;
break;
}
}
}
}
//copy missing fields to mouseX
for (int i = 0; i < NUMDEV; i++) if (input[i].mouse)
{
for (int j = 0; j < NUMDEV; j++) if (!input[j].mouse)
{
if (!strcmp(input[i].id, input[j].id))
{
input[i].bind = j;
input[i].vid = input[j].vid;
input[i].pid = input[j].pid;
input[i].version = input[j].version;
input[i].bustype = input[j].bustype;
input[i].quirk = input[j].quirk;
memcpy(input[i].name, input[j].name, sizeof(input[i].name));
memcpy(input[i].idstr, input[j].idstr, sizeof(input[i].idstr));
if (!input[i].quirk)
{
//All mice as spinners
if ((cfg.spinner_vid == 0xFFFF && cfg.spinner_pid == 0xFFFF)
//Mouse as spinner
|| (cfg.spinner_vid && cfg.spinner_pid && input[i].vid == cfg.spinner_vid && input[i].pid == cfg.spinner_pid))
{
input[i].quirk = QUIRK_MSSP;
input[i].bind = i;
input[i].spinner_prediv = 1;
}
//Arcade Spinner TS-BSP01 (X axis) and Atari (Y axis)
if (input[i].vid == 0x32be && input[i].pid == 0x1420)
{
input[i].quirk = QUIRK_MSSP;
input[i].bind = i;
input[i].spinner_prediv = 3;
}
if (input[i].quirk == QUIRK_MSSP) strcat(input[i].id, "_sp");
}
break;
}
}
}
}
// Jammasd/J-PAC/I-PAC have shifted keys: when 1P start is kept pressed, it acts as a shift key,
// outputting other key signals. Example: 1P start + 2P start = KEY_ESC
// Shifted keys are passed as normal keyboard keys.
static struct
{
uint16_t key;
uint16_t player;
uint16_t btn;
} jamma2joy[] =
{
{KEY_5, 1, 0x120}, // 1P coin
{KEY_1, 1, 0x121}, // 1P start (shift key)
{KEY_UP, 1, 0x122}, // 1P up
{KEY_DOWN, 1, 0x123}, // 1P down
{KEY_LEFT, 1, 0x124}, // 1P left
{KEY_RIGHT, 1, 0x125}, // 1P right
{KEY_LEFTCTRL, 1, 0x126}, // 1P 1
{KEY_LEFTALT, 1, 0x127}, // 1P 2
{KEY_SPACE, 1, 0x128}, // 1P 3
{KEY_LEFTSHIFT, 1, 0x129}, // 1P 4
{KEY_Z, 1, 0x12A}, // 1P 5
{KEY_X, 1, 0x12B}, // 1P 6
{KEY_C, 1, 0x12C}, // 1P 7
{KEY_V, 1, 0x12D}, // 1P 8
{KEY_9, 1, 0x12E}, // Test
{KEY_TAB, 1, 0x12F}, // Tab (shift + 1P right)
{KEY_ENTER, 1, 0x130}, // Enter (shift + 1P left)
// ~ Tidle supportted?
{KEY_P, 1, 0x131}, // P (pause) (shift + 1P down)
{KEY_F1, 1, 0x132}, // Service
{KEY_F2, 1, 0x133}, // Test
{KEY_F3, 1, 0x134}, // Tilt
{KEY_6, 2, 0x120}, // 2P coin
{KEY_2, 2, 0x121}, // 2P start
{KEY_R, 2, 0x122}, // 2P up
{KEY_F, 2, 0x123}, // 2P down
{KEY_D, 2, 0x124}, // 2P left
{KEY_G, 2, 0x125}, // 2P right
{KEY_A, 2, 0x126}, // 2P 1
{KEY_S, 2, 0x127}, // 2P 2
{KEY_Q, 2, 0x128}, // 2P 3
{KEY_W, 2, 0x129}, // 2P 4
{KEY_I, 2, 0x12A}, // 2P 5
{KEY_K, 2, 0x12B}, // 2P 6
{KEY_J, 2, 0x12C}, // 2P 7
{KEY_L, 2, 0x12D}, // 2P 8
/*
some key codes overlap with 1P/2P buttons.
{KEY_7, 3, 0x120}, // 3P coin
{KEY_3, 3, 0x121}, // 3P start
{KEY_I, 3, 0x122}, // 3P up
{KEY_K, 3, 0x123}, // 3P down
{KEY_J, 3, 0x124}, // 3P left
{KEY_L, 3, 0x125}, // 3P right
{KEY_RIGHTCTRL, 3, 0x126}, // 3P 1
{KEY_RIGHTSHIFT,3, 0x127}, // 3P 2
{KEY_ENTER, 3, 0x128}, // 3P 3
{KEY_O, 3, 0x129}, // 3P 4
{KEY_8, 4, 0x120}, // 4P coin
{KEY_4, 4, 0x121}, // 4P start
{KEY_Y, 4, 0x122}, // 4P up
{KEY_N, 4, 0x123}, // 4P down
{KEY_V, 4, 0x124}, // 4P left
{KEY_U, 4, 0x125}, // 4P right
{KEY_B, 4, 0x126}, // 4P 1
{KEY_E, 4, 0x127}, // 4P 2
{KEY_H, 4, 0x128}, // 4P 3
{KEY_M, 4, 0x129}, // 4P 4
*/
};
// Second Jammasd/J-PAC/I-PAC quirk. It's equivalent to jamma2joy but assigned to players 3 and 4
// to give support to JAMMA-VERSUS with two JAMMA USB control interfaces.
// i.e. JammaSD for Players1-2 (on a first cabinet), and J-PAC for Payers 3-4 (on a second cabinet)
static struct
{
uint16_t key;
uint16_t player;
uint16_t btn;
} jamma22joy[] =
{
{KEY_5, 3, 0x120}, // 3P coin
{KEY_1, 3, 0x121}, // 3P start
{KEY_UP, 3, 0x122}, // 3P up
{KEY_DOWN, 3, 0x123}, // 3P down
{KEY_LEFT, 3, 0x124}, // 3P left
{KEY_RIGHT, 3, 0x125}, // 3P right
{KEY_LEFTCTRL, 3, 0x126}, // 3P 1
{KEY_LEFTALT, 3, 0x127}, // 3P 2
{KEY_SPACE, 3, 0x128}, // 3P 3
{KEY_LEFTSHIFT, 3, 0x129}, // 3P 4
{KEY_Z, 3, 0x12A}, // 3P 5
{KEY_X, 3, 0x12B}, // 3P 6
{KEY_C, 3, 0x12C}, // 3P 7
{KEY_V, 3, 0x12D}, // 3P 8
{KEY_6, 4, 0x120}, // 4P coin
{KEY_2, 4, 0x121}, // 4P start
{KEY_R, 4, 0x122}, // 4P up
{KEY_F, 4, 0x123}, // 4P down
{KEY_D, 4, 0x124}, // 4P left
{KEY_G, 4, 0x125}, // 4P right
{KEY_A, 4, 0x126}, // 4P 1
{KEY_S, 4, 0x127}, // 4P 2
{KEY_Q, 4, 0x128}, // 4P 3
{KEY_W, 4, 0x129}, // 4P 4
{KEY_I, 4, 0x12A}, // 4P 5
{KEY_K, 4, 0x12B}, // 4P 6
{KEY_J, 4, 0x12C}, // 4P 7
{KEY_L, 4, 0x12D}, // 4P 8
};
static void send_mouse_with_throttle(int dev, int xval, int yval, int8_t wval)
{
int i = dev;
if (input[dev].bind >= 0) dev = input[dev].bind;
if (is_menu() && !video_fb_state()) printf("%s: dx=%d, dy=%d, scroll=%d\n", input[i].devname, xval, yval, wval);
int throttle = cfg.mouse_throttle ? cfg.mouse_throttle : 1;
if (input[dev].ds_mouse_emu) throttle *= 4;
if (input[dev].quirk == QUIRK_TOUCHGUN) throttle *= 12;
input[i].accx += xval;
xval = input[i].accx / throttle;
input[i].accx -= xval * throttle;
input[i].accy -= yval;
yval = input[i].accy / throttle;
input[i].accy -= yval * throttle;
mouse_cb(xval, yval, wval);
}
static uint32_t touch_rel = 0;
static void touchscreen_proc(int dev, input_event *ev)
{
struct input_absinfo absinfo;
int i = dev;
if (input[dev].bind >= 0) dev = input[dev].bind;
if (ev->type == EV_KEY)
{
if (ev->value == 1)
{
input[i].misc_flags = 0xC0;
touch_rel = 0;
ioctl(pool[i].fd, EVIOCGABS(ABS_X), &absinfo);
input[i].lastx = absinfo.value;
input[i].startx = absinfo.value;
ioctl(pool[i].fd, EVIOCGABS(ABS_Y), &absinfo);
input[i].lasty = absinfo.value;
input[i].starty = absinfo.value;
}
else
{
input[i].misc_flags = 0;
mice_btn = 0;
if (input[dev].lightgun)
{
menu_lightgun_cb(i, EV_KEY, 0x131, 0);
}
else
{
if (abs(input[i].lastx - input[i].startx) < 8 && abs(input[i].lasty - input[i].starty) < 8)
{
mice_btn |= 1;
touch_rel = GetTimer(100);
}
}
mouse_btn_req();
}
}
else if (ev->type == EV_ABS && ev->code == ABS_MT_SLOT && ev->value == 3 && (input[i].misc_flags & 0x80))
{
input[i].misc_flags = 0;
mice_btn = 0;
mouse_btn_req();
input[dev].lightgun = !input[dev].lightgun;
Info(input[dev].lightgun ? "Light Gun mode is ON" : "Light Gun mode is OFF");
}
if (input[dev].lightgun)
{
if (ev->type == EV_KEY && ev->value == 1)
{
mice_btn |= 1;
mouse_btn_req();
menu_lightgun_cb(i, EV_KEY, 0x131, 1);
}
else if (ev->type == EV_ABS)
{
if (ev->code == ABS_MT_POSITION_X)
{
ev->code = ABS_X;
absinfo.minimum = input[i].guncal[2];
absinfo.maximum = input[i].guncal[3];
menu_lightgun_cb(i, ev->type, ev->code, ev->value);
input_cb(ev, &absinfo, i);
}
else if (ev->code == ABS_MT_POSITION_Y)
{
ev->code = ABS_Y;
absinfo.minimum = input[i].guncal[0];
absinfo.maximum = input[i].guncal[1];
menu_lightgun_cb(i, ev->type, ev->code, ev->value);
input_cb(ev, &absinfo, i);
}
else if (ev->code == ABS_MT_SLOT && (input[i].misc_flags & 0x80))
{
if (ev->value == 1) input[i].misc_flags |= 1;
if (ev->value == 2) input[i].misc_flags |= 2;
if (input[i].misc_flags & 2) mice_btn = 4;
else if (input[i].misc_flags & 1) mice_btn = 2;
else mice_btn = 1;
mouse_btn_req();
}
}
}
else
{
if (ev->type == EV_ABS)
{
if (input[i].misc_flags & 0x80)
{
if (ev->code == ABS_MT_SLOT)
{
if (ev->value) input[i].misc_flags &= ~0x40;
else input[i].misc_flags |= 0x40;
if (ev->value == 1) input[i].misc_flags |= 1;
if (ev->value == 2) input[i].misc_flags |= 2;
if (input[i].misc_flags & 2) mice_btn = 4;
else if (input[i].misc_flags & 1) mice_btn = 2;
mouse_btn_req();
}
else if (input[i].misc_flags & 0x40)
{
if (ev->code == ABS_MT_POSITION_X)
{
int dx = ev->value - input[i].lastx;
if (dx > 255) dx = 255;
if (dx < -256) dx = -256;
input[i].lastx = ev->value;
send_mouse_with_throttle(i, dx, 0, 0);
}
else if (ev->code == ABS_MT_POSITION_Y)
{
int dy = ev->value - input[i].lasty;
if (dy > 255) dy = 255;
if (dy < -256) dy = -256;
input[i].lasty = ev->value;
send_mouse_with_throttle(i, 0, -dy, 0);
}
}
}
}
}
}
static int vcs_proc(int dev, input_event *ev)
{
devInput *inp = &input[dev];
if (ev->type == EV_KEY)
{
int flg = 0;
int alt = inp->mod && (inp->misc_flags & 2);
switch (ev->code)
{
case 0x130: // red top
if (!ev->value)
{
ev->code = !alt ? 0x135 : 0x130;
input_cb(ev, 0, dev);
}
ev->code = alt ? 0x135 : 0x130;
flg = 1;
break;
case 0x131: // red bottom
flg = 2;
break;
case 0x0AC: // atari
if (!ev->value)
{
ev->code = !alt ? 0x136 : 0x132;
input_cb(ev, 0, dev);
}
ev->code = alt ? 0x136 : 0x132;
flg = 4;
break;
case 0x09E: // back
if (!ev->value)
{
ev->code = !alt ? 0x137 : 0x133;
input_cb(ev, 0, dev);
}
ev->code = alt ? 0x137 : 0x133;
flg = 8;
break;
case 0x08B: // menu
if (!ev->value)
{
ev->code = !alt ? 0x138 : 0x134;
input_cb(ev, 0, dev);
}
ev->code = alt ? 0x138 : 0x134;
flg = 16;
break;
}
if (flg)
{
if (ev->value) inp->misc_flags |= flg;
else inp->misc_flags &= ~flg;
if ((inp->misc_flags & 0x1F) == 0x1B)
{
inp->mod = !inp->mod;
inp->has_map = 0;
inp->has_mmap = 0;
Info(inp->mod ? "8-button mode" : "5-button mode");
}
}
if (ev->code == 0x131 && inp->mod) return 0;
}
else if (ev->code == 7)
{
if (inp->spinner_prev < 0) inp->spinner_prev = ev->value;
int acc = inp->spinner_prev;
int diff =
(acc > 700 && ev->value < 300) ? (ev->value + 1024 - acc) :
(acc < 300 && ev->value > 700) ? (ev->value - 1024 - acc) : (ev->value - acc);
if (inp->spinner_accept)
{
inp->spinner_accept = (inp->spinner_dir && diff >= 0) || (!inp->spinner_dir && diff <= 0);
}
else if (diff <= -6 || diff >= 6)
{
inp->spinner_accept = 1;
inp->spinner_dir = (diff > 0) ? 1 : 0;
diff = inp->spinner_dir ? 1 : -1;
}
if (inp->spinner_accept && diff)
{
inp->spinner_prev = ev->value;
if ((inp->misc_flags & 0x1F) == 0xB && ((inp->misc_flags & 0x20) ? (diff < -30) : (diff > 30)))
{
inp->misc_flags ^= 0x20;
Info((inp->misc_flags & 0x20) ? "Spinner: Enabled" : "Spinner: Disabled");
}
if (inp->misc_flags & 0x20)
{
inp->paddle_val += diff;
if (inp->paddle_val < 0) inp->paddle_val = 0;
if (inp->paddle_val > 511) inp->paddle_val = 511;
if (is_menu()) printf("vcs: diff = %d, paddle=%d, ev.value = %d\n", diff, inp->paddle_val, ev->value);
input_absinfo absinfo;
absinfo.minimum = 0;
absinfo.maximum = 511;
ev->type = EV_ABS;
ev->code = 8;
ev->value = inp->paddle_val;
input_cb(ev, &absinfo, dev);
inp->spinner_acc += diff;
ev->type = EV_REL;
ev->code = 7;
ev->value = inp->spinner_acc / 2;
inp->spinner_acc -= ev->value * 2;
input_cb(ev, 0, dev);
}
}
return 0;
}
return 1;
}
void openfire_signal()
{
for (int i = 0; i < NUMDEV; i++)
{
if (input[i].vid == 0xf143 && strstr(input[i].name, "OpenFIRE ") &&
strstr(input[i].devname, "mouse") == NULL)
{
// OF generates 3 devices, so just focus on the one actual gamepad slot.
char *nameInit = input[i].name;
if (memcmp(nameInit+strlen(input[i].name)-5, "Mouse", 5) != 0 && memcmp(nameInit+strlen(input[i].name)-8, "Keyboard", 8) != 0)
{
char mname[strlen(input[i].name)];
strcpy(mname, input[i].name);
// Cleanup mname to replace offending characters not used in device filepath.
char *p;
while ((p = strchr(mname, '/'))) *p = '_';
while ((p = strchr(mname, ' '))) *p = '_';
while ((p = strchr(mname, '*'))) *p = '_';
while ((p = strchr(mname, ':'))) *p = '_';
char devicePath[29+strlen(mname)+strlen(strrchr(input[i].id, '/')+1)];
sprintf(devicePath, "/dev/serial/by-id/usb-%s_%s-if00", mname, strrchr(input[i].id, '/')+1);
FILE *deviceFile = fopen(devicePath, "r+");
if(deviceFile == NULL) {
printf("Failed to send command to %s: device path doesn't exist?\n", input[i].name);
} else {
fprintf(deviceFile, "M0x9");
printf("%s (device no. %i) set to MiSTer-compatible mode.\n", input[i].name, i);
fclose(deviceFile);
}
}
}
}
}
void check_joycon()
{
while (1)
{
int l = -1, r = -1;
int id_combo = 0;
for (int i = 0; i < NUMDEV; i++)
{
if (input[i].quirk == QUIRK_JOYCON && !JOYCON_COMBO(i))
{
if (JOYCON_LEFT(i))
{
int id = 0;
char *led_path = get_led_path(i);
if (led_path) id = get_led(led_path, ":combo");
if (id && (!id_combo || id_combo == id))
{
id_combo = id;
l = i;
}
}
else if (JOYCON_RIGHT(i))
{
int id = 0;
char *led_path = get_led_path(i);
if (led_path) id = get_led(led_path, ":combo");
if (id && (!id_combo || id_combo == id))
{
id_combo = id;
r = i;
}
}
}
}
if (l >= 0 && r >= 0)
{
printf("** joycon_l = %d, joycon_r = %d, id = %d\n", l, r, id_combo);
input[l].bind = r;
input[r].bind = l;
input[l].misc_flags |= 1 << 31;
input[r].misc_flags |= 1 << 31;
strcpy(input[l].idstr, "057e_2009");
strcpy(input[r].idstr, "057e_2009");
}
else break;
}
}
int process_joycon(int dev, input_event *ev, input_absinfo *absinfo)
{
if (ev->type == EV_ABS)
{
if (JOYCON_COMBO(dev)) return 0;
if (ev->code == 4 && JOYCON_RIGHT(dev)) ev->value = -ev->value;
if (ev->code == 0 && JOYCON_LEFT(dev)) ev->value = -ev->value;
return 0;
}
int mask = 0;
// simulate DPAD on left joycon
if (JOYCON_COMBO(dev) && (ev->code & ~3) == 0x220)
{
mask = 0x100 << (ev->code & 3);
input[dev].misc_flags = ev->value ? (input[dev].misc_flags | mask) : (input[dev].misc_flags & ~mask);
if (ev->value)
{
ev->value = (ev->code & 1) ? 1 : -1;
}
else
{
mask = (ev->code & 2) ? 0x400 : 0x100;
ev->value = (input[dev].misc_flags & mask) ? -1 : (input[dev].misc_flags & (mask << 1)) ? 1 : 0;
}
ev->code = (ev->code & 2) ? 16 : 17;
ev->type = EV_ABS;
absinfo->minimum = -1;
absinfo->maximum = 1;
return 0;
}
//check for request to combine/split joycons
switch (ev->code)
{
case 0x136: case 0x137: mask = 1; break;
case 0x138: case 0x139: mask = 2; break;
case 0x13D: case 0x13E: mask = 4; break;
default: return 0;
}
input[dev].misc_flags = ev->value ? (input[dev].misc_flags | mask) : (input[dev].misc_flags & ~mask);
if (JOYCON_REQ(dev))
{
int uncombo = 0;
int l = -1, r = -1;
for (int n = 0; n < NUMDEV; n++)
{
if (input[n].quirk == QUIRK_JOYCON)
{
if (JOYCON_COMBO(n))
{
if (JOYCON_REQ(n) && JOYCON_REQ(input[n].bind))
{
r = n;
l = input[n].bind;
uncombo = 1;
break;
}
}
else if (JOYCON_RIGHT(n) && JOYCON_REQ(n)) r = n;
else if (JOYCON_LEFT(n) && JOYCON_REQ(n)) l = n;
}
}
if (l >= 0 && r >= 0)
{
uint8_t id = 0;
char *led_path;
printf(uncombo ? "Joycons request split\n" : "Joycons request combo\n");
if (!uncombo)
{
FileLoad("/tmp/combo_id", &id, sizeof(id));
if (!(++id)) ++id;
FileSave("/tmp/combo_id", &id, sizeof(id));
}
led_path = get_led_path(l); if (led_path) set_led(led_path, ":combo", id);
led_path = get_led_path(r); if (led_path) set_led(led_path, ":combo", id);
printf("Close all devices.\n");
for (int i = 0; i < NUMDEV; i++) if (pool[i].fd >= 0)
{
ioctl(pool[i].fd, EVIOCGRAB, 0);
close(pool[i].fd);
}
update_num_hw(l, 7);
update_num_hw(r, 7);
usleep(500000);
update_num_hw(l, 0);
update_num_hw(r, 0);
usleep(500000);
return 1;
}
}
return 0;
}
static int get_rumble_device(int player)
{
for (int i = 0; i < NUMDEV; i++)
{
int dev = i;
if (input[i].bind >= 0) dev = input[i].bind;
if (input[dev].num == player && input[i].has_rumble)
{
return i;
}
}
return -1;
}
static int rumble_input_device(int devnum, uint16_t strong_mag, uint16_t weak_mag, uint16_t duration = 500, uint16_t delay = 0)
{
int ioret = 0;
if (!input[devnum].has_rumble) return 0;
int fd = pool[devnum].fd;
if (!(fd >= 0)) return 0;
if (!strong_mag && !weak_mag) //Stop rumble
{
if (input[devnum].rumble_effect.id == -1) return 1; //No uploaded effect
ioret = ioctl(fd, EVIOCRMFF, input[devnum].rumble_effect.id);
input[devnum].rumble_effect.id = -1; //always set to -1 even if we fail to remove it?
return ioret != -1;
}
else {
//Upload effect and then immediately play it
//If the effect id in the input struct is -1, it will be filled with the newly uploaded effect
//If it is filled with an already uploaded effect, the effect is modified in place
struct ff_effect *fef;
fef = &input[devnum].rumble_effect;
fef->type = FF_RUMBLE;
fef->direction = input[devnum].quirk == QUIRK_WHEEL ? 0x4000 : 0x0000;
fef->u.rumble.strong_magnitude = strong_mag;
fef->u.rumble.weak_magnitude = weak_mag;
fef->replay.length = duration;
fef->replay.delay = delay;
ioret = ioctl(fd, EVIOCSFF, fef);
if (ioret == -1)
{
printf("RUMBLE UPLOAD FAILED %s\n", strerror(errno));
return 0;
}
//Play effect
struct input_event play_ev;
play_ev.type = EV_FF;
play_ev.code = input[devnum].rumble_effect.id;
play_ev.value = 1;
ioret = write(fd, (const void *)&play_ev, sizeof(play_ev));
return ioret != -1;
}
return 0;
}
static void set_rumble(int dev, uint16_t rumble_val)
{
if (input[dev].last_rumble != rumble_val)
{
uint16_t strong_m, weak_m;
strong_m = (rumble_val & 0xFF00) + (rumble_val >> 8);
weak_m = (rumble_val << 8) + (rumble_val & 0x00FF);
rumble_input_device(dev, strong_m, weak_m, 0x7FFF);
input[dev].last_rumble = rumble_val;
}
}
static void set_wheel_range(int dev, int range)
{
static char path[1024];
if (range && input[dev].sysfs[0])
{
sprintf(path, "/sys%s/device/range", input[dev].sysfs);
FILE* f = fopen(path, "w");
if (f)
{
fprintf(f, "%d", range);
fclose(f);
}
}
}
static void setup_wheels()
{
if (cfg.wheel_force > 100) cfg.wheel_force = 100;
for (int i = 0; i < NUMDEV; i++)
{
if (pool[i].fd != -1)
{
// steering wheel axis
input[i].wh_steer = 0;
// accelerator pedal axis
input[i].wh_accel = -1;
// brake pedal axis
input[i].wh_brake = -1;
// clutch pedal axis
input[i].wh_clutch = -1;
// shared accel and brake pedal axis
input[i].wh_combo = -1;
// invert pedal values range (if >0)
input[i].wh_pedal_invert = -1;
// Logitech Wheels
if (input[i].vid == 0x046d)
{
switch (input[i].pid)
{
case 0xc299: // LOGITECH_G25_WHEEL
case 0xc29b: // LOGITECH_G27_WHEEL
case 0xc24f: // LOGITECH_G29_WHEEL
input[i].wh_accel = 2;
input[i].wh_brake = 5;
input[i].wh_clutch = 1;
input[i].quirk = QUIRK_WHEEL;
break;
case 0xc294: // LOGITECH_WHEEL
input[i].wh_combo = 1;
input[i].quirk = QUIRK_WHEEL;
break;
case 0xc298: // LOGITECH_DFP_WHEEL
input[i].wh_accel = 1;
input[i].wh_brake = 5;
input[i].quirk = QUIRK_WHEEL;
break;
case 0xc29a: // LOGITECH_DFGT_WHEEL
input[i].wh_accel = 1;
input[i].wh_brake = 2;
input[i].quirk = QUIRK_WHEEL;
break;
//case 0xc262: // LOGITECH_G920_WHEEL
//case 0xc295: // LOGITECH_MOMO_WHEEL
}
if (input[i].quirk == QUIRK_WHEEL)
{
struct input_event ie = {};
ie.type = EV_FF;
ie.code = FF_AUTOCENTER;
ie.value = 0xFFFFUL * cfg.wheel_force / 100;
write(pool[i].fd, &ie, sizeof(ie));
set_wheel_range(i, cfg.wheel_range);
}
}
// Fanatec Wheels
else if (input[i].vid == 0x0eb7)
{
switch (input[i].pid)
{
case 0x0004: // CLUBSPORT_V25_WHEELBASE_DEVICE_ID
case 0x0006: // PODIUM_WHEELBASE_DD1_DEVICE_ID
case 0x0007: // PODIUM_WHEELBASE_DD2_DEVICE_ID
input[i].wh_accel = 2;
input[i].wh_brake = 5;
input[i].wh_clutch = 1;
input[i].quirk = QUIRK_WHEEL;
break;
//case 0x0001: // CLUBSPORT_V2_WHEELBASE_DEVICE_ID
//case 0x0005: // CSL_ELITE_PS4_WHEELBASE_DEVICE_ID
//case 0x0011: // CSR_ELITE_WHEELBASE_DEVICE_ID
//case 0x0020: // CSL_DD_WHEELBASE_DEVICE_ID
//case 0x0E03: // CSL_ELITE_WHEELBASE_DEVICE_ID
}
if (input[i].quirk == QUIRK_WHEEL)
{
struct ff_effect fef;
fef.type = FF_SPRING;
fef.id = -1;
fef.u.condition[0].right_saturation = 0xFFFFUL * cfg.wheel_force / 100;
fef.u.condition[0].left_saturation = 0xFFFFUL * cfg.wheel_force / 100;
fef.u.condition[0].right_coeff = 0x7FFF;
fef.u.condition[0].left_coeff = 0x7FFF;
fef.u.condition[0].deadband = 0x0;
fef.u.condition[0].center = 0x0;
fef.u.condition[1] = fef.u.condition[0];
fef.replay.delay = 0;
if (ioctl(pool[i].fd, EVIOCSFF, &fef) >= 0)
{
struct input_event play_ev;
play_ev.type = EV_FF;
play_ev.code = fef.id;
play_ev.value = 1;
write(pool[i].fd, (const void *)&play_ev, sizeof(play_ev));
}
set_wheel_range(i, cfg.wheel_range);
}
}
// Thrustmaster Guillemot Wheels
else if (input[i].vid == 0x06f8)
{
switch (input[i].pid)
{
case 0x0004: // Force Feedback Racing Wheel
input[i].wh_steer = 8;
input[i].wh_accel = 9;
input[i].wh_brake = 10;
input[i].wh_pedal_invert = 1;
input[i].quirk = QUIRK_WHEEL;
break;
}
if (input[i].quirk == QUIRK_WHEEL)
{
struct input_event ie = {};
ie.type = EV_FF;
ie.code = FF_AUTOCENTER;
ie.value = 0xFFFFUL * cfg.wheel_force / 100;
write(pool[i].fd, &ie, sizeof(ie));
set_wheel_range(i, cfg.wheel_range);
}
}
// Thrustmaster Wheels
else if (input[i].vid == 0x044f)
{
switch (input[i].pid)
{
case 0xb655: // FGT Rumble 3-in-1 (PC)
case 0xb65b: // F430 Cockpit Wireless (PC)
input[i].wh_steer = 0;
input[i].wh_accel = 5;
input[i].wh_brake = 1;
input[i].quirk = QUIRK_WHEEL;
break;
case 0xb66e: // T300RS Racing Wheel (PC/PS3)
input[i].wh_steer = 0;
input[i].wh_accel = 5;
input[i].wh_brake = 1;
input[i].wh_clutch = 6;
input[i].quirk = QUIRK_WHEEL;
break;
}
}
//Namco NeGcon via Arduino, RetroZord or Reflex Adapt
else if (((input[i].vid == 0x2341 || (input[i].vid == 0x1209 && input[i].pid == 0x595A)) && strstr(input[i].name, "RZordPsWheel")) ||
(input[i].vid == 0x16D0 && input[i].pid == 0x127E && strstr(input[i].name, "ReflexPSWheel")))
{
input[i].wh_accel = 6;
input[i].wh_brake = 10;
input[i].wh_clutch = 2;
input[i].quirk = QUIRK_WHEEL;
}
}
}
}
int input_test(int getchar)
{
static char cur_leds = 0;
static int state = 0;
struct input_absinfo absinfo;
struct input_event ev;
static uint32_t timeout = 0;
if (touch_rel && CheckTimer(touch_rel))
{
touch_rel = 0;
mice_btn = 0;
mouse_btn_req();
}
if (state == 0)
{
input_uinp_setup();
memset(pool, -1, sizeof(pool));
signal(SIGINT, INThandler);
pool[NUMDEV].fd = set_watch();
pool[NUMDEV].events = POLLIN;
unlink(CMD_FIFO);
mkfifo(CMD_FIFO, 0666);
pool[NUMDEV+1].fd = open(CMD_FIFO, O_RDWR | O_NONBLOCK | O_CLOEXEC);
pool[NUMDEV+1].events = POLLIN;
pool[NUMDEV + 2].fd = open(LED_MONITOR, O_RDONLY | O_CLOEXEC);
pool[NUMDEV + 2].events = POLLPRI;
state++;
}
if (state == 1)
{
timeout = 0;
printf("Open up to %d input devices.\n", NUMDEV);
for (int i = 0; i < NUMDEV; i++)
{
pool[i].fd = -1;
pool[i].events = 0;
}
memset(input, 0, sizeof(input));
int n = 0;
DIR *d = opendir("/dev/input");
if (d)
{
struct dirent *de;
while ((de = readdir(d)))
{
if (!strncmp(de->d_name, "event", 5) || !strncmp(de->d_name, "mouse", 5))
{
memset(&input[n], 0, sizeof(input[n]));
sprintf(input[n].devname, "/dev/input/%s", de->d_name);
int fd = open(input[n].devname, O_RDWR | O_CLOEXEC);
//printf("open(%s): %d\n", input[n].devname, fd);
if (fd > 0)
{
pool[n].fd = fd;
pool[n].events = POLLIN;
input[n].mouse = !strncmp(de->d_name, "mouse", 5);
char uniq[32] = {};
if (!input[n].mouse)
{
struct input_id id;
memset(&id, 0, sizeof(id));
ioctl(pool[n].fd, EVIOCGID, &id);
input[n].vid = id.vendor;
input[n].pid = id.product;
input[n].version = id.version;
input[n].bustype = id.bustype;
ioctl(pool[n].fd, EVIOCGUNIQ(sizeof(uniq)), uniq);
ioctl(pool[n].fd, EVIOCGNAME(sizeof(input[n].name)), input[n].name);
input[n].led = has_led(pool[n].fd);
}
//skip our virtual device
if (!strcmp(input[n].name, UINPUT_NAME))
{
close(pool[n].fd);
pool[n].fd = -1;
continue;
}
input[n].bind = -1;
int effects;
input[n].has_rumble = false;
if (cfg.rumble)
{
if (ioctl(fd, EVIOCGEFFECTS, &effects) >= 0)
{
unsigned char ff_features[(FF_MAX + 7) / 8] = {};
if (ioctl(fd, EVIOCGBIT(EV_FF, sizeof(ff_features)), ff_features) != -1)
{
if (test_bit(FF_RUMBLE, ff_features)) {
input[n].rumble_effect.id = -1;
input[n].has_rumble = true;
}
}
}
}
// enable scroll wheel reading
if (input[n].mouse)
{
unsigned char buffer[4];
static const unsigned char mousedev_imps_seq[] = { 0xf3, 200, 0xf3, 100, 0xf3, 80 };
if (write(pool[n].fd, mousedev_imps_seq, sizeof(mousedev_imps_seq)) != sizeof(mousedev_imps_seq))
{
printf("Cannot switch %s to ImPS/2 protocol(1)\n", input[n].devname);
}
else if (read(pool[n].fd, buffer, sizeof buffer) != 1 || buffer[0] != 0xFA)
{
printf("Failed to switch %s to ImPS/2 protocol(2)\n", input[n].devname);
}
}
// RasPad3 touchscreen
if (input[n].vid == 0x222a && input[n].pid == 1)
{
input[n].quirk = QUIRK_TOUCHGUN;
input[n].num = 1;
input[n].map_shown = 1;
input[n].lightgun = 0;
input[n].guncal[0] = 0;
input[n].guncal[1] = 16383;
input[n].guncal[2] = 2047;
input[n].guncal[3] = 14337;
input_lightgun_load(n);
}
if (input[n].vid == 0x054c)
{
if (strcasestr(input[n].name, "Motion"))
{
// don't use Accelerometer
close(pool[n].fd);
pool[n].fd = -1;
continue;
}
if (input[n].pid == 0x0268) input[n].quirk = QUIRK_DS3;
else if (input[n].pid == 0x05c4 || input[n].pid == 0x09cc || input[n].pid == 0x0ba0 || input[n].pid == 0x0ce6)
{
input[n].quirk = QUIRK_DS4;
if (strcasestr(input[n].name, "Touchpad"))
{
input[n].quirk = QUIRK_DS4TOUCH;
}
}
}
if (input[n].vid == 0x0079 && input[n].pid == 0x1802)
{
input[n].lightgun = 1;
input[n].num = 2; // force mayflash mode 1/2 as second joystick.
}
if (input[n].vid == 0x057e && (input[n].pid == 0x0306 || input[n].pid == 0x0330))
{
if (strcasestr(input[n].name, "Accelerometer"))
{
// don't use Accelerometer
close(pool[n].fd);
pool[n].fd = -1;
continue;
}
else if (strcasestr(input[n].name, "Motion Plus"))
{
// don't use Accelerometer
close(pool[n].fd);
pool[n].fd = -1;
continue;
}
else
{
input[n].quirk = QUIRK_WIIMOTE;
input[n].guncal[0] = 0;
input[n].guncal[1] = 767;
input[n].guncal[2] = 1;
input[n].guncal[3] = 1023;
input_lightgun_load(n);
}
}
if (input[n].vid == 0x057e)
{
if (strstr(input[n].name, " IMU"))
{
// don't use Accelerometer
close(pool[n].fd);
pool[n].fd = -1;
continue;
}
}
if (input[n].vid == 0x057e && input[n].pid == 0x2006)
{
input[n].misc_flags = 1 << 30;
input[n].quirk = QUIRK_JOYCON;
}
if (input[n].vid == 0x057e && input[n].pid == 0x2007)
{
input[n].misc_flags = 1 << 29;
input[n].quirk = QUIRK_JOYCON;
}
//Ultimarc lightgun
if (input[n].vid == 0xd209 && input[n].pid == 0x1601)
{
input[n].lightgun = 1;
}
//Namco Guncon via Arduino, RetroZord or Reflex Adapt
if (((input[n].vid == 0x2341 || (input[n].vid == 0x1209 && input[n].pid == 0x595A)) && (strstr(uniq, "RZordPsGun") || strstr(input[n].name, "RZordPsGun"))) ||
(input[n].vid == 0x16D0 && input[n].pid == 0x127E && (strstr(uniq, "ReflexPSGun") || strstr(input[n].name, "ReflexPSGun"))))
{
input[n].quirk = QUIRK_LIGHTGUN;
input[n].lightgun = 1;
input[n].guncal[0] = 0;
input[n].guncal[1] = 32767;
input[n].guncal[2] = 0;
input[n].guncal[3] = 32767;
input_lightgun_load(n);
}
//Namco GunCon 2
if (input[n].vid == 0x0b9a && input[n].pid == 0x016a)
{
input[n].quirk = QUIRK_LIGHTGUN_CRT;
input[n].lightgun = 1;
input[n].guncal[0] = 25;
input[n].guncal[1] = 245;
input[n].guncal[2] = 145;
input[n].guncal[3] = 700;
input_lightgun_load(n);
}
//Namco GunCon 3
if (input[n].vid == 0x0b9a && input[n].pid == 0x0800)
{
input[n].quirk = QUIRK_LIGHTGUN;
input[n].lightgun = 1;
input[n].guncal[0] = -32768;
input[n].guncal[1] = 32767;
input[n].guncal[2] = -32768;
input[n].guncal[3] = 32767;
input_lightgun_load(n);
}
//GUN4IR Lightgun
if (input[n].vid == 0x2341 && input[n].pid >= 0x8042 && input[n].pid <= 0x8049)
{
input[n].quirk = QUIRK_LIGHTGUN;
input[n].lightgun = 1;
input[n].guncal[0] = 0;
input[n].guncal[1] = 32767;
input[n].guncal[2] = 0;
input[n].guncal[3] = 32767;
input_lightgun_load(n);
}
//OpenFIRE Lightgun
//!Note that OF has a user-configurable PID, but the VID is reserved and every device name has the prefix "OpenFIRE"
if (input[n].vid == 0xf143 && strstr(input[n].name, "OpenFIRE "))
{
// OF generates 3 devices, so just focus on the one actual gamepad slot.
char *nameInit = input[n].name;
if(memcmp(nameInit+strlen(input[n].name)-5, "Mouse", 5) != 0 && memcmp(nameInit+strlen(input[n].name)-8, "Keyboard", 8) != 0)
{
input[n].quirk = QUIRK_LIGHTGUN;
input[n].lightgun = 1;
input[n].guncal[0] = -32767;
input[n].guncal[1] = 32767;
input[n].guncal[2] = -32767;
input[n].guncal[3] = 32767;
input_lightgun_load(n);
}
}
//Blamcon Lightgun
if (input[n].vid == 0x3673 && ((input[n].pid >= 0x0100 && input[n].pid <= 0x0103) || (input[n].pid >= 0x0200 && input[n].pid <= 0x0203)))
{
input[n].quirk = QUIRK_LIGHTGUN;
input[n].lightgun = 1;
input[n].guncal[0] = 0;
input[n].guncal[1] = 32767;
input[n].guncal[2] = 0;
input[n].guncal[3] = 32767;
input_lightgun_load(n);
}
//Retroshooter
if (input[n].vid == 0x0483 && input[n].pid >= 0x5750 && input[n].pid <= 0x5753)
{
input[n].quirk = QUIRK_LIGHTGUN_MOUSE;
input[n].lightgun = 1;
input[n].guncal[0] = 0;
input[n].guncal[1] = 767;
input[n].guncal[2] = 0;
input[n].guncal[3] = 1023;
input_lightgun_load(n);
}
//Madcatz Arcade Stick 360
if (input[n].vid == 0x0738 && input[n].pid == 0x4758) input[n].quirk = QUIRK_MADCATZ360;
// mr.Spinner
// 0x120 - Button
// Axis 7 - EV_REL is spinner
// Axis 8 - EV_ABS is Paddle
// Overlays on other existing gamepads
if (strstr(uniq, "MiSTer-S1")) input[n].quirk = QUIRK_PDSP;
if (strstr(input[n].name, "MiSTer-S1")) input[n].quirk = QUIRK_PDSP;
// Arcade with spinner and/or paddle:
// Axis 7 - EV_REL is spinner
// Axis 8 - EV_ABS is Paddle
// Includes other buttons and axes, works as a full featured gamepad.
if (strstr(uniq, "MiSTer-A1")) input[n].quirk = QUIRK_PDSP_ARCADE;
if (strstr(input[n].name, "MiSTer-A1")) input[n].quirk = QUIRK_PDSP_ARCADE;
//Jamma
if (cfg.jamma_vid && cfg.jamma_pid && input[n].vid == cfg.jamma_vid && input[n].pid == cfg.jamma_pid)
{
input[n].quirk = QUIRK_JAMMA;
}
//Jamma2
if (cfg.jamma2_vid && cfg.jamma2_pid && input[n].vid == cfg.jamma2_vid && input[n].pid == cfg.jamma2_pid)
{
input[n].quirk = QUIRK_JAMMA2;
}
//Atari VCS wireless joystick with spinner
if (input[n].vid == 0x3250 && input[n].pid == 0x1001)
{
input[n].quirk = QUIRK_VCS;
input[n].spinner_acc = -1;
input[n].misc_flags = 0;
}
//Arduino and Teensy devices may share the same VID:PID, so additional field UNIQ is used to differentiate them
//Reflex Adapt also uses the UNIQ field to differentiate between device modes
//RetroZord Adapter also uses the UNIQ field to differentiate between device modes
if ((input[n].vid == 0x2341 || (input[n].vid == 0x16C0 && (input[n].pid>>8) == 0x4) || (input[n].vid == 0x16D0 && input[n].pid == 0x127E) || (input[n].vid == 0x1209 && input[n].pid == 0x595A)) && strlen(uniq))
{
snprintf(input[n].idstr, sizeof(input[n].idstr), "%04x_%04x_%s", input[n].vid, input[n].pid, uniq);
char *p;
while ((p = strchr(input[n].idstr, '/'))) *p = '_';
while ((p = strchr(input[n].idstr, ' '))) *p = '_';
while ((p = strchr(input[n].idstr, '*'))) *p = '_';
while ((p = strchr(input[n].idstr, ':'))) *p = '_';
strcpy(input[n].name, uniq);
}
else if (input[n].vid == 0x1209 && (input[n].pid == 0xFACE || input[n].pid == 0xFACA))
{
int sum = 0;
for (uint32_t i = 0; i < sizeof(input[n].name); i++)
{
if (!input[n].name[i]) break;
sum += (uint8_t)input[n].name[i];
}
snprintf(input[n].idstr, sizeof(input[n].idstr), "%04x_%04x_%d", input[n].vid, input[n].pid, sum);
}
else
{
snprintf(input[n].idstr, sizeof(input[n].idstr), "%04x_%04x", input[n].vid, input[n].pid);
}
ioctl(pool[n].fd, EVIOCGRAB, (grabbed | user_io_osd_is_visible()) ? 1 : 0);
n++;
if (n >= NUMDEV) break;
}
}
}
closedir(d);
mergedevs();
check_joycon();
openfire_signal();
setup_wheels();
for (int i = 0; i < n; i++)
{
printf("opened %d(%2d): %s (%04x:%04x:%08x) %d \"%s\" \"%s\"\n", i, input[i].bind, input[i].devname, input[i].vid, input[i].pid, input[i].unique_hash, input[i].quirk, input[i].id, input[i].name);
restore_player(i);
setup_deadzone(&ev, i);
}
unflag_players();
}
cur_leds |= 0x80;
state++;
}
if (cfg.bt_auto_disconnect)
{
if (!timeout) timeout = GetTimer(6000);
else if (CheckTimer(timeout))
{
timeout = GetTimer(6000);
for (int i = 0; i < NUMDEV; i++)
{
if (pool[i].fd >= 0 && input[i].timeout > 0)
{
if (!(JOYCON_COMBINED(i) && JOYCON_LEFT(i)) && input[i].bind != i) continue;
input[i].timeout--;
if (!input[i].timeout)
{
static char cmd[128];
sprintf(cmd, "btctl disconnect %s", input[i].mac);
system(cmd);
if (JOYCON_COMBINED(i))
{
sprintf(cmd, "btctl disconnect %s", input[input[i].bind].mac);
system(cmd);
}
}
}
}
}
}
if (state == 2)
{
int timeout = 0;
if (is_menu() && video_fb_state()) timeout = 25;
while (1)
{
if (cfg.rumble && !is_menu())
{
for (int i = 0; i < NUMDEV; i++)
{
if (!input[i].has_rumble) continue;
int dev = i;
if (input[i].bind >= 0) dev = input[i].bind;
if (!input[dev].num) continue;
set_rumble(i, spi_uio_cmd(UIO_GET_RUMBLE | ((input[dev].num - 1) << 8)));
}
}
int return_value = poll(pool, NUMDEV + 3, timeout);
if (!return_value) break;
if (return_value < 0)
{
printf("ERR: poll\n");
break;
}
if ((pool[NUMDEV].revents & POLLIN) && check_devs())
{
printf("Close all devices.\n");
for (int i = 0; i < NUMDEV; i++) if (pool[i].fd >= 0)
{
ioctl(pool[i].fd, EVIOCGRAB, 0);
close(pool[i].fd);
}
state = 1;
return 0;
}
for (int pos = 0; pos < NUMDEV; pos++)
{
int i = pos;
if ((pool[i].fd >= 0) && (pool[i].revents & POLLIN))
{
if (!input[i].mouse)
{
memset(&ev, 0, sizeof(ev));
if (read(pool[i].fd, &ev, sizeof(ev)) == sizeof(ev))
{
if (getchar)
{
if (ev.type == EV_KEY && ev.value >= 1)
{
return ev.code;
}
}
else if (ev.type)
{
int dev = i;
if (!JOYCON_COMBINED(i) && input[dev].bind >= 0) dev = input[dev].bind;
int noabs = 0;
if (input[i].quirk == QUIRK_DS4TOUCH && ev.type == EV_KEY)
{
if (ev.code == BTN_TOOL_FINGER || ev.code == BTN_TOUCH || ev.code == BTN_TOOL_DOUBLETAP) continue;
}
if (input[i].quirk == QUIRK_MADCATZ360 && ev.type == EV_KEY)
{
if (ev.code == BTN_THUMBR) input[i].misc_flags = ev.value ? (input[i].misc_flags | 1) : (input[i].misc_flags & ~1);
else if (ev.code == BTN_MODE && !user_io_osd_is_visible())
{
if (input[i].misc_flags & 1)
{
if (ev.value)
{
if ((input[i].misc_flags & 0x6) == 0) input[i].misc_flags = 0x3; // X
else if ((input[i].misc_flags & 0x6) == 2) input[i].misc_flags = 0x5; // Y
else input[i].misc_flags = 0x1; // None
Info(((input[i].misc_flags & 0x6) == 2) ? "Paddle mode" :
((input[i].misc_flags & 0x6) == 4) ? "Spinner mode" :
"Normal mode");
}
continue;
}
}
}
if (input[i].quirk == QUIRK_TOUCHGUN)
{
touchscreen_proc(i, &ev);
continue;
}
if (ev.type == EV_ABS)
{
if (input[i].quirk == QUIRK_WIIMOTE)
{
//nunchuck accel events
if (ev.code >= 3 && ev.code <= 5) continue;
}
//Dualshock: drop accelerator and raw touchpad events
if (input[i].quirk == QUIRK_DS4TOUCH && ev.code == 57)
{
input[dev].lightgun_req = (ev.value >= 0);
}
if ((input[i].quirk == QUIRK_DS4TOUCH || input[i].quirk == QUIRK_DS4 || input[i].quirk == QUIRK_DS3) && ev.code > 40)
{
continue;
}
if (ioctl(pool[i].fd, EVIOCGABS(ev.code), &absinfo) < 0) memset(&absinfo, 0, sizeof(absinfo));
else
{
//DS4 specific: touchpad as lightgun
if (input[i].quirk == QUIRK_DS4TOUCH && ev.code <= 1)
{
if (!input[dev].lightgun || user_io_osd_is_visible()) continue;
if (ev.code == 1)
{
absinfo.minimum = 300;
absinfo.maximum = 850;
}
else if (ev.code == 0)
{
absinfo.minimum = 200;
absinfo.maximum = 1720;
}
else continue;
}
if (input[i].quirk == QUIRK_DS4 && ev.code <= 1)
{
if (input[dev].lightgun) noabs = 1;
}
if (input[i].quirk == QUIRK_WIIMOTE)
{
input[dev].lightgun = 0;
if (absinfo.maximum == 1023 || absinfo.maximum == 767)
{
if (ev.code == 16)
{
ev.value = absinfo.maximum - ev.value;
ev.code = 0;
input[dev].lightgun = 1;
}
else if (ev.code == 17)
{
ev.code = 1;
input[dev].lightgun = 1;
}
// other 3 IR tracking aren't used
else continue;
}
else if (absinfo.maximum == 62)
{
//LT/RT analog
continue;
}
else if (ev.code & 1)
{
//Y axes on wiimote and accessories are inverted
ev.value = -ev.value;
}
}
}
if (input[i].quirk == QUIRK_MADCATZ360 && (input[i].misc_flags & 0x6) && (ev.code == 16) && !user_io_osd_is_visible())
{
if (ev.value)
{
if ((input[i].misc_flags & 0x6) == 2)
{
if (ev.value > 0) input[i].paddle_val += 4;
if (ev.value < 0) input[i].paddle_val -= 4;
if (input[i].paddle_val > 256) input[i].paddle_val = 256;
if (input[i].paddle_val < 0) input[i].paddle_val = 0;
absinfo.maximum = 255;
absinfo.minimum = 0;
ev.code = 8;
ev.value = input[i].paddle_val;
}
else
{
ev.type = EV_REL;
ev.code = 7;
}
}
else continue;
}
}
if (input[dev].quirk == QUIRK_VCS && !vcs_proc(i, &ev)) continue;
if (input[dev].quirk == QUIRK_JAMMA && ev.type == EV_KEY)
{
input[dev].num = 0;
for (uint32_t i = 0; i < sizeof(jamma2joy) / sizeof(jamma2joy[0]); i++)
{
if (jamma2joy[i].key == ev.code)
{
ev.code = jamma2joy[i].btn;
input[dev].num = jamma2joy[i].player;
break;
}
}
}
if (input[dev].quirk == QUIRK_JAMMA2 && ev.type == EV_KEY)
{
input[dev].num = 0;
for (uint32_t i = 0; i < sizeof(jamma22joy) / sizeof(jamma22joy[0]); i++)
{
if (jamma22joy[i].key == ev.code)
{
ev.code = jamma22joy[i].btn;
input[dev].num = jamma22joy[i].player;
break;
}
}
}
if (input[i].quirk == QUIRK_JOYCON)
{
if (process_joycon(i, &ev, &absinfo))
{
state = 1;
return 0;
}
}
//Menu combo on 8BitDo receiver in PSC mode
if (input[dev].vid == 0x054c && input[dev].pid == 0x0cda && ev.type == EV_KEY)
{
//in PSC mode these keys coming from separate virtual keyboard device
//so it's impossible to use joystick codes as keyboards aren't personalized
if (ev.code == 164 || ev.code == 1) ev.code = KEY_MENU;
}
// various controllers in X-Input mode generate keyboard key codes, remap them.
if (input[dev].vid == 0x45E && ev.type == EV_KEY)
{
switch (ev.code)
{
case KEY_BACK: ev.code = BTN_SELECT; break;
case KEY_MENU: ev.code = BTN_MODE; break;
case KEY_RECORD: ev.code = BTN_Z; break;
}
}
if (is_menu() && !video_fb_state())
{
/*
if (mapping && mapping_type <= 1 && !(ev.type==EV_KEY && ev.value>1))
{
static char str[64], str2[64];
OsdWrite(12, "\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81");
sprintf(str, " VID=%04X PID=%04X", input[i].vid, input[i].pid);
OsdWrite(13, str);
sprintf(str, "Type=%d Code=%d Value=%d", ev.type, ev.code, ev.value);
str2[0] = 0;
int len = (29 - (strlen(str))) / 2;
while (len-- > 0) strcat(str2, " ");
strcat(str2, str);
OsdWrite(14, str2);
str2[0] = 0;
if (ev.type == EV_ABS)
{
sprintf(str, "Min=%d Max=%d", absinfo.minimum, absinfo.maximum);
int len = (29 - (strlen(str))) / 2;
while (len-- > 0) strcat(str2, " ");
strcat(str2, str);
}
OsdWrite(15, str2);
}
*/
switch (ev.type)
{
//keyboard, buttons
case EV_KEY:
printf("%04x:%04x:%02d P%d Input event: type=EV_KEY, code=%d(0x%x), value=%d\n", input[dev].vid, input[dev].pid, i, input[dev].num, ev.code, ev.code, ev.value);
break;
case EV_REL:
{
//limit the amount of EV_REL messages, so Menu core won't be laggy
static unsigned long timeout = 0;
if (!timeout || CheckTimer(timeout))
{
timeout = GetTimer(20);
printf("%04x:%04x:%02d P%d Input event: type=EV_REL, Axis=%d, Offset=%d\n", input[dev].vid, input[dev].pid, i, input[dev].num, ev.code, ev.value);
}
}
break;
case EV_SYN:
case EV_MSC:
break;
//analog joystick
case EV_ABS:
{
//limit the amount of EV_ABS messages, so Menu core won't be laggy
static unsigned long timeout = 0;
if (!timeout || CheckTimer(timeout))
{
timeout = GetTimer(20);
//reduce flood from DUALSHOCK 3/4
if ((input[i].quirk == QUIRK_DS4 || input[i].quirk == QUIRK_DS3) && ev.code <= 5 && ev.value > 118 && ev.value < 138)
{
break;
}
//aliexpress USB encoder floods messages
if (input[dev].vid == 0x0079 && input[dev].pid == 0x0006)
{
if (ev.code == 2) break;
}
printf("%04x:%04x:%02d P%d Input event: type=EV_ABS, Axis=%d [%d...%d], Offset=%d", input[dev].vid, input[dev].pid, i, input[dev].num, ev.code, absinfo.minimum, absinfo.maximum, ev.value);
//if (absinfo.fuzz) printf(", fuzz = %d", absinfo.fuzz);
if (absinfo.resolution) printf(", res = %d", absinfo.resolution);
printf("\n");
}
}
break;
default:
printf("%04x:%04x:%02d P%d Input event: type=%d, code=%d(0x%x), value=%d(0x%x)\n", input[dev].vid, input[dev].pid, i, input[dev].num, ev.type, ev.code, ev.code, ev.value, ev.value);
}
if (ev.type == EV_KEY && input[dev].num)
{
if (ev.code == (input[dev].mmap[SYS_BTN_L] & 0xFFFF)) input[dev].rumble_en = ev.value;
int n = get_rumble_device(input[dev].num);
if (n >= 0 && (input[dev].rumble_en || !ev.value))
{
uint16_t rumble_val = input[n].last_rumble;
if (ev.code == (input[dev].mmap[SYS_BTN_X] & 0xFFFF)) set_rumble(n, (rumble_val & 0xFF00) | ((ev.value) ? 0xFF : 0x00));
if (ev.code == (input[dev].mmap[SYS_BTN_Y] & 0xFFFF)) set_rumble(n, (rumble_val & 0xFF) | ((ev.value) ? 0xFF00 : 0x00));
}
}
}
if (ev.type == EV_ABS && input[i].quirk == QUIRK_WIIMOTE && input[dev].lightgun)
{
menu_lightgun_cb(i, ev.type, ev.code, ev.value);
// don't pass IR tracking to OSD
if (user_io_osd_is_visible()) continue;
if (!ev.code)
{
absinfo.minimum = input[i].guncal[2];
absinfo.maximum = input[i].guncal[3];
}
else
{
absinfo.minimum = input[i].guncal[0];
absinfo.maximum = input[i].guncal[1];
}
}
if (ev.type == EV_ABS && (input[i].quirk == QUIRK_LIGHTGUN || input[i].quirk == QUIRK_LIGHTGUN_MOUSE))
{
menu_lightgun_cb(i, ev.type, ev.code, ev.value);
if (ev.code == ABS_X)
{
absinfo.minimum = input[i].guncal[2];
absinfo.maximum = input[i].guncal[3];
}
else if (ev.code == ABS_Y)
{
absinfo.minimum = input[i].guncal[0];
absinfo.maximum = input[i].guncal[1];
}
}
if (ev.type == EV_ABS && input[i].quirk == QUIRK_LIGHTGUN_CRT)
{
menu_lightgun_cb(i, ev.type, ev.code, ev.value);
if (ev.code == ABS_X)
{
absinfo.minimum = input[i].guncal[2];
absinfo.maximum = input[i].guncal[3];
// When the gun loses tracking, give it a short grace period
// before passing through the off-screen coordinates.
// The GunCon 1 and 2 both report out-of-screen x values
// more reliably than Y values, so X is used here.
if (ev.value < absinfo.minimum || ev.value > absinfo.maximum)
{
// Grace period of 50 ms. Longer times here make guns a bit
// more reliable on dark screens, but introduce lag to any mechanics
// where you want to shoot offscreen (e.g., to reload.)
if (!crtgun_timeout[i]) crtgun_timeout[i] = GetTimer(50);
}
else
{
crtgun_timeout[i] = 0;
input[i].lastx = ev.value;
}
// For the window between losing the gun signal and the timer
// running out, report the last on-screen coordinate
if (crtgun_timeout[i] && !CheckTimer(crtgun_timeout[i]))
{
ev.value = input[i].lastx;
}
}
else if (ev.code == ABS_Y)
{
absinfo.minimum = input[i].guncal[0];
absinfo.maximum = input[i].guncal[1];
// Handle gun going off-screen
if (crtgun_timeout[i])
{
// For the window between losing the gun signal and the timer
// running out, report the last on-screen coordinate
if (!CheckTimer(crtgun_timeout[i]))
{
ev.value = input[i].lasty;
}
}
else
{
input[i].lasty = ev.value;
}
}
}
if (ev.type == EV_KEY && user_io_osd_is_visible())
{
if (input[i].quirk == QUIRK_WIIMOTE || input[i].quirk == QUIRK_LIGHTGUN_CRT || input[i].quirk == QUIRK_LIGHTGUN || input[i].quirk == QUIRK_LIGHTGUN_MOUSE)
{
if (menu_lightgun_cb(i, ev.type, ev.code, ev.value)) continue;
}
}
// redirect further actions to left joycon in combined mode
if (JOYCON_COMBINED(i))
{
if (JOYCON_RIGHT(i)) i = input[i].bind;
dev = i;
}
if (!noabs) input_cb(&ev, &absinfo, i);
// simulate digital directions from analog
if (ev.type == EV_ABS && !(mapping && mapping_type <= 1 && mapping_button < -4) && !(ev.code <= 1 && input[dev].lightgun) && input[dev].quirk != QUIRK_PDSP && input[dev].quirk != QUIRK_MSSP)
{
input_absinfo *pai = 0;
uint8_t axis_edge = 0;
if ((absinfo.maximum == 1 && absinfo.minimum == -1) || (absinfo.maximum == 2 && absinfo.minimum == 0))
{
if (ev.value == absinfo.minimum) axis_edge = 1;
if (ev.value == absinfo.maximum) axis_edge = 2;
}
else
{
pai = &absinfo;
int range = absinfo.maximum - absinfo.minimum + 1;
int center = absinfo.minimum + (range / 2);
int treshold = range / 4;
int only_max = 1;
for (int n = 0; n < 4; n++) if (input[dev].mmap[SYS_AXIS1_X + n] && ((input[dev].mmap[SYS_AXIS1_X + n] & 0xFFFF) == ev.code)) only_max = 0;
if (ev.value < center - treshold && !only_max) axis_edge = 1;
if (ev.value > center + treshold) axis_edge = 2;
}
uint8_t last_state = input[dev].axis_edge[ev.code & 255];
input[dev].axis_edge[ev.code & 255] = axis_edge;
//printf("last_state=%d, axis_edge=%d\n", last_state, axis_edge);
if (last_state != axis_edge)
{
uint16_t ecode = KEY_EMU + (ev.code << 1) - 1;
ev.type = EV_KEY;
if (last_state)
{
ev.value = 0;
ev.code = ecode + last_state;
input_cb(&ev, pai, i);
}
if (axis_edge)
{
ev.value = 1;
ev.code = ecode + axis_edge;
input_cb(&ev, pai, i);
}
}
// Menu button on 8BitDo Receiver in D-Input mode
if (ev.code == 9 && input[dev].vid == 0x2dc8 && (input[dev].pid == 0x3100 || input[dev].pid == 0x3104))
{
ev.type = EV_KEY;
ev.code = KEY_EMU + (ev.code << 1);
input_cb(&ev, pai, i);
}
}
}
}
}
else
{
uint8_t data[4] = {};
if (read(pool[i].fd, data, sizeof(data)))
{
int edev = i;
int dev = i;
if (input[i].bind >= 0) edev = input[i].bind; // mouse to event
if (input[edev].bind >= 0) dev = input[edev].bind; // event to base device
if ((input[i].quirk == QUIRK_DS4TOUCH || input[i].quirk == QUIRK_DS4))
{
//disable DS4 mouse in lightgun mode
if (input[dev].lightgun) continue;
}
if (input[i].quirk == QUIRK_TOUCHGUN)
{
//don't use original raspad3 emulated mouse
continue;
}
int xval, yval, zval;
xval = ((data[0] & 0x10) ? -256 : 0) | data[1];
yval = ((data[0] & 0x20) ? -256 : 0) | data[2];
zval = ((data[3] & 0x80) ? -256 : 0) | data[3];
input_absinfo absinfo = {};
absinfo.maximum = 255;
absinfo.minimum = 0;
if (input[dev].quirk == QUIRK_MSSP)
{
int val;
if(cfg.spinner_axis == 0)
val = xval;
else if(cfg.spinner_axis == 1)
val = yval;
else
val = zval;
int btn = (data[0] & 7) ? 1 : 0;
if (input[i].misc_flags != btn)
{
input[i].misc_flags = btn;
ev.value = btn;
ev.type = EV_KEY;
ev.code = 0x120;
input_cb(&ev, &absinfo, i);
}
int throttle = (cfg.spinner_throttle ? abs(cfg.spinner_throttle) : 100) * input[i].spinner_prediv;
int inv = cfg.spinner_throttle < 0;
input[i].spinner_acc += (val * 100);
int spinner = (input[i].spinner_acc <= -throttle || input[i].spinner_acc >= throttle) ? (input[i].spinner_acc / throttle) : 0;
input[i].spinner_acc -= spinner * throttle;
if (spinner)
{
ev.value = inv ? -spinner : spinner;
ev.type = EV_REL;
ev.code = 7;
input_cb(&ev, &absinfo, i);
input[i].paddle_val += ev.value;
if (input[i].paddle_val < 0) input[i].paddle_val = 0;
if (input[i].paddle_val > 255) input[i].paddle_val = 255;
ev.value = input[i].paddle_val;
ev.type = EV_ABS;
ev.code = 8;
input_cb(&ev, &absinfo, i);
}
if (is_menu() && !video_fb_state()) printf("%s: xval=%d, btn=%d, spinner=%d, paddle=%d\n", input[i].devname, val, btn, spinner, input[i].paddle_val);
}
else
{
send_mouse_with_throttle(i, xval, yval, data[3]);
}
}
}
}
}
if ((pool[NUMDEV + 1].fd >= 0) && (pool[NUMDEV + 1].revents & POLLIN))
{
static char cmd[1024];
int len = read(pool[NUMDEV + 1].fd, cmd, sizeof(cmd) - 1);
if (len)
{
if (cmd[len - 1] == '\n') cmd[len - 1] = 0;
cmd[len] = 0;
printf("MiSTer_cmd: %s\n", cmd);
if (!strncmp(cmd, "fb_cmd", 6)) video_cmd(cmd);
else if (!strncmp(cmd, "load_core ", 10))
{
if(isXmlName(cmd)) xml_load(cmd + 10);
else fpga_load_rbf(cmd + 10);
}
else if (!strncmp(cmd, "screenshot", 10))
{
user_io_screenshot_cmd(cmd);
}
else if (!strncmp(cmd, "volume ", 7))
{
if (!strcmp(cmd + 7, "mute")) set_volume(0x81);
else if (!strcmp(cmd + 7, "unmute")) set_volume(0x80);
else if (cmd[7] >= '0' && cmd[7] <= '7') set_volume(0x40 - 0x30 + cmd[7]);
}
}
}
if ((pool[NUMDEV + 2].fd >= 0) && (pool[NUMDEV + 2].revents & POLLPRI))
{
static char status[16];
if (read(pool[NUMDEV + 2].fd, status, sizeof(status) - 1) && status[0] != '0')
{
if (sysled_is_enabled || video_fb_state()) DISKLED_ON;
}
lseek(pool[NUMDEV + 2].fd, 0, SEEK_SET);
}
}
if (cur_leds != leds_state)
{
cur_leds = leds_state;
for (int i = 0; i < NUMDEV; i++)
{
if (input[i].led)
{
ev.type = EV_LED;
ev.code = LED_SCROLLL;
ev.value = (cur_leds&HID_LED_SCROLL_LOCK) ? 1 : 0;
write(pool[i].fd, &ev, sizeof(struct input_event));
ev.code = LED_NUML;
ev.value = (cur_leds&HID_LED_NUM_LOCK) ? 1 : 0;
write(pool[i].fd, &ev, sizeof(struct input_event));
ev.code = LED_CAPSL;
ev.value = (cur_leds&HID_LED_CAPS_LOCK) ? 1 : 0;
write(pool[i].fd, &ev, sizeof(struct input_event));
}
}
}
}
return 0;
}
int input_poll(int getchar)
{
PROFILE_FUNCTION();
static int af[NUMPLAYERS] = {};
static uint32_t time[NUMPLAYERS] = {};
static uint64_t joy_prev[NUMPLAYERS] = {};
int ret = input_test(getchar);
if (getchar) return ret;
uinp_check_key();
static int prev_dx = 0;
static int prev_dy = 0;
if (mouse_emu || ((user_io_get_kbdemu() == EMU_MOUSE) && kbd_mouse_emu))
{
if((prev_dx || mouse_emu_x || prev_dy || mouse_emu_y) && (!mouse_timer || CheckTimer(mouse_timer)))
{
mouse_timer = GetTimer(20);
int dx = mouse_emu_x;
int dy = mouse_emu_y;
if (mouse_sniper ^ cfg.sniper_mode)
{
if (dx > 2) dx = 2;
if (dx < -2) dx = -2;
if (dy > 2) dy = 2;
if (dy < -2) dy = -2;
}
mouse_cb(dx, dy);
prev_dx = mouse_emu_x;
prev_dy = mouse_emu_y;
}
}
if (!mouse_emu_x && !mouse_emu_y) mouse_timer = 0;
if (grabbed)
{
for (int i = 0; i < NUMPLAYERS; i++)
{
if (af_delay[i] < AF_MIN) af_delay[i] = AF_MIN;
if (!time[i]) time[i] = GetTimer(af_delay[i]);
int send = 0;
int newdir = ((((uint32_t)(joy[i]) | (uint32_t)(joy[i] >> 32)) & 0xF) != (((uint32_t)(joy_prev[i]) | (uint32_t)(joy_prev[i] >> 32)) & 0xF));
if (joy[i] != joy_prev[i])
{
if ((joy[i] ^ joy_prev[i]) & autofire[i])
{
time[i] = GetTimer(af_delay[i]);
af[i] = 0;
}
send = 1;
joy_prev[i] = joy[i];
}
if (CheckTimer(time[i]))
{
time[i] = GetTimer(af_delay[i]);
af[i] = !af[i];
if (joy[i] & autofire[i]) send = 1;
}
if (send)
{
user_io_digital_joystick(i, af[i] ? joy[i] & ~autofire[i] : joy[i], newdir);
}
}
}
if (!grabbed || user_io_osd_is_visible())
{
for (int i = 0; i < NUMPLAYERS; i++)
{
if(joy[i]) user_io_digital_joystick(i, 0, 1);
joy[i] = 0;
af[i] = 0;
autofire[i] = 0;
}
}
if (mouse_req)
{
static uint32_t old_time = 0;
uint32_t time = GetTimer(0);
if ((time - old_time > 15) || (mouse_req & 2))
{
old_time = time;
user_io_mouse(mouse_btn | mice_btn, mouse_x, mouse_y, mouse_w);
mouse_req = 0;
mouse_x = 0;
mouse_y = 0;
mouse_w = 0;
}
}
return 0;
}
int is_key_pressed(int key)
{
unsigned char bits[(KEY_MAX + 7) / 8];
for (int i = 0; i < NUMDEV; i++)
{
if (pool[i].fd > 0)
{
unsigned long evbit = 0;
if (ioctl(pool[i].fd, EVIOCGBIT(0, sizeof(evbit)), &evbit) >= 0)
{
if (evbit & (1 << EV_KEY))
{
memset(bits, 0, sizeof(bits));
if (ioctl(pool[i].fd, EVIOCGKEY(sizeof(bits)), &bits) >= 0)
{
if (bits[key / 8] & (1 << (key % 8)))
{
return 1;
}
}
}
}
}
}
return 0;
}
void input_notify_mode()
{
//reset mouse parameters on any mode switch
kbd_mouse_emu = 1;
mouse_sniper = 0;
mouse_timer = 0;
mouse_btn = 0;
mouse_emu_x = 0;
mouse_emu_y = 0;
mouse_cb();
mouse_btn_req();
}
void input_switch(int grab)
{
if (grab >= 0) grabbed = grab;
//printf("input_switch(%d), grabbed = %d\n", grab, grabbed);
for (int i = 0; i < NUMDEV; i++)
{
if (pool[i].fd >= 0) ioctl(pool[i].fd, EVIOCGRAB, (grabbed | user_io_osd_is_visible()) ? 1 : 0);
}
}
int input_state()
{
return grabbed;
}
static char ovr_buttons[1024] = {};
static char ovr_nmap[1024] = {};
static char ovr_pmap[1024] = {};
static char *get_btn(int type)
{
int i = 2;
while (1)
{
char *p = user_io_get_confstr(i);
if (!p) break;
if ((p[0] == 'J' && !type) || (p[0] == 'j' && ((p[1] == 'n' && type == 1) || (p[1] == 'p' && type == 2))))
{
p = strchr(p, ',');
if (!p) break;
p++;
if (!strlen(p)) break;
return p;
}
i++;
}
return NULL;
}
char *get_buttons(int type)
{
if (type == 0 && ovr_buttons[0]) return ovr_buttons;
if (type == 1 && ovr_nmap[0]) return ovr_nmap;
if (type == 2 && ovr_pmap[0]) return ovr_pmap;
return get_btn(type);
}
void set_ovr_buttons(char *s, int type)
{
switch (type)
{
case 0:
snprintf(ovr_buttons, sizeof(ovr_buttons), "%s", s);
break;
case 1:
snprintf(ovr_nmap, sizeof(ovr_nmap), "%s", s);
break;
case 2:
snprintf(ovr_pmap, sizeof(ovr_pmap), "%s", s);
break;
}
}
void parse_buttons()
{
joy_bcount = 0;
char *str = get_buttons();
if (!str) return;
for (int n = 0; n < 28; n++)
{
substrcpy(joy_bnames[n], str, n);
if (!joy_bnames[n][0]) break;
joy_bcount++;
}
}