variables.c 36.2 KB
Newer Older
1 2 3
/*****************************************************************************
 * variables.c: routines for object variables handling
 *****************************************************************************
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
4
 * Copyright (C) 2002-2009 VLC authors and VideoLAN
5
 * $Id$
6 7 8
 *
 * Authors: Samuel Hocevar <sam@zoy.org>
 *
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
9 10 11
 * 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
12
 * (at your option) any later version.
13
 *
14 15
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
16 17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
18
 *
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
19 20 21
 * 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.
22 23 24 25 26
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
27 28 29 30
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

31 32 33
#ifdef HAVE_SEARCH_H
# include <search.h>
#endif
34
#include <assert.h>
35
#include <float.h>
36 37
#include <math.h>
#include <limits.h>
38

39
#include <vlc_common.h>
40
#include <vlc_arrays.h>
41 42 43 44 45
#include <vlc_charset.h>
#include "libvlc.h"
#include "variables.h"
#include "config/configuration.h"

46
typedef struct callback_entry_t
47
{
48 49 50 51 52
    union
    {
        vlc_callback_t       pf_value_callback;
        vlc_list_callback_t  pf_list_callback;
        void *               p_callback;
53
    };
54
    void *         p_data;
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
} callback_entry_t;

typedef struct variable_ops_t
{
    int  (*pf_cmp) ( vlc_value_t, vlc_value_t );
    void (*pf_dup) ( vlc_value_t * );
    void (*pf_free) ( vlc_value_t * );
} variable_ops_t;

typedef struct callback_table_t
{
    int                i_entries;
    callback_entry_t * p_entries;
} callback_table_t;

/**
 * The structure describing a variable.
 * \note vlc_value_t is the common union for variable values
 */
struct variable_t
{
    char *       psz_name; /**< The variable unique name (must be first) */

    /** The variable's exported value */
    vlc_value_t  val;

    /** The variable display name, mainly for use by the interfaces */
    char *       psz_text;

    const variable_ops_t *ops;

    int          i_type;   /**< The type of the variable */
    unsigned     i_usage;  /**< Reference count */

    /** If the variable has min/max/step values */
    vlc_value_t  min, max, step;

    /** List of choices */
    vlc_list_t   choices;
    /** List of friendly names for the choices */
    vlc_list_t   choices_text;

    /** Set to TRUE if the variable is in a callback */
    bool   b_incallback;

    /** Registered value callbacks */
    callback_table_t    value_callbacks;
    /** Registered list callbacks */
    callback_table_t    list_callbacks;
104 105
};

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
106 107 108 109 110 111 112 113 114 115
static int CmpBool( vlc_value_t v, vlc_value_t w )
{
    return v.b_bool ? w.b_bool ? 0 : 1 : w.b_bool ? -1 : 0;
}

static int CmpInt( vlc_value_t v, vlc_value_t w )
{
    return v.i_int == w.i_int ? 0 : v.i_int > w.i_int ? 1 : -1;
}

116 117 118 119 120 121 122
static int CmpString( vlc_value_t v, vlc_value_t w )
{
    if( !v.psz_string )
        return !w.psz_string ? 0 : -1;
    else
        return !w.psz_string ? 1 : strcmp( v.psz_string, w.psz_string );
}
123 124 125
static int CmpFloat( vlc_value_t v, vlc_value_t w ) { return v.f_float == w.f_float ? 0 : v.f_float > w.f_float ? 1 : -1; }
static int CmpAddress( vlc_value_t v, vlc_value_t w ) { return v.p_address == w.p_address ? 0 : v.p_address > w.p_address ? 1 : -1; }

126
static void DupDummy( vlc_value_t *p_val ) { (void)p_val; /* unused */ }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
127 128 129 130
static void DupString( vlc_value_t *p_val )
{
    p_val->psz_string = strdup( p_val->psz_string ? p_val->psz_string :  "" );
}
131

132
static void FreeDummy( vlc_value_t *p_val ) { (void)p_val; /* unused */ }
133 134
static void FreeString( vlc_value_t *p_val ) { free( p_val->psz_string ); }

135
static const struct variable_ops_t
136
void_ops   = { NULL,       DupDummy,  FreeDummy,  },
137 138 139 140 141
addr_ops   = { CmpAddress, DupDummy,  FreeDummy,  },
bool_ops   = { CmpBool,    DupDummy,  FreeDummy,  },
float_ops  = { CmpFloat,   DupDummy,  FreeDummy,  },
int_ops    = { CmpInt,     DupDummy,  FreeDummy,  },
string_ops = { CmpString,  DupString, FreeString, },
142
coords_ops = { NULL,       DupDummy,  FreeDummy,  };
143

144 145 146 147 148 149 150 151 152 153 154 155 156 157
static int varcmp( const void *a, const void *b )
{
    const variable_t *va = a, *vb = b;

    /* psz_name must be first */
    assert( va == (const void *)&va->psz_name );
    return strcmp( va->psz_name, vb->psz_name );
}

