Wednesday, September 29, 2010

Disable touchpad when USB Mouse connected on Ubuntu (Lucid)

Update: 7/11/2010 Fixed a bug that happened with my laptop. The xinput was reset periodically for some reason.

Update: 2/11/2010 Fixed it a bit to make it works better. Now it don't need fix device name.

I wrote a short script to disable touchpad when mouse attached in Jaunty, which never works for me anymore after Jaunty. The reason is that gconftool and synclient could not correctly set touchpad state (it works for a very short while, until Gnome or X enable it back again). I just found the solution for Lucid (and probably for Maverick). It's

#!/usr/bin/python


import dbus # needed to do anything
from dbus.exceptions import DBusException
import dbus.decorators # needed to receive messages
import dbus.glib # needed to receive messages
from dbus.mainloop.glib import DBusGMainLoop
import gobject # needed to loop & monitor
import os, re, time, sys

global add_action, remove_action, bus, hal_manager

_debug = True
device_added = []
touchpad_enabled = True
device_cap = 'input.mouse'

add_action = 'sleep 1s && xinput --list --short | grep -i touchpad | sed "s/^.*[[:space:]]*id=\\([0-9]*\\)[[:space:]]*.*$/\\1/g" | xargs -I id xinput --set-prop id --type=int --format=8 "Device Enabled" 0'
remove_action = 'xinput --list --short | grep -i touchpad | sed "s/^.*[[:space:]]*id=\\([0-9]*\\)[[:space:]]*.*$/\\1/g" | xargs -I id xinput --set-prop id --type=int --format=8 "Device Enabled" 1'
check_action = 'xinput --list --short | grep -i touchpad | sed "s/^.*[[:space:]]*id=\\([0-9]*\\)[[:space:]]*.*$/\\1/g" | xargs -I id xinput --list-props id | grep -qi "device.*enabled.*:[[:space:]]*0$"'

exclude_devices = ['usb_device_a5c_4503_noserial_if0_logicaldev_input']

def dprint(msg) :
    global _debug
    if _debug :
        sys.stderr.write(msg + '\n')
        sys.stderr.flush()

#@dbus.decorators.explicitly_pass_message
def add_device(*args, **keywords):
    check_and_run_add_action(args[0])

def check_and_run_add_action(device_path) :
    global device_added

    retval = False
    Path = device_path.split('/')
    dprint('device added == %s' % Path)

    if Path[-1] in exclude_devices :
        return retval

    device_obj = bus.get_object('org.freedesktop.Hal', device_path)
    device = dbus.Interface(device_obj, dbus_interface = "org.freedesktop.Hal.Device")
    cap = device.QueryCapability(device_cap)

    dprint('Device capability == %s' % cap)
    try :
        prop = device.GetPropertyString('info.subsystem')
    except DBusException :
        prop = None

    dprint('Property String == %s' % prop)

    if cap and prop and (prop == 'input') :
        device_added.append(Path[-1])
        dprint('Executing add_action')
        os.system(add_action)
        retval = True
    return retval
       
#@dbus.decorators.explicitly_pass_message
def remove_device(*args, **keywords):
    global bus, _debug, device_added

    Path = args[0].split('/')
    dprint('device removed == %s' % Path)

    try :
        if device_added :
            if Path[-1] in device_added :
                device_added.remove(Path[-1])
                dprint('Excuting remove_action')
                os.system(remove_action)
            else :
                # other device removed, run add action again to make sure
                # this workaround a bug in Lucid that device was periodically removed will reset enable flag
                os.system(add_action)
    except :
        dprint('Exception while removing device')
        if _debug :
            import traceback
            traceback.print_exc()

DBusGMainLoop(set_as_default = True)
bus = dbus.SystemBus()  # connect to system bus
hal_manager_obj = bus.get_object('org.freedesktop.Hal', '/org/freedesktop/Hal/Manager')
hal_manager = dbus.Interface(hal_manager_obj, 'org.freedesktop.Hal.Manager')

# Add listeners for all devices being added or removed
bus.add_signal_receiver(add_device, 'DeviceAdded', 'org.freedesktop.Hal.Manager',
                        'org.freedesktop.Hal', '/org/freedesktop/Hal/Manager')
bus.add_signal_receiver(remove_device, 'DeviceRemoved', 'org.freedesktop.Hal.Manager',
                        'org.freedesktop.Hal', '/org/freedesktop/Hal/Manager')

# Run remove action once to enable touchpad
dprint('Running removaction once')
os.system(remove_action)

# Find mouse first
dprint('Finding mouse first')
udis = hal_manager.FindDeviceByCapability(device_cap)
for udi in udis :
    dprint('Found device == %s' % udi)
    Path = udi.split('/')
    if check_and_run_add_action(udi) :
        break

# monitor
dprint('Start Mainloop')
loop = gobject.MainLoop()
try :
    loop.run()
except SystemExit, e :
    dprint('Got SystemExit exception %s' % e)
    raise e
except Exception, e :
    dprint('Got Exception from the loop')
    if _debug :
        import traceback
        traceback.print_exc()
    loop.quit()
    sys.exit(255)
The trick is to use xinput instead of gconftool/synclient!