Jump to content

Saitek-X52 - Map buttons to Keypresses (Python script)


Recommended Posts

Posted (edited)

Hi all.

 

I wrote up a little dirty script that allows you to use a button on the joystick for (rotators, switches etc)

It will require that you know some python.

Its far from perfect but i really dont want to spend more time on it.

But i like to share this with you.

 

NOTE. THIS IS CONSIDERED AS A HACK IN MANY GAMES.

 

 

Before you get started you need to have at least a Empty profile loaded. (i have a empty one i use called DCS)

 

 

You also need to download and install

Python 2.6

PyWinusb 0.3.3

 

Im sure there are newer version. but i have used good old python 2.6 for this.

 

 

 

Now how do i get it working?

 

Step 1.

Analyse the buttons you want to use as your rotator.

find the indexs and the values.

 

Step 2.

Setup Keys to simulate Keypresses.

 

Step 3.

Play and have fun.

 

 

Ok. so lets started and take a look at the script Saitek-X52.py file.

 

To analyse the buttons you want to use we need to print out the raw data 1st.

Scroll down to the function "joystick_data(data):"

 

You'll see a line starting like "if buttons != OLD_BTN_DATA: just below it you see

 

#print "Buttons:",buttons

 

Uncomment that line. and start up the program.

Now each time you press a button you will see it spits out an array

Example:

Buttons: [0, 0, 128, 0, 0, 136]

Buttons: [0, 128, 128, 0, 0, 136]

Buttons: [0, 0, 128, 0, 0, 136]

Buttons: [0, 128, 128, 0, 0, 136]

 

Look for changes.

In this example the Pov2 (left upper pov on stick module) is pressed UP 2 times.

We see this change in index 1 being set to 128 when pressed and 0 when released (IN MODE 1).

 

*Note. the some of the buttons uses more than one index.

 

OK now that we figured out where the button is located in the array and what value it has when pressed.

Next we need to create a statement that checks if its pressed and executes the rotation function.

 

Example... in our joystick_data function

   # Get Buttons changes.
   if buttons != OLD_BTN_DATA:
       OLD_BTN_DATA = buttons
       TimeResData = nowtime

       if buttons[1] == 128:
           print "I Pressed Pov2 UP"
           
           # lets create something like.
           rotator(MY_ROTATORS["GunsOnOff"], "+")

Ok,, now that is ready..

We need to do one more last thing, we need to scroll up to our MY_ROTATORS = {} and set the wanted keys it suppose to simulate.

 

MY_ROTATORS = {
   "GunsOnOff" : [0, 
       [
           ["DIK_LWIN", "DIK_1"] # turn on
           ["DIK_LWIN", "DIK_2"] # turn off
       ]
   ]
}

Enjoy if you like to hack around a bit :D

Edited by Lumax
  • Like 1
Posted (edited)

Saitek-x52.py

# This script is meant for saitek X52 stick. not the pro vesion
# I do not know how this will work on other systems.
# You can modify this script as much as you want. 

# This script allows you to simulate key presses using a button on the joystick.
# Example you can setup a weapon rotator switch. 
# Same for button that requires two buttons to be turned on or off.

# After googeling alot and looking at examples from everywhere.
# I cooked together something works somewhat in a decent manners.
# You will require some Python skills to know what youre doing.


import sys
import codecs
import time
import datetime
from datetime import timedelta
import pywinusb.hid as hid

# my def modules.
import SendKey
import KeyCodes



PRODUCT_NAME = "Saitek X52 Flight Control System"
OLD_BTN_DATA = []
TimeResData = datetime.datetime.now()


MOUSE_SCROLL_D    = 1                 
MOUSE_SCROLL_U    = 2                
JOY_BTN_FNC1    = 4                
JOY_BTN_FNC2    = 8                
JOY_BTN_FNC3    = 16            
JOY_BTN_MODIFIER = 32 # Pinky switch
JOY_BTN_INFO    = 32             


# Take a look in KeyCodes.py

# This is for my Mig21 Bis. 
# We use the DIK Names, this means that we will be using a prefix of "DIK_" 
# NOTE Some DIK hex values may be different on your system

