Index: configure.in
===================================================================
--- configure.in	(révision 2294)
+++ configure.in	(copie de travail)
@@ -189,6 +189,19 @@
 			  [true])
 fi
 
+AC_ARG_WITH(pulseaudio, AS_HELP_STRING(--without-pulseaudio, Compile without PulseAudio support.))
+
+if test "x$with_pulseaudio" != "xno"
+then
+	PKG_CHECK_MODULES(PULSEAUDIO, [libpulse >= 0.9, libpulse-simple >= 0.9],
+	     [SOUND_DRIVERS="$SOUND_DRIVERS PULSEAUDIO"
+	      EXTRA_OBJS="$EXTRA_OBJS pulseaudio.o"
+	      AC_DEFINE([HAVE_PULSEAUDIO], 1, [Define if you have PULSEAUDIO.])
+	      EXTRA_LIBS="$EXTRA_LIBS $PULSEAUDIO_LIBS"
+	      CFLAGS="$CFLAGS $PULSEAUDIO_CFLAGS"],
+	     [true])
+fi
+
 AC_ARG_ENABLE(debug, AS_HELP_STRING(--disable-debug,Disable debugging code))
 
 if test "x$enable_debug" = "xno"
Index: pulseaudio.c
===================================================================
--- pulseaudio.c	(révision 0)
+++ pulseaudio.c	(révision 0)
@@ -0,0 +1,593 @@
+/*
+ * MOC - music on console
+ * Copyright (C) 2011 Jérémie Laval <jeremie dot laval at gmail dot com>
+ *
+ * Portion of code are taken from PulseAudio
+ * Copyright 2004-2006 Lennart Poettering
+ * Licensed under GNU Lesser General Public License version 2.1 or any later version
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <assert.h>
+
+#include <pulse/pulseaudio.h>
+
+#include "common.h"
+#include "server.h"
+#include "audio.h"
+#include "log.h"
+#include "options.h"
+
+typedef struct {
+    pa_threaded_mainloop *mainloop;
+    pa_context *context;
+    pa_stream *stream;
+    pa_stream_direction_t direction;
+    int operation_success;
+} pa_moc_simple;
+
+#define CHECK_VALIDITY_RETURN_ANY(rerror, expression, error, ret)       \
+    do {                                                                \
+        if (!(expression)) {                                            \
+            *(rerror) = error;                                          \
+            return (ret);                                               \
+        }                                                               \
+    } while(false);
+
+#define CHECK_SUCCESS_GOTO(p, rerror, expression, label)        \
+    do {                                                        \
+        if (!(expression)) {                                    \
+            *(rerror) = pa_context_errno ((p)->context);        \
+            goto label;                                         \
+        }                                                       \
+    } while(false);
+
+#define CHECK_DEAD_GOTO(p, rerror, label)                               \
+    do {                                                                \
+        if (!(p)->context || !PA_CONTEXT_IS_GOOD(pa_context_get_state ((p)->context)) || \
+            !(p)->stream || !PA_STREAM_IS_GOOD(pa_stream_get_state ((p)->stream))) { \
+            if (((p)->context && pa_context_get_state ((p)->context) == PA_CONTEXT_FAILED) || \
+                ((p)->stream && pa_stream_get_state ((p)->stream) == PA_STREAM_FAILED)) { \
+                *(rerror) = pa_context_errno ((p)->context);            \
+            } else                                                      \
+                *(rerror) = PA_ERR_BADSTATE;                            \
+            goto label;                                                 \
+        }                                                               \
+    } while(false);
+
+static pa_moc_simple *s = NULL;
+static pa_sample_spec ss;
+static int current_volume = 100;
+static const double volume_scale = PA_VOLUME_NORM / 100.0;
+
+static void context_state_cb (pa_context *c, void *userdata) 
+{
+	pa_moc_simple *p = userdata;
+	assert (c != NULL);
+	assert (p != NULL);
+
+	switch (pa_context_get_state (c)) {
+	case PA_CONTEXT_READY:
+	case PA_CONTEXT_TERMINATED:
+	case PA_CONTEXT_FAILED:
+		pa_threaded_mainloop_signal (p->mainloop, 0);
+		break;
+	case PA_CONTEXT_UNCONNECTED:
+	case PA_CONTEXT_CONNECTING:
+	case PA_CONTEXT_AUTHORIZING:
+	case PA_CONTEXT_SETTING_NAME:
+		break;
+	}
+}
+
+static void stream_state_cb (pa_stream *s, void * userdata)
+{
+	pa_moc_simple *p = userdata;
+	assert (s != NULL);
+	assert (p != NULL);
+
+	switch (pa_stream_get_state (s)) {
+	case PA_STREAM_READY:
+	case PA_STREAM_FAILED:
+	case PA_STREAM_TERMINATED:
+		pa_threaded_mainloop_signal (p->mainloop, 0);
+		break;
+	case PA_STREAM_UNCONNECTED:
+	case PA_STREAM_CREATING:
+		break;
+	}
+}
+
+static void stream_request_cb (pa_stream *s ATTR_UNUSED, size_t length ATTR_UNUSED, void *userdata)
+{
+	pa_moc_simple *p = userdata;
+	assert (p != NULL);
+
+	pa_threaded_mainloop_signal (p->mainloop, 0);
+}
+
+static void pa_moc_simple_free (pa_moc_simple *s)
+{
+	assert (s != NULL);
+
+	if (s->mainloop)
+		pa_threaded_mainloop_stop (s->mainloop);
+
+	if (s->stream)
+		pa_stream_unref (s->stream);
+
+	if (s->context) {
+		pa_context_disconnect (s->context);
+		pa_context_unref (s->context);
+	}
+
+	if (s->mainloop)
+		pa_threaded_mainloop_free (s->mainloop);
+
+	pa_xfree (s);
+}
+
+static pa_moc_simple* pa_moc_simple_new (const char *server,
+                                         const char *name,
+                                         pa_stream_direction_t dir,
+                                         const char *dev,
+                                         const char *stream_name,
+                                         const pa_sample_spec *ss,
+                                         const pa_channel_map *map,
+                                         const pa_buffer_attr *attr,
+                                         pa_proplist* proplist,
+                                         int *rerror)
+{
+
+	pa_moc_simple *p;
+	int error = PA_ERR_INTERNAL, r;
+
+	CHECK_VALIDITY_RETURN_ANY(rerror, !server || *server, PA_ERR_INVALID, NULL);
+	CHECK_VALIDITY_RETURN_ANY(rerror, dir == PA_STREAM_PLAYBACK || dir == PA_STREAM_RECORD, PA_ERR_INVALID, NULL);
+	CHECK_VALIDITY_RETURN_ANY(rerror, !dev || *dev, PA_ERR_INVALID, NULL);
+	CHECK_VALIDITY_RETURN_ANY(rerror, ss && pa_sample_spec_valid (ss), PA_ERR_INVALID, NULL);
+	CHECK_VALIDITY_RETURN_ANY(rerror, !map || (pa_channel_map_valid (map) && map->channels == ss->channels), PA_ERR_INVALID, NULL)
+
+	p = pa_xnew0 (pa_moc_simple, 1);
+	p->direction = dir;
+
+	if (!(p->mainloop = pa_threaded_mainloop_new ()))
+		goto fail;
+
+	if (!(p->context = pa_context_new(pa_threaded_mainloop_get_api (p->mainloop), name)))
+		goto fail;
+
+	pa_context_set_state_callback (p->context, context_state_cb, p);
+
+	if (pa_context_connect (p->context, server, 0, NULL) < 0) {
+		error = pa_context_errno (p->context);
+		goto fail;
+	}
+
+	pa_threaded_mainloop_lock (p->mainloop);
+
+	if (pa_threaded_mainloop_start (p->mainloop) < 0)
+		goto unlock_and_fail;
+
+	for (;;) {
+		pa_context_state_t state;
+
+		state = pa_context_get_state (p->context);
+
+		if (state == PA_CONTEXT_READY)
+			break;
+
+		if (!PA_CONTEXT_IS_GOOD(state)) {
+			error = pa_context_errno (p->context);
+			goto unlock_and_fail;
+		}
+
+		/* Wait until the context is ready */
+		pa_threaded_mainloop_wait (p->mainloop);
+	}
+
+	if (!(p->stream = pa_stream_new_with_proplist (p->context, stream_name, ss, map, proplist))) {
+		error = pa_context_errno (p->context);
+		goto unlock_and_fail;
+	}
+
+	pa_stream_set_state_callback (p->stream, stream_state_cb, p);
+	pa_stream_set_write_callback (p->stream, stream_request_cb, p);
+
+	if (dir == PA_STREAM_PLAYBACK)
+		r = pa_stream_connect_playback (p->stream, dev, attr,
+		                                PA_STREAM_INTERPOLATE_TIMING
+		                                |PA_STREAM_ADJUST_LATENCY
+		                                |PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL);
+	else
+		r = pa_stream_connect_record (p->stream, dev, attr,
+		                              PA_STREAM_INTERPOLATE_TIMING
+		                              |PA_STREAM_ADJUST_LATENCY
+		                              |PA_STREAM_AUTO_TIMING_UPDATE);
+
+	if (r < 0) {
+		error = pa_context_errno (p->context);
+		goto unlock_and_fail;
+	}
+
+	for (;;) {
+		pa_stream_state_t state;
+
+		state = pa_stream_get_state (p->stream);
+
+		if (state == PA_STREAM_READY)
+			break;
+
+		if (!PA_STREAM_IS_GOOD (state)) {
+			error = pa_context_errno (p->context);
+			goto unlock_and_fail;
+		}
+
+		/* Wait until the stream is ready */
+		pa_threaded_mainloop_wait (p->mainloop);
+	}
+
+	pa_threaded_mainloop_unlock (p->mainloop);
+
+	return p;
+
+ unlock_and_fail:
+	pa_threaded_mainloop_unlock (p->mainloop);
+
+ fail:
+	if (rerror)
+		*rerror = error;
+	pa_moc_simple_free (p);
+	return NULL;
+}
+
+static int pa_moc_simple_write (pa_moc_simple *p, const void* data, size_t length, int *rerror)
+{
+	assert (p != NULL);
+
+	CHECK_VALIDITY_RETURN_ANY(rerror, p->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE, -1);
+	CHECK_VALIDITY_RETURN_ANY(rerror, data, PA_ERR_INVALID, -1);
+	CHECK_VALIDITY_RETURN_ANY(rerror, length > 0, PA_ERR_INVALID, -1);
+
+	pa_threaded_mainloop_lock (p->mainloop);
+
+	CHECK_DEAD_GOTO (p, rerror, unlock_and_fail);
+
+	while (length > 0) {
+		size_t l;
+		int r;
+
+		while (!(l = pa_stream_writable_size (p->stream))) {
+			pa_threaded_mainloop_wait (p->mainloop);
+			CHECK_DEAD_GOTO(p, rerror, unlock_and_fail);
+		}
+
+		CHECK_SUCCESS_GOTO(p, rerror, l != (size_t) -1, unlock_and_fail);
+
+		if (l > length)
+			l = length;
+
+		r = pa_stream_write (p->stream, data, l, NULL, 0LL, PA_SEEK_RELATIVE);
+		CHECK_SUCCESS_GOTO(p, rerror, r >= 0, unlock_and_fail);
+
+		data = (const uint8_t*) data + l;
+		length -= l;
+	}
+
+	pa_threaded_mainloop_unlock (p->mainloop);
+	return 0;
+
+ unlock_and_fail:
+	pa_threaded_mainloop_unlock (p->mainloop);
+	return -1;
+}
+
+static void success_cb (pa_stream *s, int success, void *userdata)
+{
+	pa_moc_simple *p = userdata;
+
+	assert (s != NULL);
+	assert (p != NULL);
+
+	p->operation_success = success;
+	pa_threaded_mainloop_signal (p->mainloop, 0);
+}
+
+int pa_moc_simple_flush (pa_moc_simple *p, int *rerror)
+{
+	pa_operation *o = NULL;
+
+	assert (p != NULL);
+
+	CHECK_VALIDITY_RETURN_ANY(rerror, p->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE, -1);
+
+	pa_threaded_mainloop_lock (p->mainloop);
+	CHECK_DEAD_GOTO(p, rerror, unlock_and_fail);
+
+	o = pa_stream_flush (p->stream, success_cb, p);
+	CHECK_SUCCESS_GOTO(p, rerror, o, unlock_and_fail);
+
+	p->operation_success = 0;
+	while (pa_operation_get_state (o) == PA_OPERATION_RUNNING) {
+		pa_threaded_mainloop_wait (p->mainloop);
+		CHECK_DEAD_GOTO (p, rerror, unlock_and_fail);
+	}
+	CHECK_SUCCESS_GOTO(p, rerror, p->operation_success, unlock_and_fail);
+
+	pa_operation_unref (o);
+	pa_threaded_mainloop_unlock (p->mainloop);
+
+	return 0;
+
+ unlock_and_fail:
+
+	if (o) {
+		pa_operation_cancel (o);
+		pa_operation_unref (o);
+	}
+
+	pa_threaded_mainloop_unlock (p->mainloop);
+	return -1;
+}
+
+static bool pulse_call_synchronously (pa_operation* op)
+{
+	if (!op)
+		return false;
+
+	int rerror;
+
+	while (pa_operation_get_state (op) == PA_OPERATION_RUNNING) {
+		pa_threaded_mainloop_wait (s->mainloop);
+		CHECK_DEAD_GOTO(s, &rerror, unlock_and_fail);
+	}
+	CHECK_SUCCESS_GOTO(s, &rerror, s->operation_success, unlock_and_fail);
+
+	pa_operation_unref (op);
+	pa_threaded_mainloop_unlock (s->mainloop);
+
+	return true;
+
+ unlock_and_fail:
+	if (op) {
+		pa_operation_cancel(op);
+		pa_operation_unref(op);
+	}
+
+	pa_threaded_mainloop_unlock(s->mainloop);
+	return false;
+}
+
+static int pulse_init (struct output_driver_caps *caps)
+{
+	caps->formats = SFMT_U8
+		| sfmt_set_endian(SFMT_S32, SFMT_LE)
+		| sfmt_set_endian(SFMT_S32, SFMT_BE)
+		| sfmt_set_endian(SFMT_S16, SFMT_LE)
+		| sfmt_set_endian(SFMT_S16, SFMT_BE);
+	caps->min_channels = 1;
+	caps->max_channels = 2;
+
+	return 1;
+}
+
+static void pulse_shutdown ()
+{
+	if (s != NULL) {
+		pa_moc_simple_free (s);
+		s = NULL;
+	}
+}
+
+static int pulse_open (struct sound_params *sound_params)
+{
+	int err;
+	ss.rate = sound_params->rate;
+	ss.channels = sound_params->channels;
+	switch (sound_params->fmt) {
+	case SFMT_U8:
+		ss.format = PA_SAMPLE_U8;
+		break;
+	case sfmt_set_endian(SFMT_S32, SFMT_LE):
+		ss.format = PA_SAMPLE_S32LE;
+		break;
+	case sfmt_set_endian(SFMT_S32, SFMT_BE):
+		ss.format = PA_SAMPLE_S32BE;
+		break;
+	case sfmt_set_endian(SFMT_S16, SFMT_LE):
+		ss.format = PA_SAMPLE_S16LE;
+		break;
+	case sfmt_set_endian(SFMT_S16, SFMT_BE):
+		ss.format = PA_SAMPLE_S16BE;
+		break;
+	default:
+		fatal ("Can't understand format to pass to PulseAudio");
+	}
+
+#ifdef HAVE_CONFIG_H
+	const char* propstring = "application.id=\"net.daper.moc\""
+		" application.name=\""PACKAGE_NAME"\""
+		" application.version=\""PACKAGE_VERSION"\""
+		" media.role=\"music\"";
+#else
+	const char* propstring = "application.id=\"net.daper.moc\" media.role=\"music\"";
+#endif
+
+	pid_t pid = getpid ();
+	char spid[sizeof(pid_t) + 1];
+	snprintf (spid, 21, "%lu", (unsigned long)pid);
+	pa_proplist* proplist = pa_proplist_from_string (propstring);
+	pa_proplist_sets (proplist, "application.process.id", (const char*)spid);
+
+	s = pa_moc_simple_new (NULL,
+	                       "MOC",
+	                       PA_STREAM_PLAYBACK,
+	                       NULL,
+	                       "MOC",
+	                       &ss,
+	                       NULL,
+	                       NULL,
+	                       proplist,
+	                       &err);
+
+	if (s == NULL) {
+		fatal ("Pulse Audio failed to initialize with error %s", pa_strerror (err));
+		return 0;
+	}
+
+	return 1;
+}
+
+static void pulse_close ()
+{
+	/* Do nothing ATM */
+	
+}
+
+static int pulse_play (const char *buff, const size_t size)
+{
+	int err;
+
+	if (pa_moc_simple_write (s, buff, size, &err) < 0) {
+		error ("Failed to play song, %s", pa_strerror (err));
+		return -1;
+	}
+
+	return size;
+}
+
+static void pulse_sink_input_info_volume_cb (pa_context* context ATTR_UNUSED, const pa_sink_input_info *i, int eol ATTR_UNUSED, void *userdata)
+{
+	if (i == NULL)
+		return;
+	*((struct pa_cvolume*)userdata) = i->volume;
+	pa_threaded_mainloop_signal (s->mainloop, 0);
+}
+
+static int pulse_read_mixer ()
+{
+	// We can't return a proper value until a stream is instantiated
+	if (s == NULL)
+		return 100;
+
+	struct pa_cvolume volume;
+	uint32_t streamId = pa_stream_get_index (s->stream);
+	s->operation_success = 1;
+
+	pa_threaded_mainloop_lock (s->mainloop);
+	if (!pulse_call_synchronously (pa_context_get_sink_input_info (s->context, streamId, pulse_sink_input_info_volume_cb, &volume)))
+		return -1;
+	
+	int hardware_volume = (int)(pa_cvolume_avg (&volume) / volume_scale) + 1;
+	return abs (hardware_volume - current_volume) <= 1 ? current_volume : hardware_volume;
+}
+
+static void context_success_cb (pa_context *c, int success, void *userdata) {
+	pa_moc_simple *p = userdata;
+
+	assert (c != NULL);
+	assert (p != NULL);
+
+	p->operation_success = success;
+	pa_threaded_mainloop_signal(p->mainloop, 0);
+}
+
+static void pulse_set_mixer (int volume)
+{
+	if (s == NULL)
+		return;
+	if (volume > 100)
+		volume = 100;
+	if (volume < 0)
+		volume = 0;
+	current_volume = volume;
+
+	uint32_t streamId = pa_stream_get_index (s->stream);
+	pa_cvolume cvol;
+	pa_cvolume_init (&cvol);
+	pa_cvolume_set (&cvol, ss.channels, (pa_volume_t)(volume * volume_scale));
+	s->operation_success = 0;
+
+	pa_threaded_mainloop_lock (s->mainloop);
+	pulse_call_synchronously (pa_context_set_sink_input_volume (s->context, streamId, &cvol, context_success_cb, s));
+}
+
+static int pulse_get_buff_fill ()
+{
+	/* Not applicable */
+	return 0;
+}
+
+static int pulse_reset ()
+{
+	int err;
+	if (pa_moc_simple_flush (s, &err) < 0) {
+		error ("Pulse audio error while reseting, %s", pa_strerror (err));
+		return 0;
+	}
+	return 1;
+}
+
+static int pulse_get_rate ()
+{
+	return ss.rate;
+}
+
+static void pulse_toggle_mixer_channel ()
+{
+	/* TODO */
+}
+
+static void pulse_sink_input_info_name_cb (pa_context* context ATTR_UNUSED, const pa_sink_input_info *i, int eol ATTR_UNUSED, void *userdata)
+{
+	if (i == NULL)
+		return;
+	*((char**)userdata) = strdup (i->name);
+	pa_threaded_mainloop_signal (s->mainloop, 0);
+}
+
+static char* pulse_get_mixer_channel_name ()
+{
+	if (s == NULL)
+		return strdup ("MOC");
+
+	char* name = NULL;
+	uint32_t streamId = pa_stream_get_index (s->stream);
+	s->operation_success = 1;
+
+	pa_threaded_mainloop_lock (s->mainloop);
+	if (!pulse_call_synchronously (pa_context_get_sink_input_info (s->context, streamId, pulse_sink_input_info_name_cb, &name)))
+		return strdup ("MOC");
+	
+	return name;
+}
+
+void pulseaudio_funcs (struct hw_funcs *funcs)
+{
+	funcs->init = pulse_init;
+	funcs->shutdown = pulse_shutdown;
+	funcs->open = pulse_open;
+	funcs->close = pulse_close;
+	funcs->play = pulse_play;
+	funcs->read_mixer = pulse_read_mixer;
+	funcs->set_mixer = pulse_set_mixer;
+	funcs->get_buff_fill = pulse_get_buff_fill;
+	funcs->reset = pulse_reset;
+	funcs->get_rate = pulse_get_rate;
+	funcs->toggle_mixer_channel = pulse_toggle_mixer_channel;
+	funcs->get_mixer_channel_name = pulse_get_mixer_channel_name;
+}
Index: pulseaudio.h
===================================================================
--- pulseaudio.h	(révision 0)
+++ pulseaudio.h	(révision 0)
@@ -0,0 +1,16 @@
+#ifndef PULSE_AUDIO_H
+#define PULSE_AUDIO_H
+
+#include "audio.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void pulseaudio_funcs (struct hw_funcs *funcs);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
Index: audio.c
===================================================================
--- audio.c	(révision 2294)
+++ audio.c	(copie de travail)
@@ -32,6 +32,9 @@
 #include "playlist.h"
 #include "log.h"
 