static variable_t *Lookup( vlc_object_t *obj, const char *psz_name )
{
    vlc_object_internals_t *priv = vlc_internals( obj );
    variable_t **pp_var;

158
    vlc_mutex_lock(&priv->var_lock);
159 160 161 162
    pp_var = tfind( &psz_name, &priv->var_root, varcmp );
    return (pp_var != NULL) ? *pp_var : NULL;
}

163 164 165 166 167 168 169 170 171 172 173 174 175
static void Destroy( variable_t *p_var )
{
    p_var->ops->pf_free( &p_var->val );
    if( p_var->choices.i_count )
    {
        for( int i = 0 ; i < p_var->choices.i_count ; i++ )
        {
            p_var->ops->pf_free( &p_var->choices.p_values[i] );
            free( p_var->choices_text.p_values[i].psz_string );
        }
        free( p_var->choices.p_values );
        free( p_var->choices_text.p_values );
    }
176

177 178
    free( p_var->psz_name );
    free( p_var->psz_text );
179
    free( p_var->value_callbacks.p_entries );
180 181 182
    free( p_var );
}

183 184 185 186 187 188 189 190 191 192 193 194
/**
 * Adjusts a value to fit the constraints for a certain variable:
 * - If the value is lower than the minimum, use the minimum.
 * - If the value is higher than the maximum, use the maximum.
 * - If the variable has steps, round the value to the nearest step.
 */
static void CheckValue(variable_t *var, vlc_value_t *val)
{
    /* Check that our variable is within the bounds */
    switch (var->i_type & VLC_VAR_TYPE)
    {
        case VLC_VAR_INTEGER:
195
            if (val->i_int < var->min.i_int)
196
               val->i_int = var->min.i_int;
197
            if (val->i_int > var->max.i_int)
198
                val->i_int = var->max.i_int;
199
            if (var->step.i_int != 0 && (val->i_int % var->step.i_int))
200 201 202 203 204 205 206 207
            {
                if (val->i_int > 0)
                    val->i_int = (val->i_int + (var->step.i_int / 2))
                                 / var->step.i_int * var->step.i_int;
                else
                    val->i_int = (val->i_int - (var->step.i_int / 2))
                                 / var->step.i_int * var->step.i_int;
            }
208 209 210
            break;

        case VLC_VAR_FLOAT:
211
            if (isless(val->f_float, var->min.f_float))
212
                val->f_float = var->min.f_float;
213
            if (isgreater(val->f_float, var->max.f_float))
214
                val->f_float = var->max.f_float;
215
            if (var->step.f_float != 0.f)
216 217
                val->f_float = var->step.f_float
                              * roundf(val->f_float / var->step.f_float);
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
            break;
    }
}

/**
 * Waits until the variable is inactive (i.e. not executing a callback)
 */
static void WaitUnused(vlc_object_t *obj, variable_t *var)
{
    vlc_object_internals_t *priv = vlc_internals(obj);

    mutex_cleanup_push(&priv->var_lock);
    while (var->b_incallback)
        vlc_cond_wait(&priv->var_wait, &priv->var_lock);
    vlc_cleanup_pop();
}

static void TriggerCallback(vlc_object_t *obj, variable_t *var,
                            const char *name, vlc_value_t prev)
{
    assert(obj != NULL);

    size_t count = var->value_callbacks.i_entries;
    if (count == 0)
        return;

    callback_entry_t *entries = var->value_callbacks.p_entries;
    vlc_object_internals_t *priv = vlc_internals(obj);

    assert(!var->b_incallback);
    var->b_incallback = true;
    vlc_mutex_unlock(&priv->var_lock);

    for (size_t i = 0; i < count; i++)
252 253
        entries[i].pf_value_callback(obj, name, prev, var->val,
                                     entries[i].p_data);
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276

    vlc_mutex_lock(&priv->var_lock);
    var->b_incallback = false;
    vlc_cond_broadcast(&priv->var_wait);
}

static void TriggerListCallback(vlc_object_t *obj, variable_t *var,
                                const char *name, int action, vlc_value_t *val)
{
    assert(obj != NULL);

    size_t count = var->list_callbacks.i_entries;
    if (count == 0)
        return;

    callback_entry_t *entries = var->list_callbacks.p_entries;
    vlc_object_internals_t *priv = vlc_internals(obj);

    assert(!var->b_incallback);
    var->b_incallback = true;
    vlc_mutex_unlock(&priv->var_lock);

    for (size_t i = 0; i < count; i++)
277
        entries[i].pf_list_callback(obj, name, action, val,
278 279 280 281 282 283 284
                                      entries[i].p_data);

    vlc_mutex_lock(&priv->var_lock);
    var->b_incallback = false;
    vlc_cond_broadcast(&priv->var_wait);
}