# Big dict for rotators.
# Stucture. id/name : [state, [key1,..]], id/name : [state, [keys]] 
# Define Keys for wanted rotators/switches
MY_ROTATORS = {
   
   "WeaponSelector" : [0, 
       [
           ["DIK_MINUS"],
           ["DIK_1"],
           ["DIK_2"],
           ["DIK_3"],
           ["DIK_4"],
           ["DIK_5"],
           ["DIK_6"],
           ["DIK_7"],
           ["DIK_8"],
           ["DIK_9"],
           ["DIK_0"]
       ]
   ],

   "Radio_ARK_Channels" : [0,
       [
           ["DIK_RSHIFT", "DIK_1"], 
           ["DIK_RSHIFT", "DIK_2"], 
           ["DIK_RSHIFT", "DIK_3"], 
           ["DIK_RSHIFT", "DIK_4"], 
           ["DIK_RSHIFT", "DIK_5"],
           ["DIK_RSHIFT", "DIK_6"],
           ["DIK_RSHIFT", "DIK_7"], 
           ["DIK_RSHIFT", "DIK_8"], 
           ["DIK_RSHIFT", "DIK_9"] 
       ]
   ],
   
   "Weapon_Missile_Modes" : [0,
       [
           ["DIK_3"],
           ["DIK_4"],
           ["DIK_5"],
           ["DIK_4"],
           
       ]
   ],
   
   "Weapon_Modes" : [0,
       [
           ["DIK_1"],
           ["DIK_2"],
           
       ]
   ]
}

# BIG NOTES ABOUT MODIFIERS
# If you have assigned a button on the joystick as a modifier. 
# Be aware of this.
# lets say you want to use btn 6 ("pinky switch") + btn 5 to execute a toggle which has following keyboard settings.
# Command for "Weapon On" is (LWin + 1) and "Weapon Off" is (LWin + 2)
# You can not put into MY_Rotators 
# "somename" : [0, [["DIK_LWIN", "DIK_1" ], ["DIK_LWIN", "DIK_2"]]]
# And expect it to work if you use your Joy_btn modifier to exec a function.
   
def joystick_data(data):
   '''
       This function is executed each time the joystick changes its state.
       This means when you move the axes or pushes buttons.
       
       the axes data can be found from index 0 to index 8 on the input.
       the button data is found from index 9 to 13.
       index 14 is for the ministick ("mouse")
       
       The total length of the data input is 15
       
   '''
   global OLD_BTN_DATA
   global TimeResData
   
   
   axes     = data[0:8] # We dont need the axes data..
   buttons = data[9::] # Buttons.
   

   # since we dont normally need to check so often. just return none 
   timetorec = TimeResData + timedelta(seconds=0.05)
   nowtime =  datetime.datetime.now()
   if (timetorec > nowtime): return None


   # Get Buttons changes.
   if buttons != OLD_BTN_DATA:

       ministick(buttons[5])
       
               
            

       # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
       # Mouse scroll down and scroll up on the throttle.
       # Weapon Selector
       if MOUSE_SCROLL_U == buttons[4] and JOY_BTN_MODIFIER != buttons[0]:
           rotator(MY_ROTATORS["WeaponSelector"], "+")
       
       if MOUSE_SCROLL_D == buttons[4] and JOY_BTN_MODIFIER != buttons[0]:
           rotator(MY_ROTATORS["WeaponSelector"], "-")
       
       
       # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
       # Info Button on my thorttle module..
       # Radio ARK Channels
       if JOY_BTN_INFO == buttons[3] and JOY_BTN_MODIFIER != buttons[0]:
           rotator(MY_ROTATORS["Radio_ARK_Channels"], "+")
           
       
       # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
       # My Joy_Btn6 + Joy_Fnc1_button
       # Ground or Air Switch
       if JOY_BTN_FNC1 == buttons[3] and JOY_BTN_MODIFIER == buttons[0]:
           rotator(MY_ROTATORS["Weapon_Modes"], "+")

       
       # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
       # My Joy_Btn6 + Joy_Fnc2_button
       # Ir or Sar switch
       if JOY_BTN_FNC2 == buttons[3] and JOY_BTN_MODIFIER == buttons[0]:
           rotator(MY_ROTATORS["Weapon_Missile_Modes"], "+")            
       
       
      OLD_BTN_DATA = buttons


def ministick(data):
   '''example'''
   u = 0xc0 
   r = 0xbd 
   d = 0xbe
   l = 0xbc


   w = data & 0xf
   h = data >> 4
   
   keys = []
   # up/ down
   if h < 8: keys.append(u)
   elif h > 8: keys.append(d)
       
   # right / left
   if w < 8: keys.append(r)
   elif w > 8:    keys.append(l)
       
   for k in keys: SendKey.PressKey(k)
   
   for k in keys: SendKey.ReleaseKey(k)        

