Here's a reference implementation. It works quite well on my T41.
Code:
#
# Two (or more) finger scrolling for Synaptics TouchPad and
# Windows XP.
#
# Reference implemetation in Python. Should be rewritten in C#
# or simmilar language.
#
# Uses the fact that two fingers act same as a very hard press
# of one finger.
#
# Currently supports vertical scrolling only.
#
# Supports tapping to activate the right mouse button too.
#
# Requires Python win32 extensions, download from:
# https://sourceforge.net/projects/pywin32/
#
# You will also have to run the
# <Python>\Lib\site-packages\win32com\client\makepy.py
# script and select the
# Synaptics Device Control Widgets, Version 1.0 (1.0)
# entry, to add the TouchPad library to the Python.
#
# Copyright (c) 2008 Arkadiusz Wahlig
#
# Version 1.3
#
# If the pressure of a moving finger(s) exceeds this value,
# scroll is activated.
SCROLL_PRESSURE_THRESHOLD = 90
# Scroll speed factor. Greater value = faster scroll.
SCROLL_SPEED_FACTOR = 0.7
# Minimal pressure value for the right button tap to be initiated.
TAP_PRESSURE_THRESHOLD = 90
# Maximal duration (in milliseconds) of a tap. Set to 0 to disable
# tapping.
TAP_TIMEOUT = 175
# Maximal distance the finger can traverse during a tap.
TAP_MAX_DISTANCE = 75
# =================================================================
from win32com.client import Dispatch, DispatchWithEvents, constants
import pythoncom, win32api
from ctypes import *
from win32con import *
# INPUT structure with integrated MOUSEINPUT union member, see:
# http://msdn.microsoft.com/en-us/library/ms646270(VS.85).aspx
class XMOUSEINPUT(Structure):
_fields_ = [('type', c_int),
('dx', c_long),
('dy', c_long),
('mouseData', c_int),
('dwFlags', c_int),
('time', c_int),
('dwExtraInfo', c_void_p)]
class SynDeviceCtrlEvents:
def __init__(self):
self.packet = Dispatch('SynCtrl.SynPacketCtrl')
self.__acquired = False
self.touch_pos = None
self.tap_in_progress = False
def Finish(self):
'''Clears the state of the device.'''
if self.IsPadAcquired():
self.ReleasePad()
if self.tap_in_progress:
self.SetDeviceTap(self.device_tap)
def AcquirePad(self):
'''Acquires the TouchPad for exclusive access. This will
stop the mouse pointer.'''
self.Acquire(0)
self.__acquired = True
def ReleasePad(self):
'''Release the TouchPad. The mouse pointer starts moving
again.'''
self.Unacquire()
self.__acquired = False
def IsPadAcquired(self):
'''Returns True if the TouchPad has been acquired using
AcquirePad().'''
return self.__acquired
def GetDeviceTap(self):
'''Returns True if tapping is enabled in the TouchPad
control panel.'''
g = self.GetLongProperty(constants.SP_Gestures)
return bool(g & constants.SF_GestureTap)
def SetDeviceTap(self, value):
'''Enables or disabled tapping in the TouchPad control
panel.'''
g = self.GetLongProperty(constants.SP_Gestures)
if value:
g |= constants.SF_GestureTap
else:
g &= ~constants.SF_GestureTap
self.SetLongProperty(constants.SP_Gestures, g)
def OnPacket(self):
# this event is called when the TouchPad has new data for us
p = self.packet
self.LoadPacket(p)
# handle scrolling
if p.FingerState & constants.SF_FingerPresent:
if self.touch_pos is None:
# remember the position of the pointer when the pad
# was touched
self.touch_pos = win32api.GetCursorPos()
if p.FingerState & constants.SF_FingerMotion:
if p.Z >= SCROLL_PRESSURE_THRESHOLD and \
not self.IsPadAcquired():
# finger moved with high pressure, acquire the pad
self.AcquirePad()
# reset the position of the pointer
win32api.SetCursorPos(self.touch_pos)
if self.IsPadAcquired():
# pad acquired, finger moved -> do the scroll
self.DoScroll(p.XDelta, p.YDelta)
else:
self.touch_pos = None
if self.IsPadAcquired():
# finger lifted, release the pad
self.ReleasePad()
# handle tapping
if p.FingerState & constants.SF_FingerPresent:
if p.Z >= TAP_PRESSURE_THRESHOLD and \
not self.tap_in_progress:
# high pressure detected, init a tap
self.tap_in_progress = True
self.tap_start = p.TimeStamp
self.tap_distance = 0
# disable device tap to prevent it from being triggered
self.device_tap = self.GetDeviceTap()
self.SetDeviceTap(False)
if self.tap_in_progress and \
p.FingerState & constants.SF_FingerMotion:
self.tap_distance += abs(p.XDelta) + abs(p.YDelta)
elif self.tap_in_progress:
# finger lifted, finish a tap
self.tap_in_progress = False
# check the tap duration
if p.TimeStamp - self.tap_start < TAP_TIMEOUT and \
self.tap_distance < TAP_MAX_DISTANCE:
self.DoTap()
# reenable device tap if needed
self.SetDeviceTap(self.device_tap)
# quit if requested (left button during scroll)
if self.IsPadAcquired() and \
p.ButtonState & constants.SF_ButtonLeft:
win32api.PostQuitMessage()
def DoScroll(self, dx, dy):
'''Sends a mouse wheel event to the system.'''
m = XMOUSEINPUT()
m.type = INPUT_MOUSE
m.dwFlags = MOUSEEVENTF_WHEEL
m.mouseData = int(dy * SCROLL_SPEED_FACTOR)
windll.user32.SendInput(1, byref(m), sizeof(XMOUSEINPUT))
def DoTap(self):
'''Sends a mouse right button click event to the system.'''
m = (XMOUSEINPUT * 2)()
m[0].type = INPUT_MOUSE
m[0].dwFlags = MOUSEEVENTF_RIGHTDOWN
m[1].type = INPUT_MOUSE
m[1].dwFlags = MOUSEEVENTF_RIGHTUP
windll.user32.SendInput(2, byref(m), sizeof(XMOUSEINPUT))
def main():
# create and initialize the API object
api = Dispatch('SynCtrl.SynAPICtrl')
api.Initialize()
api.Activate()
# find a TouchPad
handle = api.FindDevice(constants.SE_ConnectionAny,
constants.SE_DeviceTouchPad,
-1)
if handle < 0:
raise RuntimeError('Can\'t find a TouchPad device!')
# create and initialize the device object
device = DispatchWithEvents('SynCtrl.SynDeviceCtrl',
SynDeviceCtrlEvents)
device.Select(handle)
device.Activate()
# start pumping the windows messages, without this
# the device's OnPacket event won't ever be called
pythoncom.PumpMessages()
# clear the state of the device
device.Finish()
if __name__ == '__main__':
main()
Someone with time on his hands can use it to write a proper tool in some compilable language.
--- EDIT ---
Updated to version 1.3.