285
int (var_Create)( vlc_object_t *p_this, const char *psz_name, int i_type )
286
{
287 288
    assert( p_this );

289 290 291
    variable_t *p_var = calloc( 1, sizeof( *p_var ) );
    if( p_var == NULL )
        return VLC_ENOMEM;
292 293

    p_var->psz_name = strdup( psz_name );
294
    p_var->psz_text = NULL;
295

296
    p_var->i_type = i_type & ~VLC_VAR_DOINHERIT;
297

298
    p_var->i_usage = 1;
299

300 301
    p_var->choices.i_count = 0;
    p_var->choices.p_values = NULL;
302 303
    p_var->choices_text.i_count = 0;
    p_var->choices_text.p_values = NULL;
304

305
    p_var->b_incallback = false;
306
    p_var->value_callbacks = (callback_table_t){ 0, NULL };
307

308 309 310
    /* 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
     * not cause crashes in the variable handling. */
311
    switch( i_type & VLC_VAR_CLASS )
312
    {
313
        case VLC_VAR_BOOL:
314
            p_var->ops = &bool_ops;
315
            p_var->val.b_bool = false;
316 317
            break;
        case VLC_VAR_INTEGER:
318
            p_var->ops = &int_ops;
319
            p_var->val.i_int = 0;
320 321
            p_var->min.i_int = INT64_MIN;
            p_var->max.i_int = INT64_MAX;
322 323
            break;
        case VLC_VAR_STRING:
324
            p_var->ops = &string_ops;
325
            p_var->val.psz_string = NULL;
326 327
            break;
        case VLC_VAR_FLOAT:
328
            p_var->ops = &float_ops;
329
            p_var->val.f_float = 0.f;
330
            p_var->min.f_float = -FLT_MAX;
331
            p_var->max.f_float = FLT_MAX;
332
            break;
333 334 335 336
        case VLC_VAR_COORDS:
            p_var->ops = &coords_ops;
            p_var->val.coords.x = p_var->val.coords.y = 0;
            break;
337
        case VLC_VAR_ADDRESS:
338
            p_var->ops = &addr_ops;
339 340
            p_var->val.p_address = NULL;
            break;
341
        case VLC_VAR_VOID:
342
            p_var->ops = &void_ops;
343 344
            break;
        default:
345
            vlc_assert_unreachable ();
346
    }
347

348 349
    if (i_type & VLC_VAR_DOINHERIT)
        var_Inherit(p_this, psz_name, i_type, &p_var->val);
350 351

    vlc_object_internals_t *p_priv = vlc_internals( p_this );
352 353
    variable_t **pp_var, *p_oldvar;
    int ret = VLC_SUCCESS;
354 355 356

    vlc_mutex_lock( &p_priv->var_lock );

357 358 359
    pp_var = tsearch( p_var, &p_priv->var_root, varcmp );
    if( unlikely(pp_var == NULL) )
        ret = VLC_ENOMEM;
360 361 362
    else if( (p_oldvar = *pp_var) == p_var ) /* Variable create */
        p_var = NULL; /* Variable created */
    else /* Variable already exists */
363
    {
364
        assert (((i_type ^ p_oldvar->i_type) & VLC_VAR_CLASS) == 0);
365
        p_oldvar->i_usage++;
366
        p_oldvar->i_type |= i_type & VLC_VAR_ISCOMMAND;
367
    }
368
    vlc_mutex_unlock( &p_priv->var_lock );
369

370 371 372 373
    /* If we did not need to create a new variable, free everything... */
    if( p_var != NULL )
        Destroy( p_var );
    return ret;
374 375
}

376
void (var_Destroy)(vlc_object_t *p_this, const char *psz_name)
377
{
378
    variable_t *p_var;
379 380 381

    assert( p_this );

382
    vlc_object_internals_t *p_priv = vlc_internals( p_this );
383

384 385
    p_var = Lookup( p_this, psz_name );
    if( p_var == NULL )
386 387 388
        msg_Dbg( p_this, "attempt to destroy nonexistent variable \"%s\"",
                 psz_name );
    else if( --p_var->i_usage == 0 )
389
    {
390
        assert(!p_var->b_incallback);
391
        tdelete( p_var, &p_priv->var_root, varcmp );
392
    }
393
    else
394 395
    {
        assert(p_var->i_usage != -1u);
396
        p_var = NULL;
397
    }
398
    vlc_mutex_unlock( &p_priv->var_lock );
399

400 401
    if( p_var != NULL )
        Destroy( p_var );
402 403
}

404 405 406 407 408 409 410 411 412 413
static void CleanupVar( void *var )
{
    Destroy( var );
}

void var_DestroyAll( vlc_object_t *obj )
{
    vlc_object_internals_t *priv = vlc_internals( obj );

    tdestroy( priv->var_root, CleanupVar );
414
    priv->var_root = NULL;
415 416
}

