Friday, August 24, 2012

Linux on X220: Getting microphone mute button to work

As I promised in last blog post, I am going to write about my experiences using Debian on Lenovo Thinkpad X220 laptop.
This post is not exclusively for X220, it should work on all Thinkpads with microphone mute key.

Thinkpad laptops are known for very good support in Linux, so there is only few things that doesn't work out of the box (if you are using recent enough kernel). In today's post, I'll try to show you how to get microphone mute button (next to volume up/down buttons) to work including its LED.

Microphone mute button is by default mapped to Linux keycode that is out of range. This means you probably won't get any output from xev command when pressing this button.
If you are lucky enough, you will get an ACPI event with acpi_listen and then you can follow this how-to (askubuntu.com).

If you don't get any output neither from xev nor from acpi_listen as me, please follow these steps:

Change udev keymaps for your keyboard:

We will modify udev rules:
mkdir /etc/udev/keymaps
cp /lib/udev/keymaps/module-lenovo /etc/udev/keymaps
Now edit this newly created file and change key name at 0x1A line from micmute to prog2:
0x17 prog1 # ThinkPad/ThinkVantage button (high keycode: "vendor")
0x1A prog2 # Microphone mute
You may also change 0x13 zoom to search if you want to use Fn + Space shortcut. Now you need to reload udev rules, try:
udevadm control --reload-rules
You should now see XF86Launch2 key press in xev. If it doesn't work, just reboot.
You can now assign a keyboard shortcut to this key.

Getting LED to work

Recompile kernel

Unfortunately, you'll need to recompile a kernel to light up this diod. I won't show you how to compile a kernel, please search it somewhere else. You need to enable controlling of unsafe LEDs.
Device Drivers  --->
 [*] X86 Platform Specific Device Drivers  --->
   <M>  ThinkPad ACPI Laptop Extras
        [ ]   Maintainer debug facilities
        [ ]   Verbose debug mode
        [*]   Allow control of important LEDs (unsafe)
        [*]   Video output control support
        [*]   Support NVRAM polling for hot keys

CONFIG_THINKPAD_ACPI_UNSAFE_LEDS=y
After recompiling your kernel you are ready to light up the LED.

Writing script for turning the LED on/off

Lets write a script that will turn it on/off according to first argument and save it as led-micmute.sh:
#!/bin/bash
#
# led-micmute.sh [on|off]
#
if [ "$1" = "on" ]; then 
    echo "Turning on...";
    echo "14 on" > /proc/acpi/ibm/led
elif [ "$1" = "off" ]; then
    echo "Turning off...";
    echo "14 off" > /proc/acpi/ibm/led
else
    echo "Invalid option!"
fi;
However, there is one little issue: you need to run it as a root. It does not really fit our needs to use it from script that will run with user privileges. You can edit sudoers file so you will be able to run a script with sudo without typing password.
Note: I don't know how secure it is, but it works. So please think before copying next lines :-)

Create new file eg. /etc/sudoers.d/tp-scripts with following contents:
# needed to turn on micmute led
your_user_name ALL=NOPASSWD: /full/path/to/led-micmute.sh
You should now run sudo led-micmute.sh without need to type a password.

Create a script for muting microphone

And finally, we will create a script that will mute/unmute microphone.
#!/bin/bash
status=$( amixer sget Capture | grep '\[on\]' )

if [ "$status" = "" ]; then 
    echo "Turning on...";
    amixer sset Capture cap
    notify-send -i microphone-sensitivity-high-symbolic "Microphone" "Microphone is now <b>ON</b>";
    sudo /path/to/led-micmute.sh off
else
    echo "Turning off...";
    amixer sset Capture nocap
    notify-send -i microphone-sensitivity-muted-symbolic "Microphone" "Microphone is now <b>MUTED</b>";
    sudo /path/to/led-micmute.sh on
fi;
There is only one thing left how you can improve this script. If you press microphone mute key repeatedly, you will get multiple notifications on screen informing you about microphone state.
To use single notification that will be updated, please take a look at my notify-desktop app.


Wednesday, August 22, 2012

Sending desktop notifications from scripts

Every now and then, you may find a need for sending a notification from your script.
Recently I wrote some scripts for my Thinkpad X220 (which I am going to write about too) and I needed to show notification about microphone state (enabled/disabled).

Almost every linux DE has its own notification daemon that shows notifications over DBus using org.freedesktop.Notifications (spec).

Without knowing how it works, you can use notify-send command from libnotify-bin package. Its usage is pretty straightforward:
notify-send -i "icon-file/name" -a "application_name" "Summary" "Body of message"
For almost all cases, it can serve very well. However, there is one thing that notify-send doesn't support whilst DBus specification does. 
replaces_id UINT32 The optional notification ID that this notification replaces. The server must atomically (ie with no flicker or other visual cues) replace the given notification with this one. This allows clients to effectively modify the notification while it's active.
Without it, if you call your script more than once in a short period, you'll end up with a lot of notifications on your screen. It doesn't look very good.

That means notify-send is not good enough for my needs. Little searching on google reveals that dbus-send command can be used for sending any DBus call directly. Another searching got me to ubuntuforums where one user wrote an example usage of dbus-send for sending notifications.

However, it doesn't work: dbus-send does not supports sending a{sv} type. It is DICT containing string:variant pairs.
org.freedesktop.Notifications.Notify has susssasa{sv}i signature and dbus-send can only support susssasa{ss}i.

So dbus-send wasn't an answer too. I saw a patch for notify-send on launchpad that adds support for replaces_id, but instead of using it, I decided to write my own clone of notify-send.
I could have written it using a higher-level bindings (eg. in Qt), but I wanted it to be as small as possible without unnecessary external dependencies.

notify-desktop

Because of the requirement to make it as small as possible, language of the choice was C. It makes it possible to use direct DBus API without any additional layer.

I tried to retain arguments compatibility between notify-send and notify-desktop. The only thing that notify-desktop doesn't support is option to pass additional hints. I never used it and also don't find it useful, so I didn't bother implementing it.

The main difference is of course slightly different name and -r / --replaces-id=ID option. It does exactly what it says.

You can find it at github. Build instructions are in README.
There are also example bash functions to use in your scripts (look at doc/ directory)