def rotator(rotator_keys,sign="+"):
   # Check if we rotate "left or right"
   if sign == "+":
       rotator_keys[0] += 1
       if rotator_keys[0] > len(rotator_keys[1])-1:
           rotator_keys[0] = 0
   else:
       rotator_keys[0] -= 1
       if rotator_keys[0] < 0:
           rotator_keys[0] = len(rotator_keys[1])-1

   
   if len(rotator_keys[1][rotator_keys[0]]) >= 1:
       keys = rotator_keys[1][rotator_keys[0]]
       
       for e in keys:
           SendKey.PressKey(KeyCodes.CODES[e])
       
       
       for e in keys:
           SendKey.ReleaseKey(KeyCodes.CODES[e])

               
               

       
def main():
   print("Checking for Saitek X52 Flight Control System")
   
   # Collect all usb hid devices connected.
   all_hids = hid.find_all_hid_devices()    

   if all_hids:
       stick_index=-1

       for index, device in enumerate(all_hids):
           # Just do a simple product name check.
           if device.product_name == PRODUCT_NAME:
               device_name = unicode("{0.vendor_name} {0.product_name}" "(vID=0x{1:04x}, pID=0x{2:04x})""".format(device, device.vendor_id, device.product_id))
               stick_index = int(index)
               print "Found Device:\n",device_name
               break    
       
       if stick_index != -1:
           device = all_hids[stick_index]
           try:
               # Set custom raw data handler
               device.open()    
               device.set_raw_data_handler(joystick_data)

               # Need to create some better exit code.
               while device.is_plugged():
                   #just keep the device opened to receive events
                   time.sleep(0.5)
               return

           finally:
               device.close()

if __name__ == "__main__":
   main()

SendKey.py

import ctypes
import time


# C struct redefinitions 
PUL = ctypes.POINTER(ctypes.c_ulong)
class KeyBdInput(ctypes.Structure):
   _fields_ = [("wVk", ctypes.c_ushort),
               ("wScan", ctypes.c_ushort),
               ("dwFlagswww", ctypes.c_ulong),
               ("time", ctypes.c_ulong),
               ("dwExtraInfo", PUL)]

class HardwareInput(ctypes.Structure):
   _fields_ = [("uMsg", ctypes.c_ulong),
               ("wParamL", ctypes.c_short),
               ("wParamH", ctypes.c_ushort)]

class MouseInput(ctypes.Structure):
   _fields_ = [("dx", ctypes.c_long),
               ("dy", ctypes.c_long),
               ("mouseData", ctypes.c_ulong),
               ("dwFlags", ctypes.c_ulong),
               ("time",ctypes.c_ulong),
               ("dwExtraInfo", PUL)]

class Input_I(ctypes.Union):
   _fields_ = [("ki", KeyBdInput), ("mi", MouseInput), ("hi", HardwareInput)]



class Input(ctypes.Structure):
   _fields_ = [("type", ctypes.c_ulong), ("ii", Input_I)]



def PressKey(hexKeyCode):
   extra = ctypes.c_ulong(0)
   ii_ = Input_I()
   ii_.ki = KeyBdInput( 0, hexKeyCode, 0x0008, 0, ctypes.pointer(extra) )
   x = Input( ctypes.c_ulong(1), ii_ )
   ctypes.windll.user32.SendInput(1, ctypes.pointer(x), ctypes.sizeof(x))

def ReleaseKey(hexKeyCode):
   extra = ctypes.c_ulong(0)
   ii_ = Input_I()
   ii_.ki = KeyBdInput( 0, hexKeyCode, 0x0008 | 0x0002, 0, ctypes.pointer(extra) )
   x = Input( ctypes.c_ulong(1), ii_ )
   ctypes.windll.user32.SendInput(1, ctypes.pointer(x), ctypes.sizeof(x))

KeyCodes.py