417 418
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)
419
{
420
    int ret = VLC_SUCCESS;
421
    variable_t *p_var;
422
    vlc_value_t oldval;
Erwan Tulou's avatar
Erwan Tulou committed
423
    vlc_value_t newval;
424 425 426

    assert( p_this );

427
    vlc_object_internals_t *p_priv = vlc_internals( p_this );
428

429 430
    p_var = Lookup( p_this, psz_name );
    if( p_var == NULL )
431
    {
432
        vlc_mutex_unlock( &p_priv->var_lock );
433 434 435 436 437
        return VLC_ENOVAR;
    }

    switch( i_action )
    {
438
        case VLC_VAR_GETMIN:
439
            *p_val = p_var->min;
440 441
            break;
        case VLC_VAR_GETMAX:
442
            *p_val = p_var->max;
443
            break;
444
        case VLC_VAR_SETMINMAX:
445
            assert(p_var->ops->pf_free == FreeDummy);
446 447 448
            p_var->min = *p_val;
            p_var->max = *p_val2;
            break;
449
        case VLC_VAR_SETSTEP:
450
            assert(p_var->ops->pf_free == FreeDummy);
451
            p_var->step = *p_val;
452 453
            CheckValue( p_var, &p_var->val );
            break;
454
        case VLC_VAR_GETSTEP:
455 456 457
            switch (p_var->i_type & VLC_VAR_TYPE)
            {
                case VLC_VAR_INTEGER:
458
                    if (p_var->step.i_int == 0)
459 460 461
                        ret = VLC_EGENERIC;
                    break;
                case VLC_VAR_FLOAT:
462
                    if (p_var->step.f_float == 0.f)
463 464 465 466 467
                        ret = VLC_EGENERIC;
                    break;
                default:
                    ret = VLC_EGENERIC;
            }
468 469
            if (ret == VLC_SUCCESS)
                *p_val = p_var->step;
470
            break;
471
        case VLC_VAR_ADDCHOICE:
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
472 473
        {
            int i = p_var->choices.i_count;
474

475 476 477 478 479
            TAB_APPEND(p_var->choices.i_count,
                       p_var->choices.p_values, *p_val);
            assert(i == p_var->choices_text.i_count);
            TAB_APPEND(p_var->choices_text.i_count,
                       p_var->choices_text.p_values, *p_val);
480
            p_var->ops->pf_dup( &p_var->choices.p_values[i] );
481 482 483
            p_var->choices_text.p_values[i].psz_string =
                ( p_val2 && p_val2->psz_string ) ?
                strdup( p_val2->psz_string ) : NULL;
484

485
            TriggerListCallback(p_this, p_var, psz_name, VLC_VAR_ADDCHOICE, p_val);
486
            break;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
487
        }
488
        case VLC_VAR_DELCHOICE:
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
489 490 491
        {
            int i;

492
            for( i = 0 ; i < p_var->choices.i_count ; i++ )
493
                if( p_var->ops->pf_cmp( p_var->choices.p_values[i], *p_val ) == 0 )
494 495
                    break;

496
            if( i == p_var->choices.i_count )
497 498
            {
                /* Not found */
499
                vlc_mutex_unlock( &p_priv->var_lock );
500 501 502
                return VLC_EGENERIC;
            }

503
            p_var->ops->pf_free( &p_var->choices.p_values[i] );
504
            free( p_var->choices_text.p_values[i].psz_string );
505 506 507
            TAB_ERASE(p_var->choices.i_count, p_var->choices.p_values, i);
            TAB_ERASE(p_var->choices_text.i_count,
                      p_var->choices_text.p_values, i);
508

509
            TriggerListCallback(p_this, p_var, psz_name, VLC_VAR_DELCHOICE, p_val);
510
            break;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
511
        }
512 513 514
        case VLC_VAR_CHOICESCOUNT:
            p_val->i_int = p_var->choices.i_count;
            break;
515
        case VLC_VAR_CLEARCHOICES:
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
516
            for( int i = 0 ; i < p_var->choices.i_count ; i++ )
517
                p_var->ops->pf_free( &p_var->choices.p_values[i] );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
518
            for( int i = 0 ; i < p_var->choices_text.i_count ; i++ )
519 520
                free( p_var->choices_text.p_values[i].psz_string );

521 522
            if( p_var->choices.i_count ) free( p_var->choices.p_values );
            if( p_var->choices_text.i_count ) free( p_var->choices_text.p_values );
523 524 525

            p_var->choices.i_count = 0;
            p_var->choices.p_values = NULL;
526 527
            p_var->choices_text.i_count = 0;
            p_var->choices_text.p_values = NULL;
528
            TriggerListCallback(p_this, p_var, psz_name, VLC_VAR_CLEARCHOICES, NULL);
529 530 531
            break;
        case VLC_VAR_SETVALUE:
            /* Duplicate data if needed */
Erwan Tulou's avatar
Erwan Tulou committed
532 533
            newval = *p_val;
            p_var->ops->pf_dup( &newval );
534 535 536
            /* Backup needed stuff */
            oldval = p_var->val;
            /* Check boundaries and list */
Erwan Tulou's avatar
Erwan Tulou committed
537
            CheckValue( p_var, &newval );
538
            /* Set the variable */
Erwan Tulou's avatar
Erwan Tulou committed
539
            p_var->val = newval;
540
            /* Free data if needed */
541
            p_var->ops->pf_free( &oldval );
542
            break;
543
        case VLC_VAR_GETCHOICES:
544 545 546
            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) );
547
            p_val->p_list->i_type = p_var->i_type;
548
            p_val->p_list->i_count = p_var->choices.i_count;
549 550
            if( p_val2 )
            {
551 552 553
                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) );
554 555 556
                p_val2->p_list->i_type = VLC_VAR_STRING;
                p_val2->p_list->i_count = p_var->choices.i_count;
            }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
