dbus: Rework the main loop
authorMirsal Ennaime <mirsal.ennaime@gmail.com>
Wed, 21 Jul 2010 07:51:06 +0000 (09:51 +0200)
committerMirsal Ennaime <mirsal@mirsal.fr>
Sat, 19 Feb 2011 15:50:16 +0000 (16:50 +0100)
 * use a poll() based model instead of msleep()
 * Use input events to detect when the input thread stops playback
 * Use an enum for mpris playback statuses instead of hardcoded values

modules/control/dbus/dbus.c
modules/control/dbus/dbus.h
modules/control/dbus/dbus_common.h
modules/control/dbus/dbus_player.c
modules/control/dbus/dbus_player.h
modules/control/dbus/dbus_root.c
modules/control/dbus/dbus_root.h
modules/control/dbus/dbus_tracklist.c
modules/control/dbus/dbus_tracklist.h

index a16d920..90a77ac 100644 (file)
@@ -7,7 +7,7 @@
  * $Id$
  *
  * Authors:    Rafaël Carré <funman at videolanorg>
- *             Mirsal Ennaime <mirsal dot ennaime at gmail dot com>
+ *             Mirsal Ennaime <mirsal at mirsal fr>
  *
  * 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
 #include "dbus_tracklist.h"
 
 #include <vlc_common.h>
+#include <vlc_fixups.h>
 #include <vlc_plugin.h>
 #include <vlc_interface.h>
 #include <vlc_playlist.h>
 #include <vlc_meta.h>
+#include <vlc_mtime.h>
 
 #include <assert.h>
+#include <string.h>
+
+#include <poll.h>
+#include <errno.h>
+#include <unistd.h>
 
 /*****************************************************************************
  * Local prototypes.
  *****************************************************************************/
 
+typedef struct
+{
+    int signal;
+    int i_node;
+} callback_info_t;
+
+typedef struct
+{
+    mtime_t      i_remaining;
+    DBusTimeout *p_timeout;
+} timeout_info_t;
+
+enum
+{
+    PIPE_OUT = 0,
+    PIPE_IN  = 1
+};
+
 static int  Open    ( vlc_object_t * );
 static void Close   ( vlc_object_t * );
 static void Run     ( intf_thread_t * );
@@ -71,11 +96,39 @@ static int StateChange( intf_thread_t * );
 static int TrackChange( intf_thread_t * );
 static int AllCallback( vlc_object_t*, const char*, vlc_value_t, vlc_value_t, void* );
 
