├── LICENSE ├── README.md └── virtual_gamepad.py /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Fabio Zanini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) 2 | ## Virtual gamepad in Linux 3 | Inspiration and some code from: 4 | 5 | - https://github.com/tuomasjjrasanen/python-uinput/blob/master/examples/joystick.py 6 | - http://blog.thestateofme.com/2012/08/10/raspberry-pi-gpio-joystick/ 7 | - https://github.com/ynsta/steamcontroller 8 | 9 | ### How to use: 10 | (Install and) Load the `uinput` module: 11 | ```bash 12 | sudo modprobe uinput 13 | ``` 14 | Run the script: 15 | ```bash 16 | python virtual_gamepad.py 17 | ``` 18 | (run your game or software requiring a gamepad...) 19 | 20 | After your software is closed again, stop the script if you don't need the gamepad anymore. 21 | 22 | **NOTE**: whoever runs the script must have access to /dev/uinput. You can either run it as root/sudo, or give your user priviledges via a group, e.g. 23 | ```bash 24 | sudo groupadd uinput 25 | sudo usermod -aG uinput "$USER" 26 | sudo chmod g+rw /dev/uinput 27 | sudo chgrp uinput /dev/uinput 28 | ``` 29 | after which you won't need sudo to launch the virtual gamepad anymore. 30 | 31 | ### Technicalities 32 | - The script emulates an Xbox controller. 33 | - Take a look at the script to bind the keys differently. 34 | - The third repo link above has more keys, haptics, and more. Add them as needed. 35 | -------------------------------------------------------------------------------- /virtual_gamepad.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | import pynput 3 | import uinput 4 | import time 5 | 6 | events = ( 7 | uinput.BTN_A, 8 | uinput.BTN_B, 9 | uinput.BTN_X, 10 | uinput.BTN_Y, 11 | uinput.BTN_TL, 12 | uinput.BTN_TR, 13 | uinput.BTN_THUMBL, 14 | uinput.BTN_THUMBR, 15 | uinput.ABS_X + (0, 255, 0, 0), 16 | uinput.ABS_Y + (0, 255, 0, 0), 17 | ) 18 | device = uinput.Device( 19 | events, 20 | vendor=0x045e, 21 | product=0x028e, 22 | version=0x110, 23 | name="Microsoft X-Box 360 pad", 24 | ) 25 | 26 | # Center joystick 27 | # syn=False to emit an "atomic" (128, 128) event. 28 | device.emit(uinput.ABS_X, 128, syn=False) 29 | device.emit(uinput.ABS_Y, 128) 30 | 31 | keymap = { 32 | 'right': 'l', 33 | 'left': 'j', 34 | 'up': 'i', 35 | 'down': 'k', 36 | 'jump': '[', 37 | 'action': ']', 38 | 'inventory': '=', 39 | 'confirm': '0', 40 | '?': '-', 41 | 'requests/cancel': '9', 42 | 'zoomout': '8', 43 | 'back': 'p', 44 | } 45 | keys = list(keymap.values()) 46 | 47 | def find_key(key): 48 | #if key == keyboard.Key.esc: 49 | # return False # stop listener 50 | try: 51 | k = key.char # single-char keys 52 | except: 53 | k = key.name # other keys 54 | if k not in keys: 55 | return True 56 | 57 | return k 58 | 59 | #print('Key pressed: ' + k) 60 | #return False # stop listener; remove this if want more keys 61 | 62 | 63 | def on_press(key): 64 | k = find_key(key) 65 | if k is True: 66 | return True 67 | 68 | if k == keymap['jump']: 69 | device.emit(uinput.BTN_A, 1) 70 | elif k == keymap['back']: 71 | device.emit(uinput.BTN_B, 1) 72 | elif k == keymap['action']: 73 | device.emit(uinput.BTN_X, 1) 74 | elif k == keymap['inventory']: 75 | device.emit(uinput.BTN_Y, 1) 76 | elif k == keymap['confirm']: 77 | device.emit(uinput.BTN_TL, 1) 78 | elif k == keymap['?']: 79 | device.emit(uinput.BTN_TR, 1) 80 | elif k == keymap['requests/cancel']: 81 | device.emit(uinput.BTN_THUMBL, 1) 82 | elif k == keymap['zoomout']: 83 | device.emit(uinput.BTN_THUMBR, 1) 84 | elif k == keymap['up']: 85 | device.emit(uinput.ABS_Y, 0) # Zero Y 86 | elif k == keymap['down']: 87 | device.emit(uinput.ABS_Y, 255) # Max Y 88 | elif k == keymap['left']: 89 | device.emit(uinput.ABS_X, 0) # Zero X 90 | elif k == keymap['right']: 91 | device.emit(uinput.ABS_X, 255) # Max X 92 | 93 | return True 94 | 95 | 96 | def on_release(key): 97 | k = find_key(key) 98 | if k is True: 99 | return True 100 | 101 | if k == keymap['jump']: 102 | device.emit(uinput.BTN_A, 0) 103 | elif k == keymap['back']: 104 | device.emit(uinput.BTN_B, 0) 105 | elif k == keymap['action']: 106 | device.emit(uinput.BTN_X, 0) 107 | elif k == keymap['inventory']: 108 | device.emit(uinput.BTN_Y, 0) 109 | elif k == keymap['confirm']: 110 | device.emit(uinput.BTN_TL, 0) 111 | elif k == keymap['?']: 112 | device.emit(uinput.BTN_TR, 0) 113 | elif k == keymap['requests/cancel']: 114 | device.emit(uinput.BTN_THUMBL, 0) 115 | elif k == keymap['zoomout']: 116 | device.emit(uinput.BTN_THUMBR, 0) 117 | elif k == keymap['up']: 118 | device.emit(uinput.ABS_Y, 128) # Center Y 119 | elif k == keymap['down']: 120 | device.emit(uinput.ABS_Y, 128) # Center Y 121 | elif k == keymap['left']: 122 | device.emit(uinput.ABS_X, 128) # Center Y 123 | elif k == keymap['right']: 124 | device.emit(uinput.ABS_X, 128) # Center Y 125 | 126 | #time.sleep(.02) # Poll every 20ms (otherwise CPU load gets too high) 127 | 128 | return True 129 | 130 | 131 | if True: 132 | listener = pynput.keyboard.Listener( 133 | on_press=on_press, 134 | on_release=on_release, 135 | ) 136 | listener.start() # start to listen on a separate thread 137 | listener.join() # remove if main thread is polling self.keys 138 | --------------------------------------------------------------------------------