+#ifdef HAVE_PULSEAUDIO
+# include "pulseaudio.h"
+#endif
 #ifdef HAVE_OSS
 # include "oss.h"
 #endif
@@ -901,6 +904,16 @@
 		pos += t;
 		pos += strspn (pos, " \t,");
 
+#ifdef HAVE_PULSEAUDIO
+		if (!strcasecmp(name, "pulseaudio")) {
+			pulseaudio_funcs (funcs);
+			printf ("Trying PulseAudio...\n");
+			if (funcs->init(&hw_caps))
+				return;
+		}
+
+#endif
+
 #ifdef HAVE_OSS
 		if (!strcasecmp(name, "oss")) {
 			oss_funcs (funcs);
Index: Makefile.am
===================================================================
--- Makefile.am	(révision 2294)
+++ Makefile.am	(copie de travail)
@@ -72,6 +72,8 @@
 		     oss.h \
 		     alsa.c \
 		     alsa.h \
+		     pulse.c \
+		     pulse.h \
 		     io_curl.c \
 		     io_curl.h \
 		     jack.c \
Index: options.c
===================================================================
--- options.c	(révision 2294)
+++ options.c	(copie de travail)
@@ -427,7 +427,7 @@
 	option_add_str  ("OSSMixerDevice", "/dev/mixer", CHECK_NONE);
 	option_add_str  ("OSSMixerChannel", "pcm", CHECK_NONE);
 	option_add_str  ("OSSMixerChannel2", "master", CHECK_NONE);
-	option_add_str  ("SoundDriver", "Jack, ALSA, OSS", CHECK_NONE);
+	option_add_str  ("SoundDriver", "pulseaudio, Jack, ALSA, OSS", CHECK_NONE);
 	option_add_bool ("ShowHiddenFiles", true);
 	option_add_str  ("AlsaDevice", "default", CHECK_NONE);
 	option_add_str  ("AlsaMixer", "PCM", CHECK_NONE);