557
            for( int i = 0 ; i < p_var->choices.i_count ; i++ )
558
            {
559
                p_val->p_list->p_values[i] = p_var->choices.p_values[i];
560
                p_var->ops->pf_dup( &p_val->p_list->p_values[i] );
561 562 563 564 565 566
                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;
                }
567 568
            }
            break;
569
        case VLC_VAR_SETTEXT:
570
            free( p_var->psz_text );
571 572
            if( p_val && p_val->psz_string )
                p_var->psz_text = strdup( p_val->psz_string );
573
            else
Rémi Duraffort's avatar
Rémi Duraffort committed
574
                p_var->psz_text = NULL;
575 576
            break;
        case VLC_VAR_GETTEXT:
577 578
            p_val->psz_string = p_var->psz_text ? strdup( p_var->psz_text )
                                                : NULL;
579 580 581 582 583
            break;
        default:
            break;
    }

584
    vlc_mutex_unlock( &p_priv->var_lock );
585

586
    return ret;
587 588
}

589 590
int (var_GetAndSet)(vlc_object_t *p_this, const char *psz_name, int i_action,
                    vlc_value_t *p_val)
591 592 593
{
    variable_t *p_var;
    vlc_value_t oldval;
594 595

    assert( p_this );
596
    assert( p_val );
597

598 599
    vlc_object_internals_t *p_priv = vlc_internals( p_this );

600 601
    p_var = Lookup( p_this, psz_name );
    if( p_var == NULL )
602 603
    {
        vlc_mutex_unlock( &p_priv->var_lock );
604
        return VLC_ENOVAR;
605 606
    }

607
    WaitUnused( p_this, p_var );
608 609 610 611 612 613 614 615 616 617

    /* Duplicated data if needed */
    //p_var->ops->pf_dup( &val );

    /* Backup needed stuff */
    oldval = p_var->val;

    /* depending of the action requiered */
    switch( i_action )
    {
618
    case VLC_VAR_BOOL_TOGGLE:
619 620 621
        assert( ( p_var->i_type & VLC_VAR_BOOL ) == VLC_VAR_BOOL );
        p_var->val.b_bool = !p_var->val.b_bool;
        break;
622
    case VLC_VAR_INTEGER_ADD:
623
        assert( ( p_var->i_type & VLC_VAR_INTEGER ) == VLC_VAR_INTEGER );
624 625 626 627 628 629 630 631 632
        p_var->val.i_int += p_val->i_int;
        break;
    case VLC_VAR_INTEGER_OR:
        assert( ( p_var->i_type & VLC_VAR_INTEGER ) == VLC_VAR_INTEGER );
        p_var->val.i_int |= p_val->i_int;
        break;
    case VLC_VAR_INTEGER_NAND:
        assert( ( p_var->i_type & VLC_VAR_INTEGER ) == VLC_VAR_INTEGER );
        p_var->val.i_int &= ~p_val->i_int;
633
        break;
634 635 636 637 638 639 640
    default:
        vlc_mutex_unlock( &p_priv->var_lock );
        return VLC_EGENERIC;
    }

    /*  Check boundaries */
    CheckValue( p_var, &p_var->val );
641
    *p_val = p_var->val;
642

643
    /* Deal with callbacks.*/
644
    TriggerCallback( p_this, p_var, psz_name, oldval );
645 646

    vlc_mutex_unlock( &p_priv->var_lock );
647
    return VLC_SUCCESS;
648 649
}

650
int (var_Type)(vlc_object_t *p_this, const char *psz_name)
651
{
652 653
    variable_t *p_var;
    int i_type = 0;
654 655 656

    assert( p_this );

657
    vlc_object_internals_t *p_priv = vlc_internals( p_this );
658

659 660
    p_var = Lookup( p_this, psz_name );
    if( p_var != NULL )
661
    {
662
        i_type = p_var->i_type;
663 664 665
        if( p_var->choices.i_count > 0 )
            i_type |= VLC_VAR_HASCHOICE;
    }
666
    vlc_mutex_unlock( &p_priv->var_lock );
667 668

    return i_type;
669 670
}

671 672
int (var_SetChecked)(vlc_object_t *p_this, const char *psz_name,
                     int expected_type, vlc_value_t val)
673
{
674 675
    variable_t *p_var;
    vlc_value_t oldval;
676 677 678

    assert( p_this );

679
    vlc_object_internals_t *p_priv = vlc_internals( p_this );
680

681 682
    p_var = Lookup( p_this, psz_name );
    if( p_var == NULL )
683
    {
684
        vlc_mutex_unlock( &p_priv->var_lock );
685
        return VLC_ENOVAR;
686 687
    }

688
    assert( expected_type == 0 ||
689
            (p_var->i_type & VLC_VAR_CLASS) == expected_type );
690
    assert ((p_var->i_type & VLC_VAR_CLASS) != VLC_VAR_VOID);
691

692 693
    WaitUnused( p_this, p_var );

694
    /* Duplicate data if needed */
695
    p_var->ops->pf_dup( &val );
696

697 698
    /* Backup needed stuff */
    oldval = p_var->val;
699

700
    /* Check boundaries and list */
701 702
    CheckValue( p_var, &val );

703 704 705
    /* Set the variable */
    p_var->val = val;

706
    /* Deal with callbacks */
707
    TriggerCallback( p_this, p_var, psz_name, oldval );
708 709

    /* Free data if needed */
710
    p_var->ops->pf_free( &oldval );
711

712
    vlc_mutex_unlock( &p_priv->var_lock );
713
    return VLC_SUCCESS;
714 715
}

