]> git.sesse.net Git - vlc/blobdiff - src/misc/variables.c
Mark unreachable code on GCC even if NDEBUG
[vlc] / src / misc / variables.c
index 801782ebe5c82bfa09314b2b505b8347a9ace4a8..3a89b4156b90b7c0474b826ec349824a042cce4d 100644 (file)
@@ -1,24 +1,24 @@
 /*****************************************************************************
  * variables.c: routines for object variables handling
  *****************************************************************************
- * Copyright (C) 2002-2009 the VideoLAN team
+ * Copyright (C) 2002-2009 VLC authors and VideoLAN
  * $Id$
  *
  * Authors: Samuel Hocevar <sam@zoy.org>
  *
- * 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
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
  * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
  *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
  *****************************************************************************/
 
 /*****************************************************************************
 # include "config.h"
 #endif
 
-#include <vlc_common.h>
-#include <vlc_charset.h>
-#include "variables.h"
-
 #ifdef HAVE_SEARCH_H
 # include <search.h>
 #endif
-
-#include "libvlc.h"
-
 #include <assert.h>
+#include <float.h>
 #include <math.h>
 #include <limits.h>
+#ifdef __GLIBC__
+# include <dlfcn.h>
+#endif
+
+#include <vlc_common.h>
+#include <vlc_charset.h>
+#include "libvlc.h"
+#include "variables.h"
+#include "config/configuration.h"
 
 /*****************************************************************************
  * Private types
  *****************************************************************************/
 struct callback_entry_t
 {
-    vlc_callback_t pf_callback;
+    union
+    {
+        vlc_callback_t       pf_value_callback;
+        vlc_list_callback_t  pf_list_callback;
+        void *               p_callback;
+    } u;
     void *         p_data;
 };
 
@@ -90,33 +98,6 @@ static void DupString( vlc_value_t *p_val )
 
 static void FreeDummy( vlc_value_t *p_val ) { (void)p_val; /* unused */ }
 static void FreeString( vlc_value_t *p_val ) { free( p_val->psz_string ); }
-static void FreeMutex( vlc_value_t *p_val ) { vlc_mutex_destroy( (vlc_mutex_t*)p_val->p_address ); free( p_val->p_address ); }
-
-static void FreeList( vlc_value_t *p_val )
-{
-    int i;
-    for( i = 0; i < p_val->p_list->i_count; i++ )
-    {
-        switch( p_val->p_list->pi_types[i] & VLC_VAR_CLASS )
-        {
-        case VLC_VAR_STRING:
-            FreeString( &p_val->p_list->p_values[i] );
-            break;
-        case VLC_VAR_MUTEX:
-            FreeMutex( &p_val->p_list->p_values[i] );
-            break;
-        default:
-            break;
-        }
-    }
-
-    if( p_val->p_list->i_count )
-    {
-        free( p_val->p_list->p_values );
-        free( p_val->p_list->pi_types );
-    }
-    free( p_val->p_list );
-}
 
 static const struct variable_ops_t
 void_ops   = { NULL,       DupDummy,  FreeDummy,  },
@@ -124,7 +105,6 @@ addr_ops   = { CmpAddress, DupDummy,  FreeDummy,  },
 bool_ops   = { CmpBool,    DupDummy,  FreeDummy,  },
 float_ops  = { CmpFloat,   DupDummy,  FreeDummy,  },
 int_ops    = { CmpInt,     DupDummy,  FreeDummy,  },
-mutex_ops  = { CmpAddress, DupDummy,  FreeMutex,  },
 string_ops = { CmpString,  DupString, FreeString, },
 time_ops   = { CmpTime,    DupDummy,  FreeDummy,  },
 coords_ops = { NULL,       DupDummy,  FreeDummy,  };
@@ -139,6 +119,10 @@ static void     CheckValue  ( variable_t *, vlc_value_t * );
 static int      TriggerCallback( vlc_object_t *, variable_t *, const char *,
                                  vlc_value_t );
 
+static int      TriggerListCallback( vlc_object_t *, variable_t *,
+                                     const char *, int,
+                                     vlc_value_t * );
+
 static int varcmp( const void *a, const void *b )
 {
     const variable_t *va = a, *vb = b;
@@ -171,9 +155,29 @@ static void Destroy( variable_t *p_var )
         free( p_var->choices.p_values );
         free( p_var->choices_text.p_values );
     }
