2008-12-30

Pavucontrol sink name patch

With the number of sinks I have accumulated over time using pavucontrol has become more confusing. Therefore I checked out the git source and patched pavucontrol to show the names of my sinks and sources.

After writing the patch I got the idea to check the bug tracker and there was already a patch, although I prefer my formatting of the labels :P.

I splitted my patch into two, as the second one is kinda ugly :) (does not handle the updating flag correctly)


diff --git a/src/pavucontrol.cc b/src/pavucontrol.cc
index b7f7a9b..d79f068 100644
--- a/src/pavucontrol.cc
+++ b/src/pavucontrol.cc
@@ -794,7 +794,7 @@ void SinkInputWidget::clearMenu() {
void SinkInputWidget::buildMenu() {
for (std::map::iterator i = mainWindow->sinkWidgets.begin(); i != mainWindow->sinkWidgets.end(); ++i) {
SinkMenuItem *m;
- sinkMenuItems[i->second->index] = m = new SinkMenuItem(this, i->second->description.c_str(), i->second->index, i->second->index == sinkIndex);
+ sinkMenuItems[i->second->index] = m = new SinkMenuItem(this, (i->second->name + " (" + i->second->description + ")").c_str(), i->second->index, i->second->index == sinkIndex);
submenu.append(m->menuItem);
}

@@ -1033,7 +1033,7 @@ void MainWindow::updateSink(const pa_sink_info &info) {

w->boldNameLabel->set_text("");
gchar *txt;
- w->nameLabel->set_markup(txt = g_markup_printf_escaped("%s", info.description));
+ w->nameLabel->set_markup(txt = g_markup_printf_escaped("%s (%s)", info.name, info.description));
g_free(txt);

w->iconImage->set_from_icon_name("audio-card", Gtk::ICON_SIZE_SMALL_TOOLBAR);
@@ -1175,7 +1175,7 @@ void MainWindow::updateSource(const pa_source_info &info) {

w->boldNameLabel->set_text("");
gchar *txt;
- w->nameLabel->set_markup(txt = g_markup_printf_escaped("%s", info.description));
+ w->nameLabel->set_markup(txt = g_markup_printf_escaped("%s (%s)", info.name, info.description));
g_free(txt);

w->iconImage->set_from_icon_name("audio-input-microphone", Gtk::ICON_SIZE_SMALL_TOOLBAR);





diff --git a/src/pavucontrol.cc b/src/pavucontrol.cc
index d79f068..69dba03 100644
--- a/src/pavucontrol.cc
+++ b/src/pavucontrol.cc
@@ -1232,6 +1232,14 @@ finish:
icon->set_from_icon_name(t, Gtk::ICON_SIZE_SMALL_TOOLBAR);
}

+void sinkName_cb(pa_context *, const pa_module_info *i, int eol, void *userdata) {
+ if(i == NULL) {
+ return;
+ }
+ SinkInputWidget *w = static_cast(userdata);
+ w->nameLabel->set_label(g_markup_printf_escaped("%s (%s)", i->name, w->nameLabel->get_label().c_str()));
+}
+
void MainWindow::updateSinkInput(const pa_sink_input_info &info) {
SinkInputWidget *w;
bool is_new = false;
@@ -1265,7 +1273,11 @@ void MainWindow::updateSinkInput(const pa_sink_input_info &info) {
g_free(txt);
} else {
w->boldNameLabel->set_text("");
- w->nameLabel->set_label(info.name);
+ w->nameLabel->set_label(info.name);
+ if(info.owner_module != PA_INVALID_INDEX) {
+ g_debug(_("num: %i"), info.owner_module);
+ pa_context_get_module_info(context, info.owner_module, sinkName_cb, w);
+ }
}

setIconFromProplist(w->iconImage, info.proplist, "audio-card");
@@ -1311,8 +1323,12 @@ void MainWindow::updateSourceOutput(const pa_source_output_info &info) {
w->nameLabel->set_markup(txt = g_markup_printf_escaped(": %s", info.name));
g_free(txt);
} else {
- w->boldNameLabel->set_text("");
- w->nameLabel->set_label(info.name);
+ w->boldNameLabel->set_text("");
+ w->nameLabel->set_label(info.name);
+ if(info.owner_module != PA_INVALID_INDEX) {
+ g_debug(_("num: %i"), info.owner_module);
+ pa_context_get_module_info(context, info.owner_module, sinkName_cb, w);
+ }
}

setIconFromProplist(w->iconImage, info.proplist, "audio-input-microphone");


And here's how it looks:

2008-12-22

Solving the mono problem

In an earlier post I showed you my sink-remapping which sent audio either to my speakers or my headphones. The problem with this setup is that I needed to disable remixing, effectively muting all mono sounds (and probably screw up surround sound, haven't tried that). The solution, or rather fix, is as follows:


#mono mappings
load-module module-remap-sink sink_name=mono-spk master=sound_card channels=2 master_channel_map=front-left,front-right channel_map=mono,mono
load-module module-remap-sink sink_name=mono-head master=sound_card channels=2 master_channel_map=rear-left,rear-right channel_map=mono,mono


This lets me hear audio from mono sources by sending them to these upmixing sinks.

The two drawbacks with this solution is that I have to switch sink manually and applications playing both mono and stereo will still be muted for either mono or stereo (although I haven't used any such application)

I did try to remix the sinks without success:

load-module module-combine sink_name=speakers-comb slaves=speakers,mono-spk channels=2 channel_map=front-left,front-right

If anyone got this working, please tell me how :)

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.

2008-08-07

Dbus, knotify and pidgin

Threw together an ugly python script sendind pidgin notifications to knotify:

#!/usr/bin/env python

import re

html_tags = re.compile('<.*?>')

def my_func(account, sender, message, conversation, flags):
kn.event("receivedImMsg", "pidgin", [], '<b>' + sender + '</b>: ' + html_tags.sub('', message), [0,0,0,0], [], 0, dbus_interface="org.kde.KNotify")

import dbus, gobject
from dbus.mainloop.glib import DBusGMainLoop
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SessionBus()

kn = bus.get_object("org.kde.knotify", "/Notify")

bus.add_signal_receiver(my_func,
dbus_interface="im.pidgin.purple.PurpleInterface",
signal_name="ReceivedImMsg")

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

Combine with this file: $HOME/.kde/share/config/pidgin.notifyrc


[Event/receivedImMsg]
Action=Sound|Popup|Taskbar
Execute=
KTTS=
Logfile=
Sound=KDE-Im-Message-In.ogg

... and your're good to go ;)

Pidgin reference

2008-08-06

The story of dbus, knotify4 and erc

I'm using erc in Emacs as my irc client and by default there is no notification outside of Emacs. Trying to get something working in kde4 I first tried calling kdialog, it worked, but it wasn't a fair sight.

After deciding that the kdialog popup is too ugly I started looking for alternatives. The alternative I found where right in front of my eyes: The Notify plasmoid. The problem was that there's no easy way of calling knotify (like kdialog --passivepopup) but some googling turned up http://mvidner.blogspot.com/2008/06/knotify-client.html which describes how to patch knotify4 (the dbus signature is apparently erronous) and how to call it from python.

Sure enough, after patching knotify, the nicely skinned notices popped up, and the text were even customizable using html.

Starting from emacs 23 dbus support is built-in, and luckily I am running a cvs version. I started trying to make some dbus calls but I always got an error stating that the "event" method of knotify did not exist. After many hours and double-checking the parameters I tried it with perl, worked instantly. Now I was starting to lose hope, thinking that maybe Emacs dbus implementation didn't really work.

Just as I were beginning to give up, after staring myself blind on the function signatures, I tried the following, thinking it was really a long shot:

(dbus-call-method :session "org.kde.knotify" "/Notify" "org.kde.KNotify" "event" "warning" "kde" '(:array (:variant nil)) "message" '(:array :byte 0 :byte 0 :byte 0 :byte 0) '(:array) :int64 0)

And there it was (albeit a bit ugly)! My notification popup, called from Emacs!

But I wasn't finished yet, it still said "from kde" and it was apparently a "warning". Trying to change does values didn't bring up anything.. curios. Looking around my ~/.kde/share/config I found kopete.notifyrc. It was the key! Creating my own called emacs.notifyrc and entering


[Event/erc_nick]
Action=Sound|Popup|Taskbar
Execute=
KTTS=
Logfile=
Sound=KDE-Im-Message-In.ogg


I could call "erc_nick" and "emacs" and now I had my "own" notification. It even got the Emacs icon!

Finally I added this code to my personal erc file and I hade working erc notification:




(defun thomasa88-erc-knotify4 (type message) 
(dbus-call-method :session "org.kde.knotify" "/Notify" "org.kde.KNotify"
"event"
type "emacs" '(:array (:variant nil)) message '(:array :byte 0 :byte 0 :byte 0 :byte 0) '(:array) :int64 0)

)

;;test (dbus-call-method :session "org.kde.knotify" "/Notify" "org.kde.KNotify" "event" "erc_nick" "emacs" '(:array (:variant nil)) "message" '(:array :byte 0 :byte 0 :byte 0 :byte 0) '(:array) :int64 0)

(defun thomasa88-erc-notify-match (type from rawmsg)
(let ((erc-resp (get-text-property 0 'erc-parsed rawmsg)))
(when (and (or (eq type 'current-nick) (eq type 'keyword))
(equal (erc-response.command erc-resp) "PRIVMSG"))
(thomasa88-erc-notify erc-resp nil))))

(defun thomasa88-erc-notify-PRIVMSG (proc erc-resp)
(thomasa88-erc-notify erc-resp t))

(defun thomasa88-erc-notify (erc-resp check-privmsg)
(let ((target (first (erc-response.command-args erc-resp)))
(from-nick (first (erc-parse-user (erc-response.sender erc-resp))))
(message (erc-response.contents erc-resp)))
(when (or (not check-privmsg)
(and (erc-current-nick-p target)
(not (erc-is-message-ctcp-and-not-action-p message))))
(thomasa88-erc-knotify4 "erc_nick"
(concat target " &lt;" from-nick "&gt; " message)))))

(add-hook 'erc-text-matched-hook 'thomasa88-erc-notify-match)
(add-hook 'erc-server-PRIVMSG-functions 'thomasa88-erc-notify-PRIVMSG t)



edit: Current svn knotify has a correct signature