716
int (var_Set)(vlc_object_t *p_this, const char *psz_name, vlc_value_t val)
717 718 719 720
{
    return var_SetChecked( p_this, psz_name, 0, val );
}

721 722
int (var_GetChecked)(vlc_object_t *p_this, const char *psz_name,
                     int expected_type, vlc_value_t *p_val)
723
{
724 725
    assert( p_this );

726
    vlc_object_internals_t *p_priv = vlc_internals( p_this );
727 728
    variable_t *p_var;
    int err = VLC_SUCCESS;
729

730 731
    p_var = Lookup( p_this, psz_name );
    if( p_var != NULL )
732
    {
733
        assert( expected_type == 0 ||
734
                (p_var->i_type & VLC_VAR_CLASS) == expected_type );
735
        assert ((p_var->i_type & VLC_VAR_CLASS) != VLC_VAR_VOID);
736

737 738
        /* Really get the variable */
        *p_val = p_var->val;
739

740 741 742 743 744
        /* Duplicate value if needed */
        p_var->ops->pf_dup( p_val );
    }
    else
        err = VLC_ENOVAR;
745

746
    vlc_mutex_unlock( &p_priv->var_lock );
747
    return err;
748 749
}

750
int (var_Get)(vlc_object_t *p_this, const char *psz_name, vlc_value_t *p_val)
751 752 753
{
    return var_GetChecked( p_this, psz_name, 0, p_val );
}
754

755 756 757 758 759 760
typedef enum
{
    vlc_value_callback,
    vlc_list_callback
} vlc_callback_type_t;

761
static void AddCallback( vlc_object_t *p_this, const char *psz_name,
762
                        callback_entry_t entry, vlc_callback_type_t i_type )
763 764
{
    variable_t *p_var;
765 766 767

    assert( p_this );

768
    vlc_object_internals_t *p_priv = vlc_internals( p_this );
769

770 771
    p_var = Lookup( p_this, psz_name );
    if( p_var == NULL )
772
    {
773
        vlc_mutex_unlock( &p_priv->var_lock );
774
        msg_Err( p_this, "cannot add callback %p to nonexistent variable '%s'",
775
                 entry.p_callback, psz_name );
776
        return;
777 778
    }

779
    WaitUnused( p_this, p_var );
780 781 782 783 784 785

    callback_table_t *p_table;
    if (i_type == vlc_value_callback)
        p_table = &p_var->value_callbacks;
    else
        p_table = &p_var->list_callbacks;
786
    TAB_APPEND(p_table->i_entries, p_table->p_entries, entry);
787

788
    vlc_mutex_unlock( &p_priv->var_lock );
789 790
}

791 792
void (var_AddCallback)(vlc_object_t *p_this, const char *psz_name,
                       vlc_callback_t pf_callback, void *p_data)
793 794
{
    callback_entry_t entry;
795
    entry.pf_value_callback = pf_callback;
796 797
    entry.p_data = p_data;

798
    AddCallback(p_this, psz_name, entry, vlc_value_callback);
799 800
}

801 802
static void DelCallback( vlc_object_t *p_this, const char *psz_name,
                         callback_entry_t entry, vlc_callback_type_t i_type )
803
{
804
    int i_entry;
805
    variable_t *p_var;
806 807 808
#ifndef NDEBUG
    bool b_found_similar = false;
#endif
809 810 811

    assert( p_this );

812
    vlc_object_internals_t *p_priv = vlc_internals( p_this );
813

814 815
    p_var = Lookup( p_this, psz_name );
    if( p_var == NULL )
816
    {
817
        vlc_mutex_unlock( &p_priv->var_lock );
818 819 820
        msg_Err( p_this, "cannot delete callback %p from nonexistent "
                 "variable '%s'", entry.p_callback, psz_name );
        return;
821 822
    }

823
    WaitUnused( p_this, p_var );
824

825 826 827 828 829 830
    callback_table_t *p_table;
    if (i_type == vlc_value_callback)
        p_table = &p_var->value_callbacks;
    else
        p_table = &p_var->list_callbacks;

831
    for( i_entry = p_table->i_entries ; i_entry-- ; )
832
    {
833
        if( p_table->p_entries[i_entry].p_callback == entry.p_callback
834
            && p_table->p_entries[i_entry].p_data == entry.p_data )
835 836 837
        {
            break;
        }
Rémi Duraffort's avatar
Rémi Duraffort committed
838
#ifndef NDEBUG
839
        else if( p_table->p_entries[i_entry].p_callback == entry.p_callback )
840
            b_found_similar = true;
Rémi Duraffort's avatar
Rémi Duraffort committed
841
#endif
842 843 844 845
    }

    if( i_entry < 0 )
    {
846 847
#ifndef NDEBUG
        if( b_found_similar )
848 849
            fprintf( stderr, "Calling var_DelCallback for '%s' with the same "
                             "function but not the same data.", psz_name );
850
        vlc_assert_unreachable();
851
#endif
852
        vlc_mutex_unlock( &p_priv->var_lock );
853
        return;
854 855
    }

856
    TAB_ERASE(p_table->i_entries, p_table->p_entries, i_entry);
857

858
    vlc_mutex_unlock( &p_priv->var_lock );
859 860
}

