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.

2008-11-01

Pulseaudio: controlling your volume needs

To get the most volume out of ones speakers (literally) one must first make sure that the underlying sound systems have their volumes at 100%. In my case pulseaudio runs on top of alsa and I had to run

alsamixer -c0

and maximize the needed outputs.

Moving on to PulseAudio I put these lines in my system.pa:

### Automatically restore the volume of streams and devices
load-module module-stream-restore
load-module module-device-restore

# Set some default volumes
load-module module-match table=/etc/pulse/match.table

I think it should be pretty self-explanatory.

Lastly, about the last line, I did not use module-match in my initial configuration, but having the ability to make a stream sound higher than the others made me add it. The basic problem here is that all streams get 100% volume at creation and to have a stream louder than the others forces one to lower all the other streams.

The solution is to use the module-match module, it reads its settings from a simple table formatted like this:

regexp volume
regexp volume
...

(Notice the space separator, this makes the regexps a bit harder to write)

regexp is a (I find out after digging in the source) Posix Extended Regular Expression
volume is the volume, the range is 0-65535 (= 0%-100%)

As I wanted all streams to start at 50% I made this table.

32767

(There's a space character before 32767)

Matching every stream and setting them to 50% (65536/2 - 1) the problem was solved.

I thought.

It turns out that my nicely remapped outputs (see my previous post) have their own remapped streams which of course also were turned to 50%.

As Posix Extended Regular Expressions does not, as far as I know, have any look-behind/ahead or don't-match-this-string functionality I figured that putting the rules in the right order would make my setup work. The documentation is very sparse so I did what one should not do: depend on the internals. The relevant loop is

for (r = u->rules; r; r = r->next) {
if (!regexec(&r->regex, n, 0, NULL, 0)) {
pa_cvolume cv;
pa_log_debug("changing volume of sink input '%s' to 0x%03x", n, r->volume);
pa_cvolume_set(&cv, si->sample_spec.channels, r->volume);
pa_sink_input_set_volume(si, &cv);
}
}

and as one can see the last rule wins. (I would have thought it would have been faster just terminating at the first hit, but what do I know ;))

This gives the final table:

32767
^Remapped.Stream$ 65535

(I used a dot between the words as using a space is not possible. It would probably have been more effective to use the hex code, or maybe [[:space:]])

Pulseaudio and speakers+headphones remapping

Just thought I should share my pulseaudio configuration. It allows me to move audio streams between my speakers and my headphones. The drawback is that I had to connect my headphones to the rear-speaker output, effectively stopping any surround usage. The perfect setup would have been if I could instead have used the audio output on the front of my computer (I know this should work as the Realtek utilitiy in Windows is able to do it).

For sanity I just share the relevant parts of my system.pa

#### I do this by hand for the remaps
# ### Automatically load driver modules depending on the hardware available
# .ifexists module-hal-detect.so
# load-module module-hal-detect
# .else
# ### Alternatively use the static hardware detection module (for systems that
# ### lack HAL support)
# load-module module-detect
#.endif

### Automatically suspend sinks/sources that become idle for too long
#load-module module-suspend-on-idle
#bug: http://pulseaudio.org/ticket/326


# Set the speakers as the default device
set-default-sink speakers

# Map device 0 to sound_card (this is my sound card) and use 4 channels
load-module module-alsa-sink sink_name=sound_card device_id=0 channels=4 channel_map=front-left,front-right,rear-left,rear-right

# Map the front channels to the speakers sink
load-module module-remap-sink sink_name=speakers master=sound_card channels=2 master_channel_map=front-left,front-right channel_map=front-left,front-right

# Map the rear channels to the headphones sink
load-module module-remap-sink sink_name=headphones master=sound_card channels=2 master_channel_map=rear-left,rear-right channel_map=front-left,front-right


And here's what I changed in daemon.conf:

;make channel-splitting work (may destroy mono to stereo on mono files)
disable-remixing = yes

default-sample-channels = 4



Unfortunately the sink names are not shown in PulseAudio Volume Control but some trial and error (choosing 1 out of to 2 :P) will tell you which sink is the speakers and which is the headphones.