Created this code to allow me to use my SpaceMouse Enterprise with Nomad on an Android simulator (Waydroid) in linux (xubuntu 24.10)
Fairly simple as it maps movement / buttons to keyboard keys.
Python is not something I do much of (pascal for me), so improvements to be made.
Let me know if any questions / suggestions
import pyspacemouse
import time
import subprocess
import sys
ββ"
Based on code supplied by:
Jakub Andrysek :github.com/JakubAndrysek/pySpaceMouse
Brandon Lopez :github.com/bglopez/python-easyhid
thank you for blocks on which I build
simple program to capture analog readings from SpaceMouse (and friends) and convert
positional data to keystrokes
knob MOVE left β KP_LEFT
knob TILT left β CTRL + KP_LEFT
knob LIFT β PAGEUP
buttons on device mapped to top row ok qwerty keypad
btn1 β CTRL+1 / btn10 β CTRL+0 / btn11 β CTRLΒ±
btn13 β SHIFT+CTRL+1 / btn23 β SHIFT+CTRLΒ±
Install (debian system example):
sudo apt-get install libhidapi-dev pipx
pipx install pyspacemouse
git clone GitHub - ahtn/python-easyhid: A simple interface to the HIDAPI library.
cd python-easyhid/
sudo python3 ./setup.py install
Usage:
start process:
sudo spacemouse-nomadsculpt.py &
in NomadSculpt:
place key binding in edit mode, move mouse knob or press enter key sequence on keyboard
Notes:
needs to by run as superuser (access of system device) - fix this if possible
needs refactoring to use events/callbacks rather than sleep
STATE_HI/LO can be altered to change sensitivity of key detection
need to figure how to call xdotool once for multiple keys - Ctrl+Shift+1
cleanup at exit needs βcleaning upβ - fairly basic assumptions made
ββ"
switch activate/deactivate levels
STATE_HI = 0.7 # 0 β 1 - higher means less sensitive
STATE_LO = -0.7 # -1 β 0 - lower means less sensitive
keyboard keycodes
KEY_ALT_R = β108β
KEY_CTRL_R = β105β
KEY_SHIFT_R = β62β
KEY_MOVE_UP = β111β
KEY_MOVE_DN = β116β
KEY_MOVE_LT = β113β
KEY_MOVE_RT = β114β
KEY_PAGE_UP = β112β
KEY_PAGE_DN = β117β
keypress active - only allowing 1 action at a time
so cant move up and roll up at same time
key_act = 0
tracking mouse axis states
mouse_move_lt = 0
mouse_move_rt = 0
mouse_move_up = 0
mouse_move_dn = 0
mouse_tilt_lt = 0
mouse_tilt_rt = 0
mouse_tilt_up = 0
mouse_tilt_dn = 0
mouse_press_dn = 0
mouse_lift_up = 0
-----------------------------------------------------------------------------------
mouse_lt = keytest(mouse_lt, STATE_LO, KEY_LT)
def key_test( iCurrState, iAxisPos, iCutPt, sKeyMod, sKeyOut ):
global key_act
# axis left / down
if iCutPt == STATE_LO:
if key_act == 0 and iCurrState == 0 and iAxisPos <= iCutPt:
key_act = 1
iCurrState = 1
if sKeyMod == KEY_CTRL_R: subprocess.run(["xdotool", "keydown", KEY_CTRL_R])
subprocess.run(["xdotool", "keydown", sKeyOut])
if iCurrState == 1 and iAxisPos >= iCutPt:
key_act = 0
iCurrState = 0
subprocess.run(["xdotool", "keyup", sKeyOut])
if sKeyMod == KEY_CTRL_R: subprocess.run(["xdotool", "keyup", KEY_CTRL_R])
# axis right / up
else:
if key_act == 0 and iCurrState == 0 and iAxisPos >= iCutPt:
key_act = 1
iCurrState = 1
if sKeyMod == KEY_CTRL_R: subprocess.run(["xdotool", "keydown", KEY_CTRL_R])
subprocess.run(["xdotool", "keydown", sKeyOut])
if iCurrState == 1 and iAxisPos <= iCutPt:
key_act = 0
iCurrState = 0
subprocess.run(["xdotool", "keyup", sKeyOut])
if sKeyMod == KEY_CTRL_R: subprocess.run(["xdotool", "keyup", KEY_CTRL_R])
return( iCurrState )
-----------------------------------------------------------------------------------
Main - open connection to device
success = pyspacemouse.open()
if success:
# allow cleanup at exit
try:
while 1:
# allow downtime for cpu
# better to use callbacks, maybe later :)
time.sleep(0.01)
# get all axis / button states
state = pyspacemouse.read()
# print("Btns", state.buttons)
# print("X:", f'{state.x:.3f}', "Y:", f'{state.y:.3f}', "Z:", f'{state.z:.3f}', "Pitch:", f'{state.pitch:.3f}', "Roll:", f'{state.roll:.3f}', "Yaw:", f'{state.yaw:.3f}')
mouse_move_lt = key_test(mouse_move_lt, state.x, STATE_LO, "", KEY_MOVE_LT)
mouse_move_rt = key_test(mouse_move_rt, state.x, STATE_HI, "", KEY_MOVE_RT)
mouse_move_up = key_test(mouse_move_up, state.y, STATE_HI, "", KEY_MOVE_UP)
mouse_move_dn = key_test(mouse_move_dn, state.y, STATE_LO, "", KEY_MOVE_DN)
mouse_tilt_lt = key_test(mouse_tilt_lt, state.roll, STATE_LO, KEY_CTRL_R, KEY_MOVE_LT)
mouse_tilt_rt = key_test(mouse_tilt_rt, state.roll, STATE_HI, KEY_CTRL_R, KEY_MOVE_RT)
mouse_tilt_up = key_test(mouse_tilt_up, state.pitch, STATE_HI, KEY_CTRL_R, KEY_MOVE_UP)
mouse_tilt_dn = key_test(mouse_tilt_dn, state.pitch, STATE_LO, KEY_CTRL_R, KEY_MOVE_DN)
mouse_press_dn = key_test(mouse_press_dn, state.z, STATE_LO, "", KEY_PAGE_DN)
mouse_lift_up = key_test(mouse_lift_up, state.z, STATE_HI, "", KEY_PAGE_UP)
# for each button in array / on device (hopefully)
# ndx is zero based, button cnt 1 based
sKey = "0";
for ndx, x in enumerate(state.buttons):
# reset to first key in line at overflow - 1:10 twice
if ndx == 10 or ndx == 20:
sKey = "0"
# inc key about to be active
sKey = chr( ord(sKey)+1 )
# specific assignment for 10th key
if ndx == 9 or ndx == 19:
sKey = "0"
# button active
if x:
# print("key2:", sKey, "ndx:", ndx)
subprocess.run(["xdotool", "keydown", KEY_CTRL_R])
if ndx > 9: subprocess.run(["xdotool", "keydown", KEY_SHIFT_R])
subprocess.run(["xdotool", "key", sKey])
if ndx > 9: subprocess.run(["xdotool", "keyup", KEY_SHIFT_R])
subprocess.run(["xdotool", "keyup", KEY_CTRL_R])
# capture exit calls
except KeyboardInterrupt:
print("\nCleaning up and exiting...")
# key active, ensure key release
if key_act:
if mouse_move_lt or mouse_tilt_lt: subprocess.run(["xdotool", "keyup", KEY_MOVE_LT])
if mouse_move_rt or mouse_tilt_lt: subprocess.run(["xdotool", "keyup", KEY_MOVE_RT])
if mouse_move_up or mouse_tilt_up: subprocess.run(["xdotool", "keyup", KEY_MOVE_UP])
if mouse_move_dn or mouse_tilt_dn: subprocess.run(["xdotool", "keyup", KEY_MOVE_DN])
if mouse_press_dn: subprocess.run(["xdotool", "keyup", KEY_PAGE_UP])
if mouse_lift_up: subprocess.run(["xdotool", "keyup", KEY_PAGE_DN])
# assume these need clearing, its easier
subprocess.run(["xdotool", "keyup", KEY_ALT_R])
subprocess.run(["xdotool", "keyup", KEY_CTRL_R])
subprocess.run(["xdotool", "keyup", KEY_SHIFT_R])
sys.exit(0) # Exit gracefully with exit status 0
else:
print(β\nNo connection to SpaceMouse foundβ)
sys.exit(0)