Another methos to use a xorg pointing device/mouse

Questions and answers about 3Dconnexion devices on UNIX and Linux.

Moderator: Moderators

Post Reply
tmgoblin
Posts: 3
Joined: Mon Feb 15, 2021 3:16 pm

Another methos to use a xorg pointing device/mouse

Post by tmgoblin »

Feb 2021: On both a Debian and a Gentoo system, I could not get a SpaceNavigator 046d:c626 to register as a joystick, unlike a SpacePilot Pro 046d:c629 which still worked with the instruction from https://wiki.gentoo.org/wiki/SpaceNavigator and similar elsewhere, using driver "joystick" in an InputClass in a xorg.conf.d/ file

Using evdev driver would work with Xorg as a 2-axis mouse, but trying to force it as a joystick using udev rules did not help. Although the device would show up as /dev/input/by-id/usb-3Dconnexion_SpaceNavigator-event-joystick, no /dev/input/js* was created, and Xorg would not pick it up as an input device. Neither was it identified by jstest.

However evdev was able to identify all the axes and buttons properly. evtest shows:

Code: Select all

Input device name: "3Dconnexion SpaceNavigator"
Supported events:
  Event type 0 (EV_SYN)
  Event type 1 (EV_KEY)
    Event code 256 (BTN_0)
    Event code 257 (BTN_1)
  Event type 2 (EV_REL)
    Event code 0 (REL_X)
    Event code 1 (REL_Y)
    Event code 2 (REL_Z)
    Event code 3 (REL_RX)
    Event code 4 (REL_RY)
    Event code 5 (REL_RZ)
  Event type 4 (EV_MSC)
    Event code 4 (MSC_SCAN)
  Event type 17 (EV_LED)
    Event code 8 (LED_MISC) state 0

In order to get it to be picked up by Xorg as an input device, I used a small python wrapper using python3-evdev and python3-uinput. Along with a udev rule, xorg.conf.d/ configuration, and a systemd service to start and stop the wrapper automatically. This seems to work well and should be flexible for alternate configurations or similar deices.


FILES
/etc/udev/rules.d/41-spacenavigator.rules
/etc/systemd/system/spacenav-python-evdev.service
/usr/local/bin/spacenav-python.py
/etc/X11/xorg.conf.d/54-spacenav-pyevdev.conf


/etc/udev/rules.d/41-spacenavigator.rules

Code: Select all

 
#obseved 046d:c626
#KERNEL Line allows non root user to create uinput device.  ADD action identifies devices flags as joystick, and creates input/spacenav symlink,


KERNEL=="uinput", MODE="0666"
ACTION=="add", KERNEL=="event[0-9]*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c626", ENV{ID_INPUT_JOYSTICK}="1", SYMLINK+="input/spacenav", RUN+="/usr/bin/systemctl start spacenav-python-evdev.service"
ACTION=="remove", KERNEL=="event[0-9]*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c626", RUN+="/usr/bin/systemctl stop spacenav-python-evdev.service"


/etc/systemd/system/spacenav-python-evdev.service

Code: Select all

 
[Unit]
Description = spacenav python evdev wrapper

[Service]
Type=oneshot
ExecStart = python3 /usr/local/bin/spacenav-python.py
ExecStop = killall spacenav-python.py

[Install]


/usr/local/bin/spacenav-python.py

Code: Select all

#!/usr/bin/python
# -*- coding: utf-8 -*-

import evdev
from evdev import InputDevice, categorize, ecodes
import time
import uinput

# creates object 'sn' to store the data
sn = InputDevice('/dev/input/spacenav')

# prints out device info at start
print sn

# for event in sn.read_loop():
#    print(categorize(event))

# input codes
X = 0
Y = 1  # negative=up
Z = 2

RY = 3
RX = 4
RZ = 5

BTN_L = 256
BTN_R = 257

max = 350
min = -max
fuzz = 5
flat = 5

events = (
    uinput.BTN_LEFT,
    uinput.BTN_RIGHT,
    uinput.ABS_X + (min, max, fuzz, flat),
    uinput.ABS_Y + (min, max, fuzz, flat),
    uinput.ABS_Z + (min, max, fuzz, flat),
    uinput.ABS_RX + (min, max, fuzz, flat),
    uinput.ABS_RY + (min, max, fuzz, flat),
    uinput.ABS_RZ + (min, max, fuzz, flat),
    )

device = uinput.Device(events, name='spacenav-pyevdev')

# loop and filter by event code
for event in sn.read_loop():
    if event.type != ecodes.EV_KEY:
       # if abs(event.value) > 20:
        if event.code == Y:
#           print("Y",event.value)
            device.emit(uinput.ABS_Y, event.value)
        elif event.code == X:
#            print("X", event.value)
            device.emit(uinput.ABS_X, event.value)
        elif event.code == Z:
#           print("Z" , event.value)
            device.emit(uinput.ABS_Z, event.value)
        elif event.code == RY:
#           print("RY", event.value)
            device.emit(uinput.ABS_RY, event.value)
        elif event.code == RX:
#           print("RX", event.value)
            device.emit(uinput.ABS_RX, event.value)
        elif event.code == RZ:
#           print("RZ", event.value)
            device.emit(uinput.ABS_RZ, event.value)
    if event.type == ecodes.EV_KEY:
        if event.code == BTN_L:
            device.emit(uinput.BTN_LEFT, event.value)
        # without the emit ABS_RX 0, for some reason the ABS_RX would min out  until new value sent?
            device.emit(uinput.ABS_RX, 0)
        elif event.code == BTN_R:
            device.emit(uinput.BTN_RIGHT, event.value)
            device.emit(uinput.ABS_RX, 0)


/etc/X11/xorg.conf.d/54-spacenav-pyevdev.conf

Code: Select all


Section "InputClass"
        Identifier "spacenav-python-uinput"
        MatchProduct "spacenav-pyevdev"
        Option "AutoRepeat" "1500 1"
        Driver "joystick"

        # X: move left/right - send shift tab, tab
        Option "MapAxis1"  "keylow=50,23 keyhigh=23 mode=relative axis=+1key deadzone=6000"

        # Y: move up/down - send page up/down 
        Option "MapAxis2"   "keylow=112 keyhigh=117 mode=relative axis=+1key deadzone=6000"

        # Z: pull up / push down - vertical scroll
        Option "MapAxis3"   "mode=relative axis=+0.75zy deadzone=6500"

        # RY: tilt up/down - move pointer Y
        Option "MapAxis4"   "mode=relative axis=-1.5x deadzone=4000"

        # RX: tilt left/right -  move pointer X
        Option "MapAxis5"   "mode=relative axis=+1.5y deadzone=4000"

        # RZ: twist left/right - horizontal scroll
        Option "MapAxis6"   "mode=relative axis=+1zx deadzone=6000"

        # Button 2 - right click
        Option "MapButton2" "button=3"

EndSection
tmgoblin
Posts: 3
Joined: Mon Feb 15, 2021 3:16 pm

Re: Another method to use a xorg pointing device/mouse

Post by tmgoblin »

It is also necessary to have loaded the uinput kernel module, eg with

/etc/modules-load.d/spacenav.conf

Code: Select all

uinput
tmgoblin
Posts: 3
Joined: Mon Feb 15, 2021 3:16 pm

Re: Another method to use a xorg pointing device/mouse

Post by tmgoblin »

Also found a typo on pasting the /usr/local/bin/spacenav-python.py : line 9 should be "print(sn)". The parentheses were dropped.

/usr/local/bin/spacenav-python.py

Code: Select all

#!/usr/bin/python
# -*- coding: utf-8 -*-

import evdev
from evdev import InputDevice, categorize, ecodes, UInput
import time
import uinput

# creates object 'sn' to store the data
sn = InputDevice('/dev/input/spacenav')

# prints out device info at start
print(sn)

# for event in sn.read_loop():
#    print(categorize(event))

# input code variables
X = 0
Y = 1  # negative up
Z = 2

RY = 3
RX = 4
RZ = 5

BTN_L = 256
BTN_R = 257

max = 350
min = -max
fuzz = 5
flat = 5

events = (
    uinput.BTN_LEFT,
    uinput.BTN_RIGHT,
    uinput.ABS_X + (min, max, fuzz, flat),
    uinput.ABS_Y + (min, max, fuzz, flat),
    uinput.ABS_Z + (min, max, fuzz, flat),
    uinput.ABS_RX + (min, max, fuzz, flat),
    uinput.ABS_RY + (min, max, fuzz, flat),
    uinput.ABS_RZ + (min, max, fuzz, flat),
    )

device = uinput.Device(events, name='spacenav-pyevdev')

# loop and filter by event code
for event in sn.read_loop():
    if event.type != ecodes.EV_KEY:
       # if abs(event.value) > 20:
        if event.code == Y:
#           print("Y",event.value)
            device.emit(uinput.ABS_Y, event.value)
        elif event.code == X:
#            print("X", event.value)
            device.emit(uinput.ABS_X, event.value)
        elif event.code == Z:
#           print("Z" , event.value)
            device.emit(uinput.ABS_Z, event.value)
        elif event.code == RY:
#           print("RY", event.value)
            device.emit(uinput.ABS_RY, event.value)
        elif event.code == RX:
#           print("RX", event.value)
            device.emit(uinput.ABS_RX, event.value)
        elif event.code == RZ:
#           print("RZ", event.value)
            device.emit(uinput.ABS_RZ, event.value)
    if event.type == ecodes.EV_KEY:
        if event.code == BTN_L:
            device.emit(uinput.BTN_LEFT, event.value)
        # without the emit ABS_RX 0, for some reason the ABS_RX would min out  until new value sent?
            device.emit(uinput.ABS_RX, 0)
        elif event.code == BTN_R:
            device.emit(uinput.BTN_RIGHT, event.value)
            device.emit(uinput.ABS_RX, 0)

[/code
Post Reply