-typedef struct
-{
-    int signal;
-    int i_node;
-} callback_info_t;
+static void dispatch_status_cb( DBusConnection *p_conn,
+                                DBusDispatchStatus i_status,
+                                void *p_data);
+
+static dbus_bool_t add_timeout ( DBusTimeout *p_timeout, void *p_data );
+static dbus_bool_t add_watch   ( DBusWatch *p_watch, void *p_data );
+
+static void remove_timeout  ( DBusTimeout *p_timeout, void *p_data );
+static void remove_watch    ( DBusWatch *p_watch, void *p_data );
+
+static void timeout_toggled ( DBusTimeout *p_timeout, void *p_data );
+static void watch_toggled   ( DBusWatch *p_watch, void *p_data );
+
+static void wakeup_main_loop( void *p_data );
+
+static int GetPollFds( intf_thread_t *p_intf, struct pollfd *p_fds );
+static int UpdateTimeouts( intf_thread_t *p_intf, mtime_t i_lastrun );
+
+static void ProcessEvents  ( intf_thread_t    *p_intf,
+                             callback_info_t **p_events,
+                             int               i_events );
+
+static void ProcessWatches ( intf_thread_t    *p_intf,
+                             DBusWatch       **p_watches,
+                             int               i_watches,
+                             struct pollfd    *p_fds,
+                             int               i_fds );
+
+static void ProcessTimeouts( intf_thread_t    *p_intf,
+                             DBusTimeout     **p_timeouts,
+                             int               i_timeouts );
+
+static void DispatchDBusMessages( intf_thread_t *p_intf );
 
 /*****************************************************************************
  * Module descriptor
@@ -109,7 +162,7 @@ static int Open( vlc_object_t *p_this )
     DBusError       error;
     char            *psz_service_name = NULL;
 
-    if( !p_sys )
+    if( !p_sys || !dbus_threads_init_default())
         return VLC_ENOMEM;
 
     p_sys->b_meta_read = false;
@@ -118,6 +171,13 @@ static int Open( vlc_object_t *p_this )
     p_sys->p_input = NULL;
     p_sys->i_playing_state = -1;
 
+    if( pipe( p_sys->p_pipe_fds ) )
+    {
+        free( p_sys );
+        msg_Err( p_intf, "Could not create pipe" );
+        return VLC_EGENERIC;
+    }
+
     p_sys->b_unique = var_CreateGetBool( p_intf, "dbus-unique-service-id" );
     if( p_sys->b_unique )
     {
@@ -135,8 +195,10 @@ static int Open( vlc_object_t *p_this )
 
     dbus_error_init( &error );
 
-    /* connect to the session bus */
-    p_conn = dbus_bus_get( DBUS_BUS_SESSION, &error );
+    /* connect privately to the session bus
+     * the connection will not be shared with other vlc modules which use dbus,
+     * thus avoiding a whole class of concurrency issues */
+    p_conn = dbus_bus_get_private( DBUS_BUS_SESSION, &error );
     if( !p_conn )
     {
         msg_Err( p_this, "Failed to connect to the D-Bus session daemon: %s",
@@ -147,6 +209,8 @@ static int Open( vlc_object_t *p_this )
         return VLC_EGENERIC;
     }
 
+    dbus_connection_set_exit_on_disconnect( p_conn, FALSE );
+
     /* register a well-known name on the bus */
     dbus_bus_request_name( p_conn, psz_service_name, 0, &error );
     if( dbus_error_is_set( &error ) )
@@ -175,6 +239,8 @@ static int Open( vlc_object_t *p_this )
     p_intf->p_sys = p_sys;
     p_sys->p_conn = p_conn;
     p_sys->p_events = vlc_array_new();
+    p_sys->p_timeouts = vlc_array_new();
+    p_sys->p_watches = vlc_array_new();
     vlc_mutex_init( &p_sys->lock );
 
     p_playlist = pl_Get( p_intf );
@@ -188,6 +254,38 @@ static int Open( vlc_object_t *p_this )
     var_AddCallback( p_playlist, "repeat", AllCallback, p_intf );
     var_AddCallback( p_playlist, "loop", AllCallback, p_intf );
 
+    dbus_connection_set_dispatch_status_function( p_conn,
+                                                  dispatch_status_cb,
+                                                  p_intf, NULL );
+
+    if( !dbus_connection_set_timeout_functions( p_conn,
+                                                add_timeout,
+                                                remove_timeout,
+                                                timeout_toggled,
+                                                p_intf, NULL ) )
+    {
+        dbus_connection_unref( p_conn );
+        free( psz_service_name );
+        free( p_sys );
+        return VLC_ENOMEM;
+    }
+
+    if( !dbus_connection_set_watch_functions( p_conn,
+                                              add_watch,
+                                              remove_watch,
+                                              watch_toggled,
+                                              p_intf, NULL ) )
+    {
+        dbus_connection_unref( p_conn );
+        free( psz_service_name );
+        free( p_sys );
+        return VLC_ENOMEM;
+    }
+
+/*     dbus_connection_set_wakeup_main_function( p_conn,
+                                              wakeup_main_loop,
+                                              p_intf, NULL); */
+
     UpdateCaps( p_intf );
 
     return VLC_SUCCESS;
@@ -217,6 +315,9 @@ static void Close   ( vlc_object_t *p_this )
         vlc_object_release( p_sys->p_input );
     }
 
+    /* The dbus connection is private, so we are responsible
+     * for closing it */
+    dbus_connection_close( p_sys->p_conn );
     dbus_connection_unref( p_sys->p_conn );
 
     // Free the events array
@@ -227,71 +328,511 @@ static void Close   ( vlc_object_t *p_this )
     }
     vlc_mutex_destroy( &p_sys->lock );
     vlc_array_destroy( p_sys->p_events );
+    vlc_array_destroy( p_sys->p_timeouts );
+    vlc_array_destroy( p_sys->p_watches );
     free( p_sys );
 }
 