+#if 0 // ndef NDEBUG
+    callback_table_t *p_table = &p_var->value_callbacks;
+    for (int i = 0; i < p_table->i_entries; i++)
+    {
+        const char *file = "?", *symbol = "?";
+        const void *addr = p_table->p_entries[i].pf_callback;
+# ifdef __GLIBC__
+        Dl_info info;
+
+        if (dladdr (addr, &info))
+        {
+            if (info.dli_fname) file = info.dli_fname;
+            if (info.dli_sname) symbol = info.dli_sname;
+        }
+# endif
+        fprintf (stderr, "Error: callback on \"%s\" dangling %s(%s)[%p]\n",
+                 p_var->psz_name, file, symbol, addr);
+    }
+#endif
+
     free( p_var->psz_name );
     free( p_var->psz_text );
-    free( p_var->p_entries );
+    free( p_var->value_callbacks.p_entries );
     free( p_var );
 }
 
@@ -212,8 +216,7 @@ int var_Create( vlc_object_t *p_this, const char *psz_name, int i_type )
     p_var->choices_text.p_values = NULL;
 
     p_var->b_incallback = false;
-    p_var->i_entries = 0;
-    p_var->p_entries = NULL;
+    p_var->value_callbacks = (callback_table_t){ 0 };
 
     /* Always initialize the variable, even if it is a list variable; this
      * will lead to errors if the variable is not initialized, but it will
@@ -248,25 +251,17 @@ int var_Create( vlc_object_t *p_this, const char *psz_name, int i_type )
             p_var->ops = &addr_ops;
             p_var->val.p_address = NULL;
             break;
-        case VLC_VAR_MUTEX:
-            p_var->ops = &mutex_ops;
-            p_var->val.p_address = malloc( sizeof(vlc_mutex_t) );
-            vlc_mutex_init( (vlc_mutex_t*)p_var->val.p_address );
+        case VLC_VAR_VOID:
+            p_var->ops = &void_ops;
             break;
         default:
-            p_var->ops = &void_ops;
-#ifndef NDEBUG
-            if( (i_type & VLC_VAR_CLASS) != VLC_VAR_VOID )
-                msg_Err( p_this, "Creating the variable '%s' without a type",
-                          psz_name );
-#endif
+            vlc_assert_unreachable ();
     }
 
-    if( i_type & VLC_VAR_DOINHERIT )
+    if( (i_type & VLC_VAR_DOINHERIT)
+     && var_Inherit( p_this, psz_name, i_type, &p_var->val ) == 0 )
     {
-        if( var_Inherit( p_this, psz_name, i_type, &p_var->val ) )
-            msg_Err( p_this, "cannot inherit value for %s", psz_name );
-        else if( i_type & VLC_VAR_HASCHOICE )
+        if( i_type & VLC_VAR_HASCHOICE )
         {
             /* We must add the inherited value to our choice list */
             p_var->i_default = 0;
@@ -289,17 +284,11 @@ int var_Create( vlc_object_t *p_this, const char *psz_name, int i_type )
     pp_var = tsearch( p_var, &p_priv->var_root, varcmp );
     if( unlikely(pp_var == NULL) )
         ret = VLC_ENOMEM;
-    else if( (p_oldvar = *pp_var) == p_var )
-        p_var = NULL;
-    else if( unlikely((i_type ^ p_oldvar->i_type) & VLC_VAR_CLASS) )
-    {    /* If the types differ, variable creation failed. */
-         msg_Err( p_this, "Variable '%s' (0x%04x) already exist "
-                  "but with a different type (0x%04x)",
-                  psz_name, p_oldvar->i_type, i_type );
-         ret = VLC_EBADVAR;
-    }
-    else
+    else if( (p_oldvar = *pp_var) == p_var ) /* Variable create */
+        p_var = NULL; /* Variable created */
+    else /* Variable already exists */
     {
+        assert (((i_type ^ p_oldvar->i_type) & VLC_VAR_CLASS) == 0);
         p_oldvar->i_usage++;
         p_oldvar->i_type |= i_type & (VLC_VAR_ISCOMMAND|VLC_VAR_HASCHOICE);
     }