861 862
void (var_DelCallback)(vlc_object_t *p_this, const char *psz_name,
                       vlc_callback_t pf_callback, void *p_data)
863 864
{
    callback_entry_t entry;
865
    entry.pf_value_callback = pf_callback;
866 867
    entry.p_data = p_data;

868
    DelCallback(p_this, psz_name, entry, vlc_value_callback);
869 870
}

871
void (var_TriggerCallback)(vlc_object_t *p_this, const char *psz_name)
872
{
873
    vlc_object_internals_t *p_priv = vlc_internals( p_this );
874 875
    variable_t *p_var = Lookup( p_this, psz_name );
    if( p_var != NULL )
876
    {
877
        WaitUnused( p_this, p_var );
878

879 880 881 882
        /* Deal with callbacks. Tell we're in a callback, release the lock,
         * call stored functions, retake the lock. */
        TriggerCallback( p_this, p_var, psz_name, p_var->val );
    }
883
    vlc_mutex_unlock( &p_priv->var_lock );
884 885
}

886 887
void (var_AddListCallback)(vlc_object_t *p_this, const char *psz_name,
                           vlc_list_callback_t pf_callback, void *p_data)
888 889
{
    callback_entry_t entry;
890
    entry.pf_list_callback = pf_callback;
891 892
    entry.p_data = p_data;

893
    AddCallback(p_this, psz_name, entry, vlc_list_callback);
894 895
}

896 897
void (var_DelListCallback)(vlc_object_t *p_this, const char *psz_name,
                           vlc_list_callback_t pf_callback, void *p_data)
898 899
{
    callback_entry_t entry;
900
    entry.pf_list_callback = pf_callback;
901 902
    entry.p_data = p_data;

903
    DelCallback(p_this, psz_name, entry, vlc_list_callback);
904 905
}

906 907 908 909 910 911 912
/** Parse a stringified option
 * This function parse a string option and create the associated object
 * variable
 * The option must be of the form "[no[-]]foo[=bar]" where foo is the
 * option name and bar is the value of the option.
 * \param p_obj the object in which the variable must be created
 * \param psz_option the option to parse
913
 * \param trusted whether the option is set by a trusted input or not
914 915
 * \return nothing
 */
916 917
void var_OptionParse( vlc_object_t *p_obj, const char *psz_option,
                      bool trusted )
918
{
919 920
    char *psz_name, *psz_value;
    int  i_type;
921
    bool b_isno = false;
922 923
    vlc_value_t val;

924
    val.psz_string = NULL;
925

926
    /* It's too much of a hassle to remove the ':' when we parse
927
     * the cmd line :) */
928
    if( psz_option[0] == ':' )
929
        psz_option++;
930

931 932
    if( !psz_option[0] )
        return;
933

934 935 936 937 938 939 940
    psz_name = strdup( psz_option );
    if( psz_name == NULL )
        return;

    psz_value = strchr( psz_name, '=' );
    if( psz_value != NULL )
        *psz_value++ = '\0';
941

942
    i_type = config_GetType( psz_name );
943 944 945 946 947 948 949 950 951 952 953 954 955
    if( !i_type && !psz_value )
    {
        /* check for "no-foo" or "nofoo" */
        if( !strncmp( psz_name, "no-", 3 ) )
        {
            memmove( psz_name, psz_name + 3, strlen(psz_name) + 1 - 3 );
        }
        else if( !strncmp( psz_name, "no", 2 ) )
        {
            memmove( psz_name, psz_name + 2, strlen(psz_name) + 1 - 2 );
        }
        else goto cleanup;           /* Option doesn't exist */

956
        b_isno = true;
957
        i_type = config_GetType( psz_name );
958
    }
959
    if( !i_type ) goto cleanup; /* Option doesn't exist */
960 961 962 963

    if( ( i_type != VLC_VAR_BOOL ) &&
        ( !psz_value || !*psz_value ) ) goto cleanup; /* Invalid value */

964
    /* check if option is unsafe */
965
    if( !trusted && !config_IsSafe( psz_name ) )
966
    {
967 968 969 970
        msg_Err( p_obj, "unsafe option \"%s\" has been ignored for "
                        "security reasons", psz_name );
        free( psz_name );
        return;
971 972
    }

973
    /* Create the variable in the input object.
974
     * Children of the input object will be able to retrieve this value
975
     * thanks to the inheritance property of the object variables. */
976
    var_Create( p_obj, psz_name, i_type );
977 978 979 980 981 982 983 984

    switch( i_type )
    {
    case VLC_VAR_BOOL:
        val.b_bool = !b_isno;
        break;

    case VLC_VAR_INTEGER:
985
        val.i_int = strtoll( psz_value, NULL, 0 );
986 987 988
        break;

    case VLC_VAR_FLOAT:
989
        val.f_float = us_atof( psz_value );
990 991 992 993 994 995 996 997 998 999
        break;

    case VLC_VAR_STRING:
        val.psz_string = psz_value;
        break;

    default:
        goto cleanup;
    }

1000
    var_Set( p_obj, psz_name, val );
1001

1002
cleanup:
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1003
    free( psz_name );
1004 1005
}