+static void dispatch_status_cb( DBusConnection *p_conn,
+                                DBusDispatchStatus i_status,
+                                void *p_data)
+{
+    (void) p_conn;
+    intf_thread_t *p_intf = (intf_thread_t*) p_data;
+
+    static const char *p_statuses[] = { "DATA_REMAINS",
+                                        "COMPLETE",
+                                        "NEED_MEMORY" };
+
+    msg_Dbg( p_intf,
+             "DBus dispatch status changed to %s.",
+             p_statuses[i_status]);
+}
+
+static dbus_bool_t add_timeout( DBusTimeout *p_timeout, void *p_data )
+{
+    intf_thread_t *p_intf = (intf_thread_t*) p_data;
+    intf_sys_t    *p_sys  = (intf_sys_t*) p_intf->p_sys;
+
+    timeout_info_t *p_info = calloc( sizeof( timeout_info_t ), 1 );
+    p_info->i_remaining = dbus_timeout_get_interval( p_timeout ) * 1000;/* µs */
+    p_info->p_timeout = p_timeout;
+
+    dbus_timeout_set_data( p_timeout, p_info, free );
+
+    vlc_mutex_lock( &p_sys->lock );
+    vlc_array_append( p_sys->p_timeouts, p_timeout );
+    vlc_mutex_unlock( &p_sys->lock );
+
+    return TRUE;
+}
+
+static void remove_timeout( DBusTimeout *p_timeout, void *p_data )
+{
+    intf_thread_t *p_intf = (intf_thread_t*) p_data;
+    intf_sys_t    *p_sys  = (intf_sys_t*) p_intf->p_sys;
+
+    vlc_mutex_lock( &p_sys->lock );
+
+    vlc_array_remove( p_sys->p_timeouts,
+                      vlc_array_index_of_item( p_sys->p_timeouts, p_timeout ) );
+
+    vlc_mutex_unlock( &p_sys->lock );
+}
+
+static void timeout_toggled( DBusTimeout *p_timeout, void *p_data )
+{
+    intf_thread_t *p_intf = (intf_thread_t*) p_data;
+
+    msg_Dbg( p_intf, "Toggling dbus timeout" );
+
+    if( dbus_timeout_get_enabled( p_timeout ) )
+    {
+        msg_Dbg( p_intf, "Timeout is enabled, main loop needs to wake up" );
+        wakeup_main_loop( p_intf );
+    }
+}
+
+static dbus_bool_t add_watch( DBusWatch *p_watch, void *p_data )
+{
+    intf_thread_t *p_intf = (intf_thread_t*) p_data;
+    intf_sys_t    *p_sys  = (intf_sys_t*) p_intf->p_sys;
+    int            i_fd   = dbus_watch_get_unix_fd( p_watch );
+
+    msg_Dbg( p_intf, "Adding dbus watch on fd %d", i_fd );
+
+    if( dbus_watch_get_flags( p_watch ) & DBUS_WATCH_READABLE )
+        msg_Dbg( p_intf, "Watching fd %d for readability", i_fd );
+
+    if( dbus_watch_get_flags( p_watch ) & DBUS_WATCH_WRITABLE )
+        msg_Dbg( p_intf, "Watching fd %d for writeability", i_fd );
+
+    vlc_mutex_lock( &p_sys->lock );
+    vlc_array_append( p_sys->p_watches, p_watch );
+    vlc_mutex_unlock( &p_sys->lock );
+
+    return TRUE;
+}
+
+static void remove_watch( DBusWatch *p_watch, void *p_data )
+{
+    intf_thread_t *p_intf = (intf_thread_t*) p_data;
+    intf_sys_t    *p_sys  = (intf_sys_t*) p_intf->p_sys;
+
+    msg_Dbg( p_intf, "Removing dbus watch on fd %d",
+              dbus_watch_get_unix_fd( p_watch ) );
+
+    vlc_mutex_lock( &p_sys->lock );
+
+    vlc_array_remove( p_sys->p_watches,
+                      vlc_array_index_of_item( p_sys->p_watches, p_watch ) );
+
+    vlc_mutex_unlock( &p_sys->lock );
+}
+
+static void watch_toggled( DBusWatch *p_watch, void *p_data )
+{
+    intf_thread_t *p_intf = (intf_thread_t*) p_data;
+
+    msg_Dbg( p_intf, "Toggling dbus watch on fd %d",
+             dbus_watch_get_unix_fd( p_watch ) );
+
+    if( dbus_watch_get_enabled( p_watch ) )
+    {
+        msg_Dbg( p_intf,
+                  "Watch on fd %d has been enabled, "
+                  "the main loops needs to wake up",
+                  dbus_watch_get_unix_fd( p_watch ) );
+
+        wakeup_main_loop( p_intf );
+    }
+}
+
+/**
+ * GetPollFds() fills an array of pollfd data structures with :
+ *  - the set of enabled dbus watches
+ *  - the unix pipe which we use to manually wake up the main loop
+ *
+ * This function must be called with p_sys->lock locked
+ *
+ * @return The number of file descriptors
+ *
+ * @param intf_thread_t *p_intf this interface thread's state
+ * @param struct pollfd *p_fds a pointer to a pollfd array large enough to
+ * contain all the returned data (number of enabled dbus watches + 1)
+ */
+static int GetPollFds( intf_thread_t *p_intf, struct pollfd *p_fds )
+{
+    intf_sys_t *p_sys = p_intf->p_sys;
+    int i_fds = 1, i_watches = vlc_array_count( p_sys->p_watches );
+
+    p_fds[0].fd = p_sys->p_pipe_fds[PIPE_OUT];
+    p_fds[0].events = POLLIN | POLLPRI;
+
+    for( int i = 0; i < i_watches; i++ )
+    {
+        DBusWatch *p_watch = NULL;
+        p_watch = vlc_array_item_at_index( p_sys->p_watches, i );
+        if( !dbus_watch_get_enabled( p_watch ) )
+            continue;
+
+        p_fds[i_fds].fd = dbus_watch_get_unix_fd( p_watch );
+        int i_flags = dbus_watch_get_flags( p_watch );
+
+        if( i_flags & DBUS_WATCH_READABLE )
+            p_fds[i_fds].events |= POLLIN | POLLPRI;
+
+        if( i_flags & DBUS_WATCH_WRITABLE )
+            p_fds[i_fds].events |= POLLOUT;
+
+        i_fds++;
+    }
+
+    return i_fds;
+}
+
+/**
+ * UpdateTimeouts() updates the remaining time for each timeout and
+ * returns how much time is left until the next timeout.
+ *
+ * This function must be called with p_sys->lock locked
+ *
+ * @return int The time remaining until the next timeout, in milliseconds
+ * or -1 if there are no timeouts
+ *
+ * @param intf_thread_t *p_intf This interface thread's state
+ * @param mtime_t i_loop_interval The time which has elapsed since the last
+ * call to this function
+ */
+static int UpdateTimeouts( intf_thread_t *p_intf, mtime_t i_loop_interval )
+{
+    intf_sys_t *p_sys = p_intf->p_sys;
+    mtime_t i_next_timeout = LAST_MDATE;
+    unsigned int i_timeouts = vlc_array_count( p_sys->p_timeouts );
+
+    if( 0 == i_timeouts )
+        return -1;
+
+    for( unsigned int i = 0; i < i_timeouts; i++ )
+    {
+        timeout_info_t *p_info = NULL;
+        DBusTimeout    *p_timeout = NULL;
+        mtime_t         i_interval = 0;
+
+        p_timeout = vlc_array_item_at_index( p_sys->p_timeouts, i );
+        i_interval = dbus_timeout_get_interval( p_timeout ) * 1000; /* µs */
+        p_info = (timeout_info_t*) dbus_timeout_get_data( p_timeout );
+
+        p_info->i_remaining -= __MAX( 0, i_loop_interval ) % i_interval;
+
+        if( !dbus_timeout_get_enabled( p_timeout ) )
+            continue;
+
+        /* The correct poll timeout value is the shortest one
+         * in the dbus timeouts list */
+        i_next_timeout = __MIN( i_next_timeout,
+                                __MAX( 0, p_info->i_remaining ) );
+    }
+
+    /* next timeout in milliseconds */
+    return i_next_timeout / 1000;
+}
+
+/**
+ * ProcessEvents() reacts to a list of events originating from other VLC threads
+ *
+ * This function must be called with p_sys->lock unlocked
+ *
+ * @param intf_thread_t *p_intf This interface thread state
+ * @param callback_info_t *p_events the list of events to process
+ */
+static void ProcessEvents( intf_thread_t *p_intf,
+                           callback_info_t **p_events, int i_events )
+{
+    for( int i = 0; i < i_events; i++ )
+    {
+        switch( p_events[i]->signal )
+        {
+        case SIGNAL_ITEM_CURRENT:
+            TrackChange( p_intf );
+            break;
+        case SIGNAL_INTF_CHANGE:
+        case SIGNAL_PLAYLIST_ITEM_APPEND:
+        case SIGNAL_PLAYLIST_ITEM_DELETED:
+            TrackListChangeEmit( p_intf,
+                                 p_events[i]->signal,
+                                 p_events[i]->i_node );
+            break;
+        case SIGNAL_RANDOM:
+        case SIGNAL_REPEAT:
+        case SIGNAL_LOOP:
+            StatusChangeEmit( p_intf );
+            break;
+        case SIGNAL_STATE:
+            StateChange( p_intf );
+            break;
+        case SIGNAL_INPUT_METADATA:
+            break;
+        default:
+            assert(0);
+        }
+        free( p_events[i] );
+    }
+}
+
+/**
+ * ProcessWatches() handles a list of dbus watches after poll() has returned
+ *
+ * This function must be called with p_sys->lock unlocked
+ *
+ * @param intf_thread_t *p_intf This interface thread state
+ * @param DBusWatch **p_watches The list of dbus watches to process
+ * @param int i_watches The size of the p_watches array
+ * @param struct pollfd *p_fds The result of a poll() call
+ * @param int i_fds The number of file descriptors processed by poll()
+ */
+static void ProcessWatches( intf_thread_t *p_intf,
+                            DBusWatch **p_watches, int i_watches,
+                            struct pollfd *p_fds,  int i_fds )
+{
+    /* Process watches */
+    for( int i = 0; i < i_watches; i++ )
+    {
+        DBusWatch *p_watch = p_watches[i];
+        if( !dbus_watch_get_enabled( p_watch ) )
+            continue;
+
+        for( int j = 0; j < i_fds; j++ )
+        {
+            if( p_fds[j].fd != dbus_watch_get_unix_fd( p_watch ) )
+                continue;
+
+            int i_flags   = 0;
+            int i_revents = p_fds[j].revents;
+            int i_fd      = p_fds[j].fd;
+
+            if( i_revents & POLLIN )
+            {
+                msg_Dbg( p_intf, "fd %d is ready for reading", i_fd );
+                i_flags |= DBUS_WATCH_READABLE;
+            }
+
+            if( i_revents & POLLOUT )
+            {
+                msg_Dbg( p_intf, "fd %d is ready for writing", i_fd );
+                i_flags |= DBUS_WATCH_WRITABLE;
+            }
+
+            if( i_revents & POLLERR )
+            {
+                msg_Dbg( p_intf, "error when polling fd %d", i_fd );
+                i_flags |= DBUS_WATCH_ERROR;
+            }
+
+            if( i_revents & POLLHUP )
+            {
+                msg_Dbg( p_intf, "Hangup signal on fd %d", i_fd );
+                i_flags |= DBUS_WATCH_HANGUP;
+            }
+
+            if( i_flags )
+            {
+                msg_Dbg( p_intf, "Handling dbus watch on fd %d", i_fd );
+                dbus_watch_handle( p_watch, i_flags );
+            }
+            else
+                msg_Dbg( p_intf, "Nothing happened on fd %d", i_fd );
+        }
+    }
+}
+
+/**
+ * ProcessTimeouts() handles DBus timeouts
+ *
+ * This function must be called with p_sys->lock locked
+ *
+ * @param intf_thread_t *p_intf This interface thread state
+ * @param DBusTimeout **p_timeouts List of timeouts to process
+ * @param int i_timeouts Size of p_timeouts
+ */
+static void ProcessTimeouts( intf_thread_t *p_intf,
+                             DBusTimeout  **p_timeouts, int i_timeouts )
+{
+    VLC_UNUSED( p_intf );
+
+    for( int i = 0; i < i_timeouts; i++ )
+    {
+        timeout_info_t *p_info = NULL;
+
+        p_info = (timeout_info_t*) dbus_timeout_get_data( p_timeouts[i] );
+
+        if( !dbus_timeout_get_enabled( p_info->p_timeout ) )
+            continue;
+
+        if( p_info->i_remaining > 0 )
+            continue;
+
+        dbus_timeout_handle( p_info->p_timeout );
+        p_info->i_remaining = dbus_timeout_get_interval( p_info->p_timeout );
+    }
+}
+
+/**
+ * DispatchDBusMessages() dispatches incoming dbus messages
+ * (indirectly invoking the callbacks), then it sends outgoing
+ * messages which needs to be sent on the bus (method replies and signals)
+ *
+ * This function must be called with p_sys->lock unlocked
+ *
+ * @param intf_thread_t *p_intf This interface thread state
+ */
+static void DispatchDBusMessages( intf_thread_t *p_intf )
+{
+    DBusDispatchStatus status;
+    intf_sys_t *p_sys = p_intf->p_sys;
+
+    /* Dispatch incoming messages */
+    status = dbus_connection_get_dispatch_status( p_sys->p_conn );
+    while( status != DBUS_DISPATCH_COMPLETE )
+    {
+        msg_Dbg( p_intf, "Dispatching incoming dbus message" );
+        dbus_connection_dispatch( p_sys->p_conn );
+        status = dbus_connection_get_dispatch_status( p_sys->p_conn );
+    }
+
+    /* Send outgoing data */
+    if( dbus_connection_has_messages_to_send( p_sys->p_conn ) )
+    {
+        msg_Dbg( p_intf, "Sending outgoing data" );
+        dbus_connection_flush( p_sys->p_conn );
+    }
+}
+
 /*****************************************************************************
  * Run: main loop
  *****************************************************************************/
 
 static void Run          ( intf_thread_t *p_intf )
 {
+    intf_sys_t    *p_sys = p_intf->p_sys;
+    mtime_t        i_last_run = mdate();
+
     for( ;; )
     {
-        if( dbus_connection_get_dispatch_status(p_intf->p_sys->p_conn)
-                                             == DBUS_DISPATCH_COMPLETE )
-            msleep( INTF_IDLE_SLEEP );
         int canc = vlc_savecancel();
-        dbus_connection_read_write_dispatch( p_intf->p_sys->p_conn, 0 );
+        vlc_mutex_lock( &p_sys->lock );
+
+        int i_watches = vlc_array_count( p_sys->p_watches );
+        struct pollfd *p_fds = calloc( sizeof( struct pollfd ), i_watches );
+
+        int i_fds = GetPollFds( p_intf, p_fds );
+
+        mtime_t i_now = mdate(), i_loop_interval = i_now - i_last_run;
+
+        msg_Dbg( p_intf,
+                 "%lld µs elapsed since last wakeup",
+                 (long long) i_loop_interval );
 
-        /* Get the list of events to process
+        int i_next_timeout = UpdateTimeouts( p_intf, i_loop_interval );
+        i_last_run = i_now;
+
+        vlc_mutex_unlock( &p_sys->lock );
+
+        if( -1 != i_next_timeout )
+            msg_Dbg( p_intf, "next timeout is in %d ms", i_next_timeout );
+        msg_Dbg( p_intf, "Sleeping until something happens" );
+
+        /* thread cancellation is allowed while the main loop sleeps */
+        vlc_restorecancel( canc );
+
+        int i_pollres = poll( p_fds, i_fds, i_next_timeout );
+        int i_errsv   = errno;
+
+        canc = vlc_savecancel();
+
+        msg_Dbg( p_intf, "the main loop has been woken up" );
+
+        if( -1 == i_pollres )
+        { /* XXX: What should we do when poll() fails ? */
+            char buf[64];
+            msg_Err( p_intf, "poll() failed: %s", strerror_r( i_errsv, buf, 64 ) );
+            free( p_fds ); p_fds = NULL;
+            vlc_restorecancel( canc );
+            continue;
+        }
+
+        /* Was the main loop woken up manually ? */
+        if( 0 < i_pollres && ( p_fds[0].revents & POLLIN ) )
+        {
+            char buf;
+            msg_Dbg( p_intf, "Removing a byte from the self-pipe" );
+            (void)read( p_fds[0].fd, &buf, 1 );
+        }
+
+        /* We need to lock the mutex while building lists of events,
+         * timeouts and watches to process but we can't keep the lock while
+         * processing them, or else we risk a deadlock:
          *
-         * We can't keep the lock on p_intf->p_sys->p_events, else we risk a
-         * deadlock:
          * The signal functions could lock mutex X while p_events is locked;
          * While some other function in vlc (playlist) might lock mutex X
          * and then set a variable which would call AllCallback(), which itself
          * needs to lock p_events to add a new event.
          */
         vlc_mutex_lock( &p_intf->p_sys->lock );
+
+        /* Get the list of timeouts to process */
+        unsigned int i_timeouts = vlc_array_count( p_sys->p_timeouts );
+        DBusTimeout *p_timeouts[i_timeouts];
+        for( unsigned int i = 0; i < i_timeouts; i++ )
+        {
+            p_timeouts[i] = vlc_array_item_at_index( p_sys->p_timeouts, i );
+        }
+
+        /* Get the list of watches to process */
+        i_watches = vlc_array_count( p_sys->p_watches );
+        DBusWatch *p_watches[i_watches];
+        for( int i = 0; i < i_watches; i++ )
+        {
+            p_watches[i] = vlc_array_item_at_index( p_sys->p_watches, i );
+        }
+
+        /* Get the list of events to process */
         int i_events = vlc_array_count( p_intf->p_sys->p_events );
-        callback_info_t* info[i_events];
+        callback_info_t* p_info[i_events];
         for( int i = i_events - 1; i >= 0; i-- )
         {
-            info[i] = vlc_array_item_at_index( p_intf->p_sys->p_events, i );
+            p_info[i] = vlc_array_item_at_index( p_intf->p_sys->p_events, i );
             vlc_array_remove( p_intf->p_sys->p_events, i );
         }
+
+        /* now we can release the lock and process what's pending */
         vlc_mutex_unlock( &p_intf->p_sys->lock );
 
-        for( int i = 0; i < i_events; i++ )
-        {
-            switch( info[i]->signal )
-            {
-            case SIGNAL_ITEM_CURRENT:
-                TrackChange( p_intf );
-                break;
-            case SIGNAL_INTF_CHANGE:
-            case SIGNAL_PLAYLIST_ITEM_APPEND:
-            case SIGNAL_PLAYLIST_ITEM_DELETED:
-                TrackListChangeEmit( p_intf, info[i]->signal, info[i]->i_node );
-                break;
-            case SIGNAL_RANDOM:
-            case SIGNAL_REPEAT:
-            case SIGNAL_LOOP:
-                StatusChangeEmit( p_intf );
-                break;
-            case SIGNAL_STATE:
-                StateChange( p_intf );
-                break;
-            default:
-                assert(0);
-            }
-            free( info[i] );
-        }
+        ProcessEvents( p_intf, p_info, i_events );
+        ProcessWatches( p_intf, p_watches, i_watches, p_fds, i_fds );
+
+        free( p_fds ); p_fds = NULL;
+
+        ProcessTimeouts( p_intf, p_timeouts, i_timeouts );
+        DispatchDBusMessages( p_intf );
+
         vlc_restorecancel( canc );
     }
 }
 