CODES = {
   "DIK_ESCAPE"        : 0x01,
   "DIK_1"                : 0x02,
   "DIK_2"             : 0x03,
   "DIK_3"             : 0x04,
   "DIK_4"             : 0x05,
   "DIK_5"             : 0x06,
   "DIK_6"             : 0x07,
   "DIK_7"             : 0x08,
   "DIK_8"             : 0x09,
   "DIK_9"             : 0x0A,
   "DIK_0"             : 0x0B,
   "DIK_MINUS"         : 0x0C,  # on main keyboard
   "DIK_EQUALS"         : 0x0D,
   "DIK_BACK"             : 0x0E,  # backspace
   "DIK_TAB"             : 0x0F,
   "DIK_Q"             : 0x10,
   "DIK_W"             : 0x11,
   "DIK_E"             : 0x12,
   "DIK_R"             : 0x13,
   "DIK_T"             : 0x14,
   "DIK_Y"             : 0x15,
   "DIK_U"             : 0x16,
   "DIK_I"                 : 0x17,
   "DIK_O"             : 0x18,
   "DIK_P"             : 0x19,
   "DIK_LBRACKET"         : 0x1A,
   "DIK_RBRACKET"         : 0x1B,
   "DIK_RETURN"         : 0x1C, #  Enter on main keyboard
   "DIK_LCONTROL"        : 0x1D,
   "DIK_A"             : 0x1E,
   "DIK_S"             : 0x1F,
   "DIK_D"             : 0x20,
   "DIK_F"             : 0x21,
   "DIK_G"             : 0x22,
   "DIK_H"             : 0x23,
   "DIK_J"             : 0x24,
   "DIK_K"             : 0x25,
   "DIK_L"             : 0x26,
   "DIK_SEMICOLON"        : 0x27,
   "DIK_APOSTROPHE"    : 0x28,
   "DIK_GRAVE"         : 0x29, # accent grave 
   "DIK_LSHIFT"        : 0x2A,
   "DIK_BACKSLASH"        : 0x2B,
   "DIK_Z"             : 0x2C,
   "DIK_X"             : 0x2D,
   "DIK_C"             : 0x2E,
   "DIK_V"             : 0x2F,
   "DIK_B"             : 0x30,
   "DIK_N"             : 0x31,
   "DIK_M"             : 0x32,
   "DIK_COMMA"         : 0x33,
   "DIK_PERIOD"         : 0x34,   # on main keyboard 
   "DIK_SLASH"         : 0x35,    # on main keyboard
   "DIK_RSHIFT"         : 0x36,
   "DIK_MULTIPLY"        : 0x37,   # * on numeric keypad
   "DIK_LMENU"         : 0x38,  # left Alt
   "DIK_SPACE"         : 0x39,
   "DIK_CAPITAL"        : 0x3A,
   "DIK_F1"             : 0x3B,
   "DIK_F2"             : 0x3C,
   "DIK_F3"             : 0x3D,
   "DIK_F4"             : 0x3E,
   "DIK_F5"             : 0x3F,
   "DIK_F6"             : 0x40,
   "DIK_F7"             : 0x41,
   "DIK_F8"             : 0x42,
   "DIK_F9"             : 0x43,
   "DIK_F10"             : 0x44,
   "DIK_NUMLOCK"        : 0x45,
   "DIK_SCROLL"        : 0x46,
   "DIK_NUMPAD7"        : 0x47,
   "DIK_NUMPAD8"        : 0x48,
   "DIK_NUMPAD9"        : 0x49,
   "DIK_SUBTRACT"        : 0x4A, # - on numeric keypad
   "DIK_NUMPAD4"        : 0x4B,
   "DIK_NUMPAD5"        : 0x4C,
   "DIK_NUMPAD6"        : 0x4D,
   "DIK_ADD"            : 0x4E, # + on numeric keypad
   "DIK_NUMPAD1"        : 0x4F,
   "DIK_NUMPAD2"        : 0x50,
   "DIK_NUMPAD3"        : 0x51,
   "DIK_NUMPAD0"        : 0x52,
   "DIK_DECIMAL"        : 0x53, # . on numeric keypad
   "DIK_F11"             : 0x57,
   "DIK_F12"             : 0x58,
   "DIK_F13"             : 0x64, # (NEC PC98)
   "DIK_F14"             : 0x65, # (NEC PC98)
   "DIK_F15"             : 0x66, # (NEC PC98)
   "DIK_KANA"             : 0x70,    # (Japanese keyboard)
   "DIK_CONVERT"        : 0x79, # (Japanese keyboard)
   "DIK_NOCONVERT"        : 0x7B, # (Japanese keyboard)
   "DIK_YEN"             : 0x7D, # (Japanese keyboard)
   "DIK_NUMPADEQUALS"    : 0x8D, #  on numeric keypad (NEC PC98)
   "DIK_CIRCUMFLEX"     : 0x90, # (Japanese keyboard)
   "DIK_AT"             : 0x91, # (NEC PC98)
   "DIK_COLON"         : 0x92, # (NEC PC98)
   "DIK_UNDERLINE"     : 0x93, # (NEC PC98)
   "DIK_KANJI"         : 0x94, # (Japanese keyboard)
   "DIK_STOP"             : 0x95, # (NEC PC98)
   "DIK_AX"             : 0x96, # (Japan AX)
   "DIK_UNLABELED"     : 0x97, # (J3100)
   "DIK_NUMPADENTER"     : 0x9C, # Enter on numeric keypad
   "DIK_RCONTROL"         : 0x9D,
   "DIK_NUMPADCOMMA"     : 0xB3, # , on numeric keypad (NEC PC98)
   "DIK_DIVIDE"         : 0xB5, # / on numeric keypad
   "DIK_SYSRQ"         : 0xB7,
   "DIK_RMENU"         : 0xB8, # right Alt 
   "DIK_HOME"             : 0xC7, # Home on arrow keypad 
   "DIK_UP"             : 0xC8, # UpArrow on arrow keypad 
   "DIK_PRIOR"         : 0xC9, # PgUp on arrow keypad 
   "DIK_LEFT"             : 0xCB, # LeftArrow on arrow keypad 
   "DIK_RIGHT"         : 0xCD, # RightArrow on arrow keypad 
   "DIK_END"             : 0xCF, # End on arrow keypad 
   "DIK_DOWN"             : 0xD0, # DownArrow on arrow keypad 
   "DIK_NEXT"             : 0xD1, # PgDn on arrow keypad 
   "DIK_INSERT"         : 0xD2, # Insert on arrow keypad 
   "DIK_DELETE"         : 0xD3, # Delete on arrow keypad 
   "DIK_APPS"             : 0xDD, # AppMenu key 
   
   "DIK_BACKSPACE"     : 0x0E,    # backspace 
   "DIK_NUMPADSTAR"     : 0x37, # * on numeric keypad 
   "DIK_LALT"             : 0x38, # left Alt 
   "DIK_CAPSLOCK"        : 0x3A,    # CapsLock 
   "DIK_NUMPADMINUS"     : 0x4A, # - on numeric keypad 
   "DIK_NUMPADPLUS"    : 0x4E, # + on numeric keypad 
   "DIK_NUMPADPERIOD"    : 0x53,    # . on numeric keypad 
   "DIK_NUMPADSLASH"    : 0xB5, # / on numeric keypad 
   "DIK_RALT"             : 0xB8, # right Alt 
   "DIK_UPARROW"        : 0xC8, # UpArrow on arrow keypad 
   "DIK_PGUP"             : 0xC9,    # PgUp on arrow keypad 
   "DIK_LEFTARROW"     : 0xCB, # LeftArrow on arrow keypad 
   "DIK_RIGHTARROW"     : 0xCD, # RightArrow on arrow keypad 
   "DIK_DOWNARROW"     : 0xD0, # DownArrow on arrow keypad 
   "DIK_PGDN"             : 0xD1,    # PgDn on arrow keypad 
   "DIK_LWIN"            : 0x5b,
   "DIK_RWIN"            : 0x5c
} 

