2008-11-29

Suspending usb mouse on screensaver

Having one of the smaller desks in the universe I often encounter trouble when trying to do my homework without accidentally moving the mouse. Also sometimes I get disturbed by its red light when trying to sleep. Therefore I decided that my mouse should be turned off when the screensaver starts. And here's how to do it:

First open up your kernel config and make sure CONFIG_USB_SUSPEND (USB selective suspend/resume and wakeup) is enabled. Rebuild and reboot as necessary.

Now we are ready to start playing with the mouse. Run lsusb to find the proper bus and device id, here's the output from my system: (to anyone reading this through rss/planet lysator I recommend reading the original post if you want nice code formatting)

$ lsusb
Bus 002 Device 004: ID 046d:c30e Logitech, Inc.
Bus 002 Device 003: ID 0424:2504 Standard Microsystems Corp. USB 2.0 Hub
Bus 002 Device 001: ID 1d6b:0002
Bus 008 Device 001: ID 1d6b:0001
Bus 007 Device 001: ID 1d6b:0001
Bus 006 Device 001: ID 1d6b:0001
Bus 005 Device 002: ID 045e:0095 Microsoft Corp.
Bus 005 Device 001: ID 1d6b:0001
Bus 004 Device 001: ID 1d6b:0001
Bus 001 Device 001: ID 1d6b:0002
Bus 003 Device 001: ID 1d6b:0001


Unfortunately I have not found a way to use the device name to identify it, so it has to be done by bus and device numbers. I have a Microsoft mouse so I notice the numbers and go looking under /sys/bus/usb/devices/5-2/power/ (bus 5, dev 2). (The numbering can get more interesting if your device is connected through a hub, then you have to start guessing the correct directory and cat busnum and devnum respectively)

$ ls /sys/bus/usb/devices/5-2/power
active_duration autosuspend connected_duration level persist wakeup


The important "file" for our purpose is the one named level. But before continuing one should also note wakeup (tell the kernel whether the device should be able to wake itself up or not) and persist (really cool feature: if a device gets disconnected and the connected again this will make it continue running as it were never disconnected. Really nifty when one is hibernating with mounted usb disks and such.) (More info: /usr/src/linux/Documentation/usb/{power-management,persist}.txt)

Returning to level, this "file" has three states: on, auto and suspend. Setting level to on will force the device to never suspend, setting it to auto will automatically suspend it after autosuspend seconds (at least in theory, does not seem to work for me (maybe my mouse is never idle/the driver does not support it?)) and suspend will suspend the device (it will still have some power to be able to wake up and for the system to know if it is still there (yep, I lost the sources)).

Make sure you have found the correct device (it isn't equally funny to suspend your keyboard ;)) and try the following: (replacing the bus and dev num for the ones your mouse have)

$ cat /sys/bus/usb/devices/5-2/power/level
auto
# sudo -s
# echo suspend > /sys/bus/usb/devices/5-2/power/level
# echo auto > /sys/bus/usb/devices/5-2/power/level

This should tell you which state your mouse initially is in, suspend the mouse (LEDs/laser turns off, try to move it ;)) and finally turn the mouse back to the original state (if it was auto). (Try echoing enabled/disabled to wakeup and click the mouse while it is suspended)

Having got all that working it is time to set up the automatics: First we need a way for a user to turn the mouse on/off (okay, we don't really need that but I find it nicer to run as much as possible as a normal user ;)).

With my small c skills and google I threw this together:
suspend_mouse.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[])
{
FILE *file = fopen("/sys/bus/usb/devices/5-2/power/level", "w");

if(file == NULL)
{
fprintf(stderr,"Can not open device file for writing, are you root?\n");
exit(1);
}

if(argc == 1 || strcmp("1", argv[1]) == 0)
fputs("suspend", file); //check error
else if(argc > 1 && strcmp("0", argv[1]) == 0)
fputs("auto", file);
fclose(file); //try-catch?

return 0;
}

Makefile

default:
gcc suspend_mouse.c -o suspend_mouse


Compile it by running

$ make
gcc suspend_mouse.c -o suspend_mouse


Copy it to /usr/bin and setuid it so all users can turn off the mouse (maybe not the safest way :)) (oh, and here is why you shouldn't do this in sh/bash: How can I get setuid shell scripts to work?)

$ sudo cp suspend_mouse /usr/bin
$ sudo chmod +sx /usr/bin/suspend_mouse
$ ls -l /usr/bin/suspend_mouse
-rwsr-sr-x 1 root root 11K 2008-11-29 18:14 /usr/bin/suspend_mouse


And try it out

$ suspend_mouse
$ suspend_mouse 1
$ suspend_mouse 0
$ suspend_mouse 1


There we are, almost finished. Now we just need the mouse to suspend when the screensaver is running. Poking around a bit with qdbus gave me ActiveChanged under org.freedesktop.ScreenSaver. Picking up and modifying one of my earlier dbus experiments this is the result in python:
screensaver_dbus.py

#!/usr/bin/env python

import dbus, gobject
from dbus.mainloop.glib import DBusGMainLoop
import os

def on_activechanged(active):
if(active):
os.system('/usr/bin/suspend_mouse 1')
else:
os.system('/usr/bin/suspend_mouse 0')

dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SessionBus()

screensaver = bus.get_object("org.kde.screensaver", "/ScreenSaver")

bus.add_signal_receiver(on_activechanged,
dbus_interface="org.freedesktop.ScreenSaver",
signal_name="ActiveChanged")

loop = gobject.MainLoop()
loop.run()

This script basically registers itself for the ActiveChanged signal and the goes into a dbus listening loop and reacts every time the screensaver is turned on or off. It should preferably be started when logging in.

And we are finished!

Two seconds after writing the code I realized that I don't use the screensaver and I haven't found a way to detect when DPMS is turned on/off by means of using dbus. Any information on this is welcome ;) Also one would maybe want to make this more global and not depending on the current user session.