+static void   wakeup_main_loop( void *p_data )
+{
+    intf_thread_t *p_intf = (intf_thread_t*) p_data;
+
+    msg_Dbg( p_intf, "Sending wakeup signal to the main loop" );
+
+    if( !write( p_intf->p_sys->p_pipe_fds[PIPE_IN], "\0", 1 ) )
+    {
+        msg_Err( p_intf,
+            "Could not wake up the main loop: %s", strerror( errno ) );
+    }
+}
+
 /*****************************************************************************
  * UpdateCaps: update p_sys->i_caps
  * This function have to be called with the playlist unlocked
@@ -331,15 +872,63 @@ int UpdateCaps( intf_thread_t* p_intf )
     return VLC_SUCCESS;
 }
 
+/* InputIntfEventCallback() fills a callback_info_t data structure in response
+ * to an "intf-event" input event.
+ *
+ * Caution: This function executes in the input thread
+ *
+ * This function must be called with p_sys->lock locked
+ *
+ * @return int VLC_SUCCESS on success, VLC_E* on error
+ * @param intf_thread_t *p_intf the interface thread
+ * @param input_thread_t *p_input This input thread
+ * @param const int i_event input event type
+ * @param callback_info_t *p_info Location of the callback info to fill
+ */
+static int InputIntfEventCallback( intf_thread_t   *p_intf,
+                                   input_thread_t  *p_input,
+                                   const int        i_event,
+                                   callback_info_t *p_info )
+{
+    dbus_int32_t i_state = PLAYBACK_STATE_INVALID;
+    assert(!p_info->signal);
+
+    switch( i_event )
+    {
+        case INPUT_EVENT_DEAD:
+        case INPUT_EVENT_ABORT:
+            i_state = PLAYBACK_STATE_STOPPED;
+            break;
+        case INPUT_EVENT_STATE:
+            i_state = ( var_GetInteger( p_input, "state" ) == PAUSE_S ) ?
+                PLAYBACK_STATE_PAUSED : PLAYBACK_STATE_PLAYING;
+            break;
+        case INPUT_EVENT_ITEM_META:
+            p_info->signal = SIGNAL_INPUT_METADATA;
+            return VLC_SUCCESS;
+        default:
+            return VLC_EGENERIC;
+    }
+
+    if( i_state != p_intf->p_sys->i_playing_state )
+    {
+        p_intf->p_sys->i_playing_state = i_state;
+        p_info->signal = SIGNAL_STATE;
+    }
+
+    return p_info->signal ? VLC_SUCCESS : VLC_EGENERIC;
+}
+
 // Get all the callbacks
 static int AllCallback( vlc_object_t *p_this, const char *psz_var,
                         vlc_value_t oldval, vlc_value_t newval, void *p_data )
 {
     (void)p_this;
     (void)oldval;
+
     intf_thread_t *p_intf = (intf_thread_t*)p_data;
+    callback_info_t *info = calloc( 1, sizeof( callback_info_t ) );
 
-    callback_info_t *info = malloc( sizeof( callback_info_t ) );
     if( !info )
         return VLC_ENOMEM;
 
@@ -348,43 +937,55 @@ static int AllCallback( vlc_object_t *p_this, const char *psz_var,
     // Wich event is it ?
     if( !strcmp( "item-current", psz_var ) )
         info->signal = SIGNAL_ITEM_CURRENT;
+
     else if( !strcmp( "intf-change", psz_var ) )
         info->signal = SIGNAL_INTF_CHANGE;
+
     else if( !strcmp( "playlist-item-append", psz_var ) )
     {
         info->signal = SIGNAL_PLAYLIST_ITEM_APPEND;
         info->i_node = ((playlist_add_t*)newval.p_address)->i_node;
     }
+
     else if( !strcmp( "playlist-item-deleted", psz_var ) )
         info->signal = SIGNAL_PLAYLIST_ITEM_DELETED;
+
     else if( !strcmp( "random", psz_var ) )
         info->signal = SIGNAL_RANDOM;
+
     else if( !strcmp( "repeat", psz_var ) )
         info->signal = SIGNAL_REPEAT;
+
     else if( !strcmp( "loop", psz_var ) )
         info->signal = SIGNAL_LOOP;
+
     else if( !strcmp( "intf-event", psz_var ) )
     {
-        dbus_int32_t state;
-        state = (var_GetInteger(p_this, "state") == PAUSE_S) ? 1 : 0;
+        int i_res;
+        i_res = InputIntfEventCallback( p_intf, p_this, newval.i_int, info );
 
-        if( state == p_intf->p_sys->i_playing_state )
+        if( VLC_SUCCESS != i_res )
         {
+            vlc_mutex_unlock( &p_intf->p_sys->lock );
             free( info );
-            goto end;
-        }
 
-        p_intf->p_sys->i_playing_state = state;
-        info->signal = SIGNAL_STATE;
+            return i_res;
+        }
     }
+
     else
         assert(0);
 
     // Append the event
     vlc_array_append( p_intf->p_sys->p_events, info );
-
-end:
     vlc_mutex_unlock( &p_intf->p_sys->lock );
+
+    msg_Dbg( p_intf,
+             "Got a VLC event on %s. The main loop needs to wake up "
+             "in order to process it", psz_var );
+
+    wakeup_main_loop( p_intf );
+
     return VLC_SUCCESS;
 }
 