1006
int (var_LocationParse)(vlc_object_t *obj, const char *mrl, const char *pref)
1007 1008
{
    int ret = VLC_SUCCESS;
1009
    size_t preflen = strlen (pref) + 1;
1010 1011 1012 1013

    assert(mrl != NULL);
    while (*mrl != '\0')
    {
1014
        mrl += strspn (mrl, ":;"); /* skip leading colon(s) */
1015

1016
        size_t len = strcspn (mrl, ":;");
1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030
        char *buf = malloc (preflen + len);

        if (likely(buf != NULL))
        {
            /* NOTE: this does not support the "no-<varname>" bool syntax. */
            /* DO NOT use asprintf() here; it won't work! Think again. */
            snprintf (buf, preflen + len, "%s%s", pref, mrl);
            var_OptionParse (obj, buf, false);
            free (buf);
        }
        else
            ret = VLC_ENOMEM;
        mrl += len;
    }
1031

1032 1033
    return ret;
}
1034

1035 1036
int var_Inherit( vlc_object_t *p_this, const char *psz_name, int i_type,
                 vlc_value_t *p_val )
1037
{
1038
    i_type &= VLC_VAR_CLASS;
1039
    for( vlc_object_t *obj = p_this; obj != NULL; obj = obj->obj.parent )
1040
    {
1041
        if( var_GetChecked( obj, psz_name, i_type, p_val ) == VLC_SUCCESS )
1042
            return VLC_SUCCESS;
1043
    }
1044

1045
    /* else take value from config */
1046 1047
    switch( i_type & VLC_VAR_CLASS )
    {
1048
        case VLC_VAR_STRING:
1049
            p_val->psz_string = config_GetPsz( psz_name );
1050 1051 1052
            if( !p_val->psz_string ) p_val->psz_string = strdup("");
            break;
        case VLC_VAR_FLOAT:
1053
            p_val->f_float = config_GetFloat( psz_name );
1054 1055
            break;
        case VLC_VAR_INTEGER:
1056
            p_val->i_int = config_GetInt( psz_name );
1057 1058
            break;
        case VLC_VAR_BOOL:
1059
            p_val->b_bool = config_GetInt( psz_name ) > 0;
1060 1061
            break;
        default:
1062
            vlc_assert_unreachable();
1063
        case VLC_VAR_ADDRESS:
1064 1065
            return VLC_ENOOBJ;
    }
1066
    return VLC_SUCCESS;
1067
}
1068

1069 1070 1071 1072
int (var_InheritURational)(vlc_object_t *object,
                           unsigned *num, unsigned *den,
                           const char *var)
{
1073 1074
    char *str = var_InheritString(object, var);
    if (str == NULL)
1075 1076
        goto error;

1077 1078 1079
    char *sep;
    unsigned n = strtoul(str, &sep, 10);
    unsigned d;
1080

1081 1082 1083 1084 1085
    switch (*sep) {
        case '\0':
            /* Decimal integer */
            d = 1;
            break;
1086

1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097
        case ':':
        case '/':
            /* Decimal fraction */
            d = strtoul(sep + 1, &sep, 10);
            if (*sep != '\0')
                goto error;
            break;

        case '.': {
            /* Decimal number */
            unsigned char c;
1098 1099

            d = 1;
1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113
            while ((c = *(++sep)) != '\0') {
                c -= '0';

                if (c >= 10)
                    goto error;

                n = n * 10 + c;
                d *= 10;
            }
            break;
        }

        default:
            goto error;
1114 1115
    }

1116 1117 1118 1119 1120 1121 1122 1123 1124
    free(str);

    if (n == 0) {
        *num = 0;
        *den = d ? 1 : 0;
    } else if (d == 0) {
        *num = 1;
        *den = 0;
    } else
1125 1126 1127 1128 1129
        vlc_ureduce(num, den, n, d, 0);

    return VLC_SUCCESS;

error:
1130 1131 1132
    free(str);
    *num = 0;
    *den = 0;
1133 1134 1135
    return VLC_EGENERIC;
}

1136 1137
void var_FreeList( vlc_value_t *p_val, vlc_value_t *p_val2 )
{
1138
    switch( p_val->p_list->i_type & VLC_VAR_CLASS )
1139
    {
1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153
        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 );

1154 1155
        for( int i = 0; i < p_val2->p_list->i_count; i++ )
            free( p_val2->p_list->p_values[i].psz_string );
1156
        free( p_val2->p_list->p_values );