#!/usr/bin/python
#
# pymouse -- PyMouse ( ExplorerPS/2 emulator from an USB pointer device )
#
# Copyright (C) 2002 Frederic Jolliton -- <pymouse@tuxee.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
#-----------------------------------------------------------------------------
#
# WARNING: This is a big HACK and doesn't emulate all the PS/2 feature,
# BUT this work very well for me for my Logitech Cordless Trackman Optical
# trackball.
#
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
#
# Most of the information needed to build this program come from
# this page:
#
# http://panda.cs.ndsu.nodak.edu/~achapwes/PICmicro/mouse/mouse.html
#
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
#
# TODO: If possible use the three small buttons arround the wheel
# as real buttons (numbered 8 9 10)
#
#-----------------------------------------------------------------------------
# Config module
# This is the file that will be symlinked to the real pseudo-device
# at runtime.
aliasDevice = '/tmp/mouse'
# The event source
usbMouse = '/dev/input/event%s'
#-----------------------------------------------------------------------------
#
# PyMouse must be started *BEFORE* X Window.
#
#
# Configure XWindow by editing /etc/X11/XF86Config with:
#
# + - - - - - - - - - - - - - - - - - - - - - -
# : Section "InputDevice"
# : Identifier "Mouse1"
# : Driver "mouse"
# : Option "Device" "/tmp/mouse"
# : Option "Protocol" "ExplorerPS/2"
# : Option "Buttons" "7"
# : Option "ZAxisMapping" "6 7"
# : [...]
# : EndSection
# + - - - - - - - - - - - - - - - - - - - - - -
#
# And modify your xmodmap file or add the following line to your .xinitrc (or
# any other file executed when X Window start) to reorder buttons number
# (otherwise you can not use correctly the new buttons or lose the wheel
# feature):
#
# xmodmap -e "pointer = 1 2 3 6 7 4 5"
#
# Start X Window, and check with the 'xev' command that your mouse is
# now sending correct button event.
#
#-----------------------------------------------------------------------------
import struct
import pty
import os
import sys
import select
import signal
import time
#
# Open a new PTY master
#
def openPty() :
fdPty , slave = pty.openpty()
# Retrieve the filename from the file descriptor
slave = os.readlink( '/proc/self/fd/%s' % slave )
os.system( 'stty cs8 -icanon min 1 time 0 -isig -xcase -inpck -echo < %s' % slave )
try :
os.unlink( pymouseconf.aliasDevice )
except OSError :
pass
os.symlink( slave , pymouseconf.aliasDevice )
os.chmod( slave , 0666 )
return fdPty
#
# Read an USB event
#
# Return ( unixTime , unixTimeMsec , eventType , eventCode , value )
#
def readEvent( fdInput ) :
return struct.unpack( "LLHHl" , fdInput.read( 16 ) )
#
# Output an PS/2 event
#
# If wheel == None then output a 3 bytes data packet,
# otherwise output a 4 bytes data packet.
#
def produceEvent( fdPty , button , dx , dy , wheel ) :
if not dataReporting : return
while dx < 0 : dx += 512
while dy < 0 : dy += 512
if wheel == None :
msg = struct.pack( '@BBB' ,
( ( dy >> 3 ) & 32 ) + ( ( dx >> 4 ) & 16 ) + 8 + ( button & 7 ) ,
dx & 255 ,
dy & 255 )
else :
while wheel < 0 : wheel += 256
msg = struct.pack( '@BBBB' ,
( ( dy >> 3 ) & 32 ) + ( ( dx >> 4 ) & 16 ) + 8 + ( button & 7 ) ,
dx & 255 ,
dy & 255 ,
( ( button << 1 ) & 48 ) + ( wheel & 15 ) )
os.write( fdPty , msg )
EVENT_CLICK = 1
EVENT_MOVE = 2
X_AXIS = 0
Y_AXIS = 1
WHEEL_AXIS = 8
# Map code to button number
button = {
272 : 0 ,
273 : 1 ,
274 : 2 ,
275 : 3 ,
276 : 4
}
#
# We use a small automate to check if client is trying to
# activate the ExplorerPS/2 protocol. So, the amRate function
# is called with the rate value, and if 3 consecutive setting
# for this value are [ 200 , 200 , 80 ] then we activate
# the ExplorerPS/2 protocol.
#
amRateState = 0
def amRate( rate = None ) :
global amRateState
if rate == None : return amRateState
if amRateState == 0 :
if rate == 200 : amRateState = 1
elif amRateState == 1 :
if rate == 200 : amRateState = 2
else : amRateState = 0
elif amRateState == 2 :
if rate == 80 : amRateState = 3
elif rate == 200 : amRateState = 1
else : amRateState = 0
elif amRateState == 3 :
if rate == 80 : amRateState = 0
elif rate == 200 : amRateState = 1
else : amRateState = 0
return amRateState
def signalUsr1( signalNumber , stackFrame ) :
global reopenInput
reopenInput = 1
def openMouse() :
fdInput = None
devFilename = None
while 1 :
for n in range( 15 ) :
try :
devFilename = pymouseconf.usbMouse % n
fdInput = open( devFilename )
except :
pass
if fdInput != None : break
if fdInput != None : break
print 'Unable to open mouse. Retrying in 1 second.'
sys.stdout.flush()
time.sleep( 1 )
return fdInput , devFilename
signal.signal( signal.SIGUSR1 , signalUsr1 )
# Indicate if data must be generated
dataReporting = None
# Indicate if input must be reopened
reopenInput = None
fdInput = None
def main() :
global dataReporting , fdInput , reopenInput
imps2 = 0
imps2Misc = None
nextAuto = time.time() + 10
fdInput , devFilename = openMouse()
fdPty = openPty()
print 'PyMouse 0.0 started'
print 'USB device is %s' % devFilename
print '(SIGUSR1 reopen the input)'
sys.stdout.flush()
buttonPressed = 0
while 1 :
try :
r , w , x = select.select( [ fdInput , fdPty ] , [] , [] , 1 )
except select.error , e :
if e[ 0 ] != 4 : raise
r , w , x = [] , [] , []
if nextAuto <= time.time() :
print 'AutoReopen after 10 seconds of inactivity'
sys.stdout.flush()
reopenInput = 1
nextAuto = time.time() + 10
if fdPty in r :
cmd = os.read( fdPty , 1 )
if cmd == '\xff' : # Reset
os.write( fdPty , '\xfa' )
elif cmd == '\xfe' : # Resend
# FIXME: Must resend the last packet
pass
elif cmd == '\xf6' : # Set Defaults
os.write( fdPty , '\xfa' )
elif cmd == '\xf5' : # Disable Data Reporting
os.write( fdPty , '\xfa' )
dataReporting = 0
elif cmd == '\xf4' : # Enable Data Reporting
os.write( fdPty , '\xfa' )
dataReporting = 1
elif cmd == '\xf3' : # Set Sample Rate
os.write( fdPty , '\xfa' )
rate = os.read( fdPty , 1 )
rate = ord( rate )
if amRate( rate ) == 3 :
imps2 = 1
imps2Misc = 0
os.write( fdPty , '\xfa' )
elif cmd == '\xf2' : # Get Device ID
if imps2 :
os.write( fdPty , '\xfa\x04' ) # device id
else :
os.write( fdPty , '\xfa\x00' ) # device id
elif cmd == '\xf0' : # Set Remote Mode
os.write( fdPty , '\xfa' )
elif cmd == '\xee' : # Set Wrap Mode
os.write( fdPty , '\xfa' )
elif cmd == '\xec' : # Reset Wrap Mode (back to remote or stream mode)
os.write( fdPty , '\xfa' )
elif cmd == '\xeb' : # Read Data
os.write( fdPty , '\xfa' )
# FIXME: Send movement packet
elif cmd == '\xea' : # Set Stream Mode
os.write( fdPty , '\xfa' )
elif cmd == '\xe9' : # Status Request
os.write( fdPty , '\xfa' )
# FIXME: Send status packet
elif cmd == '\xe8' : # Set Resolution
os.write( fdPty , '\xfa' )
os.read( fdPty , 1 ) # discard resolution value
os.write( fdPty , '\xfa' )
elif cmd == '\xe7' : # Set Scaling 2:1
os.write( fdPty , '\xfa' )
elif cmd == '\xe6' : # Set Scaling 1:1
os.write( fdPty , '\xfa' )
if fdInput in r :
nextReopen = time.time() + 10
unixTime , unixTimeMsec , eventType , eventCode , data = readEvent( fdInput )
if eventType == EVENT_CLICK :
buttonNumber = button.get( eventCode )
if buttonNumber != None :
if data == 0 : buttonPressed &= ~( 1 << buttonNumber )
elif data == 1 : buttonPressed |= ( 1 << buttonNumber )
produceEvent( fdPty , buttonPressed , 0 , 0 , imps2Misc )
elif eventType == EVENT_MOVE :
if eventCode == X_AXIS :
produceEvent( fdPty , buttonPressed , data , 0 , imps2Misc )
elif eventCode == Y_AXIS :
produceEvent( fdPty , buttonPressed , 0 , -data , imps2Misc )
elif eventCode == WHEEL_AXIS and imps2 :
produceEvent( fdPty , buttonPressed , 0 , 0 , -data )
if reopenInput :
reopenInput = None
fdInput.close()
fdInput = None
print 'Input closed (%s)' % devFilename
sys.stdout.flush()
reload( pymouseconf )
fdInput , dev = openMouse()
print 'Input opened (%s)' % devFilename
sys.stdout.flush()
try :
main()
except KeyboardInterrupt :
print 'Bye'
os.unlink( pymouseconf.aliasDevice )