index 7a6a5e5..0a5ac8a 100644 (file)
@@ -7,7 +7,7 @@
  * $Id$
  *
  * Authors:    Rafaël Carré <funman at videolanorg>
- *             Mirsal ENNAIME <mirsal dot ennaime at gmail dot com>
+ *             Mirsal Ennaime <mirsal at mirsal fr>
  *
  * 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
index a7db1ef..ce9b927 100644 (file)
@@ -6,7 +6,8 @@
  * Copyright © 2009-2010 The VideoLAN team
  * $Id$
  *
- * Authors:    Mirsal ENNAIME <mirsal dot ennaime at gmail dot com>
+ * Authors:    Mirsal Ennaime <mirsal dot ennaime at gmailcom>
+ *             Rafaël Carré <funman at videolanorg>
  *
  * 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
@@ -88,6 +89,9 @@ struct intf_sys_t
     dbus_int32_t    i_playing_state;
     bool            b_dead;
     vlc_array_t    *p_events;
+    vlc_array_t    *p_timeouts;
+    vlc_array_t    *p_watches;
+    int             p_pipe_fds[2];
     vlc_mutex_t     lock;
     input_thread_t *p_input;
     bool            b_unique;
@@ -99,12 +103,21 @@ enum
     SIGNAL_INTF_CHANGE,
     SIGNAL_PLAYLIST_ITEM_APPEND,
     SIGNAL_PLAYLIST_ITEM_DELETED,