Edited by Lumax
Posted

What's the difference to the ST Software? :huh:

 

 

Kind regards,

Fire

Hardware: Intel i5 4670K | Zalman NPS9900MAX | GeIL 16GB @1333MHz | Asrock Z97 Pro4 | Sapphire Radeon R9 380X Nitro | Samsung SSDs 840 series 120GB & 250 GB | Samsung HD204UI 2TB | be quiet! Pure Power 530W | Aerocool RS-9 Devil Red | Samsung SyncMaster SA350 24" + ASUS VE198S 19" | Saitek X52 | TrackIR 5 | Thrustmaster MFD Cougar | Speedlink Darksky LED | Razor Diamondback | Razor X-Mat Control | SoundBlaster Tactic 3D Rage ### Software: Windows 10 Pro 64Bit

[sIGPIC][/sIGPIC]

Posted (edited)
What's the difference to the ST Software? :huh:

 

 

Kind regards,

Fire

 

Lots.

 

1st.

Adjusting the code should allows you to use any usb hid devices.

 

2nd.

You can create sequenses of keypresses which you can not with the ST software..

Example. press Pov2 Up once. represent Key1 , next time you press Pov2 Up it represent key 2.

 

Or you can create advance macros.

 

 

This was more of a POC "proof of concept".

It requires some work set up as one want it and one needs some python skills to create functions etc.

Edited by Lumax
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...