10
\$\begingroup\$

I've been working on a project to map a joystick (in my case Logitech Extreme 3D) to mouse and keyboard so I can use it for web games that don't support gamesticks. My design goals are:

  1. Flexibility - I want to be able to modify this for almost any combination of joystick/gamepad to a keyboard/mouse easily.
  2. Readability - I'd like to be able to publish this so others can use it without much trouble.

Here is my code:

import pygame
from pygame.locals import *
import ctypes
import math

def main():
   print('Python Slither controller is ready.')
   pygame.init()
   clock = pygame.time.Clock()
   joysticks = []
   for i in range(0, pygame.joystick.get_count()):
      joysticks.append(pygame.joystick.Joystick(i))
      joysticks[-1].init()

   while 1:
      clock.tick(60)

      updateData(pygame.joystick.Joystick(0))
      doActions()

      for event in pygame.event.get():
         if event.type == QUIT:
             print ("Received event 'Quit', exiting.")
             return


controls = {'trigger': 0, 
            'x-axis': 0,
            'y-axis': 0,
            'z-axis': 0,
            'hat-x': 0,
            'hat-y': 0,
            'slider': 0,
            'button-2': 0,
            'button-3': 0,
            'button-4': 0,
            'button-5': 0,
            'button-6': 0,
            'button-7': 0,
            'button-8': 0,
            'button-9': 0,
            'button-10': 0,
            'button-11': 0,
            'button-12': 0}

old_controls = controls.copy()

def updateData(joystick):
    global old_controls
    old_controls = controls.copy()

    controls['trigger'] = joystick.get_button(0)
    for i in range(1, 12):
        controls['button-'+str(i+1)] = joystick.get_button(i)
    controls['slider'] = joystick.get_axis(2)
    controls['x-axis'] = joystick.get_axis(0)
    controls['y-axis'] = joystick.get_axis(1)
    controls['z-axis'] = joystick.get_axis(3)
    controls['hat-x'] = joystick.get_hat(0)[0]
    controls['hat-y'] = joystick.get_hat(0)[1]


######################################################
######################################################
######################################################


def keyAction(keyChar):
    return (lambda old, new:
        keyEvent(bool(new), keyChar))

def mouseMoveAction():
    pass

def mouseScrollAction(scale):
    return (lambda old, new: 
        mouseScroll(int((old-new)*scale)))

def mousePressAction():
    return (lambda old, new:
        mouseEvent(new))

mousePos = [0,0]
def mouseMoveAction(rangeOfMotion, axis, minDist):
    def ret(old, new):
        if dist(mousePos[0], mousePos[1],
                centerOfDisplay[0], centerOfDisplay[1]) > minDist:
            mousePos[axis] = int(rangeOfMotion * new)
        mouseMoveAroundCenter(mousePos[0], mousePos[1]) 
    return ret


actions = {
    'trigger': mousePressAction(),
    'x-axis': mouseMoveAction(150, 0, 50),
    'y-axis': mouseMoveAction(150, 1, 50),
    'z-axis': lambda old, new: 0,
    'hat-x': lambda old, new: 0,
    'hat-y': lambda old, new: 0,
    'slider': mouseScrollAction(100),
    'button-2': lambda old, new: 0,
    'button-3': keyAction('.'),
    'button-4': lambda old, new: 0,
    'button-5': keyAction('f'),
    'button-6': lambda old, new: 0,
    'button-7': keyAction('w'),
    'button-8': keyAction('q'),
    'button-9': lambda old, new: 0,
    'button-10': keyAction('tab'),
    'button-11': lambda old, new: 0,
    'button-12': keyAction('esc')
}


def doActions():
    for key in controls:
        if old_controls[key] != controls[key]:
            actions[key](old_controls[key], controls[key])


######################################################
######################################################
######################################################


# https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx    
keys = {'f': 0x46,
        'esc': 0x1B,
        'q': 0x51,
        '.': 0xBE,
        'tab': 0x09,
        'w': 0x57}

def keyEvent(keyDown, keyName):
    eventConstants = {'key_down': 0, 'key_up': 0x0002}
    ctypes.windll.user32.keybd_event(keys[keyName], 0, eventConstants['key_down' if keyDown else 'key_up'], 0)


# https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
# only the required consts are shown, more can be found at the link
mouseEventConstants = {'absolute_pos': 0x8000,
                       'left_down': 0x0002,
                       'left_up': 0x0004,
                       'wheel': 0x0800}

# https://msdn.microsoft.com/en-us/library/ms724385(v=vs.85).aspx
centerOfDisplay = ( int(ctypes.windll.user32.GetSystemMetrics(0) / 2),
                    int(ctypes.windll.user32.GetSystemMetrics(1) / 2))
def mouseMoveAroundCenter(x, y):
    ctypes.windll.user32.SetCursorPos(x + centerOfDisplay[0],
                                      y + centerOfDisplay[1])

def mouseScroll(ticks):
    # https://msdn.microsoft.com/en-us/library/ms646260(v=vs.85).aspx
    amtPerTick = 120
    ctypes.windll.user32.mouse_event(
            mouseEventConstants['wheel'],
            0, 0, ticks * amtPerTick, 0)

def mouseEvent(pressed):
    ctypes.windll.user32.mouse_event(
            mouseEventConstants['left_down' if pressed else 'left_up'],
            0, 0, 0, 0)

def dist(x1, y1, x2, y2):
    return math.sqrt( (x1-x2)**2 + (y1-y2)**2 )

if __name__ == "__main__":
    main()

(If you want to run it, you need pygame. The program was written with python 3.x in mind, but it may work on python 2.x as well.)

Any feedback would be helpful! Thanks!

\$\endgroup\$
2
  • \$\begingroup\$ Please add a summary of what this code does. How does the mapping work? Does it currently work as expected? \$\endgroup\$ May 12, 2022 at 4:09
  • \$\begingroup\$ I'm not sure I could tell ya anymore. Anyway, I don't code in python anymore and I wrote it when I was 16 (: Don't worry about answering it. \$\endgroup\$
    – J Atkin
    May 13, 2022 at 9:20

1 Answer 1

2
\$\begingroup\$

I really like this in the main loop:

      updateData(pygame.joystick.Joystick(0))
      doActions()

Beautifully concise.

You might continue with that theme by doing Extract Helper to produce def look_for_quit_event():


DRY. The controls assignment is a bit verbose, and could happen further down in the code. Prefer:

controls = { key: 0
             for key in actions }

The keys assignment is needlessly opaque:

keys = {'f': 0x46,
        'esc': 0x1B,
        'q': 0x51,
        '.': 0xBE,
        'tab': 0x09,
        'w': 0x57}

Please prefer this.

keys = {ch: ord(ch) for ch in "fqw."}
keys["tab"] = 9
keys["esc"] = 0x1B

Thank you for the various MSDN URLs, they're very helpful.

\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.