+    SIGNAL_INPUT_METADATA,
     SIGNAL_RANDOM,
     SIGNAL_REPEAT,
     SIGNAL_LOOP,
     SIGNAL_STATE
 };
 
+enum
+{
+    PLAYBACK_STATE_INVALID = -1,
+    PLAYBACK_STATE_PLAYING = 0,
+    PLAYBACK_STATE_PAUSED  = 1,
+    PLAYBACK_STATE_STOPPED = 2
+};
+
 int GetInputMeta  ( input_item_t* p_input, DBusMessageIter *args );
 int UpdateCaps    ( intf_thread_t* );
 
index 39d6e62..54d1af1 100644 (file)
@@ -6,7 +6,8 @@
  * Copyright © 2009-2010 The VideoLAN team
  * $Id$
  *
- * Authors:    Mirsal ENNAIME <mirsal dot ennaime at gmail dot com>
+ * Authors:    Mirsal Ennaime <mirsal at mirsal fr>
+ *             Rafaël Carré <funman at videolanorg>
  *
  * 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
index db8a031..2934b17 100644 (file)
@@ -6,7 +6,8 @@
  * Copyright © 2009-2010 The VideoLAN team
  * $Id$
  *
- * Authors:    Mirsal ENNAIME <mirsal dot ennaime at gmail dot com>
+ * Authors:    Mirsal Ennaime <mirsal at mirsal fr>
+ *             Rafaël Carré <funman at videolanorg>
  *
  * 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