@@ -377,7 +366,7 @@ void var_DestroyAll( vlc_object_t *obj )
 int var_Change( vlc_object_t *p_this, const char *psz_name,
                 int i_action, vlc_value_t *p_val, vlc_value_t *p_val2 )
 {
-    int i;
+    int ret = VLC_SUCCESS;
     variable_t *p_var;
     vlc_value_t oldval;
     vlc_value_t newval;
@@ -409,9 +398,9 @@ int var_Change( vlc_object_t *p_this, const char *psz_name,
             break;
         case VLC_VAR_GETMIN:
             if( p_var->i_type & VLC_VAR_HASMIN )
-            {
                 *p_val = p_var->min;
-            }
+            else
+                ret = VLC_EGENERIC;
             break;
         case VLC_VAR_SETMAX:
             if( p_var->i_type & VLC_VAR_HASMAX )
@@ -425,9 +414,9 @@ int var_Change( vlc_object_t *p_this, const char *psz_name,
             break;
         case VLC_VAR_GETMAX:
             if( p_var->i_type & VLC_VAR_HASMAX )
-            {
                 *p_val = p_var->max;
-            }
+            else
+                ret = VLC_EGENERIC;
             break;
         case VLC_VAR_SETSTEP:
             if( p_var->i_type & VLC_VAR_HASSTEP )
@@ -441,12 +430,13 @@ int var_Change( vlc_object_t *p_this, const char *psz_name,
             break;
         case VLC_VAR_GETSTEP:
             if( p_var->i_type & VLC_VAR_HASSTEP )
-            {
                 *p_val = p_var->step;
-            }
+            else
+                ret = VLC_EGENERIC;
             break;
         case VLC_VAR_ADDCHOICE:
-            i = p_var->choices.i_count;
+        {
+            int i = p_var->choices.i_count;
 
             INSERT_ELEM( p_var->choices.p_values, p_var->choices.i_count,
                          i, *p_val );
@@ -458,15 +448,17 @@ int var_Change( vlc_object_t *p_this, const char *psz_name,
                 strdup( p_val2->psz_string ) : NULL;
 
             CheckValue( p_var, &p_var->val );
+
+            TriggerListCallback(p_this, p_var, psz_name, VLC_VAR_ADDCHOICE, p_val);
             break;
+        }
         case VLC_VAR_DELCHOICE:
+        {
+            int i;
+
             for( i = 0 ; i < p_var->choices.i_count ; i++ )
-            {
                 if( p_var->ops->pf_cmp( p_var->choices.p_values[i], *p_val ) == 0 )
-                {
                     break;
-                }
-            }
 
             if( i == p_var->choices.i_count )
             {
@@ -476,13 +468,9 @@ int var_Change( vlc_object_t *p_this, const char *psz_name,
             }
 
             if( p_var->i_default > i )
-            {
                 p_var->i_default--;
-            }
             else if( p_var->i_default == i )
-            {
                 p_var->i_default = -1;
-            }
 
             p_var->ops->pf_free( &p_var->choices.p_values[i] );
             free( p_var->choices_text.p_values[i].psz_string );
@@ -491,16 +479,17 @@ int var_Change( vlc_object_t *p_this, const char *psz_name,
                          p_var->choices_text.i_count, i );
 
             CheckValue( p_var, &p_var->val );
+
+            TriggerListCallback(p_this, p_var, psz_name, VLC_VAR_DELCHOICE, p_val);
             break;
+        }
         case VLC_VAR_CHOICESCOUNT:
             p_val->i_int = p_var->choices.i_count;
             break;
         case VLC_VAR_CLEARCHOICES:
-            for( i = 0 ; i < p_var->choices.i_count ; i++ )
-            {
+            for( int i = 0 ; i < p_var->choices.i_count ; i++ )
                 p_var->ops->pf_free( &p_var->choices.p_values[i] );
-            }
-            for( i = 0 ; i < p_var->choices_text.i_count ; i++ )
+            for( int i = 0 ; i < p_var->choices_text.i_count ; i++ )
                 free( p_var->choices_text.p_values[i].psz_string );
 
             if( p_var->choices.i_count ) free( p_var->choices.p_values );
@@ -511,26 +500,24 @@ int var_Change( vlc_object_t *p_this, const char *psz_name,
             p_var->choices_text.i_count = 0;
             p_var->choices_text.p_values = NULL;
             p_var->i_default = -1;
+            TriggerListCallback(p_this, p_var, psz_name, VLC_VAR_CLEARCHOICES, NULL);
             break;
         case VLC_VAR_SETDEFAULT:
+        {
+            int i;
             /* FIXME: the list is sorted, dude. Use something cleverer. */
             for( i = 0 ; i < p_var->choices.i_count ; i++ )
-            {
                 if( p_var->ops->pf_cmp( p_var->choices.p_values[i], *p_val ) == 0 )
-                {
                     break;
-                }
-            }
 
             if( i == p_var->choices.i_count )
-            {
                 /* Not found */
                 break;
-            }
 
             p_var->i_default = i;
             CheckValue( p_var, &p_var->val );
             break;
+        }
         case VLC_VAR_SETVALUE:
             /* Duplicate data if needed */
             newval = *p_val;
@@ -545,36 +532,28 @@ int var_Change( vlc_object_t *p_this, const char *psz_name,
             p_var->ops->pf_free( &oldval );
             break;
         case VLC_VAR_GETCHOICES:
-        case VLC_VAR_GETLIST:
-            p_val->p_list = malloc( sizeof(vlc_list_t) );
-            if( p_val2 ) p_val2->p_list = malloc( sizeof(vlc_list_t) );
-            if( p_var->choices.i_count )
+            p_val->p_list = xmalloc( sizeof(vlc_list_t) );
+            p_val->p_list->p_values =
+                xmalloc( p_var->choices.i_count * sizeof(vlc_value_t) );
+            p_val->p_list->i_type = p_var->i_type;
+            p_val->p_list->i_count = p_var->choices.i_count;
+            if( p_val2 )
             {
-                p_val->p_list->p_values = malloc( p_var->choices.i_count
-                                                  * sizeof(vlc_value_t) );
-                p_val->p_list->pi_types = malloc( p_var->choices.i_count
-                                                  * sizeof(int) );
-                if( p_val2 )
-                {
-                    p_val2->p_list->p_values =
-                        malloc( p_var->choices.i_count * sizeof(vlc_value_t) );
-                    p_val2->p_list->pi_types =
-                        malloc( p_var->choices.i_count * sizeof(int) );
-                }
+                p_val2->p_list = xmalloc( sizeof(vlc_list_t) );
+                p_val2->p_list->p_values =
+                    xmalloc( p_var->choices.i_count * sizeof(vlc_value_t) );
+                p_val2->p_list->i_type = VLC_VAR_STRING;
+                p_val2->p_list->i_count = p_var->choices.i_count;
             }
-            p_val->p_list->i_count = p_var->choices.i_count;
-            if( p_val2 ) p_val2->p_list->i_count = p_var->choices.i_count;
-            for( i = 0 ; i < p_var->choices.i_count ; i++ )
+            for( int i = 0 ; i < p_var->choices.i_count ; i++ )
             {
                 p_val->p_list->p_values[i] = p_var->choices.p_values[i];
-                p_val->p_list->pi_types[i] = p_var->i_type;
                 p_var->ops->pf_dup( &p_val->p_list->p_values[i] );
                 if( p_val2 )
                 {
                     p_val2->p_list->p_values[i].psz_string =
                         p_var->choices_text.p_values[i].psz_string ?
                     strdup(p_var->choices_text.p_values[i].psz_string) : NULL;
-                    p_val2->p_list->pi_types[i] = VLC_VAR_STRING;
                 }
             }
             break;
@@ -589,17 +568,13 @@ int var_Change( vlc_object_t *p_this, const char *psz_name,
             p_val->psz_string = p_var->psz_text ? strdup( p_var->psz_text )
                                                 : NULL;
             break;
-        case VLC_VAR_SETISCOMMAND:
-            p_var->i_type |= VLC_VAR_ISCOMMAND;
-            break;
-
         default:
             break;
     }
 
     vlc_mutex_unlock( &p_priv->var_lock );
 
-    return VLC_SUCCESS;
+    return ret;
 }
 
 #undef var_GetAndSet
@@ -697,8 +672,11 @@ int var_Type( vlc_object_t *p_this, const char *psz_name )
 
     p_var = Lookup( p_this, psz_name );
     if( p_var != NULL )
+    {
         i_type = p_var->i_type;
-
+        if( p_var->choices.i_count > 0 )
+            i_type |= VLC_VAR_HASCHOICE;
+    }
     vlc_mutex_unlock( &p_priv->var_lock );
 
     return i_type;
@@ -727,12 +705,7 @@ int var_SetChecked( vlc_object_t *p_this, const char *psz_name,
 
     assert( expected_type == 0 ||
             (p_var->i_type & VLC_VAR_CLASS) == expected_type );
-#ifndef NDEBUG
-        /* Alert if the type is VLC_VAR_VOID */
-        if( ( p_var->i_type & VLC_VAR_TYPE ) == VLC_VAR_VOID )
-            msg_Warn( p_this, "Calling var_Set on the void variable '%s' (0x%04x)", psz_name, p_var->i_type );
-#endif
-
+    assert ((p_var->i_type & VLC_VAR_CLASS) != VLC_VAR_VOID);
 
     WaitUnused( p_this, p_var );
 
@@ -789,16 +762,11 @@ int var_GetChecked( vlc_object_t *p_this, const char *psz_name,
     {
         assert( expected_type == 0 ||
                 (p_var->i_type & VLC_VAR_CLASS) == expected_type );
+        assert ((p_var->i_type & VLC_VAR_CLASS) != VLC_VAR_VOID);
 
         /* Really get the variable */
         *p_val = p_var->val;
 
-#ifndef NDEBUG
-        /* Alert if the type is VLC_VAR_VOID */
-        if( ( p_var->i_type & VLC_VAR_TYPE ) == VLC_VAR_VOID )
-            msg_Warn( p_this, "Calling var_Get on the void variable '%s' (0x%04x)", psz_name, p_var->i_type );
-#endif
-
         /* Duplicate value if needed */
         p_var->ops->pf_dup( p_val );
     }
@@ -823,69 +791,72 @@ int var_Get( vlc_object_t *p_this, const char *psz_name, vlc_value_t *p_val )
     return var_GetChecked( p_this, psz_name, 0, p_val );
 }
 
-#undef var_AddCallback
-/**
- * Register a callback in a variable
- *
- * We store a function pointer that will be called upon variable
- * modification.
- *
- * \param p_this The object that holds the variable
- * \param psz_name The name of the variable
- * \param pf_callback The function pointer
- * \param p_data A generic pointer that will be passed as the last
- *               argument to the callback function.
- *
- * \warning The callback function is run in the thread that calls var_Set on
- *          the variable. Use proper locking. This thread may not have much
- *          time to spare, so keep callback functions short.
- */
-int var_AddCallback( vlc_object_t *p_this, const char *psz_name,
-                     vlc_callback_t pf_callback, void *p_data )
+static int AddCallback( vlc_object_t *p_this, const char *psz_name,
+                        callback_entry_t entry, vlc_callback_type_t i_type )
 {
     variable_t *p_var;
-    callback_entry_t entry;
 
     assert( p_this );
 
     vlc_object_internals_t *p_priv = vlc_internals( p_this );
 
-    entry.pf_callback = pf_callback;
-    entry.p_data = p_data;
-
     vlc_mutex_lock( &p_priv->var_lock );
 
     p_var = Lookup( p_this, psz_name );
     if( p_var == NULL )
     {
-#ifndef NDEBUG
-        msg_Warn( p_this, "Failed to add a callback to the non-existing "
-                          "variable '%s'", psz_name );
-#endif
         vlc_mutex_unlock( &p_priv->var_lock );
+        msg_Err( p_this, "cannot add callback %p to nonexistent "
+                         "variable '%s'", entry.u.p_callback, psz_name );
         return VLC_ENOVAR;
     }
 
     WaitUnused( p_this, p_var );
-    INSERT_ELEM( p_var->p_entries,
-                 p_var->i_entries,
-                 p_var->i_entries,
-                 entry );
+
+    callback_table_t *p_table;
+    if (i_type == vlc_value_callback)
+        p_table = &p_var->value_callbacks;
+    else
+        p_table = &p_var->list_callbacks;
+    INSERT_ELEM( p_table->p_entries,
+                 p_table->i_entries,
+                 p_table->i_entries,
+                 entry);
 
     vlc_mutex_unlock( &p_priv->var_lock );
 
     return VLC_SUCCESS;
 }
 
-#undef var_DelCallback
+#undef var_AddCallback
 /**
- * Remove a callback from a variable
+ * Register a callback in a variable
  *
- * pf_callback and p_data have to be given again, because different objects
- * might have registered the same callback function.
+ * We store a function pointer that will be called upon variable
+ * modification.
+ *
+ * \param p_this The object that holds the variable
+ * \param psz_name The name of the variable
+ * \param pf_callback The function pointer
+ * \param p_data A generic pointer that will be passed as the last
+ *               argument to the callback function.
+ *
+ * \warning The callback function is run in the thread that calls var_Set on
+ *          the variable. Use proper locking. This thread may not have much
+ *          time to spare, so keep callback functions short.
  */
-int var_DelCallback( vlc_object_t *p_this, const char *psz_name,
+int var_AddCallback( vlc_object_t *p_this, const char *psz_name,
                      vlc_callback_t pf_callback, void *p_data )
+{
+    callback_entry_t entry;
+    entry.u.pf_value_callback = pf_callback;
+    entry.p_data = p_data;
+
+    return AddCallback(p_this, psz_name, entry, vlc_value_callback);
+}
+
+static int DelCallback( vlc_object_t *p_this, const char *psz_name,
+                        callback_entry_t entry, vlc_callback_type_t i_type )
 {
     int i_entry;
     variable_t *p_var;
@@ -908,15 +879,21 @@ int var_DelCallback( vlc_object_t *p_this, const char *psz_name,
 
     WaitUnused( p_this, p_var );
 
-    for( i_entry = p_var->i_entries ; i_entry-- ; )
+    callback_table_t *p_table;
+    if (i_type == vlc_value_callback)
+        p_table = &p_var->value_callbacks;
+    else
+        p_table = &p_var->list_callbacks;
+
+    for( i_entry = p_table->i_entries ; i_entry-- ; )
     {
-        if( p_var->p_entries[i_entry].pf_callback == pf_callback
-            && p_var->p_entries[i_entry].p_data == p_data )
+        if( p_table->p_entries[i_entry].u.p_callback == entry.u.p_callback
+            && p_table->p_entries[i_entry].p_data == entry.p_data )
         {
             break;
         }
 #ifndef NDEBUG
-        else if( p_var->p_entries[i_entry].pf_callback == pf_callback )
+        else if( p_table->p_entries[i_entry].u.p_callback == entry.u.p_callback )
             b_found_similar = true;
 #endif
     }
@@ -927,19 +904,36 @@ int var_DelCallback( vlc_object_t *p_this, const char *psz_name,
         if( b_found_similar )
             fprintf( stderr, "Calling var_DelCallback for '%s' with the same "
                              "function but not the same data.", psz_name );
-        assert( 0 );
+        vlc_assert_unreachable();
 #endif
         vlc_mutex_unlock( &p_priv->var_lock );
         return VLC_EGENERIC;
     }
 
-    REMOVE_ELEM( p_var->p_entries, p_var->i_entries, i_entry );
+    REMOVE_ELEM( p_table->p_entries, p_table->i_entries, i_entry );
 
     vlc_mutex_unlock( &p_priv->var_lock );
 
     return VLC_SUCCESS;
 }
 
+#undef var_DelCallback
+/**
+ * Remove a callback from a variable
+ *
+ * pf_callback and p_data have to be given again, because different objects
+ * might have registered the same callback function.
+ */
+int var_DelCallback( vlc_object_t *p_this, const char *psz_name,
+                     vlc_callback_t pf_callback, void *p_data )
+{
+    callback_entry_t entry;
+    entry.u.pf_value_callback = pf_callback;
+    entry.p_data = p_data;
+
+    return DelCallback(p_this, psz_name, entry, vlc_value_callback);
+}
+
 #undef var_TriggerCallback
 /**
  * Trigger callback on a variable
@@ -975,6 +969,41 @@ int var_TriggerCallback( vlc_object_t *p_this, const char *psz_name )
     return i_ret;
 }
 
+#undef var_AddListCallback
+/**
+ * Register a callback for a list variable
+ *
+ * The callback is triggered when an element is added/removed from the
+ * list or when the list is cleared.
+ *
+ * See var_AddCallback().
+ */
+int var_AddListCallback( vlc_object_t *p_this, const char *psz_name,
+                         vlc_list_callback_t pf_callback, void *p_data )
+{
+    callback_entry_t entry;
+    entry.u.pf_list_callback = pf_callback;
+    entry.p_data = p_data;
+
+    return AddCallback(p_this, psz_name, entry, vlc_list_callback);
+}
+
+#undef var_DelListCallback
+/**
+ * Remove a callback from a list variable
+ *
+ * See var_DelCallback().
+ */
+int var_DelListCallback( vlc_object_t *p_this, const char *psz_name,
+                         vlc_list_callback_t pf_callback, void *p_data )
+{
+    callback_entry_t entry;
+    entry.u.pf_list_callback = pf_callback;
+    entry.p_data = p_data;
+
+    return DelCallback(p_this, psz_name, entry, vlc_list_callback);
+}
+
 /** Parse a stringified option
  * This function parse a string option and create the associated object
  * variable
@@ -1077,8 +1106,10 @@ cleanup:
 
 #undef var_LocationParse
 /**
- * Parses a set of colon-separated <variable name>=<value> pairs. Some access
- * (or access_demux) plugins uses this scheme in media resource location.
+ * Parses a set of colon-separated or semicolon-separated
+ * <variable name>=<value> pairs.
+ * Some access (or access_demux) plugins uses this scheme
+ * in media resource location.
  * @note Only trusted/safe variables are allowed. This is intended.
  *
  * @warning Only use this for plugins implementing VLC-specific resource
@@ -1098,9 +1129,9 @@ int var_LocationParse (vlc_object_t *obj, const char *mrl, const char *pref)
     assert(mrl != NULL);
     while (*mrl != '\0')
     {
-        mrl += strspn (mrl, ":"); /* skip leading colon(s) */
+        mrl += strspn (mrl, ":;"); /* skip leading colon(s) */
 
-        size_t len = strcspn (mrl, ":");
+        size_t len = strcspn (mrl, ":;");
         char *buf = malloc (preflen + len);
 
         if (likely(buf != NULL))
@@ -1190,12 +1221,9 @@ static void CheckValue ( variable_t *p_var, vlc_value_t *p_val )
         case VLC_VAR_FLOAT:
             if( p_var->i_type & VLC_VAR_HASSTEP && p_var->step.f_float )
             {
-                float f_round = p_var->step.f_float * (float)(int)( 0.5 +
-                                        p_val->f_float / p_var->step.f_float );
-                if( p_val->f_float != f_round )
-                {
-                    p_val->f_float = f_round;
-                }
+                float f_round = p_var->step.f_float
+                              * roundf( p_val->f_float / p_var->step.f_float );
+                p_val->f_float = f_round;
             }
             if( p_var->i_type & VLC_VAR_HASMIN
                  && p_val->f_float < p_var->min.f_float )
@@ -1223,28 +1251,11 @@ static void CheckValue ( variable_t *p_var, vlc_value_t *p_val )
 int var_Inherit( vlc_object_t *p_this, const char *psz_name, int i_type,
                  vlc_value_t *p_val )
 {
-#ifndef NDEBUG
-    if (p_this != VLC_OBJECT(p_this->p_libvlc)
-     && unlikely(p_this->p_parent == NULL))
-    {
-        msg_Info (p_this, "%s(%s) on detached object", __func__, psz_name);
-        //vlc_backtrace ();
-    }
-#endif
     i_type &= VLC_VAR_CLASS;
     for( vlc_object_t *obj = p_this; obj != NULL; obj = obj->p_parent )
     {
         if( var_GetChecked( obj, psz_name, i_type, p_val ) == VLC_SUCCESS )
             return VLC_SUCCESS;
-#ifndef NDEBUG
-        if (obj != p_this && obj != VLC_OBJECT(p_this->p_libvlc)
-         && unlikely(obj->p_parent == NULL))
-        {
-            msg_Info (p_this, "%s(%s) on detached tree [%p] %s", __func__,
-                      psz_name, obj, obj->psz_object_type);
-            //vlc_backtrace ();
-        }
-#endif
     }
 
     /* else take value from config */
@@ -1263,14 +1274,11 @@ int var_Inherit( vlc_object_t *p_this, const char *psz_name, int i_type,
         case VLC_VAR_BOOL:
             p_val->b_bool = config_GetInt( p_this, psz_name );
             break;
-        case VLC_VAR_ADDRESS:
-            return VLC_ENOOBJ;
         default:
-            msg_Warn( p_this, "Could not inherit value for var %s "
-                              "from config. Invalid Type", psz_name );
+            vlc_assert_unreachable();
+        case VLC_VAR_ADDRESS:
             return VLC_ENOOBJ;
     }
-    /*msg_Dbg( p_this, "Inherited value for var %s from config", psz_name );*/
     return VLC_SUCCESS;
 }
 
@@ -1300,19 +1308,32 @@ int (var_InheritURational)(vlc_object_t *object,
     unsigned d = strtol(*next ? &next[1] : "0", NULL, 0);
 
     if (*next == '.') {
-        /* Interpret as a float number */
-        double r = us_atof(tmp);
-        double c = ceil(r);
-        if (c >= UINT_MAX)
-            goto error;
-        unsigned m = c;
-        if (m > 0) {
-            d = UINT_MAX / m;
-            n = r * d;
-        } else {
-            n = 0;
-            d = 0;
-        }
+        /* Interpret as a (finite positive) float number */
+        const int ubits = CHAR_BIT * sizeof (unsigned);
+        int exp;
+        double f = frexp(us_atof(tmp), &exp);
+
+        if (!isgreaterequal(f, 0.))
+            goto error; /* negative or not a number */
+
+        if (exp <= 1 - ubits) {
+            n = 0; /* too small */
+            d = 1;
+        } else if (exp <= 0) {
+            n = floor(scalbn(f, ubits - 1 + exp));
+#if (FLT_RADIX != 2)
+# error Floating point configuration not supported.
+#endif
+            d = 1u << (ubits - 1);
+        } else if (exp <= ubits) {
+            n = floor(scalbn(f, ubits));
+            d = 1u << (ubits - exp);
+        } else
+            goto error; /* too big */
+    } else if ( *next == '\0' ) {
+        /* plain integer given */
+        *num = n;
+        *den = 1;
     }
 
     if (n > 0 && d > 0)
@@ -1336,11 +1357,12 @@ static int TriggerCallback( vlc_object_t *p_this, variable_t *p_var,
 {
     assert( p_this );
 
-    int i_entries = p_var->i_entries;
+    callback_table_t *p_table = &p_var->value_callbacks;
+    int i_entries = p_table->i_entries;
     if( i_entries == 0 )
         return VLC_SUCCESS;
 
-    callback_entry_t *p_entries = p_var->p_entries;
+    callback_entry_t *p_entries = p_table->p_entries;
     vlc_object_internals_t *p_priv = vlc_internals( p_this );
 
     assert( !p_var->b_incallback );
@@ -1350,8 +1372,8 @@ static int TriggerCallback( vlc_object_t *p_this, variable_t *p_var,
     /* The real calls */
     for( ; i_entries-- ; )
     {
-        p_entries[i_entries].pf_callback( p_this, psz_name, oldval, p_var->val,
-                                          p_entries[i_entries].p_data );
+        p_entries[i_entries].u.pf_value_callback( p_this, psz_name, oldval, p_var->val,
+                                                  p_entries[i_entries].p_data );
     }
 
     vlc_mutex_lock( &p_priv->var_lock );
@@ -1361,65 +1383,36 @@ static int TriggerCallback( vlc_object_t *p_this, variable_t *p_var,
     return VLC_SUCCESS;
 }
 
-#undef var_Command
-/**********************************************************************
- * Execute a var command on an object identified by its name
- **********************************************************************/
-int var_Command( vlc_object_t *p_this, const char *psz_name,
-                 const char *psz_cmd, const char *psz_arg, char **psz_msg )
+static int TriggerListCallback( vlc_object_t *p_this, variable_t *p_var,
+                                const char *psz_name, int i_action,
+                                vlc_value_t *val )
 {
-    vlc_object_t *p_obj = vlc_object_find_name( p_this->p_libvlc,
-                                                psz_name );
-    int i_type, i_ret;
-
-    if( !p_obj )
-    {
-        if( psz_msg )
-            *psz_msg = strdup( "Unknown destination object." );
-        return VLC_ENOOBJ;
-    }
+    assert( p_this );
 
-    i_type = var_Type( p_obj, psz_cmd );
-    if( !( i_type&VLC_VAR_ISCOMMAND ) )
-    {
-        vlc_object_release( p_obj );
-        if( psz_msg )
-            *psz_msg = strdup( "Variable doesn't exist or isn't a command." );
-        return VLC_EGENERIC;
-    }
+    callback_table_t *p_table = &p_var->list_callbacks;
+    int i_entries = p_table->i_entries;
+    if( i_entries == 0 )
+        return VLC_SUCCESS;
 
-    i_type &= VLC_VAR_CLASS;
-    switch( i_type )
-    {
-        case VLC_VAR_INTEGER:
-            i_ret = var_SetInteger( p_obj, psz_cmd, atoi( psz_arg ) );
-            break;
-        case VLC_VAR_FLOAT:
-            i_ret = var_SetFloat( p_obj, psz_cmd, us_atof( psz_arg ) );
-            break;
-        case VLC_VAR_STRING:
-            i_ret = var_SetString( p_obj, psz_cmd, psz_arg );
-            break;
-        case VLC_VAR_BOOL:
-            i_ret = var_SetBool( p_obj, psz_cmd, atoi( psz_arg ) );
-            break;
-        default:
-            i_ret = VLC_EGENERIC;
-            break;
-    }
+    callback_entry_t *p_entries = p_table->p_entries;
+    vlc_object_internals_t *p_priv = vlc_internals( p_this );
 
-    vlc_object_release( p_obj );
+    assert( !p_var->b_incallback );
+    p_var->b_incallback = true;
+    vlc_mutex_unlock( &p_priv->var_lock );
 
-    if( psz_msg )
+    for( ; i_entries-- ; )
     {
-        if( asprintf( psz_msg, "%s on object %s returned %i (%s)",
-                  psz_cmd, psz_name, i_ret, vlc_error( i_ret ) ) == -1)
-            *psz_msg = NULL;
+        p_entries[i_entries].u.pf_list_callback( p_this, psz_name, i_action, val,
+                                                 p_entries[i_entries].p_data );
     }
 
-    return i_ret;
-}
+    vlc_mutex_lock( &p_priv->var_lock );
+    p_var->b_incallback = false;
+    vlc_cond_broadcast( &p_priv->var_wait );
 
+    return VLC_SUCCESS;
+}
 
 /**
  * Free a list and the associated strings
@@ -1428,16 +1421,25 @@ int var_Command( vlc_object_t *p_this, const char *psz_name,
  */
 void var_FreeList( vlc_value_t *p_val, vlc_value_t *p_val2 )
 {
-    FreeList( p_val );
-    if( p_val2 && p_val2->p_list )
+    switch( p_val->p_list->i_type & VLC_VAR_CLASS )
     {
+        case VLC_VAR_STRING:
+            for( int i = 0; i < p_val->p_list->i_count; i++ )
+                free( p_val->p_list->p_values[i].psz_string );
+            break;
+    }
+
+    free( p_val->p_list->p_values );
+    free( p_val->p_list );
+
+    if( p_val2 != NULL )
+    {
+        assert( p_val2->p_list != NULL );
+        assert( p_val2->p_list->i_type == VLC_VAR_STRING );
+
         for( int i = 0; i < p_val2->p_list->i_count; i++ )
             free( p_val2->p_list->p_values[i].psz_string );
-        if( p_val2->p_list->i_count )
-        {
-            free( p_val2->p_list->p_values );
-            free( p_val2->p_list->pi_types );
-        }
+        free( p_val2->p_list->p_values );
         free( p_val2->p_list );
     }
 }