├── Makefile ├── README.md ├── arcadeBonnet.py ├── arcade_bonnet_test.sh ├── configs ├── retrogame.cfg ├── retrogame.cfg.2button ├── retrogame.cfg.6button ├── retrogame.cfg.bonnet ├── retrogame.cfg.cupcade-orig ├── retrogame.cfg.pigrrl2 ├── retrogame.cfg.pocket ├── retrogame.cfg.super └── retrogame.cfg.zero ├── gamera.c ├── joyBonnet.py ├── joyBonnetAsMouse.py ├── keyTable.h ├── keyTableGen.sh ├── retrogame └── retrogame.c /Makefile: -------------------------------------------------------------------------------- 1 | #EXECS = retrogame gamera 2 | EXECS = retrogame 3 | CFLAGS = -Wall -Ofast -fomit-frame-pointer -funroll-loops -s \ 4 | -I/opt/vc/include \ 5 | -I/opt/vc/include/interface/vcos/pthreads \ 6 | -I/opt/vc/include/interface/vmcs_host \ 7 | -I/opt/vc/include/interface/vmcs_host/linux \ 8 | -L/opt/vc/lib 9 | LIBS = -lbcm_host 10 | CC = gcc $(CFLAGS) 11 | 12 | all: $(EXECS) 13 | 14 | retrogame: retrogame.c keyTable.h 15 | $(CC) $< $(LIBS) -o $@ 16 | strip $@ 17 | 18 | # KEYFILE = /usr/include/linux/input.h 19 | KEYFILE = /usr/include/linux/input-event-codes.h 20 | keyTable.h: keyTableGen.sh $(KEYFILE) 21 | sh $^ >$@ 22 | 23 | gamera: gamera.c 24 | $(CC) $< -lncurses -lmenu -lexpat -o $@ 25 | strip $@ 26 | 27 | install: 28 | mv $(EXECS) /usr/local/bin 29 | 30 | clean: 31 | rm -f $(EXECS) keyTable.h 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Adafruit-Retrogame 2 | ================== 3 | 4 | Raspberry Pi GPIO-to-virtual-keyboard utility for classic game emulators. 5 | 6 | How-to: https://learn.adafruit.com/retro-gaming-with-raspberry-pi 7 | 8 | ### NEED HELP? 9 | 10 | __Visit forums.adafruit.com (General Project Help) for retrogame-related questions.__ GitHub's "Issues" tab is for bug reports and feature requests. __99% of retrogame problems are configuration, not bugs.__ Make sure to follow the "RetroPie 2.0+ Compatibility" directions below. Also, use Broadcom GPIO numbers, _not_ the physical pin index. 11 | 12 | When requesting help, please be thorough in your description. Which model of Raspberry Pi, what release of RetroPie (or other OS image), and (if the trouble is localized) which system emulator exactly? For hardware-related issues, photos are extremely helpful. Thanks! 13 | 14 | __For emulation-related questions (e.g. individual games not working), please use a support forum relevant to the software in use, e.g. the RetroPie Forum at https://retropie.org.uk/forum/ if using that package.__ Adafruit does not develop these emulators or the EmulationStation front-end. 15 | 16 | ### NEW: Configuration file 17 | 18 | retrogame now loads its pin/key settings from a file; no code editing required. An example file 'retrogame.cfg' is included in the 'configs' directory, copy this file to the /boot directory so retrogame can find it (/boot makes it easier to edit with the card in a reader on another system). Alternately, an absolute pathname to a settings file can be passed on the command line. This file can be edited live, no need to restart retrogame after making changes. 19 | 20 | __THE ioStandard[] AND ioTFT[] TABLES NO LONGER EXIST IN THE SOURCE CODE. You should not need to edit ANY source code to make retrogame work.__ Everything is handled through the configuration file now. Some guides may be out of date and still refer to the old way; these will be updated over time. 21 | 22 | ### RetroPie 2.0+ Compatibility 23 | 24 | Note that by default retrogame won't work with SDL2 applications that depend on evdev for input events. Specifically this means applications like the latest version of RetroPie and EmulationStation won't be able to see key events generated by retrogame. However you can fix this issue by adding a small custom udev rule to make retrogame keyboard events visible to SDL2. 25 | 26 | Connect to your Raspberry Pi in a terminal/SSH session and execute the following command to create and edit the file /etc/udev/rules.d/10-retrogame.rules: 27 | 28 | ```` 29 | sudo nano /etc/udev/rules.d/10-retrogame.rules 30 | ```` 31 | 32 | Once the nano text editor loads, copy this single line into the file: 33 | 34 | ```` 35 | SUBSYSTEM=="input", ATTRS{name}=="retrogame", ENV{ID_INPUT_KEYBOARD}="1" 36 | ```` 37 | 38 | Save the file by pressing Ctrl-O and enter, then press Ctrl-x to exit. Restart your Raspberry Pi and run retrogame again, now button presses should register in SDL2 applications like the EmulationStation frontend to RetroPie 39 | 40 | --- 41 | 42 | ### Roadmap 43 | 44 | Development of the C retrogame program at this point is in maintenance mode -- bugs will be looked at and corrected where possible, but don't expect significant new features (such as support for more I/O port expander types), nor will pull requests for such be merged. The code has grown untenably complex. 45 | 46 | retrogame is a product of its time; first-generation Raspberry Pi devices with sub-optimal emulation software were common and obtuse C code was used to minimize memory use and CPU load. There's little benefit to such an approach now. For the Arcade Bonnet and Joy Bonnet, you'll see there are now simpler, device-specific Python scripts being used...either one consumes just a fraction of a percent of a system's resources. Rather than wrestling the retrogame code into doing Yet Another Thing, consider using one of these Python scripts as a starting point, and focus on just that one task. 47 | -------------------------------------------------------------------------------- /arcadeBonnet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Somewhat minimal Adafruit Arcade Bonnet handler. Runs in background, 4 | # translates inputs from MCP23017 port expander to virtual USB keyboard 5 | # events. Not -quite- as efficient or featuretastic as retrogame, but 6 | # still reasonably lightweight and may be easier and/or more reliable than 7 | # retrogame for some users. Supports ONE port expander, no regular GPIO 8 | # (non-port-expander) or "Vulcan nerve pinch" features. 9 | # Prerequisites: 10 | # sudo apt-get install python-pip python-smbus python-dev 11 | # sudo pip install evdev 12 | # Be sure to enable I2C via raspi-config. Also, udev rules will need to 13 | # be set up per retrogame directions. 14 | # Credit to Pimoroni for Picade HAT scripts as starting point. 15 | 16 | import os 17 | import time 18 | import RPi.GPIO as gpio 19 | from evdev import uinput, UInput, ecodes as e 20 | from smbus import SMBus 21 | 22 | key = [ # EDIT KEYCODES IN THIS TABLE TO YOUR PREFERENCES: 23 | # See /usr/include/linux/input.h for keycode names 24 | # Keyboard Bonnet EmulationStation 25 | e.KEY_LEFTCTRL, # 1A 'A' button 26 | e.KEY_LEFTALT, # 1B 'B' button 27 | e.KEY_A, # 1C 'X' button 28 | e.KEY_S, # 1D 'Y' button 29 | e.KEY_5, # 1E 'Select' button 30 | e.KEY_1, # 1F 'Start' button 31 | 0, # Bit 6 NOT CONNECTED on Bonnet 32 | 0, # Bit 7 NOT CONNECTED on Bonnet 33 | e.KEY_DOWN, # 4-way down D-pad down 34 | e.KEY_UP, # 4-way up D-pad up 35 | e.KEY_RIGHT, # 4-way right D-pad right 36 | e.KEY_LEFT, # 4-way left D-pad left 37 | e.KEY_L, # Analog right 38 | e.KEY_H, # Analog left 39 | e.KEY_J, # Analog down 40 | e.KEY_K # Analog up 41 | ] 42 | 43 | addr = 0x26 # I2C Address of MCP23017 44 | irqPin = 17 # IRQ pin for MCP23017 45 | 46 | os.system("sudo modprobe uinput") 47 | 48 | ui = UInput({e.EV_KEY: key}, name="retrogame", bustype=e.BUS_USB) 49 | bus = SMBus(1) 50 | IODIRA = 0x00 51 | IOCONA = 0x0A 52 | INTCAPA = 0x10 53 | 54 | # Initial MCP23017 config: 55 | bus.write_byte_data(addr, 0x05 , 0x00) # If bank 1, switch to 0 56 | bus.write_byte_data(addr, IOCONA, 0x44) # Bank 0, INTB=A, seq, OD IRQ 57 | 58 | # Read/modify/write remaining MCP23017 config: 59 | cfg = bus.read_i2c_block_data(addr, IODIRA, 14) 60 | cfg[ 0] = 0xFF # Input bits 61 | cfg[ 1] = 0xFF 62 | cfg[ 2] = 0x00 # Polarity 63 | cfg[ 3] = 0x00 64 | cfg[ 4] = 0xFF # Interrupt pins 65 | cfg[ 5] = 0xFF 66 | cfg[12] = 0xFF # Pull-ups 67 | cfg[13] = 0xFF 68 | bus.write_i2c_block_data(addr, IODIRA, cfg) 69 | 70 | # Clear interrupt by reading INTCAP and GPIO registers 71 | x = bus.read_i2c_block_data(addr, INTCAPA, 4) 72 | oldState = x[2] | (x[3] << 8) 73 | 74 | # Callback for MCP23017 interrupt request 75 | def mcp_irq(pin): 76 | global oldState 77 | x = bus.read_i2c_block_data(addr, INTCAPA, 4) 78 | newState = x[2] | (x[3] << 8) 79 | for i in range(16): 80 | bit = 1 << i 81 | lvl = newState & bit 82 | if lvl != (oldState & bit): 83 | ui.write(e.EV_KEY, key[i], 0 if lvl else 1) 84 | ui.syn() 85 | oldState = newState 86 | 87 | # GPIO init 88 | gpio.setwarnings(False) 89 | gpio.setmode(gpio.BCM) 90 | 91 | # Enable pullup and callback on MCP23017 IRQ pin 92 | gpio.setup(irqPin, gpio.IN, pull_up_down=gpio.PUD_UP) 93 | gpio.add_event_detect(irqPin, gpio.FALLING, callback=mcp_irq) 94 | 95 | while True: time.sleep(1) 96 | -------------------------------------------------------------------------------- /arcade_bonnet_test.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | 3 | # Arcade Bonnet troubleshooting tool. Checks whether prerequisite 4 | # config and tools are present, and runs live status of inputs. 5 | 6 | # Test if primary I2C enabled, offer help if needed 7 | if [ ! -c /dev/i2c-1 ] 8 | then 9 | echo "I2C not present. Enable with:" 10 | echo " sudo raspi-config nonint do_i2c 0" 11 | echo "or use raspi-config 'Interface Options' menu" 12 | exit 1 13 | fi 14 | 15 | # Test if i2ctools installed, offer help if needed 16 | if ! type i2cset >/dev/null 2>&1 17 | then 18 | echo "i2c-tools not present. Install with:" 19 | echo " sudo apt-get install i2c-tools" 20 | exit 1 21 | fi 22 | 23 | # MCP23017 I2C address default is 0x20, Arcade Bonnet uses 0x26: 24 | MCP_ADDR=0x26 25 | 26 | # registers 27 | IODIRA=0x00 28 | IODIRB=0x01 29 | GPPUA=0x0C 30 | GPPUB=0x0D 31 | GPIOA=0x12 32 | GPIOB=0x13 33 | 34 | # set all pins to input 35 | i2cset -y 1 $MCP_ADDR $IODIRA 0xFF 36 | i2cset -y 1 $MCP_ADDR $IODIRB 0xFF 37 | 38 | # enable internal pull up on all pins 39 | i2cset -y 1 $MCP_ADDR $GPPUA 0xFF 40 | i2cset -y 1 $MCP_ADDR $GPPUB 0xFF 41 | 42 | # Display one input state, showing '*' if currently active 43 | disp() { 44 | if [ $(($2 >> $3 & 0x01)) -ne 0 ] 45 | then 46 | echo " $1 :" 47 | else 48 | echo " $1 : *" 49 | fi 50 | } 51 | 52 | # Test all Arcade Bonnet inputs in a loop 53 | while : 54 | do 55 | # read pin state 56 | GPA=$(i2cget -y 1 $MCP_ADDR $GPIOA) 57 | GPB=$(i2cget -y 1 $MCP_ADDR $GPIOB) 58 | 59 | # report 60 | clear 61 | echo "===== BUTTONS =====" 62 | echo "raw value = $GPA" 63 | disp "1A" $GPA 0 64 | disp "1B" $GPA 1 65 | disp "1C" $GPA 2 66 | disp "1D" $GPA 3 67 | disp "1E" $GPA 4 68 | disp "1F" $GPA 5 69 | 70 | echo "==== JOYSTICKS ====" 71 | echo "raw value = $GPB" 72 | echo "4-WAY" 73 | disp "L" $GPB 3 74 | disp "R" $GPB 2 75 | disp "U" $GPB 1 76 | disp "D" $GPB 0 77 | echo "ANALOG" 78 | disp "L" $GPB 5 79 | disp "R" $GPB 4 80 | disp "U" $GPB 6 81 | disp "D" $GPB 7 82 | 83 | echo "== CTRL-C TO EXIT ==" 84 | done 85 | -------------------------------------------------------------------------------- /configs/retrogame.cfg: -------------------------------------------------------------------------------- 1 | # Sample configuration file for retrogame. 2 | # Really minimal syntax, typically two elements per line w/space delimiter: 3 | # 1) a key name (from keyTable.h; shortened from /usr/include/linux/input.h). 4 | # 2) a GPIO pin number; when grounded, will simulate corresponding keypress. 5 | # Uses Broadcom pin numbers for GPIO. 6 | # If first element is GND, the corresponding pin (or pins, multiple can be 7 | # given) is a LOW-level output; an extra ground pin for connecting buttons. 8 | # A '#' character indicates a comment to end-of-line. 9 | # File can be edited "live," no need to restart retrogame! 10 | 11 | # Here's a pin configuration for the PiGRRL 2 project: 12 | 13 | LEFT 4 # Joypad left 14 | RIGHT 19 # Joypad right 15 | UP 16 # Joypad up 16 | DOWN 26 # Joypad down 17 | LEFTCTRL 14 # 'A' button 18 | LEFTALT 15 # 'B' button 19 | Z 20 # 'X' button 20 | X 18 # 'Y' button 21 | SPACE 5 # 'Select' button 22 | ENTER 6 # 'Start' button 23 | A 12 # Left shoulder button 24 | S 13 # Right shoulder button 25 | ESC 17 # Exit ROM; PiTFT Button 1 26 | 1 22 # PiTFT Button 2 27 | 2 23 # PiTFT Button 3 28 | 3 27 # PiTFT Button 4 29 | 30 | # For configurations with few buttons (e.g. Cupcade), a key can be followed 31 | # by multiple pin numbers. When those pins are all held for a few seconds, 32 | # this will generate the corresponding keypress (e.g. ESC to exit ROM). 33 | # Only ONE such combo is supported within the file though; later entries 34 | # will override earlier. 35 | -------------------------------------------------------------------------------- /configs/retrogame.cfg.2button: -------------------------------------------------------------------------------- 1 | # Sample configuration file for retrogame. 2 | # Really minimal syntax, typically two elements per line w/space delimiter: 3 | # 1) a key name (from keyTable.h; shortened from /usr/include/linux/input.h). 4 | # 2) a GPIO pin number; when grounded, will simulate corresponding keypress. 5 | # Uses Broadcom pin numbers for GPIO. 6 | # If first element is GND, the corresponding pin (or pins, multiple can be 7 | # given) is a LOW-level output; an extra ground pin for connecting buttons. 8 | # A '#' character indicates a comment to end-of-line. 9 | # File can be edited "live," no need to restart retrogame! 10 | 11 | # Here's a minimal config for the "Retro Gaming" guide, 12 | # 4-way stick + 2 buttons; have keyboard handy for other functions. 13 | 14 | LEFTCTRL 23 # Left control key = fire/jump/primary/'A' button 15 | LEFTALT 7 # Left alt key = thrust/seconady/'B' button 16 | UP 10 17 | DOWN 17 18 | LEFT 25 19 | RIGHT 9 20 | ESC 23 7 # Esc key = hold 'A'+'B' buttons (exit ROM) 21 | 22 | # For configurations with few buttons (e.g. Cupcade), a key can be followed 23 | # by multiple pin numbers. When those pins are all held for a few seconds, 24 | # this will generate the corresponding keypress (e.g. ESC to exit ROM). 25 | # Only ONE such combo is supported within the file though; later entries 26 | # will override earlier. 27 | -------------------------------------------------------------------------------- /configs/retrogame.cfg.6button: -------------------------------------------------------------------------------- 1 | # Sample configuration file for retrogame. 2 | # Really minimal syntax, typically two elements per line w/space delimiter: 3 | # 1) a key name (from keyTable.h; shortened from /usr/include/linux/input.h). 4 | # 2) a GPIO pin number; when grounded, will simulate corresponding keypress. 5 | # Uses Broadcom pin numbers for GPIO. 6 | # If first element is GND, the corresponding pin (or pins, multiple can be 7 | # given) is a LOW-level output; an extra ground pin for connecting buttons. 8 | # A '#' character indicates a comment to end-of-line. 9 | # File can be edited "live," no need to restart retrogame! 10 | 11 | # Here's a 6-button configuration: 12 | 13 | LEFT 10 # Joypad left 14 | RIGHT 22 # Joypad right 15 | UP 23 # Joypad up 16 | DOWN 27 # Joypad down 17 | LEFTCTRL 4 # 'A' button 18 | LEFTALT 25 # 'B' button 19 | Z 11 # 'X' button 20 | X 5 # 'Y' button 21 | GND 6 # Spare ground point for 'Y' button 22 | SPACE 16 # 'Select' button 23 | ENTER 26 # 'Start' button 24 | ESC 16 26 # Hold Start+Select to exit ROM 25 | 26 | # For configurations with few buttons (e.g. Cupcade), a key can be followed 27 | # by multiple pin numbers. When those pins are all held for a few seconds, 28 | # this will generate the corresponding keypress (e.g. ESC to exit ROM). 29 | # Only ONE such combo is supported within the file though; later entries 30 | # will override earlier. 31 | -------------------------------------------------------------------------------- /configs/retrogame.cfg.bonnet: -------------------------------------------------------------------------------- 1 | # Sample configuration file for retrogame. 2 | # Really minimal syntax, typically two elements per line w/space delimiter: 3 | # 1) a key name (from keyTable.h; shortened from /usr/include/linux/input.h). 4 | # 2) a GPIO pin number; when grounded, will simulate corresponding keypress. 5 | # Uses Broadcom pin numbers for GPIO. 6 | # If first element is GND, the corresponding pin (or pins, multiple can be 7 | # given) is a LOW-level output; an extra ground pin for connecting buttons. 8 | # A '#' character indicates a comment to end-of-line. 9 | # File can be edited "live," no need to restart retrogame! 10 | 11 | # Here's a pin configuration for the Adafruit Arcade Bonnet. This board is 12 | # based on the MCP23017 I2C port expander, with a default address of 0x26 13 | # (solder jumper selects 0x27 if needed). retrogame treats port expanders 14 | # as their own GPIO pin numbers beyond the normal 0-31: 15 | 16 | # 0 - 31 GPIO header 'P5' (Broadcom pin numbers) 17 | # 32 - 47 MCP23017 at address 0x20 18 | # 48 - 63 MCP23017 at address 0x21 19 | # 64 - 79 MCP23017 at address 0x22 20 | # 80 - 95 MCP23017 at address 0x23 21 | # 96 - 111 MCP23017 at address 0x24 22 | # 112 - 127 MCP23017 at address 0x25 23 | # 128 - 143 MCP23017 at address 0x26 *** Arcade Bonnet default address 24 | # 144 - 159 MCP23017 at address 0x27 *** Arcade Bonnet alt address 25 | 26 | # The Arcade Bonnet MUST be enabled with the IRQ command to 27 | # assign an interrupt request GPIO pin and I2C bus address. 28 | # IRQ pin for this board is hardwired as 17. 29 | 30 | IRQ 17 0x26 # Arcade Bonnet default address, use GPIO 128-143 31 | 32 | # Keyboard Bonnet EmulationStation 33 | LEFTCTRL 128 # Left control key 1A 'A' button 34 | LEFTALT 129 # Left alt key 1B 'B' button 35 | Z 130 # Z key 1C 'X' button 36 | X 131 # X key 1D 'Y' button 37 | SPACE 132 # Space bar 1E 'Select' button 38 | ENTER 133 # Enter key 1F 'Start' button 39 | ESC 132 133 # Escape key N/A Select + Start 40 | # 134, 135 not connected on Bonnet 41 | DOWN 136 # Down arrow 4-way down D-pad down 42 | UP 137 # Up arrow 4-way up D-pad up 43 | RIGHT 138 # Right arrow 4-way right D-pad right 44 | LEFT 139 # Left arrow 4-way left D-pad left 45 | L 140 # L key Analog right 46 | H 141 # H key Analog left 47 | J 142 # J key Analog down 48 | K 143 # K key Analog up 49 | -------------------------------------------------------------------------------- /configs/retrogame.cfg.cupcade-orig: -------------------------------------------------------------------------------- 1 | # Sample configuration file for retrogame. 2 | # Really minimal syntax, typically two elements per line w/space delimiter: 3 | # 1) a key name (from keyTable.h; shortened from /usr/include/linux/input.h). 4 | # 2) a GPIO pin number; when grounded, will simulate corresponding keypress. 5 | # Uses Broadcom pin numbers for GPIO. 6 | # If first element is GND, the corresponding pin (or pins, multiple can be 7 | # given) is a LOW-level output; an extra ground pin for connecting buttons. 8 | # A '#' character indicates a comment to end-of-line. 9 | # File can be edited "live," no need to restart retrogame! 10 | 11 | # Here's a pin configuration for the original Cupdade project: 12 | 13 | LEFT 2 # Joystick (4 pins) 14 | RIGHT 3 15 | DOWN 4 16 | UP 17 17 | LEFTCTRL 27 # Fire/jump/primary 18 | LEFTALT 22 # Bomb/secondary 19 | 5 23 # Credit 20 | 1 18 # Start 1P 21 | ESC 23 18 # Credit + Start = exit emulator 22 | 23 | # For configurations with few buttons (e.g. Cupcade), a key can be followed 24 | # by multiple pin numbers. When those pins are all held for a few seconds, 25 | # this will generate the corresponding keypress (e.g. ESC to exit ROM). 26 | # Only ONE such combo is supported within the file though; later entries 27 | # will override earlier. 28 | 29 | -------------------------------------------------------------------------------- /configs/retrogame.cfg.pigrrl2: -------------------------------------------------------------------------------- 1 | # Sample configuration file for retrogame. 2 | # Really minimal syntax, typically two elements per line w/space delimiter: 3 | # 1) a key name (from keyTable.h; shortened from /usr/include/linux/input.h). 4 | # 2) a GPIO pin number; when grounded, will simulate corresponding keypress. 5 | # Uses Broadcom pin numbers for GPIO. 6 | # If first element is GND, the corresponding pin (or pins, multiple can be 7 | # given) is a LOW-level output; an extra ground pin for connecting buttons. 8 | # A '#' character indicates a comment to end-of-line. 9 | # File can be edited "live," no need to restart retrogame! 10 | 11 | # Here's a pin configuration for the PiGRRL 2 project: 12 | 13 | LEFT 4 # Joypad left 14 | RIGHT 19 # Joypad right 15 | UP 16 # Joypad up 16 | DOWN 26 # Joypad down 17 | LEFTCTRL 14 # 'A' button 18 | LEFTALT 15 # 'B' button 19 | Z 20 # 'X' button 20 | X 18 # 'Y' button 21 | SPACE 5 # 'Select' button 22 | ENTER 6 # 'Start' button 23 | A 12 # Left shoulder button 24 | S 13 # Right shoulder button 25 | ESC 17 # Exit ROM; PiTFT Button 1 26 | 1 22 # PiTFT Button 2 27 | 2 23 # PiTFT Button 3 28 | 3 27 # PiTFT Button 4 29 | 30 | # For configurations with few buttons (e.g. Cupcade), a key can be followed 31 | # by multiple pin numbers. When those pins are all held for a few seconds, 32 | # this will generate the corresponding keypress (e.g. ESC to exit ROM). 33 | # Only ONE such combo is supported within the file though; later entries 34 | # will override earlier. 35 | -------------------------------------------------------------------------------- /configs/retrogame.cfg.pocket: -------------------------------------------------------------------------------- 1 | # Sample configuration file for retrogame. 2 | # Really minimal syntax, typically two elements per line w/space delimiter: 3 | # 1) a key name (from keyTable.h; shortened from /usr/include/linux/input.h). 4 | # 2) a GPIO pin number; when grounded, will simulate corresponding keypress. 5 | # Uses Broadcom pin numbers for GPIO. 6 | # If first element is GND, the corresponding pin (or pins, multiple can be 7 | # given) is a LOW-level output; an extra ground pin for connecting buttons. 8 | # A '#' character indicates a comment to end-of-line. 9 | # File can be edited "live," no need to restart retrogame! 10 | 11 | # Here's a pin configuration for the Pocket PiGRRL project: 12 | 13 | LEFT 4 # Joypad left 14 | RIGHT 17 # Joypad right 15 | UP 18 # Joypad up 16 | DOWN 27 # Joypad down 17 | LEFTCTRL 22 # 'A' button 18 | LEFTALT 23 # 'B' button 19 | Z 5 # 'X' button 20 | ESC 6 # Exit ROM; PiTFT Button 1 21 | ENTER 12 # 'Start' button 22 | SPACE 13 # 'Select' button 23 | X 16 # 'Y' button 24 | 25 | # For configurations with few buttons (e.g. Cupcade), a key can be followed 26 | # by multiple pin numbers. When those pins are all held for a few seconds, 27 | # this will generate the corresponding keypress (e.g. ESC to exit ROM). 28 | # Only ONE such combo is supported within the file though; later entries 29 | # will override earlier. 30 | -------------------------------------------------------------------------------- /configs/retrogame.cfg.super: -------------------------------------------------------------------------------- 1 | # Sample configuration file for retrogame. 2 | # Really minimal syntax, typically two elements per line w/space delimiter: 3 | # 1) a key name (from keyTable.h; shortened from /usr/include/linux/input.h). 4 | # 2) a GPIO pin number; when grounded, will simulate corresponding keypress. 5 | # Uses Broadcom pin numbers for GPIO. 6 | # If first element is GND, the corresponding pin (or pins, multiple can be 7 | # given) is a LOW-level output; an extra ground pin for connecting buttons. 8 | # A '#' character indicates a comment to end-of-line. 9 | # File can be edited "live," no need to restart retrogame! 10 | 11 | # Here's a pin configuration for the Super Game Pi project: 12 | 13 | LEFT 22 # Joypad left 14 | RIGHT 23 # Joypad right 15 | UP 17 # Joypad up 16 | DOWN 27 # Joypad down 17 | Z 24 # 'A' button 18 | X 10 # 'B' button 19 | ESC 18 # 'Select' button 20 | ENTER 4 # 'Start' button 21 | S 9 # 'X' button 22 | A 25 # 'Y' button 23 | Q 11 # Left shoulder button 24 | W 8 # Right shoulder button 25 | 26 | # For configurations with few buttons (e.g. Cupcade), a key can be followed 27 | # by multiple pin numbers. When those pins are all held for a few seconds, 28 | # this will generate the corresponding keypress (e.g. ESC to exit ROM). 29 | # Only ONE such combo is supported within the file though; later entries 30 | # will override earlier. 31 | -------------------------------------------------------------------------------- /configs/retrogame.cfg.zero: -------------------------------------------------------------------------------- 1 | # Sample configuration file for retrogame. 2 | # Really minimal syntax, typically two elements per line w/space delimiter: 3 | # 1) a key name (from keyTable.h; shortened from /usr/include/linux/input.h). 4 | # 2) a GPIO pin number; when grounded, will simulate corresponding keypress. 5 | # Uses Broadcom pin numbers for GPIO. 6 | # If first element is GND, the corresponding pin (or pins, multiple can be 7 | # given) is a LOW-level output; an extra ground pin for connecting buttons. 8 | # A '#' character indicates a comment to end-of-line. 9 | # File can be edited "live," no need to restart retrogame! 10 | 11 | # Here's a pin configuration for the PiGRRL Zero project: 12 | 13 | LEFT 18 # Joypad left 14 | RIGHT 24 # Joypad right 15 | DOWN 5 # Joypad down 16 | UP 6 # Joypad up 17 | Z 12 # 'A' button 18 | X 13 # 'B' button 19 | A 16 # 'X' button 20 | S 19 # 'Y' button 21 | Q 20 # Left shoulder button 22 | W 21 # Right shoulder button 23 | ESC 17 # Exit ROM; PiTFT Button 1 24 | LEFTCTRL 22 # 'Select' button; PiTFT Button 2 25 | ENTER 23 # 'Start' button; PiTFT Button 3 26 | 4 27 # PiTFT Button 4 27 | 28 | # For configurations with few buttons (e.g. Cupcade), a key can be followed 29 | # by multiple pin numbers. When those pins are all held for a few seconds, 30 | # this will generate the corresponding keypress (e.g. ESC to exit ROM). 31 | # Only ONE such combo is supported within the file though; later entries 32 | # will override earlier. 33 | -------------------------------------------------------------------------------- /gamera.c: -------------------------------------------------------------------------------- 1 | /* 2 | DEPRECATED CODE - better handled by EmulationStation now. 3 | This code was a ROM file selector for MAME + fceu. 4 | Keeping it here for now in case any Cupcade users need it. 5 | 6 | 7 | gamera (Game Rom Aggregator): simple game selection interface for the 8 | advmame (arcade) and fceu (NES) emulators (maybe others in the future). 9 | 10 | Data specific to each emulator are currently set in global variables near 11 | the top of the code. Emulator-specific code likewise appears early. 12 | 13 | If /boot/advmame/advmame.xml exists, MAME games will have human-readable 14 | titles. Otherwise the ROM filename (sometimes cryptic) is displayed. 15 | Use the following command to generate the XML file: 16 | 17 | advmame -listxml > /boot/advmame/advmame.xml 18 | 19 | fceu does not have this option; the ROM filename is the only name displayed. 20 | 21 | advmame -must- be configured with 'z' and 'x' as the primary and secondary 22 | buttons, respectively (normally left ctrl and alt) for a seamless 23 | retrogame/gamera/advmame experience. This is because handling raw keycodes 24 | with ncurses is a Pandora's Box of pure evil. These lines should exist in 25 | the advmame.rc file: 26 | 27 | device_keyboard raw 28 | input_map[p1_button1] keyboard[0,lcontrol] or keyboard[0,z] 29 | input_map[p1_button2] keyboard[0,lalt] or keyboard[0,x] 30 | input_map[ui_select] keyboard[0,enter] or keyboard[0,lcontrol] or keyboard[0,z] 31 | 32 | fceu likewise needs a configuration file with similar input mapping for 33 | the controls. It's a binary file and not easily edited; a valid config 34 | file is included on the Cupcade disk image. 35 | 36 | The pre-built gamera executable should run as-is for most users. If you 37 | need to tweak and recompile, it requires the ncurses and expat C libraries: 38 | 39 | sudo apt-get install ncurses-dev libexpat1-dev 40 | 41 | Written by Phil Burgess for Adafruit Industries, distributed under BSD 42 | License. Adafruit invests time and resources providing this open source 43 | code, please support Adafruit and open-source hardware by purchasing 44 | products from Adafruit! 45 | 46 | 47 | Copyright (c) 2014 Adafruit Industries. 48 | All rights reserved. 49 | 50 | Redistribution and use in source and binary forms, with or without 51 | modification, are permitted provided that the following conditions are met: 52 | 53 | - Redistributions of source code must retain the above copyright notice, 54 | this list of conditions and the following disclaimer. 55 | - Redistributions in binary form must reproduce the above copyright notice, 56 | this list of conditions and the following disclaimer in the documentation 57 | and/or other materials provided with the distribution. 58 | 59 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 60 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 61 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 62 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 63 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 64 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 65 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 66 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 67 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 68 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 69 | POSSIBILITY OF SUCH DAMAGE. 70 | */ 71 | 72 | #include 73 | #include 74 | #include 75 | #include 76 | #include 77 | #include 78 | #include 79 | 80 | 81 | // START HERE: configurable stuff ---------------------------------------- 82 | 83 | // TFT rotation setting may be stored in different places depending 84 | // on kernel vs. module usage. This table lists all the likely culprits. 85 | static const struct { 86 | const char *filename; // Absolute path to config file 87 | const char *keyword; // Rotation setting string 88 | } tftCfg[] = { 89 | { "/etc/modprobe.d/adafruit.conf", "rotate" }, 90 | { "/boot/cmdline.txt" , "fbtft_device.rotate" } }; 91 | #define N_TFT_FILES (sizeof(tftCfg) / sizeof(tftCfg[0])) 92 | 93 | // For each emulator, a linked list of these Game structs is generated 94 | // when scanning the corresponding ROM directory. 95 | typedef struct Game { 96 | unsigned char emu; // Index of parent emulator 97 | char *name; // ROM name (as passed to emulator; may be sans .zip) 98 | struct Game *next; // Next game in linked list 99 | } Game; 100 | 101 | // A few function prototypes needed for elements of the subsequent struct. 102 | // The functions themselves are described later, don't panic. 103 | static void 104 | mameInit(void), mameCommand(Game *, char *), fceuCommand(Game *, char *); 105 | static int 106 | mameFilter(const struct dirent *), mameItemize(Game *, int), 107 | fceuFilter(const struct dirent *), fceuItemize(Game *, int); 108 | 109 | // List of supported emulators 110 | static struct Emulator { 111 | const char *title; // Emulator name on menu 112 | const char *romPath; // Absolute path to ROMs 113 | Game *gameList; // Linked list of Games 114 | void (*init)(void); // Emulator-specific setup 115 | int (*filter)(const struct dirent *); // ID ROMs for scandir() 116 | int (*compar)(const struct dirent **, const struct dirent**); 117 | int (*itemize)(Game *, int); // Filenames to item list 118 | void (*command)(Game *, char *); // Prepare command line 119 | } emulator[] = { 120 | { "MAME:", "/boot/advmame/rom", NULL, 121 | mameInit, mameFilter, NULL , mameItemize, mameCommand }, 122 | { "NES:" , "/boot/fceu/rom" , NULL, 123 | NULL , fceuFilter, alphasort, fceuItemize, fceuCommand } 124 | }; 125 | #define N_EMULATORS (sizeof(emulator) / sizeof(emulator[0])) 126 | 127 | // A few global ncurses elements 128 | WINDOW *mainWin = NULL, 129 | *noRomWin = NULL; 130 | MENU *menu = NULL; 131 | ITEM **items = NULL; 132 | 133 | 134 | // MAME-specific globals and code ---------------------------------------- 135 | 136 | static const char 137 | mameCfgTall[] = "/boot/advmame/advmame.rc.portrait", // Absolute paths 138 | mameCfgWide[] = "/boot/advmame/advmame.rc.landscape", // to config and 139 | mameXmlFile[] = "/boot/advmame/advmame.xml", // data files. 140 | *mameCfg; // Active config. 141 | 142 | // Each emulator's Games are stored in a linked list. The titles 143 | // displayed for MAME are alphabetically sorted by the XML-derived title, 144 | // not filename. In order to use qsort() -- which is array-oriented -- 145 | // MAME titles are placed in an array with a pointer back to the Game 146 | // struct. fceu sorts by filename during scandir(); it doesn't have 147 | // XML verbose titles and doesn't do this. 148 | typedef struct { 149 | Game *g; 150 | char *title; 151 | } mameID; 152 | 153 | mameID *mameArray; 154 | 155 | // Speaking of Pandora's Box of pure evil...XML cross-referencing (for 156 | // human-readable MAME titles) is a spaghetti-fest involving globals 157 | // for maintaining state across callbacks invoked by the expat library. 158 | static Game *mameGameList = NULL, // -> MAME game linked list 159 | *gameToDescribe = NULL; // Game being XML-parsed 160 | static unsigned char descFlag = 0; // Enable name parser 161 | static int mameIdx; // Counter into mameArray[] 162 | 163 | static void mameInit(void) { 164 | char cmdline[1024]; 165 | int i; 166 | 167 | // Determine if screen is in portrait or landscape mode, get 168 | // path to corresponding advmame config file. Method is to 169 | // check for 'rotate=0' in TFT module config file. If present 170 | // (system() returns 0), is portrait screen, else landscape. 171 | mameCfg = mameCfgWide; // Assume landscape screen to start 172 | for(i=0; id_type == DT_REG) || (d->d_type == DT_LNK)) && 188 | (d->d_name[0] != '.') && // Ignore dotfiles 189 | (ptr = strrchr(d->d_name, '.')) && !strcasecmp(ptr, ".zip")) { 190 | *ptr = 0; // Truncate .zip extension 191 | return 1; 192 | } 193 | return 0; 194 | } 195 | 196 | // Called at the start of each XML element: e.g. 197 | static void XMLCALL startElement( 198 | void *depth, const char *name, const char **attr) { 199 | if((*(int *)depth += 1) == 2) { // If level 2 nesting... 200 | // If element is and at least 2 attributes present... 201 | if(!strcmp(name, "game") && attr[0] && attr[1]) { 202 | // Compare attr[1] against list of game names... 203 | Game *g; 204 | for(mameIdx=0,g=mameGameList;g;g=g->next,mameIdx++) { 205 | if(!strcmp(attr[1], g->name)) { 206 | // Found match, save pointer to game 207 | gameToDescribe = g; 208 | break; 209 | // The element data parser, if 210 | // subsequently enabled, may then 211 | // create the desc for this game. 212 | } 213 | } 214 | } 215 | // else if element is '' at level 3... 216 | } else if((*(int *)depth == 3) && !strcmp(name, "description")) { 217 | descFlag = 1; // Enable element data parser 218 | } 219 | } 220 | 221 | // Called at the end of each XML element: e.g. 222 | static void XMLCALL endElement(void *depth, const char *name) { 223 | if((*(int *)depth -= 1) == 1) { // End of 'game' element? 224 | gameToDescribe = NULL; // Deactivate description search 225 | descFlag = 0; 226 | } 227 | } 228 | 229 | // Called for element data between start/end: e.g. DATA 230 | static void elementData(void *data, const char *content, int length) { 231 | // gameToDescribe and descFlag must both be set; avoid false positives 232 | if(descFlag && gameToDescribe) { 233 | if(mameArray && (!mameArray[mameIdx].title)) 234 | mameArray[mameIdx].title = strndup(content, length); 235 | descFlag = 0; // Found description, we're done 236 | } 237 | } 238 | 239 | // Compare function for qsort() -- for alphabetizing MAME game list 240 | static int mameCompare(const void *a, const void *b) { 241 | return strcasecmp(((mameID *)a)->title, ((mameID *)b)->title); 242 | } 243 | 244 | // After scanning folder for MAME ROM files, cross-reference XML file 245 | // against filenames and populate the items[] array with human-readable 246 | // game descriptions. Fall back on names alone for items. 247 | static int mameItemize(Game *gList, int i) { 248 | FILE *fp; 249 | Game *g; 250 | int gCount; 251 | 252 | // Count number of games, alloc and init array of mameIDs. 253 | for(gCount=0, g=gList; g; g=g->next, gCount++); 254 | if((mameArray = (mameID *)malloc(gCount * sizeof(mameID)))) { 255 | for(gCount=0, g=gList; g; g=g->next, gCount++) { 256 | mameArray[gCount].g = g; 257 | mameArray[gCount].title = NULL; 258 | } 259 | } 260 | 261 | mameGameList = NULL; // -> MAME linked list doubles as success flag 262 | if((fp = fopen(mameXmlFile, "r"))) { 263 | fseek(fp, 0, SEEK_END); 264 | char *buf; 265 | int len = ftell(fp); 266 | if((buf = (char *)malloc(len))) { 267 | int depth = 0; 268 | fseek(fp, 0, SEEK_SET); 269 | fread(buf, 1, len, fp); 270 | mameGameList = gList; // Opened & alloc'd OK 271 | XML_Parser parser = XML_ParserCreate(NULL); 272 | XML_SetUserData(parser, &depth); 273 | XML_SetElementHandler(parser, 274 | startElement, endElement); 275 | XML_SetCharacterDataHandler(parser, elementData); 276 | XML_Parse(parser, buf, len, 1); 277 | XML_ParserFree(parser); 278 | free(buf); 279 | } 280 | fclose(fp); 281 | } 282 | 283 | // Make a second pass through games list... 284 | if((mameArray)) { 285 | 286 | // For any games that are unlabeled (either due to above 287 | // open or malloc failing, or a game simply not being 288 | // located in the XML file), fall back on filename alone. 289 | for(gCount=0, g=gList; g; g=g->next, gCount++) { 290 | if(!mameArray[gCount].title) { 291 | mameArray[gCount].title = strdup(g->name); 292 | } 293 | } 294 | // Alphabetize MAME game list... 295 | qsort(mameArray, gCount, sizeof(mameID), mameCompare); 296 | // And populate menu... 297 | for(gCount=0, g=gList; g; g=g->next, gCount++) { 298 | if((mameArray[gCount].title)) { 299 | items[i] = new_item( 300 | mameArray[gCount].title, NULL); 301 | set_item_userptr(items[i], 302 | mameArray[gCount].g); 303 | i++; 304 | } 305 | } 306 | // mameArray is freed, but the title elements are not. 307 | // This is on purpose, not a memory leak. The titles 308 | // have been assigned to the ncruses menu items and need 309 | // to stay alloc'd; they'll be freed automatically when 310 | // the corresponding items are destroyed. 311 | free(mameArray); 312 | } 313 | 314 | return i; // Return next items[] index 315 | } 316 | 317 | // Given a Game struct and an output buffer, format a command string 318 | // for invoking advmame via system() 319 | static void mameCommand(Game *g, char *cmdline) { 320 | (void)sprintf(cmdline, "advmame -cfg %s %s", mameCfg, g->name); 321 | } 322 | 323 | // NES-specific globals and code ----------------------------------------- 324 | 325 | // fceu-specific filter function for scandir() -- given a dirent struct, 326 | // returns 1 if it's a likely ROM file candidate (ends in .zip or .nes). 327 | static int fceuFilter(const struct dirent *d) { 328 | static const char *ext[] = { "zip", "nes" }; 329 | char *ptr; 330 | int i; 331 | 332 | if(((d->d_type == DT_REG) || (d->d_type == DT_LNK)) && 333 | (d->d_name[0] != '.') && (ptr = strrchr(d->d_name,'.'))) { 334 | for(++ptr, i=0; inext) { 345 | if((str = strndup(gList->name, 346 | strrchr(gList->name,'.') - gList->name))) { 347 | items[i] = new_item(str, NULL); 348 | set_item_userptr(items[i++], gList); 349 | } 350 | } 351 | return i; // Return next items[] index 352 | } 353 | 354 | // Given a Game struct and an output buffer, format a command string 355 | // for invoking fceu via system() 356 | static void fceuCommand(Game *g, char *cmdline) { 357 | (void)sprintf(cmdline, "fceu \"%s/%s\"", 358 | emulator[g->emu].romPath, g->name); 359 | } 360 | 361 | 362 | // Utility functions ----------------------------------------------------- 363 | 364 | // Delete existing ROM list, scan all emulators' ROM folders, generate 365 | // new ROM menu for ncurses. 366 | int find_roms(void) { 367 | struct dirent **dirList; 368 | int i, e, nFiles, nGames = 0, nEmuTitles = 0; 369 | Game *g; // For traversing Game linked list 370 | WINDOW *scanWin; // Modal 'Scanning...' window 371 | 372 | if(noRomWin) { // Delete 'No ROMS' window if present 373 | delwin(noRomWin); 374 | noRomWin = NULL; 375 | werase(mainWin); 376 | box(mainWin, 0, 0); 377 | } 378 | 379 | if(items) { // Delete old ROM menu and contents, if any 380 | if(menu) { 381 | unpost_menu(menu); 382 | free_menu(menu); 383 | menu = NULL; 384 | } 385 | for(i=0; items[i]; i++) free_item(items[i]); 386 | free(items); 387 | items = NULL; 388 | } 389 | 390 | const char scanMsg[] = "Scanning ROM folder..."; 391 | scanWin = newwin(3, strlen(scanMsg) + 4, 392 | (LINES - 4) / 2 - 1, (COLS - strlen(scanMsg)) / 2 - 2); 393 | box(scanWin, 0, 0); 394 | mvwprintw(scanWin, 1, 2, scanMsg); 395 | 396 | wnoutrefresh(mainWin); 397 | wnoutrefresh(scanWin); 398 | doupdate(); 399 | 400 | delwin(scanWin); 401 | werase(mainWin); 402 | box(mainWin, 0, 0); 403 | 404 | for(e=0; enext; 409 | if(emulator[e].gameList->name) 410 | free(emulator[e].gameList->name); 411 | free(emulator[e].gameList); 412 | emulator[e].gameList = g; 413 | } 414 | 415 | // Scan ROM folder, build new gameList 416 | if((nFiles = scandir(emulator[e].romPath, &dirList, 417 | emulator[e].filter, emulator[e].compar)) > 0) { 418 | nEmuTitles++; 419 | // Copy dirent array to a Game linked list. 420 | while(nFiles--) { // Assembled in reverse 421 | if((g = (Game *)malloc(sizeof(Game)))) { 422 | if((g->name = strdup( 423 | dirList[nFiles]->d_name))) { 424 | g->emu = e; 425 | g->next = emulator[e].gameList; 426 | emulator[e].gameList = g; 427 | nGames++; // A winner is you 428 | } else { 429 | free(g); 430 | } 431 | } 432 | // dirList contents are freed as we go 433 | free(dirList[nFiles]); 434 | } 435 | free(dirList); 436 | } 437 | } 438 | 439 | // nGames is the total number of game files found. nEmuTitles 440 | // is the number of emulators for which games were found (ones 441 | // without games aren't listed in menu). If only one emulator 442 | // is active, set to 0 to convey that no title is needed. 443 | if(nEmuTitles == 1) nEmuTitles = 0; 444 | 445 | if(nGames && 446 | (items = (ITEM**)malloc((nGames+nEmuTitles+1) * sizeof(ITEM *)))) { 447 | i = 0; 448 | for(e=0; e 0), move the default 516 | // selection down one item -- the first is an emulator name, not 517 | // a game title. 518 | if(find_roms()) menu_driver(menu, REQ_DOWN_ITEM); 519 | 520 | for(;;) { 521 | switch(wgetch(mainWin)) { 522 | case KEY_DOWN: 523 | menu_driver(menu, REQ_DOWN_ITEM); 524 | if(!item_userptr(current_item(menu))) // Emu name 525 | menu_driver(menu, REQ_DOWN_ITEM); // Skip 526 | break; 527 | case KEY_UP: 528 | menu_driver(menu, REQ_UP_ITEM); 529 | if(!item_userptr(current_item(menu))) 530 | menu_driver(menu, REQ_UP_ITEM); 531 | break; 532 | case KEY_NPAGE: 533 | menu_driver(menu, REQ_SCR_DPAGE); 534 | break; 535 | case KEY_PPAGE: 536 | menu_driver(menu, REQ_SCR_UPAGE); 537 | break; 538 | case 'r': // Re-scan ROM folder 539 | if(find_roms()) menu_driver(menu, REQ_DOWN_ITEM); 540 | break; 541 | case 'R': // Rotate-and-reboot 542 | if(!geteuid()) { // Must be root 543 | clear(); 544 | refresh(); 545 | endwin(); 546 | for(i=0; iemu].command)(g, cmdline); 583 | i = system(cmdline); 584 | reset_prog_mode(); 585 | if(i) { // If error message, wait for input 586 | (void)printf("Press any button..."); 587 | fflush(stdout); 588 | while(!getch()); 589 | } 590 | 591 | delwin(launchWin); 592 | redrawwin(mainWin); 593 | } 594 | break; 595 | } 596 | wrefresh(mainWin); 597 | } 598 | 599 | return 0; 600 | } 601 | 602 | -------------------------------------------------------------------------------- /joyBonnet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Somewhat minimal Adafruit Joy Bonnet handler. Runs in background, 4 | # translates inputs from buttons and ADC joystick to virtual USB keyboard 5 | # events. 6 | # Prerequisites: 7 | # sudo apt-get install python-pip python-smbus python-dev 8 | # sudo pip install evdev 9 | # Be sure to enable I2C via raspi-config. Also, udev rules will need to 10 | # be set up per retrogame directions. 11 | # Credit to Pimoroni for Picade HAT scripts as starting point. 12 | 13 | import time 14 | import signal 15 | import os 16 | import sys 17 | from datetime import datetime 18 | 19 | try: 20 | from evdev import uinput, UInput, ecodes as e 21 | except ImportError: 22 | exit("This library requires the evdev module\nInstall with: sudo pip install evdev") 23 | 24 | try: 25 | import RPi.GPIO as gpio 26 | except ImportError: 27 | exit("This library requires the RPi.GPIO module\nInstall with: sudo pip install RPi.GPIO") 28 | 29 | try: 30 | from smbus import SMBus 31 | except ImportError: 32 | exit("This library requires the smbus module\nInstall with: sudo pip install smbus") 33 | 34 | DEBUG = False 35 | BOUNCE_TIME = 0.01 # Debounce time in seconds 36 | 37 | BUTTON_A = 12 38 | BUTTON_B = 6 39 | BUTTON_X = 16 40 | BUTTON_Y = 13 41 | SELECT = 20 42 | START = 26 43 | PLAYER1 = 23 44 | PLAYER2 = 22 45 | BUTTONS = [BUTTON_A, BUTTON_B, BUTTON_X, BUTTON_Y, SELECT, START, PLAYER1, PLAYER2] 46 | 47 | ANALOG_THRESH_NEG = -600 48 | ANALOG_THRESH_POS = 600 49 | analog_states = [False, False, False, False] # up down left right 50 | 51 | KEYS= { # EDIT KEYCODES IN THIS TABLE TO YOUR PREFERENCES: 52 | # See /usr/include/linux/input.h for keycode names 53 | # Keyboard Bonnet EmulationStation 54 | BUTTON_A: e.KEY_LEFTCTRL, # 'A' button 55 | BUTTON_B: e.KEY_LEFTALT, # 'B' button 56 | BUTTON_X: e.KEY_Z, # 'X' button 57 | BUTTON_Y: e.KEY_X, # 'Y' button 58 | SELECT: e.KEY_SPACE, # 'Select' button 59 | START: e.KEY_ENTER, # 'Start' button 60 | PLAYER1: e.KEY_1, # '#1' button 61 | PLAYER2: e.KEY_2, # '#2' button 62 | 1000: e.KEY_UP, # Analog up 63 | 1001: e.KEY_DOWN, # Analog down 64 | 1002: e.KEY_LEFT, # Analog left 65 | 1003: e.KEY_RIGHT, # Analog right 66 | } 67 | 68 | ###################################### ADS1015 microdriver ################################# 69 | # Register and other configuration values: 70 | ADS1x15_DEFAULT_ADDRESS = 0x48 71 | ADS1x15_POINTER_CONVERSION = 0x00 72 | ADS1x15_POINTER_CONFIG = 0x01 73 | 74 | ADS1015_REG_CONFIG_CQUE_NONE = 0x0003 # Disable the comparator and put ALERT/RDY in high state (default) 75 | ADS1015_REG_CONFIG_CLAT_NONLAT = 0x0000 # Non-latching comparator (default) 76 | ADS1015_REG_CONFIG_CPOL_ACTVLOW = 0x0000 # ALERT/RDY pin is low when active (default) 77 | ADS1015_REG_CONFIG_CMODE_TRAD = 0x0000 # Traditional comparator with hysteresis (default) 78 | ADS1015_REG_CONFIG_DR_1600SPS = 0x0080 # 1600 samples per second (default) 79 | ADS1015_REG_CONFIG_MODE_SINGLE = 0x0100 # Power-down single-shot mode (default) 80 | ADS1015_REG_CONFIG_GAIN_ONE = 0x0200 # gain of 1 81 | 82 | ADS1015_REG_CONFIG_MUX_SINGLE_0 = 0x4000 # channel 0 83 | ADS1015_REG_CONFIG_MUX_SINGLE_1 = 0x5000 # channel 1 84 | ADS1015_REG_CONFIG_MUX_SINGLE_2 = 0x6000 # channel 2 85 | ADS1015_REG_CONFIG_MUX_SINGLE_3 = 0x7000 # channel 3 86 | 87 | ADS1015_REG_CONFIG_OS_SINGLE = 0x8000 # start a single conversion 88 | 89 | ADS1015_REG_CONFIG_CHANNELS = (ADS1015_REG_CONFIG_MUX_SINGLE_0, ADS1015_REG_CONFIG_MUX_SINGLE_1, 90 | ADS1015_REG_CONFIG_MUX_SINGLE_2, ADS1015_REG_CONFIG_MUX_SINGLE_3) 91 | 92 | def ads_read(channel): 93 | #configdata = bus.read_i2c_block_data(ADS1x15_DEFAULT_ADDRESS, ADS1x15_POINTER_CONFIG, 2) 94 | #print("Getting config byte = 0x%02X%02X" % (configdata[0], configdata[1])) 95 | 96 | configword = ADS1015_REG_CONFIG_CQUE_NONE | ADS1015_REG_CONFIG_CLAT_NONLAT | ADS1015_REG_CONFIG_CPOL_ACTVLOW | ADS1015_REG_CONFIG_CMODE_TRAD | ADS1015_REG_CONFIG_DR_1600SPS | ADS1015_REG_CONFIG_MODE_SINGLE | ADS1015_REG_CONFIG_GAIN_ONE | ADS1015_REG_CONFIG_CHANNELS[channel] | ADS1015_REG_CONFIG_OS_SINGLE 97 | configdata = [configword >> 8, configword & 0xFF] 98 | 99 | #print("Setting config byte = 0x%02X%02X" % (configdata[0], configdata[1])) 100 | bus.write_i2c_block_data(ADS1x15_DEFAULT_ADDRESS, ADS1x15_POINTER_CONFIG, configdata) 101 | 102 | configdata = bus.read_i2c_block_data(ADS1x15_DEFAULT_ADDRESS, ADS1x15_POINTER_CONFIG, 2) 103 | #print("Getting config byte = 0x%02X%02X" % (configdata[0], configdata[1])) 104 | 105 | while True: 106 | try: 107 | configdata = bus.read_i2c_block_data(ADS1x15_DEFAULT_ADDRESS, ADS1x15_POINTER_CONFIG, 2) 108 | #print("Getting config byte = 0x%02X%02X" % (configdata[0], configdata[1])) 109 | if (configdata[0] & 0x80): 110 | break 111 | except: 112 | pass 113 | # read data out! 114 | analogdata = bus.read_i2c_block_data(ADS1x15_DEFAULT_ADDRESS, ADS1x15_POINTER_CONVERSION, 2) 115 | #print(analogdata), 116 | retval = (analogdata[0] << 8) | analogdata[1] 117 | retval /= 16 118 | #print("-> %d" %retval) 119 | return retval 120 | 121 | ######################## main program 122 | 123 | os.system("sudo modprobe uinput") 124 | 125 | bus = SMBus(1) 126 | 127 | # GPIO init 128 | gpio.setwarnings(False) 129 | gpio.setmode(gpio.BCM) 130 | gpio.setup(BUTTONS, gpio.IN, pull_up_down=gpio.PUD_UP) 131 | 132 | try: 133 | ui = UInput({e.EV_KEY: KEYS.values()}, name="retrogame", bustype=e.BUS_USB) 134 | except uinput.UInputError as e: 135 | sys.stdout.write(e.message) 136 | sys.stdout.write("Have you tried running as root? sudo {}".format(sys.argv[0])) 137 | sys.exit(0) 138 | 139 | def log(msg): 140 | sys.stdout.write(str(datetime.now())) 141 | sys.stdout.write(": ") 142 | sys.stdout.write(msg) 143 | sys.stdout.write("\n") 144 | sys.stdout.flush() 145 | 146 | def handle_button(pin): 147 | key = KEYS[pin] 148 | time.sleep(BOUNCE_TIME) 149 | if pin >= 1000: 150 | state = analog_states[pin-1000] 151 | else: 152 | state = 0 if gpio.input(pin) else 1 153 | ui.write(e.EV_KEY, key, state) 154 | ui.syn() 155 | if DEBUG: 156 | log("Pin: {}, KeyCode: {}, Event: {}".format(pin, key, 'press' if state else 'release')) 157 | 158 | 159 | for button in BUTTONS: 160 | gpio.add_event_detect(button, gpio.BOTH, callback=handle_button, bouncetime=1) 161 | 162 | while True: 163 | try: 164 | y = 800 - ads_read(0) 165 | x = ads_read(1) - 800 166 | except IOError: 167 | continue 168 | #print("(%d , %d)" % (x, y)) 169 | 170 | if (y > ANALOG_THRESH_POS) and not analog_states[0]: 171 | analog_states[0] = True 172 | handle_button(1000) # send UP press 173 | if (y < ANALOG_THRESH_POS) and analog_states[0]: 174 | analog_states[0] = False 175 | handle_button(1000) # send UP release 176 | if (y < ANALOG_THRESH_NEG) and not analog_states[1]: 177 | analog_states[1] = True 178 | handle_button(1001) # send DOWN press 179 | if (y > ANALOG_THRESH_NEG) and analog_states[1]: 180 | analog_states[1] = False 181 | handle_button(1001) # send DOWN release 182 | if (x < ANALOG_THRESH_NEG) and not analog_states[2]: 183 | analog_states[2] = True 184 | handle_button(1002) # send LEFT press 185 | if (x > ANALOG_THRESH_NEG) and analog_states[2]: 186 | analog_states[2] = False 187 | handle_button(1002) # send LEFT release 188 | if (x > ANALOG_THRESH_POS) and not analog_states[3]: 189 | analog_states[3] = True 190 | handle_button(1003) # send RIGHT press 191 | if (x < ANALOG_THRESH_POS) and analog_states[3]: 192 | analog_states[3] = False 193 | handle_button(1003) # send RIGHT release 194 | 195 | time.sleep(0.01) 196 | -------------------------------------------------------------------------------- /joyBonnetAsMouse.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Somewhat minimal Adafruit Joy Bonnet handler. Runs in background, 4 | # translates inputs from buttons and ADC joystick to virtual USB keyboard 5 | # events. 6 | # Prerequisites: 7 | # sudo apt-get install python-pip python-smbus python-dev 8 | # sudo pip install evdev 9 | # Be sure to enable I2C via raspi-config. Also, udev rules will need to 10 | # be set up per retrogame directions. 11 | # Credit to Pimoroni for Picade HAT scripts as starting point. 12 | 13 | import time 14 | import signal 15 | import os 16 | import sys 17 | from datetime import datetime 18 | 19 | try: 20 | from evdev import uinput, UInput, ecodes as e 21 | except ImportError: 22 | exit("This library requires the evdev module\nInstall with: sudo pip install evdev") 23 | 24 | try: 25 | import RPi.GPIO as gpio 26 | except ImportError: 27 | exit("This library requires the RPi.GPIO module\nInstall with: sudo pip install RPi.GPIO") 28 | 29 | try: 30 | from smbus import SMBus 31 | except ImportError: 32 | exit("This library requires the smbus module\nInstall with: sudo pip install smbus") 33 | 34 | DEBUG = False 35 | BOUNCE_TIME = 0.01 # Debounce time in seconds 36 | 37 | BUTTON_A = 12 38 | BUTTON_B = 6 39 | BUTTON_X = 16 40 | BUTTON_Y = 13 41 | SELECT = 20 42 | START = 26 43 | PLAYER1 = 23 44 | PLAYER2 = 22 45 | BUTTONS = [BUTTON_A, BUTTON_B, BUTTON_X, BUTTON_Y, SELECT, START, PLAYER1, PLAYER2] 46 | 47 | MOUSE_SENSITIVITY = 4 # 0-10 48 | MOUSE_DEADZONE = 40 # Values under this are zeroed 49 | 50 | 51 | KEYS= { # EDIT KEYCODES IN THIS TABLE TO YOUR PREFERENCES: 52 | # See /usr/include/linux/input.h for keycode names 53 | # Keyboard Bonnet EmulationStation 54 | BUTTON_A: e.KEY_RIGHT, # 'A' button as mouse click 55 | BUTTON_B: e.KEY_DOWN, # 'B' button as right click 56 | BUTTON_X: e.KEY_UP, # 'X' button 57 | BUTTON_Y: e.KEY_LEFT, # 'Y' button 58 | SELECT: e.BTN_RIGHT, # 'Select' button 59 | START: e.BTN_MOUSE, # 'Start' button 60 | PLAYER1: e.KEY_PAGEUP, # '#1' button 61 | PLAYER2: e.KEY_PAGEDOWN, # '#2' button 62 | } 63 | 64 | 65 | 66 | ###################################### ADS1015 microdriver ################################# 67 | # Register and other configuration values: 68 | ADS1x15_DEFAULT_ADDRESS = 0x48 69 | ADS1x15_POINTER_CONVERSION = 0x00 70 | ADS1x15_POINTER_CONFIG = 0x01 71 | 72 | ADS1015_REG_CONFIG_CQUE_NONE = 0x0003 # Disable the comparator and put ALERT/RDY in high state (default) 73 | ADS1015_REG_CONFIG_CLAT_NONLAT = 0x0000 # Non-latching comparator (default) 74 | ADS1015_REG_CONFIG_CPOL_ACTVLOW = 0x0000 # ALERT/RDY pin is low when active (default) 75 | ADS1015_REG_CONFIG_CMODE_TRAD = 0x0000 # Traditional comparator with hysteresis (default) 76 | ADS1015_REG_CONFIG_DR_1600SPS = 0x0080 # 1600 samples per second (default) 77 | ADS1015_REG_CONFIG_MODE_SINGLE = 0x0100 # Power-down single-shot mode (default) 78 | ADS1015_REG_CONFIG_GAIN_ONE = 0x0200 # gain of 1 79 | 80 | ADS1015_REG_CONFIG_MUX_SINGLE_0 = 0x4000 # channel 0 81 | ADS1015_REG_CONFIG_MUX_SINGLE_1 = 0x5000 # channel 1 82 | ADS1015_REG_CONFIG_MUX_SINGLE_2 = 0x6000 # channel 2 83 | ADS1015_REG_CONFIG_MUX_SINGLE_3 = 0x7000 # channel 3 84 | 85 | ADS1015_REG_CONFIG_OS_SINGLE = 0x8000 # start a single conversion 86 | 87 | ADS1015_REG_CONFIG_CHANNELS = (ADS1015_REG_CONFIG_MUX_SINGLE_0, ADS1015_REG_CONFIG_MUX_SINGLE_1, 88 | ADS1015_REG_CONFIG_MUX_SINGLE_2, ADS1015_REG_CONFIG_MUX_SINGLE_3) 89 | 90 | def ads_read(channel): 91 | #configdata = bus.read_i2c_block_data(ADS1x15_DEFAULT_ADDRESS, ADS1x15_POINTER_CONFIG, 2) 92 | #print("Getting config byte = 0x%02X%02X" % (configdata[0], configdata[1])) 93 | 94 | configword = ADS1015_REG_CONFIG_CQUE_NONE | ADS1015_REG_CONFIG_CLAT_NONLAT | ADS1015_REG_CONFIG_CPOL_ACTVLOW | ADS1015_REG_CONFIG_CMODE_TRAD | ADS1015_REG_CONFIG_DR_1600SPS | ADS1015_REG_CONFIG_MODE_SINGLE | ADS1015_REG_CONFIG_GAIN_ONE | ADS1015_REG_CONFIG_CHANNELS[channel] | ADS1015_REG_CONFIG_OS_SINGLE 95 | configdata = [configword >> 8, configword & 0xFF] 96 | 97 | #print("Setting config byte = 0x%02X%02X" % (configdata[0], configdata[1])) 98 | bus.write_i2c_block_data(ADS1x15_DEFAULT_ADDRESS, ADS1x15_POINTER_CONFIG, configdata) 99 | 100 | configdata = bus.read_i2c_block_data(ADS1x15_DEFAULT_ADDRESS, ADS1x15_POINTER_CONFIG, 2) 101 | #print("Getting config byte = 0x%02X%02X" % (configdata[0], configdata[1])) 102 | 103 | while True: 104 | try: 105 | configdata = bus.read_i2c_block_data(ADS1x15_DEFAULT_ADDRESS, ADS1x15_POINTER_CONFIG, 2) 106 | #print("Getting config byte = 0x%02X%02X" % (configdata[0], configdata[1])) 107 | if (configdata[0] & 0x80): 108 | break 109 | except: 110 | pass 111 | # read data out! 112 | analogdata = bus.read_i2c_block_data(ADS1x15_DEFAULT_ADDRESS, ADS1x15_POINTER_CONVERSION, 2) 113 | #print(analogdata), 114 | retval = (analogdata[0] << 8) | analogdata[1] 115 | retval /= 16 116 | #print("-> %d" %retval) 117 | return retval 118 | 119 | ######################## main program 120 | 121 | os.system("sudo modprobe uinput") 122 | 123 | bus = SMBus(1) 124 | 125 | # GPIO init 126 | gpio.setwarnings(False) 127 | gpio.setmode(gpio.BCM) 128 | gpio.setup(BUTTONS, gpio.IN, pull_up_down=gpio.PUD_UP) 129 | 130 | try: 131 | caps= {e.EV_KEY: KEYS.values(), 132 | e.EV_REL: [e.REL_X, e.REL_Y], 133 | } 134 | ui = UInput(caps, name="retrogame", bustype=e.BUS_USB) 135 | except uinput.UInputError as e: 136 | sys.stdout.write(e.message) 137 | sys.stdout.write("Have you tried running as root? sudo {}".format(sys.argv[0])) 138 | sys.exit(0) 139 | 140 | def log(msg): 141 | sys.stdout.write(str(datetime.now())) 142 | sys.stdout.write(": ") 143 | sys.stdout.write(msg) 144 | sys.stdout.write("\n") 145 | sys.stdout.flush() 146 | 147 | def handle_button(pin): 148 | key = KEYS[pin] 149 | time.sleep(BOUNCE_TIME) 150 | state = 0 if gpio.input(pin) else 1 151 | ui.write(e.EV_KEY, key, state) 152 | ui.syn() 153 | if DEBUG: 154 | log("Pin: {}, KeyCode: {}, Event: {}".format(pin, key, 'press' if state else 'release')) 155 | 156 | 157 | for button in BUTTONS: 158 | gpio.add_event_detect(button, gpio.BOTH, callback=handle_button, bouncetime=1) 159 | 160 | while True: 161 | try: 162 | y = 800 - ads_read(0) 163 | x = ads_read(1) - 800 164 | except IOError: 165 | continue 166 | # print("(%d , %d)" % (x, y)) 167 | 168 | 169 | if abs(x) < MOUSE_DEADZONE: 170 | x=0 171 | if abs(y) < MOUSE_DEADZONE: 172 | y=0 173 | x = x >> (10 - MOUSE_SENSITIVITY) 174 | y = y >> (10 - MOUSE_SENSITIVITY) 175 | ui.write(e.EV_REL, e.REL_X, x) 176 | ui.write(e.EV_REL, e.REL_Y, -y) 177 | ui.syn() 178 | time.sleep(0.01) 179 | -------------------------------------------------------------------------------- /keyTable.h: -------------------------------------------------------------------------------- 1 | // This file is software-generated; do not edit. See keyTableGen.sh instead. 2 | 3 | #include 4 | 5 | typedef struct { 6 | char *name; 7 | int value; 8 | } dict; 9 | 10 | dict keyTable[] = { 11 | { "RESERVED", KEY_RESERVED }, 12 | { "ESC", KEY_ESC }, 13 | { "1", KEY_1 }, 14 | { "2", KEY_2 }, 15 | { "3", KEY_3 }, 16 | { "4", KEY_4 }, 17 | { "5", KEY_5 }, 18 | { "6", KEY_6 }, 19 | { "7", KEY_7 }, 20 | { "8", KEY_8 }, 21 | { "9", KEY_9 }, 22 | { "0", KEY_0 }, 23 | { "MINUS", KEY_MINUS }, 24 | { "EQUAL", KEY_EQUAL }, 25 | { "BACKSPACE", KEY_BACKSPACE }, 26 | { "TAB", KEY_TAB }, 27 | { "Q", KEY_Q }, 28 | { "W", KEY_W }, 29 | { "E", KEY_E }, 30 | { "R", KEY_R }, 31 | { "T", KEY_T }, 32 | { "Y", KEY_Y }, 33 | { "U", KEY_U }, 34 | { "I", KEY_I }, 35 | { "O", KEY_O }, 36 | { "P", KEY_P }, 37 | { "LEFTBRACE", KEY_LEFTBRACE }, 38 | { "RIGHTBRACE", KEY_RIGHTBRACE }, 39 | { "ENTER", KEY_ENTER }, 40 | { "LEFTCTRL", KEY_LEFTCTRL }, 41 | { "A", KEY_A }, 42 | { "S", KEY_S }, 43 | { "D", KEY_D }, 44 | { "F", KEY_F }, 45 | { "G", KEY_G }, 46 | { "H", KEY_H }, 47 | { "J", KEY_J }, 48 | { "K", KEY_K }, 49 | { "L", KEY_L }, 50 | { "SEMICOLON", KEY_SEMICOLON }, 51 | { "APOSTROPHE", KEY_APOSTROPHE }, 52 | { "GRAVE", KEY_GRAVE }, 53 | { "LEFTSHIFT", KEY_LEFTSHIFT }, 54 | { "BACKSLASH", KEY_BACKSLASH }, 55 | { "Z", KEY_Z }, 56 | { "X", KEY_X }, 57 | { "C", KEY_C }, 58 | { "V", KEY_V }, 59 | { "B", KEY_B }, 60 | { "N", KEY_N }, 61 | { "M", KEY_M }, 62 | { "COMMA", KEY_COMMA }, 63 | { "DOT", KEY_DOT }, 64 | { "SLASH", KEY_SLASH }, 65 | { "RIGHTSHIFT", KEY_RIGHTSHIFT }, 66 | { "KPASTERISK", KEY_KPASTERISK }, 67 | { "LEFTALT", KEY_LEFTALT }, 68 | { "SPACE", KEY_SPACE }, 69 | { "CAPSLOCK", KEY_CAPSLOCK }, 70 | { "F1", KEY_F1 }, 71 | { "F2", KEY_F2 }, 72 | { "F3", KEY_F3 }, 73 | { "F4", KEY_F4 }, 74 | { "F5", KEY_F5 }, 75 | { "F6", KEY_F6 }, 76 | { "F7", KEY_F7 }, 77 | { "F8", KEY_F8 }, 78 | { "F9", KEY_F9 }, 79 | { "F10", KEY_F10 }, 80 | { "NUMLOCK", KEY_NUMLOCK }, 81 | { "SCROLLLOCK", KEY_SCROLLLOCK }, 82 | { "KP7", KEY_KP7 }, 83 | { "KP8", KEY_KP8 }, 84 | { "KP9", KEY_KP9 }, 85 | { "KPMINUS", KEY_KPMINUS }, 86 | { "KP4", KEY_KP4 }, 87 | { "KP5", KEY_KP5 }, 88 | { "KP6", KEY_KP6 }, 89 | { "KPPLUS", KEY_KPPLUS }, 90 | { "KP1", KEY_KP1 }, 91 | { "KP2", KEY_KP2 }, 92 | { "KP3", KEY_KP3 }, 93 | { "KP0", KEY_KP0 }, 94 | { "KPDOT", KEY_KPDOT }, 95 | { "ZENKAKUHANKAKU", KEY_ZENKAKUHANKAKU }, 96 | { "102ND", KEY_102ND }, 97 | { "F11", KEY_F11 }, 98 | { "F12", KEY_F12 }, 99 | { "RO", KEY_RO }, 100 | { "KATAKANA", KEY_KATAKANA }, 101 | { "HIRAGANA", KEY_HIRAGANA }, 102 | { "HENKAN", KEY_HENKAN }, 103 | { "KATAKANAHIRAGANA", KEY_KATAKANAHIRAGANA }, 104 | { "MUHENKAN", KEY_MUHENKAN }, 105 | { "KPJPCOMMA", KEY_KPJPCOMMA }, 106 | { "KPENTER", KEY_KPENTER }, 107 | { "RIGHTCTRL", KEY_RIGHTCTRL }, 108 | { "KPSLASH", KEY_KPSLASH }, 109 | { "SYSRQ", KEY_SYSRQ }, 110 | { "RIGHTALT", KEY_RIGHTALT }, 111 | { "LINEFEED", KEY_LINEFEED }, 112 | { "HOME", KEY_HOME }, 113 | { "UP", KEY_UP }, 114 | { "PAGEUP", KEY_PAGEUP }, 115 | { "LEFT", KEY_LEFT }, 116 | { "RIGHT", KEY_RIGHT }, 117 | { "END", KEY_END }, 118 | { "DOWN", KEY_DOWN }, 119 | { "PAGEDOWN", KEY_PAGEDOWN }, 120 | { "INSERT", KEY_INSERT }, 121 | { "DELETE", KEY_DELETE }, 122 | { "MACRO", KEY_MACRO }, 123 | #if 0 // Esoteric keys disabled, smaller program size; edit if needed 124 | { "MUTE", KEY_MUTE }, 125 | { "VOLUMEDOWN", KEY_VOLUMEDOWN }, 126 | { "VOLUMEUP", KEY_VOLUMEUP }, 127 | { "POWER", KEY_POWER }, 128 | { "KPEQUAL", KEY_KPEQUAL }, 129 | { "KPPLUSMINUS", KEY_KPPLUSMINUS }, 130 | { "PAUSE", KEY_PAUSE }, 131 | { "SCALE", KEY_SCALE }, 132 | { "KPCOMMA", KEY_KPCOMMA }, 133 | { "HANGEUL", KEY_HANGEUL }, 134 | { "HANGUEL", KEY_HANGUEL }, 135 | { "HANJA", KEY_HANJA }, 136 | { "YEN", KEY_YEN }, 137 | { "LEFTMETA", KEY_LEFTMETA }, 138 | { "RIGHTMETA", KEY_RIGHTMETA }, 139 | { "COMPOSE", KEY_COMPOSE }, 140 | { "STOP", KEY_STOP }, 141 | { "AGAIN", KEY_AGAIN }, 142 | { "PROPS", KEY_PROPS }, 143 | { "UNDO", KEY_UNDO }, 144 | { "FRONT", KEY_FRONT }, 145 | { "COPY", KEY_COPY }, 146 | { "OPEN", KEY_OPEN }, 147 | { "PASTE", KEY_PASTE }, 148 | { "FIND", KEY_FIND }, 149 | { "CUT", KEY_CUT }, 150 | { "HELP", KEY_HELP }, 151 | { "MENU", KEY_MENU }, 152 | { "CALC", KEY_CALC }, 153 | { "SETUP", KEY_SETUP }, 154 | { "SLEEP", KEY_SLEEP }, 155 | { "WAKEUP", KEY_WAKEUP }, 156 | { "FILE", KEY_FILE }, 157 | { "SENDFILE", KEY_SENDFILE }, 158 | { "DELETEFILE", KEY_DELETEFILE }, 159 | { "XFER", KEY_XFER }, 160 | { "PROG1", KEY_PROG1 }, 161 | { "PROG2", KEY_PROG2 }, 162 | { "WWW", KEY_WWW }, 163 | { "MSDOS", KEY_MSDOS }, 164 | { "COFFEE", KEY_COFFEE }, 165 | { "SCREENLOCK", KEY_SCREENLOCK }, 166 | { "ROTATE_DISPLAY", KEY_ROTATE_DISPLAY }, 167 | { "DIRECTION", KEY_DIRECTION }, 168 | { "CYCLEWINDOWS", KEY_CYCLEWINDOWS }, 169 | { "MAIL", KEY_MAIL }, 170 | { "BOOKMARKS", KEY_BOOKMARKS }, 171 | { "COMPUTER", KEY_COMPUTER }, 172 | { "BACK", KEY_BACK }, 173 | { "FORWARD", KEY_FORWARD }, 174 | { "CLOSECD", KEY_CLOSECD }, 175 | { "EJECTCD", KEY_EJECTCD }, 176 | { "EJECTCLOSECD", KEY_EJECTCLOSECD }, 177 | { "NEXTSONG", KEY_NEXTSONG }, 178 | { "PLAYPAUSE", KEY_PLAYPAUSE }, 179 | { "PREVIOUSSONG", KEY_PREVIOUSSONG }, 180 | { "STOPCD", KEY_STOPCD }, 181 | { "RECORD", KEY_RECORD }, 182 | { "REWIND", KEY_REWIND }, 183 | { "PHONE", KEY_PHONE }, 184 | { "ISO", KEY_ISO }, 185 | { "CONFIG", KEY_CONFIG }, 186 | { "HOMEPAGE", KEY_HOMEPAGE }, 187 | { "REFRESH", KEY_REFRESH }, 188 | { "EXIT", KEY_EXIT }, 189 | { "MOVE", KEY_MOVE }, 190 | { "EDIT", KEY_EDIT }, 191 | { "SCROLLUP", KEY_SCROLLUP }, 192 | { "SCROLLDOWN", KEY_SCROLLDOWN }, 193 | { "KPLEFTPAREN", KEY_KPLEFTPAREN }, 194 | { "KPRIGHTPAREN", KEY_KPRIGHTPAREN }, 195 | { "NEW", KEY_NEW }, 196 | { "REDO", KEY_REDO }, 197 | { "F13", KEY_F13 }, 198 | { "F14", KEY_F14 }, 199 | { "F15", KEY_F15 }, 200 | { "F16", KEY_F16 }, 201 | { "F17", KEY_F17 }, 202 | { "F18", KEY_F18 }, 203 | { "F19", KEY_F19 }, 204 | { "F20", KEY_F20 }, 205 | { "F21", KEY_F21 }, 206 | { "F22", KEY_F22 }, 207 | { "F23", KEY_F23 }, 208 | { "F24", KEY_F24 }, 209 | { "PLAYCD", KEY_PLAYCD }, 210 | { "PAUSECD", KEY_PAUSECD }, 211 | { "PROG3", KEY_PROG3 }, 212 | { "PROG4", KEY_PROG4 }, 213 | { "DASHBOARD", KEY_DASHBOARD }, 214 | { "SUSPEND", KEY_SUSPEND }, 215 | { "CLOSE", KEY_CLOSE }, 216 | { "PLAY", KEY_PLAY }, 217 | { "FASTFORWARD", KEY_FASTFORWARD }, 218 | { "BASSBOOST", KEY_BASSBOOST }, 219 | { "PRINT", KEY_PRINT }, 220 | { "HP", KEY_HP }, 221 | { "CAMERA", KEY_CAMERA }, 222 | { "SOUND", KEY_SOUND }, 223 | { "QUESTION", KEY_QUESTION }, 224 | { "EMAIL", KEY_EMAIL }, 225 | { "CHAT", KEY_CHAT }, 226 | { "SEARCH", KEY_SEARCH }, 227 | { "CONNECT", KEY_CONNECT }, 228 | { "FINANCE", KEY_FINANCE }, 229 | { "SPORT", KEY_SPORT }, 230 | { "SHOP", KEY_SHOP }, 231 | { "ALTERASE", KEY_ALTERASE }, 232 | { "CANCEL", KEY_CANCEL }, 233 | { "BRIGHTNESSDOWN", KEY_BRIGHTNESSDOWN }, 234 | { "BRIGHTNESSUP", KEY_BRIGHTNESSUP }, 235 | { "MEDIA", KEY_MEDIA }, 236 | { "SWITCHVIDEOMODE", KEY_SWITCHVIDEOMODE }, 237 | { "KBDILLUMTOGGLE", KEY_KBDILLUMTOGGLE }, 238 | { "KBDILLUMDOWN", KEY_KBDILLUMDOWN }, 239 | { "KBDILLUMUP", KEY_KBDILLUMUP }, 240 | { "SEND", KEY_SEND }, 241 | { "REPLY", KEY_REPLY }, 242 | { "FORWARDMAIL", KEY_FORWARDMAIL }, 243 | { "SAVE", KEY_SAVE }, 244 | { "DOCUMENTS", KEY_DOCUMENTS }, 245 | { "BATTERY", KEY_BATTERY }, 246 | { "BLUETOOTH", KEY_BLUETOOTH }, 247 | { "WLAN", KEY_WLAN }, 248 | { "UWB", KEY_UWB }, 249 | { "UNKNOWN", KEY_UNKNOWN }, 250 | { "VIDEO_NEXT", KEY_VIDEO_NEXT }, 251 | { "VIDEO_PREV", KEY_VIDEO_PREV }, 252 | { "BRIGHTNESS_CYCLE", KEY_BRIGHTNESS_CYCLE }, 253 | { "BRIGHTNESS_AUTO", KEY_BRIGHTNESS_AUTO }, 254 | { "BRIGHTNESS_ZERO", KEY_BRIGHTNESS_ZERO }, 255 | { "DISPLAY_OFF", KEY_DISPLAY_OFF }, 256 | { "WWAN", KEY_WWAN }, 257 | { "WIMAX", KEY_WIMAX }, 258 | { "RFKILL", KEY_RFKILL }, 259 | { "MICMUTE", KEY_MICMUTE }, 260 | { "OK", KEY_OK }, 261 | { "SELECT", KEY_SELECT }, 262 | { "GOTO", KEY_GOTO }, 263 | { "CLEAR", KEY_CLEAR }, 264 | { "POWER2", KEY_POWER2 }, 265 | { "OPTION", KEY_OPTION }, 266 | { "INFO", KEY_INFO }, 267 | { "TIME", KEY_TIME }, 268 | { "VENDOR", KEY_VENDOR }, 269 | { "ARCHIVE", KEY_ARCHIVE }, 270 | { "PROGRAM", KEY_PROGRAM }, 271 | { "CHANNEL", KEY_CHANNEL }, 272 | { "FAVORITES", KEY_FAVORITES }, 273 | { "EPG", KEY_EPG }, 274 | { "PVR", KEY_PVR }, 275 | { "MHP", KEY_MHP }, 276 | { "LANGUAGE", KEY_LANGUAGE }, 277 | { "TITLE", KEY_TITLE }, 278 | { "SUBTITLE", KEY_SUBTITLE }, 279 | { "ANGLE", KEY_ANGLE }, 280 | { "ZOOM", KEY_ZOOM }, 281 | { "MODE", KEY_MODE }, 282 | { "KEYBOARD", KEY_KEYBOARD }, 283 | { "SCREEN", KEY_SCREEN }, 284 | { "PC", KEY_PC }, 285 | { "TV", KEY_TV }, 286 | { "TV2", KEY_TV2 }, 287 | { "VCR", KEY_VCR }, 288 | { "VCR2", KEY_VCR2 }, 289 | { "SAT", KEY_SAT }, 290 | { "SAT2", KEY_SAT2 }, 291 | { "CD", KEY_CD }, 292 | { "TAPE", KEY_TAPE }, 293 | { "RADIO", KEY_RADIO }, 294 | { "TUNER", KEY_TUNER }, 295 | { "PLAYER", KEY_PLAYER }, 296 | { "TEXT", KEY_TEXT }, 297 | { "DVD", KEY_DVD }, 298 | { "AUX", KEY_AUX }, 299 | { "MP3", KEY_MP3 }, 300 | { "AUDIO", KEY_AUDIO }, 301 | { "VIDEO", KEY_VIDEO }, 302 | { "DIRECTORY", KEY_DIRECTORY }, 303 | { "LIST", KEY_LIST }, 304 | { "MEMO", KEY_MEMO }, 305 | { "CALENDAR", KEY_CALENDAR }, 306 | { "RED", KEY_RED }, 307 | { "GREEN", KEY_GREEN }, 308 | { "YELLOW", KEY_YELLOW }, 309 | { "BLUE", KEY_BLUE }, 310 | { "CHANNELUP", KEY_CHANNELUP }, 311 | { "CHANNELDOWN", KEY_CHANNELDOWN }, 312 | { "FIRST", KEY_FIRST }, 313 | { "LAST", KEY_LAST }, 314 | { "AB", KEY_AB }, 315 | { "NEXT", KEY_NEXT }, 316 | { "RESTART", KEY_RESTART }, 317 | { "SLOW", KEY_SLOW }, 318 | { "SHUFFLE", KEY_SHUFFLE }, 319 | { "BREAK", KEY_BREAK }, 320 | { "PREVIOUS", KEY_PREVIOUS }, 321 | { "DIGITS", KEY_DIGITS }, 322 | { "TEEN", KEY_TEEN }, 323 | { "TWEN", KEY_TWEN }, 324 | { "VIDEOPHONE", KEY_VIDEOPHONE }, 325 | { "GAMES", KEY_GAMES }, 326 | { "ZOOMIN", KEY_ZOOMIN }, 327 | { "ZOOMOUT", KEY_ZOOMOUT }, 328 | { "ZOOMRESET", KEY_ZOOMRESET }, 329 | { "WORDPROCESSOR", KEY_WORDPROCESSOR }, 330 | { "EDITOR", KEY_EDITOR }, 331 | { "SPREADSHEET", KEY_SPREADSHEET }, 332 | { "GRAPHICSEDITOR", KEY_GRAPHICSEDITOR }, 333 | { "PRESENTATION", KEY_PRESENTATION }, 334 | { "DATABASE", KEY_DATABASE }, 335 | { "NEWS", KEY_NEWS }, 336 | { "VOICEMAIL", KEY_VOICEMAIL }, 337 | { "ADDRESSBOOK", KEY_ADDRESSBOOK }, 338 | { "MESSENGER", KEY_MESSENGER }, 339 | { "DISPLAYTOGGLE", KEY_DISPLAYTOGGLE }, 340 | { "BRIGHTNESS_TOGGLE", KEY_BRIGHTNESS_TOGGLE }, 341 | { "SPELLCHECK", KEY_SPELLCHECK }, 342 | { "LOGOFF", KEY_LOGOFF }, 343 | { "DOLLAR", KEY_DOLLAR }, 344 | { "EURO", KEY_EURO }, 345 | { "FRAMEBACK", KEY_FRAMEBACK }, 346 | { "FRAMEFORWARD", KEY_FRAMEFORWARD }, 347 | { "CONTEXT_MENU", KEY_CONTEXT_MENU }, 348 | { "MEDIA_REPEAT", KEY_MEDIA_REPEAT }, 349 | { "10CHANNELSUP", KEY_10CHANNELSUP }, 350 | { "10CHANNELSDOWN", KEY_10CHANNELSDOWN }, 351 | { "IMAGES", KEY_IMAGES }, 352 | { "DEL_EOL", KEY_DEL_EOL }, 353 | { "DEL_EOS", KEY_DEL_EOS }, 354 | { "INS_LINE", KEY_INS_LINE }, 355 | { "DEL_LINE", KEY_DEL_LINE }, 356 | { "FN", KEY_FN }, 357 | { "FN_ESC", KEY_FN_ESC }, 358 | { "FN_F1", KEY_FN_F1 }, 359 | { "FN_F2", KEY_FN_F2 }, 360 | { "FN_F3", KEY_FN_F3 }, 361 | { "FN_F4", KEY_FN_F4 }, 362 | { "FN_F5", KEY_FN_F5 }, 363 | { "FN_F6", KEY_FN_F6 }, 364 | { "FN_F7", KEY_FN_F7 }, 365 | { "FN_F8", KEY_FN_F8 }, 366 | { "FN_F9", KEY_FN_F9 }, 367 | { "FN_F10", KEY_FN_F10 }, 368 | { "FN_F11", KEY_FN_F11 }, 369 | { "FN_F12", KEY_FN_F12 }, 370 | { "FN_1", KEY_FN_1 }, 371 | { "FN_2", KEY_FN_2 }, 372 | { "FN_D", KEY_FN_D }, 373 | { "FN_E", KEY_FN_E }, 374 | { "FN_F", KEY_FN_F }, 375 | { "FN_S", KEY_FN_S }, 376 | { "FN_B", KEY_FN_B }, 377 | { "BRL_DOT1", KEY_BRL_DOT1 }, 378 | { "BRL_DOT2", KEY_BRL_DOT2 }, 379 | { "BRL_DOT3", KEY_BRL_DOT3 }, 380 | { "BRL_DOT4", KEY_BRL_DOT4 }, 381 | { "BRL_DOT5", KEY_BRL_DOT5 }, 382 | { "BRL_DOT6", KEY_BRL_DOT6 }, 383 | { "BRL_DOT7", KEY_BRL_DOT7 }, 384 | { "BRL_DOT8", KEY_BRL_DOT8 }, 385 | { "BRL_DOT9", KEY_BRL_DOT9 }, 386 | { "BRL_DOT10", KEY_BRL_DOT10 }, 387 | { "NUMERIC_0", KEY_NUMERIC_0 }, 388 | { "NUMERIC_1", KEY_NUMERIC_1 }, 389 | { "NUMERIC_2", KEY_NUMERIC_2 }, 390 | { "NUMERIC_3", KEY_NUMERIC_3 }, 391 | { "NUMERIC_4", KEY_NUMERIC_4 }, 392 | { "NUMERIC_5", KEY_NUMERIC_5 }, 393 | { "NUMERIC_6", KEY_NUMERIC_6 }, 394 | { "NUMERIC_7", KEY_NUMERIC_7 }, 395 | { "NUMERIC_8", KEY_NUMERIC_8 }, 396 | { "NUMERIC_9", KEY_NUMERIC_9 }, 397 | { "NUMERIC_STAR", KEY_NUMERIC_STAR }, 398 | { "NUMERIC_POUND", KEY_NUMERIC_POUND }, 399 | { "NUMERIC_A", KEY_NUMERIC_A }, 400 | { "NUMERIC_B", KEY_NUMERIC_B }, 401 | { "NUMERIC_C", KEY_NUMERIC_C }, 402 | { "NUMERIC_D", KEY_NUMERIC_D }, 403 | { "CAMERA_FOCUS", KEY_CAMERA_FOCUS }, 404 | { "WPS_BUTTON", KEY_WPS_BUTTON }, 405 | { "TOUCHPAD_TOGGLE", KEY_TOUCHPAD_TOGGLE }, 406 | { "TOUCHPAD_ON", KEY_TOUCHPAD_ON }, 407 | { "TOUCHPAD_OFF", KEY_TOUCHPAD_OFF }, 408 | { "CAMERA_ZOOMIN", KEY_CAMERA_ZOOMIN }, 409 | { "CAMERA_ZOOMOUT", KEY_CAMERA_ZOOMOUT }, 410 | { "CAMERA_UP", KEY_CAMERA_UP }, 411 | { "CAMERA_DOWN", KEY_CAMERA_DOWN }, 412 | { "CAMERA_LEFT", KEY_CAMERA_LEFT }, 413 | { "CAMERA_RIGHT", KEY_CAMERA_RIGHT }, 414 | { "ATTENDANT_ON", KEY_ATTENDANT_ON }, 415 | { "ATTENDANT_OFF", KEY_ATTENDANT_OFF }, 416 | { "ATTENDANT_TOGGLE", KEY_ATTENDANT_TOGGLE }, 417 | { "LIGHTS_TOGGLE", KEY_LIGHTS_TOGGLE }, 418 | { "ALS_TOGGLE", KEY_ALS_TOGGLE }, 419 | { "BUTTONCONFIG", KEY_BUTTONCONFIG }, 420 | { "TASKMANAGER", KEY_TASKMANAGER }, 421 | { "JOURNAL", KEY_JOURNAL }, 422 | { "CONTROLPANEL", KEY_CONTROLPANEL }, 423 | { "APPSELECT", KEY_APPSELECT }, 424 | { "SCREENSAVER", KEY_SCREENSAVER }, 425 | { "VOICECOMMAND", KEY_VOICECOMMAND }, 426 | { "BRIGHTNESS_MIN", KEY_BRIGHTNESS_MIN }, 427 | { "BRIGHTNESS_MAX", KEY_BRIGHTNESS_MAX }, 428 | { "KBDINPUTASSIST_PREV", KEY_KBDINPUTASSIST_PREV }, 429 | { "KBDINPUTASSIST_NEXT", KEY_KBDINPUTASSIST_NEXT }, 430 | { "KBDINPUTASSIST_PREVGROUP", KEY_KBDINPUTASSIST_PREVGROUP }, 431 | { "KBDINPUTASSIST_NEXTGROUP", KEY_KBDINPUTASSIST_NEXTGROUP }, 432 | { "KBDINPUTASSIST_ACCEPT", KEY_KBDINPUTASSIST_ACCEPT }, 433 | { "KBDINPUTASSIST_CANCEL", KEY_KBDINPUTASSIST_CANCEL }, 434 | { "RIGHT_UP", KEY_RIGHT_UP }, 435 | { "RIGHT_DOWN", KEY_RIGHT_DOWN }, 436 | { "LEFT_UP", KEY_LEFT_UP }, 437 | { "LEFT_DOWN", KEY_LEFT_DOWN }, 438 | { "ROOT_MENU", KEY_ROOT_MENU }, 439 | { "MEDIA_TOP_MENU", KEY_MEDIA_TOP_MENU }, 440 | { "NUMERIC_11", KEY_NUMERIC_11 }, 441 | { "NUMERIC_12", KEY_NUMERIC_12 }, 442 | { "AUDIO_DESC", KEY_AUDIO_DESC }, 443 | { "3D_MODE", KEY_3D_MODE }, 444 | { "NEXT_FAVORITE", KEY_NEXT_FAVORITE }, 445 | { "STOP_RECORD", KEY_STOP_RECORD }, 446 | { "PAUSE_RECORD", KEY_PAUSE_RECORD }, 447 | { "VOD", KEY_VOD }, 448 | { "UNMUTE", KEY_UNMUTE }, 449 | { "FASTREVERSE", KEY_FASTREVERSE }, 450 | { "SLOWREVERSE", KEY_SLOWREVERSE }, 451 | { "DATA", KEY_DATA }, 452 | { "MIN_INTERESTING", KEY_MIN_INTERESTING }, 453 | { "MAX", KEY_MAX }, 454 | { "CNT", KEY_CNT }, 455 | #endif 456 | { NULL, -1 } // END-OF-LIST 457 | }; 458 | -------------------------------------------------------------------------------- /keyTableGen.sh: -------------------------------------------------------------------------------- 1 | echo "// This file is software-generated; do not edit. See $0 instead." 2 | echo 3 | echo "#include <$1>" 4 | echo 5 | echo "typedef struct {" 6 | echo "\tchar *name;" 7 | echo "\tint value;" 8 | echo "} dict;" 9 | echo 10 | echo "dict keyTable[] = {" 11 | grep '#define* KEY_' $1 | awk '{ print "\t{ \"" substr($2,5) "\", " $2 " }," }' | sed '/MACRO/a#if 0 // Esoteric keys disabled, smaller program size; edit if needed' 12 | echo "#endif\n\t{ NULL, -1 } // END-OF-LIST" 13 | echo "};" 14 | -------------------------------------------------------------------------------- /retrogame: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adafruit/Adafruit-Retrogame/89b32aff4e971d4ecd057d1359699eebbf3df8f5/retrogame -------------------------------------------------------------------------------- /retrogame.c: -------------------------------------------------------------------------------- 1 | /* 2 | ADAFRUIT RETROGAME UTILITY: remaps buttons on Raspberry Pi GPIO header to 3 | virtual USB keyboard presses. Great for classic game emulators! Retrogame 4 | is interrupt-driven and efficient (typically < 0.3% CPU use, even w/heavy 5 | button-mashing) and debounces inputs for glitch-free gaming. 6 | 7 | ****** IF YOU ARE SEARCHING FOR THE ioStandard[] OR ioTFT[] TABLES: ****** 8 | GPIO pin and key mapping is now set in a configuration file; there's no 9 | fixed table to edit in this code (as in earlier releases). An example 10 | config file is provided in retrogame.cfg. By default, retrogame looks for 11 | this file in /boot, but an alternate (full pathname) can be passed as a 12 | command line argument. 13 | 14 | Connect one side of button(s) to GND pin (there are several on the GPIO 15 | header, but see later notes) and the other side to GPIO pin of interest. 16 | Internal pullups are used; no resistors required. MCP23017 I2C port 17 | expanders are also supported (up to 8). Pin mapping is: 18 | 19 | 0 - 31 GPIO header 'P5' (Broadcom pin numbers) 20 | 32 - 47 MCP23017 at address 0x20 21 | 48 - 63 MCP23017 at address 0x21 22 | 64 - 79 MCP23017 at address 0x22 23 | 80 - 95 MCP23017 at address 0x23 24 | 96 - 111 MCP23017 at address 0x24 25 | 112 - 127 MCP23017 at address 0x25 26 | 128 - 143 MCP23017 at address 0x26 *** Arcade Bonnet default address 27 | 144 - 159 MCP23017 at address 0x27 *** Arcade Bonnet alt address 28 | 29 | Config file IRQ command must be used to bind a GPIO pin to an I2C address! 30 | 31 | Must be run as root, i.e. 'sudo ./retrogame &' or edit /etc/rc.local to 32 | launch automatically at system startup. 33 | 34 | Early Raspberry Pi Linux distributions might not have the uinput kernel 35 | module installed by default. To enable this, add a line to /etc/modules: 36 | 37 | uinput 38 | 39 | This code has bloated into a stinking seven-headed hydra. 40 | Please no more feature creep. 41 | 42 | Written by Phil Burgess for Adafruit Industries, distributed under BSD 43 | License. Adafruit invests time and resources providing this open source 44 | code, please support Adafruit and open-source hardware by purchasing 45 | products from Adafruit! 46 | 47 | Copyright (c) 2013, 2016 Adafruit Industries. 48 | All rights reserved. 49 | 50 | Redistribution and use in source and binary forms, with or without 51 | modification, are permitted provided that the following conditions are met: 52 | 53 | - Redistributions of source code must retain the above copyright notice, 54 | this list of conditions and the following disclaimer. 55 | - Redistributions in binary form must reproduce the above copyright notice, 56 | this list of conditions and the following disclaimer in the documentation 57 | and/or other materials provided with the distribution. 58 | 59 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 60 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 61 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 62 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 63 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 64 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 65 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 66 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 67 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 68 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 69 | POSSIBILITY OF SUCH DAMAGE. 70 | */ 71 | 72 | #include 73 | #include 74 | #include 75 | #include 76 | #include 77 | #include 78 | #include 79 | #include 80 | #include 81 | #include 82 | #include 83 | #include 84 | #include 85 | #include 86 | #include 87 | #include 88 | #include 89 | #include 90 | #include "keyTable.h" 91 | 92 | // Global variables and such ----------------------------------------------- 93 | 94 | bool 95 | running = true, // Signal handler will set false (exit) 96 | isEarlyPi = false; // true=Pi1Rev1, false=all other 97 | extern char 98 | *__progname, // Program name (for error reporting) 99 | *program_invocation_name; // Full name as invoked (path, etc.) 100 | char 101 | sysfs_root[] = "/sys/class/gpio", // Location of Sysfs GPIO files 102 | *cfgPath, // Directory containing config file 103 | *cfgName = NULL, // Name (no path) of config 104 | *cfgPathname, // Full path/name to config file 105 | debug = 0, // 0=off, 1=cfg file, 2=live buttons 106 | startupDebug = 0, // Initial debug level before cfg load 107 | readAddr = 0x10; // For MCP23017 reads (INTCAPA reg addr) 108 | int 109 | key[161], // Keycodes assigned to GPIO pins 110 | fileWatch, // inotify file descriptor 111 | keyfd1 = -1, // /dev/uinput file descriptor 112 | keyfd2 = -1, // /dev/input/eventX file descriptor 113 | keyfd = -1, // = (keyfd2 >= 0) ? keyfd2 : keyfd1; 114 | i2cfd[8], // /dev/i2c-1 MCP23017 file descriptors 115 | vulcanTime = 1500, // Pinch time in milliseconds 116 | debounceTime = 20, // 20 ms for button debouncing 117 | repTime1 = 500, // Key hold time to begin repeat 118 | repTime2 = 100; // Time between key repetitions 119 | // Note: auto-repeat is for navigating the game-selection menu using the 120 | // 'gamera' utility; MAME disregards key repeat events (as it should). 121 | uint32_t 122 | intstate[5], // Button last-read state (bitmask) 123 | extstate[5], // Button debounced state 124 | vulcanMask[5], // Bitmask of 'Vulcan nerve pinch' keys 125 | mcpMask = 0; // Bitmask of GPIOs assigned to MCP IRQs 126 | uint8_t 127 | mcpI2C[32]; // GPIO index to MCP23017 I2C addr 128 | volatile unsigned int 129 | *gpio = NULL; // GPIO register table 130 | struct pollfd 131 | p[35]; // File descriptors for poll() 132 | 133 | enum commandNum { 134 | CMD_NONE, // Used during config file read (no command ID'd yet) 135 | CMD_KEY, // Key-to-GPIO mapping command 136 | CMD_IRQ, // MCP23017 IRQ pin & address assignment 137 | CMD_GND, // Pin-to-ground assignment 138 | CMD_DEBUG // Set debug level 139 | }; 140 | 141 | // dict of config file commands that AREN'T keys (KEY_*) 142 | dict command[] = { // Struct is defined in keyTable.h 143 | { "GND" , CMD_GND }, 144 | { "GROUND" , CMD_GND }, 145 | { "IRQ" , CMD_IRQ }, 146 | { "DEBUG" , CMD_DEBUG }, 147 | // Might add commands here for fine-tuning debounce & repeat settings 148 | { NULL , -1 } }; // END-OF-LIST 149 | 150 | #define GPIO_BASE 0x200000 151 | #define BLOCK_SIZE (4*1024) 152 | #define GPPUD (0x94 / 4) 153 | #define GPPUDCLK0 (0x98 / 4) 154 | #define PULLUPDN_OFFSET_2711_0 57 155 | #define PULLUPDN_OFFSET_2711_1 58 156 | #define PULLUPDN_OFFSET_2711_2 59 157 | #define PULLUPDN_OFFSET_2711_3 60 158 | 159 | #define IODIRA 0x00 160 | #define IOCONA 0x0A 161 | 162 | #define GND KEY_CNT 163 | 164 | // Debug levels: 0 = off, 1 = config file errors, 2 = + config file status, 165 | // 3 = + report button states 'live'. 166 | 167 | 168 | // Some utility functions -------------------------------------------------- 169 | 170 | // Detect Pi board type. Not detailed, just enough for GPIO compatibilty: 171 | // true = Pi 1 Model B revision 1 (some GPIO pin numbers were different) 172 | // false = All other board types 173 | static bool earlyPiDetect(void) { 174 | FILE *fp; 175 | char buf[1024], *ptr; 176 | int n; 177 | bool isEarly = false; // Assume "modern" Pi by default 178 | 179 | // Relies on info in /proc/cmdline. If this becomes unreliable 180 | // in the future, alt code below uses /proc/cpuinfo if any better. 181 | #if 1 182 | if((fp = fopen("/proc/cmdline", "r"))) { 183 | while(fgets(buf, sizeof(buf), fp)) { 184 | if((ptr = strstr(buf, "boardrev=")) && 185 | (sscanf(&ptr[9], "%x", &n) == 1) && 186 | ((n == 0x02) || (n == 0x03))) { 187 | isEarly = true; // Appears to be an early Pi 188 | break; 189 | } 190 | } 191 | fclose(fp); 192 | } 193 | #else 194 | char s[8]; 195 | if((fp = fopen("/proc/cpuinfo", "r"))) { 196 | while(fgets(buf, sizeof(buf), fp)) { 197 | if((ptr = strstr(buf, "Revision")) && 198 | (sscanf(&ptr[8], " : %x", &n) == 1) && 199 | ((n == 0x02) || (n == 0x03))) { 200 | isEarly = true; // Appears to be an early Pi 201 | break; 202 | } 203 | } 204 | fclose(fp); 205 | } 206 | #endif 207 | 208 | return isEarly; 209 | } 210 | 211 | // Set one GPIO pin attribute through the Sysfs interface. 212 | static int pinSetup(int pin, char *attr, char *value) { 213 | char filename[50]; 214 | int fd, w, len = strlen(value); 215 | sprintf(filename, "%s/gpio%d/%s", sysfs_root, pin, attr); 216 | if((fd = open(filename, O_WRONLY)) < 0) return -1; 217 | w = write(fd, value, len); 218 | close(fd); 219 | return (w != len); // 0 = success 220 | } 221 | 222 | // Configure GPIO internal pull up/down/none 223 | static void pull(int bitmask, int state) { 224 | if(gpio[PULLUPDN_OFFSET_2711_3] != 0x6770696f) { 225 | // Pi 4 insights from RPi.GPIO: 226 | unsigned int pull = state ? (3 - state) : state; 227 | for(int bit=0; bit<32; bit++) { 228 | if(!(bitmask & (1UL << bit))) continue; 229 | int pullreg = PULLUPDN_OFFSET_2711_0 + (bit >> 4); 230 | int pullshift = (bit & 0xF) << 1; 231 | unsigned int pullbits; 232 | pullbits = gpio[pullreg]; 233 | pullbits &= ~(3 << pullshift); 234 | pullbits |= (pull << pullshift); 235 | gpio[pullreg] = pullbits; 236 | } 237 | } else { 238 | // Legacy pull up/down method: 239 | volatile unsigned char shortWait; 240 | gpio[GPPUD] = state; // 2=up, 1=down, 0=none 241 | for(shortWait=150;--shortWait;); // Min 150 cycle wait 242 | gpio[GPPUDCLK0] = bitmask; // Set pullup mask 243 | for(shortWait=150;--shortWait;); // Wait again 244 | gpio[GPPUD] = 0; // Reset pullup registers 245 | gpio[GPPUDCLK0] = 0; 246 | } 247 | } 248 | 249 | // Restore GPIO and uinput to startup state; un-export any Sysfs pins used, 250 | // don't leave any filesystem cruft; restore any GND pins to inputs and 251 | // disable previously-set pull-ups. Write errors are ignored as pins may be 252 | // in a partially-initialized state. 253 | static void pinConfigUnload() { 254 | char buf[50]; 255 | int fd, i; 256 | 257 | if(debug >= 2) printf("%s: Unloading config\n", __progname); 258 | 259 | // Close GPIO file descriptors 260 | for(i=0; i<32; i++) { 261 | if(p[i].fd >= 0) { 262 | close(p[i].fd); 263 | p[i].fd = -1; 264 | } 265 | p[i].events = p[i].revents = 0; 266 | } 267 | 268 | // Close uinput file descriptors 269 | keyfd = -1; 270 | if(keyfd2 >= 0) { 271 | close(keyfd2); 272 | keyfd2 = -1; 273 | } 274 | if(keyfd1 >= 0) { 275 | ioctl(keyfd1, UI_DEV_DESTROY); 276 | close(keyfd1); 277 | keyfd1 = -1; 278 | } 279 | 280 | // Un-export GPIO pins (0-31) 281 | sprintf(buf, "%s/unexport", sysfs_root); 282 | if((fd = open(buf, O_WRONLY)) >= 0) { 283 | for(i=0; i<32; i++) { 284 | // Restore GND items to inputs 285 | if(key[i] >= GND) pinSetup(i, "direction", "in"); 286 | // And un-export all items regardless 287 | sprintf(buf, "%d", i); 288 | write(fd, buf, strlen(buf)); 289 | } 290 | close(fd); 291 | } 292 | 293 | // Disable GPIO pullups (0-31) 294 | uint32_t mask = vulcanMask[0] | mcpMask; 295 | for(i=0; i<32; i++) { 296 | if((key[i] > KEY_RESERVED) && (key[i] < GND)) 297 | mask |= (1 << i); 298 | } 299 | pull(mask, 0); // Disable pullups 300 | 301 | // Do some GPIO dis-configuration for any MCO23017(s). 302 | // GNDs are set back to inputs; other config (pullups, etc.) 303 | // are currently left in whatever state. 304 | for(i=0; i<8; i++) { 305 | if(i2cfd[i] > 0) { 306 | // Determine which bits were previously set GND 307 | uint8_t j; 308 | uint16_t gndMask = 0; 309 | for(j=0; j<16; j++) { // 16 bits per MCP 310 | if(key[32 + i * 16 + j] == GND) 311 | gndMask |= (1 << j); 312 | } 313 | // Read chip config 314 | uint8_t cfg[3]; 315 | cfg[0] = IODIRA; 316 | write(i2cfd[i], cfg, 1); 317 | read(i2cfd[i], &cfg[1], sizeof(cfg) - 1); 318 | // Change IODIRA,B GND bits back to inputs 319 | cfg[1] = (cfg[1] | gndMask ); 320 | cfg[2] = (cfg[2] | (gndMask >> 8)); 321 | // Write to chip, close device 322 | write(i2cfd[i], cfg, sizeof(cfg)); 323 | close(i2cfd[i]); 324 | i2cfd[i] = 0; 325 | } 326 | } 327 | 328 | // Reset pin-and-key-related globals 329 | for(i=0; i<161; i++) key[i] = KEY_RESERVED; 330 | memset(intstate , 0, sizeof(intstate)); 331 | memset(extstate , 0, sizeof(extstate)); 332 | memset(vulcanMask, 0, sizeof(vulcanMask)); 333 | memset(mcpI2C , 0, sizeof(mcpI2C)); 334 | memset(i2cfd , 0, sizeof(i2cfd)); 335 | mcpMask = 0; 336 | } 337 | 338 | // Quick-n-dirty error reporter; print message, clean up and exit. 339 | static void err(char *msg) { 340 | printf("%s: %s. Try 'sudo %s' ?\n", __progname, msg, 341 | program_invocation_name); 342 | if(gpio) pinConfigUnload(); 343 | exit(1); 344 | } 345 | 346 | // Filter function for scandir(), identifies possible device candidates for 347 | // simulated keypress events (distinct from actual USB keyboard(s)). 348 | static int filter1(const struct dirent *d) { 349 | if(!strncmp(d->d_name, "input", 5)) { // Name usu. 'input' + # 350 | // Read contents of 'name' file inside this subdirectory, 351 | // if it matches the retrogame executable, that's probably 352 | // the device we want... 353 | char filename[100], line[100]; 354 | FILE *fp; 355 | sprintf(filename, "/sys/devices/virtual/input/%s/name", 356 | d->d_name); 357 | memset(line, 0, sizeof(line)); 358 | if((fp = fopen(filename, "r"))) { 359 | fgets(line, sizeof(line), fp); 360 | fclose(fp); 361 | } 362 | if(!strncmp(line, __progname, strlen(__progname))) return 1; 363 | } 364 | return 0; 365 | } 366 | 367 | // A second scandir() filter, checks for filename of 'event' + # 368 | static int filter2(const struct dirent *d) { 369 | return !strncmp(d->d_name, "event", 5); 370 | } 371 | 372 | // Search for name in dictionary, return assigned value (-1 = not found) 373 | static int dictSearch(char *str, dict *d) { 374 | int i; 375 | for(i=0; d[i].name && strcasecmp(str, d[i].name); i++); 376 | return d[i].value; 377 | } 378 | 379 | // If this is a "Revision 1" Pi board (no mounting holes), remap certain 380 | // pin numbers for compatibility. Can then use 'modern' pin numbers 381 | // regardless of board type. 382 | static int pinRemap(int i) { 383 | if(isEarlyPi) { 384 | if (i == 2) return 0; 385 | else if(i == 3) return 1; 386 | else if(i == 27) return 21; 387 | } 388 | return i; 389 | } 390 | 391 | // Config file handlage ---------------------------------------------------- 392 | 393 | // Load pin/key configuration from cfgPathname. 394 | static void pinConfigLoad() { 395 | 396 | // Config file format is super simple, just per-line keyword and 397 | // argument(s) with whitespace delimiters...can parse it ourselves 398 | // here. Configuration libraries such as libconfig, libconfuse 399 | // have some potent features but enforce a correspondingly more 400 | // exacting syntax on the user; do not want if we can avoid it. 401 | 402 | FILE *fp; 403 | char buf[50]; 404 | enum commandNum cmd = CMD_NONE; 405 | int stringLen = 0, 406 | wordCount = 0, 407 | keyCode = KEY_RESERVED, 408 | i, c, k, fd, bitmask, dLevel = -1, 409 | mcpPin = -1, mcpAddr = -1; 410 | bool readingString = false, 411 | isComment = false; 412 | uint32_t pinMask[5]; 413 | 414 | if(debug >= 2) printf("%s: Loading config\n", __progname); 415 | 416 | // Read config file into key[] table ------------------------------- 417 | 418 | if(NULL == (fp = fopen(cfgPathname, "r"))) { 419 | if(debug >= 1) printf("%s: could not open config file '%s' " 420 | "(not fatal, continuing)\n", __progname, cfgPathname); 421 | return; // Not fatal; file might be created later 422 | } 423 | 424 | do { // Deep nesting, please excuse shift to two-space indents... 425 | c = getc(fp); 426 | if(isspace(c) || (c <= 0)) { // If whitespace char... 427 | if(readingString && !isComment) { 428 | // Switching from string-reading to whitespace-skipping. 429 | // Cap off & process current string, reset readingString flag. 430 | buf[stringLen] = 0; 431 | if(wordCount == 1) { 432 | // First word on line. Search key dict, then command dict 433 | memset(pinMask, 0, sizeof(pinMask)); 434 | keyCode = KEY_RESERVED; 435 | if((k = dictSearch(buf, keyTable)) >= 0) { 436 | // Start of key command 437 | cmd = CMD_KEY; 438 | keyCode = k; 439 | } else if((k = dictSearch(buf, command)) >= 0) { 440 | // Not a key, is other command (e.g. GND, DEBUG) 441 | cmd = k; 442 | } else if(debug >= 1) { 443 | printf("%s: unknown key or command '%s' (not fatal, " 444 | "continuing)\n", __progname, buf); 445 | } 446 | } else { 447 | // Word #2+ on line; Certain commands may accumulate 448 | // values (e.g. keys w/pinch). 449 | char *endptr; 450 | int arg = strtol(buf, &endptr, 0); 451 | switch(cmd) { 452 | case CMD_KEY: 453 | case CMD_GND: 454 | if((*endptr) || (arg < 0) || (arg > 159)) { 455 | // Non-NUL character indicates not full string 456 | // was parsed, i.e. bad numeric input. 457 | if(debug >= 1) { 458 | printf("%s: invalid pin '%s' (not fatal, " 459 | "continuing)\n", __progname, buf); 460 | } 461 | } else { 462 | // Add pin # (in 'arg') to list 463 | arg = pinRemap(arg); // Handle early Pi boards 464 | pinMask[arg/32] |= (1 << (arg&31)); 465 | } 466 | break; 467 | case CMD_IRQ: 468 | switch(wordCount) { 469 | case 2: // word 2 = GPIO pin number for IRQ, MUST be 0-31 470 | if((*endptr) || (arg < 0) || (arg > 31)) { 471 | if(debug >= 1) { 472 | printf("%s: invalid pin '%s' (not fatal, " 473 | "continuing)\n", __progname, buf); 474 | } 475 | } else { 476 | mcpPin = pinRemap(arg); // Handle early Pi boards 477 | } 478 | break; 479 | case 3: // word 3 = I2C addr, must be 0-7 or 0x20-0x27 480 | if((*endptr) || (arg < 0) || (arg > 0x27) || 481 | ((arg > 7) && (arg < 0x20))) { 482 | if(debug >= 1) { 483 | printf("%s: invalid I2C address '%s' (not fatal, " 484 | "continuing)\n", __progname, buf); 485 | } 486 | } else { 487 | mcpAddr = (arg < 0x20) ? (arg + 0x20) : arg; 488 | } 489 | break; 490 | default: 491 | if(debug >= 1) { 492 | printf("%s: extraneous parameter '%s' (not fatal, " 493 | "continuing)\n", __progname, buf); 494 | } 495 | break; 496 | } 497 | break; 498 | case CMD_DEBUG: 499 | if((*endptr) || (arg < 0) || (arg > 3)) { 500 | if(debug >= 1) { 501 | printf("%s: invalid debug level '%s' " 502 | "(not fatal, continuing)\n", __progname, buf); 503 | } 504 | } else { 505 | dLevel = arg; 506 | } 507 | break; 508 | default: 509 | break; 510 | } 511 | } 512 | readingString = false; 513 | } 514 | if((c == '\n') || (c <= 0)) { // If EOF or EOL 515 | // Execute last line if useful command 516 | switch(cmd) { 517 | case CMD_KEY: 518 | // Count number of pins on line (k) 519 | for(k=i=0; i<160; i++) { 520 | if(pinMask[i/32] & (1 << (i&31))) { 521 | k++; 522 | // Un-assign any pins previously assigned GND. 523 | if(key[i] == GND) key[i] = KEY_RESERVED; 524 | } 525 | } 526 | if(k == 1) { // Key assigned to one pin 527 | for(i=0; !(pinMask[i/32] & (1<<(i&31))); i++); // Find bit 528 | key[i] = keyCode; 529 | if(debug >= 2) { 530 | printf("%s: virtual key %d assigned to GPIO%02d\n", 531 | __progname, keyCode, i); 532 | } 533 | } else if(k > 1) { 534 | memcpy(vulcanMask, pinMask, sizeof(pinMask)); 535 | key[160] = keyCode; 536 | if(debug >= 2) { 537 | printf("%s: virtual key %d has GPIO bitmask " 538 | "%04X%04X%04X%04X%04X\n", __progname, key[160], 539 | vulcanMask[4], vulcanMask[3], vulcanMask[2], 540 | vulcanMask[1], vulcanMask[0]); 541 | } 542 | } 543 | break; 544 | case CMD_IRQ: 545 | if((mcpPin >= 0) && (mcpAddr >= 0)) { // Got all params? 546 | if(debug >= 2) { 547 | printf("%s: MCP23017 on GPIO%02d, I2C address 0x%02X\n", 548 | __progname, mcpPin, mcpAddr); 549 | } 550 | mcpI2C[mcpPin] = mcpAddr; // GPIO pin # to I2C address 551 | mcpMask |= (1 << mcpPin); 552 | mcpPin = mcpAddr = -1; 553 | } 554 | break; 555 | case CMD_GND: 556 | // One or more GND pins 557 | for(i=0; i<160; i++) { 558 | if(pinMask[i/32] & (1 << (i&31))) { 559 | key[i] = GND; 560 | if(debug >= 2) { 561 | printf("%s: GPIO%02d assigned GND\n", __progname, i); 562 | } 563 | } 564 | } 565 | // Clear any vulcanMask bits that are now GNDs 566 | for(i=k=0; i<5; i++) { 567 | vulcanMask[i] &= ~pinMask[i]; 568 | if(vulcanMask[i]) k = 1; 569 | } 570 | if(!k) key[160] = KEY_RESERVED; // All vulcan bits clobbered 571 | break; 572 | case CMD_DEBUG: 573 | if(debug || (dLevel > 0)) { 574 | printf("%s: debug level %d\n", __progname, dLevel); 575 | } 576 | debug = dLevel; 577 | break; 578 | default: 579 | break; 580 | } 581 | // Reset ALL string-reading flags 582 | readingString = false; 583 | stringLen = 0; 584 | wordCount = 0; 585 | isComment = false; 586 | cmd = CMD_NONE; 587 | } 588 | } else { // Non-whitespace char 589 | if(isComment) continue; 590 | if(!readingString) { 591 | // Switching from whitespace-skipping 592 | // to string-reading. Reset string. 593 | readingString = true; 594 | stringLen = 0; 595 | // Is it beginning of a comment? 596 | // If so, ignore chars to next EOL. 597 | if(c == '#') { 598 | isComment = true; 599 | continue; 600 | } 601 | wordCount++; 602 | } 603 | // Append characer to current string 604 | if(stringLen < (sizeof(buf) - 1)) buf[stringLen++] = c; 605 | } 606 | } while(c > 0); 607 | 608 | fclose(fp); 609 | 610 | // If debug was previously set (either in file or by starting 611 | // in foreground) but line is now deleted, set debug level to 612 | // whatever its startup case was. 613 | if(debug && (dLevel < 0)) { 614 | debug = startupDebug; 615 | printf("%s: debug level %d\n", __progname, debug); 616 | } 617 | 618 | // Set up GPIO ----------------------------------------------------- 619 | 620 | bitmask = vulcanMask[0] | mcpMask; 621 | for(i=0; i<32; i++) { 622 | if((key[i] > KEY_RESERVED) && (key[i] < GND)) 623 | bitmask |= (1 << i); 624 | } 625 | pull(bitmask, 2); // Enable pullups on input pins 626 | for(i=0; (i<5) && !vulcanMask[i]; i++); // If no vulcanMask bits, 627 | if(i >= 5) key[160] = KEY_RESERVED; // make sure no vulcanKey 628 | // Pullups on MCP23017 devices will be a separate pass later 629 | 630 | // All other GPIO config is handled through the sysfs interface. 631 | 632 | sprintf(buf, "%s/export", sysfs_root); 633 | if((fd = open(buf, O_WRONLY)) < 0) // Open Sysfs export file 634 | err("Can't open GPIO export file"); 635 | intstate[0] = 0; 636 | for(i=0; i<32; i++) { 637 | if((key[i] == KEY_RESERVED) && !(bitmask & (1<= GND) { 643 | // Set pin to output, value 0 (ground) 644 | if(pinSetup(i, "direction", "out") || 645 | pinSetup(i, "value" , "0")) 646 | err("Pin config failed (GND)"); 647 | } else { 648 | // Set pin to input, detect edge events 649 | char x; 650 | // Plain GPIOs: detect both RISING and FALLING 651 | // edges. Port expanders: detect FALLING only. 652 | if(pinSetup(i, "direction", "in") || 653 | pinSetup(i, "edge", 654 | (mcpMask & (1<= 0) { 674 | (void)ioctl(keyfd1, UI_SET_EVBIT, EV_KEY); 675 | for(i=0; i<161; i++) { 676 | if((key[i] >= KEY_RESERVED) && (key[i] < GND)) 677 | (void)ioctl(keyfd1, UI_SET_KEYBIT, key[i]); 678 | } 679 | struct uinput_user_dev uidev; 680 | memset(&uidev, 0, sizeof(uidev)); 681 | snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, "retrogame"); 682 | uidev.id.bustype = BUS_USB; 683 | uidev.id.vendor = 0x1; 684 | uidev.id.product = 0x1; 685 | uidev.id.version = 1; 686 | if(write(keyfd1, &uidev, sizeof(uidev)) < 0) 687 | err("write failed"); 688 | if(ioctl(keyfd1, UI_DEV_CREATE) < 0) 689 | err("DEV_CREATE failed"); 690 | if(debug >= 3) printf("%s: uidev init OK\n", __progname); 691 | } 692 | 693 | // SDL2 (used by some newer emulators) wants /dev/input/eventX 694 | // instead -- BUT -- this only exists if there's a physical USB 695 | // keyboard attached or if the above code has run and created a 696 | // virtual keyboard. On older systems this method doesn't apply, 697 | // events can be sent to the keyfd1 virtual keyboard above...so, 698 | // this code looks for an eventX device and (if present) will use 699 | // that as the destination for events, else fallback on keyfd1. 700 | 701 | // The 'X' in eventX is a unique identifier (typically a numeric 702 | // digit or two) for each input device, dynamically assigned as 703 | // USB input devices are plugged in or disconnected (or when the 704 | // above code runs, creating a virtual keyboard). As it's 705 | // dynamically assigned, we can't rely on a fixed number -- it 706 | // will vary if there's a keyboard connected at startup. 707 | 708 | struct dirent **namelist; 709 | int n; 710 | char evName[100] = ""; 711 | 712 | if((n = scandir("/sys/devices/virtual/input", 713 | &namelist, filter1, NULL)) > 0) { 714 | // Got a list of device(s). In theory there should 715 | // be only one that makes it through the filter (name 716 | // matches retrogame)...if there's multiples, only 717 | // the first is used. (namelist can then be freed) 718 | char path[100]; 719 | sprintf(path, "/sys/devices/virtual/input/%s", 720 | namelist[0]->d_name); 721 | for(i=0; i 0) { 727 | sprintf(evName, "/dev/input/%s", 728 | namelist[0]->d_name); 729 | for(i=0; i=0; i--) { 745 | sprintf(buf, "/dev/input/event%d", i); 746 | if(!stat(buf, &st)) break; // last valid device 747 | } 748 | strcpy(evName, (i >= 0) ? buf : "/dev/input/event0"); 749 | } 750 | 751 | keyfd2 = open(evName, O_WRONLY | O_NONBLOCK); 752 | keyfd = (keyfd2 >= 0) ? keyfd2 : keyfd1; 753 | // keyfd1 and 2 are global and are held open (as a destination for 754 | // key events) until pinConfigUnload() is called. 755 | if((debug >= 3) && keyfd2) printf("%s: SDL2 init OK\n", __progname); 756 | 757 | // Configure MCP23017 port expander(s) 758 | 759 | uint8_t cfg1[] = { 0x05 , 0x00 }, // If bank 1, switch to 0 760 | cfg2[] = { IOCONA, 0x44 }, // Bank 0, INTB=A, seq, OD IRQ 761 | cfg3[23]; // Read-modify-write chip cfg 762 | uint16_t inputMask, gndMask; 763 | 764 | if(mcpMask) { // Any port expanders mentioned in config? 765 | for(i=0; i<8; i++) { // 8 possible MCP23017 indices 766 | uint8_t j; 767 | inputMask = gndMask = 0; // Bitmasks of keys, gnds on this device 768 | for(j=0; j<16; j++) { // 16 bits per MCP 769 | k = key[32 + i * 16 + j]; 770 | if(k == GND) gndMask |= (1 << j); 771 | else if(k > KEY_RESERVED) inputMask |= (1 << j); 772 | } 773 | 774 | if(inputMask || gndMask) { // Referenced in config? 775 | // Each MCP23017 is assigned a separate file descriptor; 776 | // each bonded once to a specific I2C address (via ioctl) 777 | // so that the ioctl isn't required for every transaction. 778 | if((i2cfd[i] = open("/dev/i2c-1", O_RDWR | O_NONBLOCK)) > 0) { 779 | ioctl(i2cfd[i], I2C_SLAVE, 0x20 + i); 780 | // Configure chip as we need it (sequential addr, etc.). 781 | // This does mean any other application also using the 782 | // chip might be clobbered if it uses a different config. 783 | write(i2cfd[i], cfg1, sizeof(cfg1)); 784 | write(i2cfd[i], cfg2, sizeof(cfg2)); 785 | // Some bits are preserved as best we can...read 786 | // registers, change bits for retrogame, write back. 787 | // This is done in two passes; first one does some 788 | // polarity stuff, second pass sets more and reads state. 789 | cfg3[0] = IODIRA; 790 | write(i2cfd[i], cfg3, 1); 791 | read(i2cfd[i], &cfg3[1], 4); // Read partial config 792 | // Change IODIRA,B bits for inputs & GNDs (leave others) 793 | cfg3[1] = (cfg3[1] | inputMask ) & ~gndMask; 794 | cfg3[2] = (cfg3[2] | (inputMask >> 8)) & ~(gndMask >> 8); 795 | // Set IPOLA,B for inputs+GNDs (polarity matches input logic) 796 | cfg3[3] &= ~( inputMask | gndMask); 797 | cfg3[4] &= ~((inputMask | gndMask) >> 8); 798 | write(i2cfd[i], cfg3, 5); // Write partial config 799 | write(i2cfd[i], cfg3, 1); // Next read is from IODIRA 800 | read(i2cfd[i], &cfg3[1], sizeof(cfg3) - 1); // Read full cfg 801 | // Enable interrupts on input pins (GPINTENA,B) 802 | cfg3[5] |= inputMask; 803 | cfg3[6] |= inputMask >> 8; 804 | // Skip DEFVALA,B 805 | cfg3[9] = cfg3[10] = 0; // INTCONA,B: compre prev pin value 806 | // Skip IOCON (x2) 807 | // Set GPPUA,B bits on input pins 808 | cfg3[13] |= inputMask; 809 | cfg3[14] |= inputMask >> 8; 810 | // Skip INTFA,B, INTCAPA,B, read GPIOA,B into int/extstate[] 811 | int idx = 1 + i / 2; // Index (1-4) into int/extstate[] 812 | uint8_t bit; // Bit to read in GPIOA/B 813 | uint32_t abit, bbit; // Bit to set in int/ext state 814 | if(i & 1) { // In upper half of int/ext state 815 | abit = 0x00800000; 816 | bbit = 0x80000000; 817 | } else { // In lower half 818 | abit = 0x00000080; 819 | bbit = 0x00008000; 820 | } 821 | for(bit=0x80; bit; bit >>= 1, abit >>= 1, bbit >>= 1) { 822 | // Invert logic; set bits in intstate[] are buttons 823 | // pressed, while set bits in config are pulled up. 824 | if(cfg3[19] & bit) intstate[idx] &= ~abit; 825 | else intstate[idx] |= abit; 826 | if(cfg3[20] & bit) intstate[idx] &= ~bbit; 827 | else intstate[idx] |= bbit; 828 | } 829 | // Clear OLATA,B bits on GND outputs 830 | cfg3[21] &= ~gndMask; 831 | cfg3[22] &= ~(gndMask >> 8); 832 | write(i2cfd[i], cfg3, sizeof(cfg3)); 833 | // Clear interrupt by reading GPIOA/B+INTCAPA/B 834 | write(i2cfd[i], &readAddr, 1); 835 | read(i2cfd[i], cfg3, 4); 836 | } 837 | } 838 | } 839 | } 840 | 841 | memcpy(extstate, intstate, sizeof(extstate)); 842 | } 843 | 844 | // Handle signal events (i=32), config file change events (33) or 845 | // config directory contents change events (34). 846 | static void pollHandler(int i) { 847 | 848 | if(i == 32) { // Signal event 849 | struct signalfd_siginfo info; 850 | read(p[i].fd, &info, sizeof(info)); 851 | if(info.ssi_signo == SIGHUP) { // kill -1 = force reload 852 | if(debug >= 2) { 853 | printf("%s: SIGHUP received; force config " 854 | "reload\n", __progname); 855 | } 856 | pinConfigUnload(); 857 | pinConfigLoad(); 858 | } else { // Other signal = abort program 859 | running = false; 860 | } 861 | } else { // Change in config file or directory contents 862 | char evBuf[1000]; 863 | //int evCount = 0; 864 | int bufPos = 0, 865 | bytesRead = read(p[i].fd, evBuf, sizeof(evBuf)); 866 | while(bufPos < bytesRead) { 867 | struct inotify_event *ev = 868 | (struct inotify_event *)&evBuf[bufPos]; 869 | 870 | //printf("EVENT %d:\n", evCount++); 871 | //printf("\tinotify event mask: %08x\n", ev->mask); 872 | //printf("\tlen: %d\n", ev->len); 873 | //if(ev->len > 0) 874 | //printf("\tname: '%s'\n", ev->name); 875 | 876 | if(ev->mask & IN_MODIFY) { 877 | if(debug >= 2) { 878 | printf("%s: Config file changed\n", 879 | __progname); 880 | } 881 | pinConfigUnload(); 882 | pinConfigLoad(); 883 | } else if(ev->mask & IN_IGNORED) { 884 | // Config file deleted -- stop watching it 885 | if(debug >= 2) { 886 | printf("%s: Config file removed\n", 887 | __progname); 888 | } 889 | inotify_rm_watch(p[1].fd, fileWatch); 890 | // Closing the descriptor turns out to be 891 | // important, as removing the watch itself 892 | // creates another IN_IGNORED event. 893 | // Avoids turtles all the way down. 894 | close(p[33].fd); 895 | p[33].fd = -1; 896 | p[33].events = 0; 897 | // Pin config is NOT unloaded... 898 | // keep using prior values for now. 899 | } else if(ev->mask & IN_MOVED_FROM) { 900 | // File moved/renamed from directory... 901 | // check if it's the one we're monitoring. 902 | if(!strcmp(ev->name, cfgName)) { 903 | // It's our file -- stop watching it 904 | if(debug >= 2) { 905 | printf("%s: Config file " 906 | "moved out\n", __progname); 907 | } 908 | inotify_rm_watch(p[33].fd, fileWatch); 909 | close(p[33].fd); 910 | p[33].fd = -1; 911 | p[33].events = 0; 912 | // Pin config is NOT unloaded... 913 | // keep using prior values for now. 914 | } else { 915 | // Some other file -- disregard 916 | } 917 | } else if(ev->mask & (IN_CREATE | IN_MOVED_TO)) { 918 | // File moved/renamed to directory... 919 | // check if it's the one we're monitoring for. 920 | if(!strcmp(ev->name, cfgName)) { 921 | // It's our file -- start watching it! 922 | if(debug >= 2) { 923 | printf("%s: Config file " 924 | "moved in\n", __progname); 925 | } 926 | if(p[33].fd >= 0) { // Existing file? 927 | inotify_rm_watch( 928 | p[33].fd, fileWatch); 929 | close(p[33].fd); 930 | } 931 | p[33].fd = inotify_init(); 932 | fileWatch = inotify_add_watch( 933 | p[33].fd, cfgPathname, 934 | IN_MODIFY | IN_IGNORED); 935 | p[33].events = POLLIN; 936 | pinConfigUnload(); 937 | pinConfigLoad(); 938 | } else { 939 | // Some other file -- disregard 940 | } 941 | } 942 | 943 | bufPos += sizeof(struct inotify_event) + ev->len; 944 | } 945 | } 946 | } 947 | 948 | 949 | // Init and main loop ------------------------------------------------------ 950 | 951 | int main(int argc, char *argv[]) { 952 | 953 | char c; // Pin input value ('0'/'1') 954 | int fd, // For mmap, sysfs 955 | i, // Generic counter 956 | timeout = -1, // poll() timeout 957 | lastKey = -1; // Last key down (for repeat) 958 | uint32_t pressMask[5]; // For Vulcan pinch detect 959 | struct input_event keyEv, synEv; // uinput events 960 | sigset_t sigset; // Signal mask 961 | 962 | // If in foreground, set max debug level (config may override) 963 | if(getpgrp() == tcgetpgrp(STDOUT_FILENO)) startupDebug = debug = 99; 964 | 965 | // Locate configuration file (if any) and path --------------------- 966 | 967 | if(argc > 1) { // First argument (if given) is config file name 968 | char *ptr = strrchr(argv[1], '/'); // Full pathname given? 969 | if(ptr) { // Pathname given; separate into path & name 970 | if(!(cfgPathname = strdup(argv[1]))) 971 | err("malloc() fail"); 972 | int len = ptr - argv[1]; // Length of path component 973 | if(!len) { // Root path? 974 | cfgPath = "/"; 975 | cfgName = &cfgPathname[1]; 976 | } else { 977 | if(!(cfgPath = (char *)malloc(len + 1))) 978 | err("malloc() fail"); 979 | memcpy(cfgPath, argv[1], len); 980 | cfgPath[len] = 0; 981 | } 982 | } else { // No path given; use /boot directory. 983 | cfgPath = "/boot"; 984 | if(!(cfgPathname = (char *)malloc( 985 | strlen(cfgPath) + strlen(argv[1]) + 2))) 986 | err("malloc() fail"); 987 | sprintf(cfgPathname, "%s/%s", cfgPath, argv[1]); 988 | } 989 | } else { 990 | // No argument passed -- config file is located in /boot, 991 | // name is "[program name].cfg" (e.g. /boot/retrogame.cfg) 992 | cfgPath = "/boot"; 993 | if(!(cfgPathname = (char *)malloc( 994 | strlen(cfgPath) + strlen(__progname) + 6))) 995 | err("malloc() fail"); 996 | sprintf(cfgPathname, "%s/%s.cfg", cfgPath, __progname); 997 | } 998 | if(!cfgName) cfgName = &cfgPathname[strlen(cfgPath) + 1]; 999 | 1000 | if(debug) { 1001 | printf("%s: Config file is '%s'\n", __progname, cfgPathname); 1002 | } 1003 | 1004 | // Catch signals, config file changes ------------------------------ 1005 | 1006 | // Clear all descriptors and GPIO state, init input event structures 1007 | memset(p, 0, sizeof(p)); 1008 | for(i=0; i<35; i++) p[i].fd = -1; 1009 | for(i=0; i<161; i++) key[i] = KEY_RESERVED; 1010 | memset(intstate , 0, sizeof(intstate)); 1011 | memset(extstate , 0, sizeof(extstate)); 1012 | memset(vulcanMask, 0, sizeof(vulcanMask)); 1013 | memset(mcpI2C , 0, sizeof(mcpI2C)); 1014 | memset(i2cfd , 0, sizeof(i2cfd)); 1015 | memset(&keyEv , 0, sizeof(keyEv)); 1016 | memset(&synEv , 0, sizeof(synEv)); 1017 | keyEv.type = EV_KEY; 1018 | synEv.type = EV_SYN; 1019 | synEv.code = SYN_REPORT; 1020 | mcpMask = 0; 1021 | 1022 | sigfillset(&sigset); 1023 | sigprocmask(SIG_BLOCK, &sigset, NULL); 1024 | // pollfd #32 catches signals, so GPIO cleanup on exit is possible 1025 | p[32].fd = signalfd(-1, &sigset, 0); 1026 | p[32].events = POLLIN; 1027 | 1028 | // pollfd #33 and #34 will be used for detecting changes in the 1029 | // config file and its parent directory. This will let you edit 1030 | // the config and have immediate feedback without needing to kill 1031 | // the process or reboot the system. 1032 | for(i=33; i<=34; i++) { 1033 | p[i].fd = inotify_init(); 1034 | p[i].events = POLLIN; 1035 | } 1036 | fileWatch = inotify_add_watch(p[33].fd, cfgPathname, 1037 | IN_MODIFY | IN_IGNORED); 1038 | inotify_add_watch(p[34].fd, cfgPath, 1039 | IN_CREATE | IN_MOVED_FROM | IN_MOVED_TO); 1040 | 1041 | // p[0-31] are related to GPIO states, and will be reconfigured 1042 | // each time the config file is loaded. 1043 | 1044 | // GPIO startup ---------------------------------------------------- 1045 | 1046 | isEarlyPi = earlyPiDetect(); 1047 | if(debug & isEarlyPi) { 1048 | printf("%s: running on Rev1 Pi 1 Board\n", __progname); 1049 | } 1050 | 1051 | // Although Sysfs provides solid GPIO interrupt handling, there's 1052 | // no interface to the internal pull-up resistors (this is by 1053 | // design, being a hardware-dependent feature). It's necessary to 1054 | // grapple with the GPIO configuration registers directly to enable 1055 | // the pull-ups. Based on GPIO example code by Dom and Gert van 1056 | // Loo on elinux.org 1057 | if((fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) 1058 | err("Can't open /dev/mem"); 1059 | gpio = mmap( // Memory-mapped I/O 1060 | NULL, // Any adddress will do 1061 | BLOCK_SIZE, // Mapped block length 1062 | PROT_READ|PROT_WRITE, // Enable read+write 1063 | MAP_SHARED, // Shared with other processes 1064 | fd, // File to map 1065 | bcm_host_get_peripheral_address() + GPIO_BASE); 1066 | close(fd); // Not needed after mmap() 1067 | if(gpio == MAP_FAILED) err("Can't mmap()"); 1068 | 1069 | pinConfigLoad(); 1070 | 1071 | // Main loop ------------------------------------------------------- 1072 | 1073 | // Monitor GPIO file descriptors for button events. The poll() 1074 | // function watches for GPIO IRQs in this case; it is NOT 1075 | // continually polling the pins! Processor load is near zero. 1076 | 1077 | if(debug) printf("%s: Entering main loop\n", __progname); 1078 | 1079 | // As in the pinConfigLoad() function, the nesting here gets 1080 | // pretty deep, please excuse the mid-function shift here to 1081 | // 2-space indenting. 1082 | 1083 | while(running) { // Signal handler will set this to 0 to exit 1084 | // Wait for IRQ on pin (or timeout for button debounce) 1085 | c = 0; // By default, don't issue SYN event 1086 | if(poll(p, 35, timeout) > 0) { // If IRQ... 1087 | for(i=0; i<32; i++) { // For each GPIO bit... 1088 | if(p[i].revents) { // Event received? 1089 | if(mcpI2C[i]) { // Is port expander (0x20-0x27) 1090 | uint8_t c, buf[4], idx = mcpI2C[i] - 0x20; // 0-7 1091 | // Must drain fd every time else it triggers forever 1092 | lseek(p[i].fd, 0, SEEK_SET); 1093 | while(read(p[i].fd, &c, 1) > 0); // Ignore value 1094 | write(i2cfd[idx], &readAddr, 1); 1095 | if(read(i2cfd[idx], buf, 4) == 4) { // INTCAP+GPIO 1096 | // Buttons pull GPIO low, so invert into intstate[] 1097 | uint16_t merged = ~((buf[3] << 8) | buf[2]); 1098 | uint8_t i2 = 1 + idx / 2; // Index of 32-bit state 1099 | if(idx & 1) { // Upper half of state 1100 | intstate[i2] = (intstate[i2]&0x0000FFFF)|(merged<<16); 1101 | } else { // Lower half of state 1102 | intstate[i2] = (intstate[i2]&0xFFFF0000)|merged; 1103 | } 1104 | } 1105 | } else { // Is regular GPIO 1106 | // Read current pin state, store in internal state flag, 1107 | // flag, but don't issue to uinput yet -- must debounce! 1108 | lseek(p[i].fd, 0, SEEK_SET); 1109 | read(p[i].fd, &c, 1); 1110 | if(c == '0') intstate[0] |= (1 << i); 1111 | else if(c == '1') intstate[0] &= ~(1 << i); 1112 | } 1113 | timeout = debounceTime; 1114 | p[i].revents = 0; 1115 | } 1116 | } 1117 | for(; i<35; i++) { // Check signals, etc. 1118 | if(p[i].revents) { // Event received? 1119 | pollHandler(i); 1120 | p[i].revents = 0; 1121 | } 1122 | } 1123 | // Else timeout occurred 1124 | } else if(timeout == debounceTime) { // Debounce timeout 1125 | memset(pressMask, 0, sizeof(pressMask)); 1126 | uint8_t a; 1127 | uint32_t b; 1128 | for(a=i=0; a<5; a++) { 1129 | for(b=1; b; b <<= 1, i++) { // i=0 to 159 1130 | if((key[i] > KEY_RESERVED) && (key[i] < GND)) { 1131 | // Compare internal state against previously-issued value. 1132 | // Send keys only for changed states. 1133 | if((intstate[a] & b) != (extstate[a] & b)) { 1134 | // Man this is starting to get ugly. Y'know this could 1135 | // be done by typedefing a structure with bit fields... 1136 | // it'd be doing about the same thing behind the scenes, 1137 | // but might be more legible in source form. 1138 | extstate[a] = (extstate[a] & ~b) | (intstate[a] & b); 1139 | keyEv.code = key[i]; 1140 | keyEv.value = ((intstate[a] & b) > 0); 1141 | write(keyfd, &keyEv, sizeof(keyEv)); 1142 | c = 1; // Follow w/SYN event 1143 | if(intstate[a] & b) { // Press? 1144 | // Note pressed key and set initial repeat interval. 1145 | lastKey = i; 1146 | timeout = repTime1; 1147 | if(debug >= 3) { 1148 | printf("%s: GPIO%02d key press code %d\n", 1149 | __progname, i, key[i]); 1150 | } 1151 | } else { // Release? 1152 | // Stop repeat and return to normal IRQ monitoring 1153 | // (no timeout). 1154 | lastKey = timeout = -1; 1155 | if(debug >= 3) { 1156 | printf("%s: GPIO%02d key release code %d\n", 1157 | __progname, i, key[i]); 1158 | } 1159 | } 1160 | } 1161 | if(intstate[a] & b) pressMask[a] |= b; 1162 | } 1163 | } 1164 | } 1165 | // There's an occasional case where it seems the MCP will 1166 | // trigger a pin-change IRQ but then the GPIO pin state 1167 | // reverts to its prior value due to switch bounce; this 1168 | // fails to activate the press *or* release cases above, 1169 | // and the debounce timeout is never reset. Test 'c' 1170 | // (SYN event flag) as timeout reset fallback. If not 1171 | // reset, the debounce code above is called on every pass 1172 | // regardless whether input is received, wasting CPU cycles. 1173 | if(!c) timeout = -1; 1174 | 1175 | // If the "Vulcan nerve pinch" buttons are pressed, 1176 | // set long timeout -- if this time elapses without 1177 | // a button state change, esc keypress will be sent. 1178 | if(key[160] != KEY_RESERVED) { // Any vulcan key defined? 1179 | for(a=0; (a<5) && 1180 | ((pressMask[a] & vulcanMask[a]) == vulcanMask[a]); a++); 1181 | if(a == 5) timeout = vulcanTime; 1182 | } 1183 | } else if(timeout == vulcanTime) { // Vulcan key timeout 1184 | // Send keycode (MAME exits or displays exit menu) 1185 | keyEv.code = key[160]; 1186 | if(debug >= 3) { 1187 | printf("%s: GPIO combo %04X%04X%04X%04X%04X press, " 1188 | "release code %d\n", __progname, vulcanMask[4], 1189 | vulcanMask[3], vulcanMask[2], vulcanMask[1], vulcanMask[0], 1190 | key[160]); 1191 | } 1192 | for(i=1; i>= 0; i--) { // Press, release 1193 | keyEv.value = i; 1194 | write(keyfd, &keyEv, sizeof(keyEv)); 1195 | usleep(10000); // Be slow, else MAME flakes 1196 | write(keyfd, &synEv, sizeof(synEv)); 1197 | usleep(10000); 1198 | } 1199 | timeout = -1; // Return to normal processing 1200 | c = 0; // No add'l SYN required 1201 | } else if(lastKey >= 0) { // Else key repeat timeout 1202 | if(timeout == repTime1) timeout = repTime2; 1203 | else if(timeout > 30) timeout -= 5; // Accelerate 1204 | c = 1; // Follow w/SYN event 1205 | keyEv.code = key[lastKey]; 1206 | keyEv.value = 2; // Key repeat event 1207 | if(debug >= 3) { 1208 | printf("%s: repeating key code %d\n", 1209 | __progname, keyEv.code); 1210 | } 1211 | write(keyfd, &keyEv, sizeof(keyEv)); 1212 | } 1213 | if(c) write(keyfd, &synEv, sizeof(synEv)); 1214 | } 1215 | 1216 | // Clean up -------------------------------------------------------- 1217 | 1218 | pinConfigUnload(); // Close uinput, un-export pins 1219 | 1220 | if(debug) printf("%s: Done.", __progname); 1221 | 1222 | return 0; 1223 | } 1224 | --------------------------------------------------------------------------------