index aa43caa..3b49648 100644 (file)
@@ -6,7 +6,8 @@
  * Copyright © 2009-2010 The VideoLAN team
  * $Id$
  *
- * Authors:    Mirsal ENNAIME <mirsal dot ennaime at gmail dot com>
+ * Authors:    Mirsal Ennaime <mirsal at mirsal fr>
+ *             Rafaël Carré <funman at videolanorg>
  *
  * 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
index 03a2e99..43724cf 100644 (file)
@@ -6,7 +6,8 @@
  * Copyright © 2009-2010 The VideoLAN team
  * $Id$
  *
- * Authors:    Mirsal ENNAIME <mirsal dot ennaime at gmail dot com>
+ * Authors:    Mirsal Ennaime <mirsal at mirsal fr>
+ *             Rafaël Carré <funman at videolanorg>
  *
  * 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
index a4e6a62..311b2d6 100644 (file)
@@ -6,7 +6,8 @@
  * Copyright © 2009-2010 The VideoLAN team
  * $Id$
  *
- * Authors:    Mirsal ENNAIME <mirsal dot ennaime at gmail dot com>
+ * Authors:    Mirsal Ennaime <mirsal at mirsal fr>
+ *             Rafaël Carré <funman at videolanorg>
  *
  * 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
index c28f30f..d8d5576 100644 (file)
@@ -6,7 +6,8 @@
  * Copyright © 2009-2010 The VideoLAN team
  * $Id$
  *
- * Authors:    Mirsal ENNAIME <mirsal dot ennaime at gmail dot com>
+ * Authors:    Mirsal Ennaime <mirsal at mirsal fr>
+ *             Rafaël Carré <funman at videolanorg>
  *
  * 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