~ubuntu-branches/ubuntu/natty/vlc/natty

« back to all changes in this revision

Viewing changes to modules/gui/skins2/src/skin_main.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Benjamin Drung
  • Date: 2010-06-25 01:09:16 UTC
  • mfrom: (1.1.30 upstream)
  • Revision ID: james.westby@ubuntu.com-20100625010916-asxhep2mutg6g6pd
Tags: 1.1.0-1ubuntu1
* Merge from Debian unstable, remaining changes:
  - build and install the libx264 plugin
  - add Xb-Npp header to vlc package
  - Add apport hook to include more vlc dependencies in bug reports
* Drop xulrunner patches.
* Drop 502_xulrunner_191.diff.

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
 * skin_main.cpp
3
3
 *****************************************************************************
4
4
 * Copyright (C) 2003 the VideoLAN team
5
 
 * $Id: f5608699fbda8e87981a4e1f028965abaa751fda $
 
5
 * $Id: 4d9ea1cb8055a1ab09c334c23896fb3ea3412665 $
6
6
 *
7
7
 * Authors: Cyril Deguet     <asmax@via.ecp.fr>
8
8
 *          Olivier Teulière <ipkiss@via.ecp.fr>
32
32
#include <vlc_demux.h>
33
33
#include <vlc_playlist.h>
34
34
#include <vlc_threads.h>
35
 
#include <vlc_window.h>
 
35
#include <vlc_vout_window.h>
36
36
 
37
37
#include "dialogs.hpp"
38
38
#include "os_factory.hpp"
65
65
//---------------------------------------------------------------------------
66
66
static int  Open  ( vlc_object_t * );
67
67
static void Close ( vlc_object_t * );
68
 
static void Run   ( intf_thread_t * );
 
68
static void *Run  ( void * );
69
69
 
70
70
static int DemuxOpen( vlc_object_t * );
71
71
static int Demux( demux_t * );
81
81
                            vlc_value_t oldVal, vlc_value_t newVal,
82
82
                            void *pParam );
83
83
 
84
 
 
85
84
static struct
86
85
{
87
86
    intf_thread_t *intf;
96
95
    intf_thread_t *p_intf = (intf_thread_t *)p_this;
97
96
 
98
97
    // Allocate instance and initialize some members
99
 
    p_intf->p_sys = (intf_sys_t *) malloc( sizeof( intf_sys_t ) );
 
98
    p_intf->p_sys = (intf_sys_t *) calloc( 1, sizeof( intf_sys_t ) );
100
99
    if( p_intf->p_sys == NULL )
101
 
        return( VLC_ENOMEM );
102
 
 
103
 
    p_intf->pf_run = Run;
 
100
        return VLC_ENOMEM;
104
101
 
105
102
    // Suscribe to messages bank
106
103
#if 0
108
105
#endif
109
106
 
110
107
    p_intf->p_sys->p_input = NULL;
111
 
    p_intf->p_sys->p_playlist = pl_Hold( p_intf );
 
108
    p_intf->p_sys->p_playlist = pl_Get( p_intf );
112
109
 
113
110
    // Initialize "singleton" objects
114
111
    p_intf->p_sys->p_logger = NULL;
122
119
    p_intf->p_sys->p_vlcProc = NULL;
123
120
    p_intf->p_sys->p_repository = NULL;
124
121
 
125
 
#ifdef WIN32
126
 
    p_intf->p_sys->b_exitRequested = false;
127
 
    p_intf->p_sys->b_exitOK = false;
128
 
#endif
129
 
 
130
122
    // No theme yet
131
123
    p_intf->p_sys->p_theme = NULL;
132
124
 
133
125
    // Create a variable to be notified of skins to be loaded
134
126
    var_Create( p_intf, "skin-to-load", VLC_VAR_STRING );
135
127
 
 
128
    vlc_mutex_init( &p_intf->p_sys->vout_lock );
 
129
    vlc_cond_init( &p_intf->p_sys->vout_wait );
 
130
 
 
131
    vlc_mutex_init( &p_intf->p_sys->init_lock );
 
132
    vlc_cond_init( &p_intf->p_sys->init_wait );
 
133
 
 
134
    vlc_mutex_lock( &p_intf->p_sys->init_lock );
 
135
    p_intf->p_sys->b_ready = false;
 
136
 
 
137
    if( vlc_clone( &p_intf->p_sys->thread, Run, p_intf,
 
138
                               VLC_THREAD_PRIORITY_LOW ) )
 
139
    {
 
140
        vlc_mutex_unlock( &p_intf->p_sys->init_lock );
 
141
 
 
142
        vlc_cond_destroy( &p_intf->p_sys->init_wait );
 
143
        vlc_mutex_destroy( &p_intf->p_sys->init_lock );
 
144
        vlc_cond_destroy( &p_intf->p_sys->vout_wait );
 
145
        vlc_mutex_destroy( &p_intf->p_sys->vout_lock );
 
146
        free( p_intf->p_sys );
 
147
        return VLC_EGENERIC;
 
148
    }
 
149
 
 
150
    while( !p_intf->p_sys->b_ready )
 
151
        vlc_cond_wait( &p_intf->p_sys->init_wait, &p_intf->p_sys->init_lock );
 
152
    vlc_mutex_unlock( &p_intf->p_sys->init_lock );
 
153
 
 
154
    vlc_mutex_lock( &skin_load.mutex );
 
155
    skin_load.intf = p_intf;
 
156
    vlc_mutex_unlock( &skin_load.mutex );
 
157
 
 
158
    return VLC_SUCCESS;
 
159
}
 
160
 
 
161
//---------------------------------------------------------------------------
 
162
// Close: destroy interface
 
163
//---------------------------------------------------------------------------
 
164
static void Close( vlc_object_t *p_this )
 
165
{
 
166
    intf_thread_t *p_intf = (intf_thread_t *)p_this;
 
167
 
 
168
    msg_Dbg( p_intf, "closing skins2 module" );
 
169
 
 
170
    vlc_mutex_lock( &skin_load.mutex );
 
171
    skin_load.intf = NULL;
 
172
    vlc_mutex_unlock( &skin_load.mutex);
 
173
 
 
174
    vlc_join( p_intf->p_sys->thread, NULL );
 
175
 
 
176
    vlc_mutex_destroy( &p_intf->p_sys->init_lock );
 
177
    vlc_cond_destroy( &p_intf->p_sys->init_wait );
 
178
 
 
179
    // Unsubscribe from messages bank
 
180
#if 0
 
181
    msg_Unsubscribe( p_intf, p_intf->p_sys->p_sub );
 
182
#endif
 
183
 
 
184
    vlc_cond_destroy( &p_intf->p_sys->vout_wait );
 
185
    vlc_mutex_destroy( &p_intf->p_sys->vout_lock );
 
186
 
 
187
    // Destroy structure
 
188
    free( p_intf->p_sys );
 
189
}
 
190
 
 
191
 
 
192
//---------------------------------------------------------------------------
 
193
// Run: main loop
 
194
//---------------------------------------------------------------------------
 
195
static void *Run( void * p_obj )
 
196
{
 
197
    int canc = vlc_savecancel();
 
198
 
 
199
    intf_thread_t *p_intf = (intf_thread_t *)p_obj;
 
200
 
 
201
    bool b_error = false;
 
202
    char *skin_last = NULL;
 
203
    ThemeLoader *pLoader = NULL;
 
204
    OSLoop *loop = NULL;
 
205
 
 
206
    vlc_mutex_lock( &p_intf->p_sys->init_lock );
 
207
 
136
208
    // Initialize singletons
137
209
    if( OSFactory::instance( p_intf ) == NULL )
138
210
    {
139
211
        msg_Err( p_intf, "cannot initialize OSFactory" );
140
 
        vlc_object_release( p_intf->p_sys->p_playlist );
141
 
#if 0
142
 
        msg_Unsubscribe( p_intf, p_intf->p_sys->p_sub );
143
 
#endif
144
 
        return VLC_EGENERIC;
 
212
        b_error = true;
 
213
        goto end;
145
214
    }
146
215
    if( AsyncQueue::instance( p_intf ) == NULL )
147
216
    {
148
217
        msg_Err( p_intf, "cannot initialize AsyncQueue" );
149
 
        vlc_object_release( p_intf->p_sys->p_playlist );
150
 
#if 0
151
 
        msg_Unsubscribe( p_intf, p_intf->p_sys->p_sub );
152
 
#endif
153
 
        return VLC_EGENERIC;
 
218
        b_error = true;
 
219
        goto end;
154
220
    }
155
221
    if( Interpreter::instance( p_intf ) == NULL )
156
222
    {
157
223
        msg_Err( p_intf, "cannot instanciate Interpreter" );
158
 
        vlc_object_release( p_intf->p_sys->p_playlist );
159
 
#if 0
160
 
        msg_Unsubscribe( p_intf, p_intf->p_sys->p_sub );
161
 
#endif
162
 
        return VLC_EGENERIC;
 
224
        b_error = true;
 
225
        goto end;
163
226
    }
164
227
    if( VarManager::instance( p_intf ) == NULL )
165
228
    {
166
229
        msg_Err( p_intf, "cannot instanciate VarManager" );
167
 
        vlc_object_release( p_intf->p_sys->p_playlist );
168
 
#if 0
169
 
        msg_Unsubscribe( p_intf, p_intf->p_sys->p_sub );
170
 
#endif
171
 
        return VLC_EGENERIC;
 
230
        b_error = true;
 
231
        goto end;
172
232
    }
173
233
    if( VlcProc::instance( p_intf ) == NULL )
174
234
    {
175
235
        msg_Err( p_intf, "cannot initialize VLCProc" );
176
 
        vlc_object_release( p_intf->p_sys->p_playlist );
177
 
#if 0
178
 
        msg_Unsubscribe( p_intf, p_intf->p_sys->p_sub );
179
 
#endif
180
 
        return VLC_EGENERIC;
 
236
        b_error = true;
 
237
        goto end;
181
238
    }
182
239
    if( VoutManager::instance( p_intf ) == NULL )
183
240
    {
184
241
        msg_Err( p_intf, "cannot instanciate VoutManager" );
185
 
        vlc_object_release( p_intf->p_sys->p_playlist );
186
 
        return VLC_EGENERIC;
187
 
    }
188
 
    vlc_mutex_lock( &skin_load.mutex );
189
 
    skin_load.intf = p_intf;
190
 
    vlc_mutex_unlock( &skin_load.mutex );
191
 
 
192
 
    Dialogs::instance( p_intf );
193
 
    ThemeRepository::instance( p_intf );
 
242
        b_error = true;
 
243
        goto end;
 
244
    }
 
245
    if( ThemeRepository::instance( p_intf ) == NULL )
 
246
    {
 
247
        msg_Err( p_intf, "cannot instanciate ThemeRepository" );
 
248
        b_error = true;
 
249
        goto end;
 
250
    }
 
251
    if( Dialogs::instance( p_intf ) == NULL )
 
252
    {
 
253
        msg_Err( p_intf, "cannot instanciate qt4 dialogs provider" );
 
254
        b_error = true;
 
255
        goto end;
 
256
    }
194
257
 
195
258
    // Load a theme
196
 
    char *skin_last = config_GetPsz( p_intf, "skins2-last" );
197
 
 
198
 
    ThemeLoader *pLoader = new ThemeLoader( p_intf );
199
 
 
200
 
    if( !skin_last || !*skin_last || !pLoader->load( skin_last ) )
 
259
    skin_last = config_GetPsz( p_intf, "skins2-last" );
 
260
    pLoader = new ThemeLoader( p_intf );
 
261
 
 
262
    if( !skin_last || !pLoader->load( skin_last ) )
201
263
    {
202
 
        // Get the resource path and try to load the default skin
203
 
        OSFactory *pOSFactory = OSFactory::instance( p_intf );
204
 
        const list<string> &resPath = pOSFactory->getResourcePath();
205
 
        const string &sep = pOSFactory->getDirSeparator();
206
 
 
207
 
        list<string>::const_iterator it;
208
 
        for( it = resPath.begin(); it != resPath.end(); it++ )
209
 
        {
210
 
            string path = (*it) + sep + "default.vlt";
211
 
            if( pLoader->load( path ) )
212
 
            {
213
 
                // Theme loaded successfully
214
 
                break;
215
 
            }
216
 
        }
217
 
        if( it == resPath.end() )
218
 
        {
219
 
            // Last chance: the user can select a new theme file
220
 
            if( Dialogs::instance( p_intf ) )
221
 
            {
222
 
                CmdDlgChangeSkin *pCmd = new CmdDlgChangeSkin( p_intf );
223
 
                AsyncQueue *pQueue = AsyncQueue::instance( p_intf );
224
 
                pQueue->push( CmdGenericPtr( pCmd ) );
225
 
            }
226
 
            else
227
 
            {
228
 
                // No dialogs provider, just quit...
229
 
                CmdQuit *pCmd = new CmdQuit( p_intf );
230
 
                AsyncQueue *pQueue = AsyncQueue::instance( p_intf );
231
 
                pQueue->push( CmdGenericPtr( pCmd ) );
232
 
                msg_Err( p_intf,
233
 
                         "cannot show the \"open skin\" dialog: exiting...");
234
 
            }
235
 
        }
 
264
        // No skins (not even the default one). let's quit
 
265
        CmdQuit *pCmd = new CmdQuit( p_intf );
 
266
        AsyncQueue *pQueue = AsyncQueue::instance( p_intf );
 
267
        pQueue->push( CmdGenericPtr( pCmd ) );
 
268
        msg_Err( p_intf, "no skins found : exiting");
236
269
    }
 
270
 
237
271
    delete pLoader;
238
 
 
239
272
    free( skin_last );
240
273
 
241
 
#ifdef WIN32
242
 
 
243
 
    p_intf->b_should_run_on_first_thread = true;
244
 
 
245
 
    // enqueue a command to automatically start the first playlist item
246
 
    AsyncQueue *pQueue = AsyncQueue::instance( p_intf );
247
 
    CmdPlaylistFirst *pCmd = new CmdPlaylistFirst( p_intf );
248
 
    pQueue->push( CmdGenericPtr( pCmd ) );
249
 
 
250
 
#endif
251
 
 
252
 
    return( VLC_SUCCESS );
253
 
}
254
 
 
255
 
//---------------------------------------------------------------------------
256
 
// Close: destroy interface
257
 
//---------------------------------------------------------------------------
258
 
static void Close( vlc_object_t *p_this )
259
 
{
260
 
    intf_thread_t *p_intf = (intf_thread_t *)p_this;
261
 
 
262
 
    msg_Dbg( p_intf, "closing skins2 module" );
263
 
 
264
 
    vlc_mutex_lock( &skin_load.mutex );
265
 
    skin_load.intf = NULL;
266
 
    vlc_mutex_unlock( &skin_load.mutex);
267
 
 
 
274
    // Get the instance of OSLoop
 
275
    loop = OSFactory::instance( p_intf )->getOSLoop();
 
276
 
 
277
    // Signal the main thread this thread is now ready
 
278
    p_intf->p_sys->b_ready = true;
 
279
    vlc_cond_signal( &p_intf->p_sys->init_wait );
 
280
    vlc_mutex_unlock( &p_intf->p_sys->init_lock );
 
281
 
 
282
    // Enter the main event loop
 
283
    loop->run();
 
284
 
 
285
    // Destroy OSLoop
 
286
    OSFactory::instance( p_intf )->destroyOSLoop();
 
287
 
 
288
    // save and delete the theme
268
289
    if( p_intf->p_sys->p_theme )
269
290
    {
 
291
        p_intf->p_sys->p_theme->saveConfig();
 
292
 
270
293
        delete p_intf->p_sys->p_theme;
271
294
        p_intf->p_sys->p_theme = NULL;
 
295
 
272
296
        msg_Dbg( p_intf, "current theme deleted" );
273
297
    }
274
298
 
 
299
    // save config file
 
300
    config_SaveConfigFile( p_intf, NULL );
 
301
 
 
302
end:
275
303
    // Destroy "singleton" objects
276
 
    OSFactory::instance( p_intf )->destroyOSLoop();
 
304
    Dialogs::destroy( p_intf );
277
305
    ThemeRepository::destroy( p_intf );
278
306
    VoutManager::destroy( p_intf );
279
 
    //Dialogs::destroy( p_intf );
 
307
    VlcProc::destroy( p_intf );
 
308
    VarManager::destroy( p_intf );
280
309
    Interpreter::destroy( p_intf );
281
310
    AsyncQueue::destroy( p_intf );
282
 
    VarManager::destroy( p_intf );
283
 
    VlcProc::destroy( p_intf );
284
311
    OSFactory::destroy( p_intf );
285
312
 
286
 
    if( p_intf->p_sys->p_playlist )
287
 
    {
288
 
        vlc_object_release( p_intf->p_sys->p_playlist );
289
 
    }
290
 
 
291
 
    // Unsubscribe from messages bank
292
 
#if 0
293
 
    msg_Unsubscribe( p_intf, p_intf->p_sys->p_sub );
294
 
#endif
295
 
 
296
 
    // Destroy structure
297
 
    free( p_intf->p_sys );
298
 
}
299
 
 
300
 
 
301
 
//---------------------------------------------------------------------------
302
 
// Run: main loop
303
 
//---------------------------------------------------------------------------
304
 
static void Run( intf_thread_t *p_intf )
305
 
{
306
 
    int canc = vlc_savecancel();
307
 
 
308
 
    // Get the instance of OSLoop
309
 
    OSLoop *loop = OSFactory::instance( p_intf )->getOSLoop();
310
 
 
311
 
    // Enter the main event loop
312
 
    loop->run();
313
 
 
314
 
    // Delete the theme and save the configuration of the windows
315
 
    if( p_intf->p_sys->p_theme )
316
 
    {
317
 
        p_intf->p_sys->p_theme->saveConfig();
318
 
    }
319
 
 
320
 
    // cannot be called in "Close", because it refcounts skins2
321
 
    Dialogs::destroy( p_intf );
322
 
 
323
 
    // save config file
324
 
    config_SaveConfigFile( p_intf, NULL );
 
313
    if( b_error )
 
314
    {
 
315
        p_intf->p_sys->b_ready = true;
 
316
        vlc_cond_signal( &p_intf->p_sys->init_wait );
 
317
        vlc_mutex_unlock( &p_intf->p_sys->init_lock );
 
318
 
 
319
        libvlc_Quit( p_intf->p_libvlc );
 
320
    }
325
321
 
326
322
    vlc_restorecancel(canc);
 
323
    return NULL;
327
324
}
328
325
 
 
326
static vlc_mutex_t serializer = VLC_STATIC_MUTEX;
329
327
 
330
328
// Callbacks for vout requests
331
329
static int WindowOpen( vlc_object_t *p_this )
332
330
{
333
331
    vout_window_t *pWnd = (vout_window_t *)p_this;
334
 
    intf_thread_t *pIntf = (intf_thread_t *)
335
 
        vlc_object_find_name( p_this, "skins2", FIND_ANYWHERE );
 
332
 
 
333
    vlc_mutex_lock( &skin_load.mutex );
 
334
    intf_thread_t *pIntf = skin_load.intf;
 
335
    if( pIntf )
 
336
        vlc_object_hold( pIntf );
 
337
    vlc_mutex_unlock( &skin_load.mutex );
336
338
 
337
339
    if( pIntf == NULL )
338
340
        return VLC_EGENERIC;
339
341
 
340
 
    if( !config_GetInt( pIntf, "skinned-video") )
 
342
    if( !vlc_object_alive( pIntf ) ||
 
343
        !var_InheritBool( pIntf, "skinned-video") ||
 
344
        pWnd->cfg->is_standalone )
341
345
    {
342
346
        vlc_object_release( pIntf );
343
347
        return VLC_EGENERIC;
344
348
    }
345
349
 
346
 
    vlc_object_release( pIntf );
 
350
    vlc_mutex_lock( &serializer );
347
351
 
348
352
    pWnd->handle.hwnd = VoutManager::getWindow( pIntf, pWnd );
349
353
 
350
354
    if( pWnd->handle.hwnd )
351
355
    {
352
 
        pWnd->p_private = pIntf;
353
356
        pWnd->control = &VoutManager::controlWindow;
 
357
        pWnd->sys = (vout_window_sys_t*)pIntf;
 
358
 
 
359
        vlc_mutex_unlock( &serializer );
354
360
        return VLC_SUCCESS;
355
361
    }
356
362
    else
357
363
    {
 
364
        vlc_object_release( pIntf );
 
365
        vlc_mutex_unlock( &serializer );
358
366
        return VLC_EGENERIC;
359
367
    }
360
368
}
362
370
static void WindowClose( vlc_object_t *p_this )
363
371
{
364
372
    vout_window_t *pWnd = (vout_window_t *)p_this;
365
 
    intf_thread_t *pIntf = (intf_thread_t *)p_this->p_private;
 
373
    intf_thread_t *pIntf = (intf_thread_t *)pWnd->sys;
366
374
 
367
375
    VoutManager::releaseWindow( pIntf, pWnd );
 
376
 
 
377
    vlc_object_release( pIntf );
368
378
}
369
379
 
370
380
//---------------------------------------------------------------------------
381
391
    p_demux->pf_control = DemuxControl;
382
392
 
383
393
    // Test that we have a valid .vlt or .wsz file, based on the extension
384
 
    // TODO: an actual check of the contents would be better...
385
 
    if( ( ext = strchr( p_demux->psz_path, '.' ) ) == NULL ||
 
394
    if( ( ext = strrchr( p_demux->psz_path, '.' ) ) == NULL ||
386
395
        ( strcasecmp( ext, ".vlt" ) && strcasecmp( ext, ".wsz" ) ) )
387
396
    {
388
397
        return VLC_EGENERIC;
396
405
 
397
406
    if( p_intf != NULL )
398
407
    {
399
 
        playlist_t *p_playlist = pl_Hold( p_this );
 
408
        playlist_t *p_playlist = pl_Get( p_this );
 
409
 
 
410
        PL_LOCK;
400
411
        // Make sure the item is deleted afterwards
401
412
        /// \bug does not always work
402
413
        playlist_CurrentPlayingItem( p_playlist )->i_flags |= PLAYLIST_REMOVE_FLAG;
403
 
        vlc_object_release( p_playlist );
 
414
        PL_UNLOCK;
404
415
 
405
416
        var_SetString( p_intf, "skin-to-load", p_demux->psz_path );
406
417
        vlc_object_release( p_intf );
429
440
//---------------------------------------------------------------------------
430
441
static int DemuxControl( demux_t *p_demux, int i_query, va_list args )
431
442
{
432
 
    return demux_vaControlHelper( p_demux->s, 0, 0, 0, 1, i_query, args );
 
443
    switch( i_query )
 
444
    {
 
445
    case DEMUX_GET_PTS_DELAY:
 
446
    {
 
447
        int64_t *pi_pts_delay = va_arg( args, int64_t * );
 
448
        *pi_pts_delay = 10;
 
449
        return VLC_SUCCESS;
 
450
    }
 
451
    default:
 
452
        return VLC_EGENERIC;
 
453
    }
 
454
 
433
455
}
434
456
 
435
457
 
539
561
    add_string( "skins2-config", "", NULL, SKINS2_CONFIG, SKINS2_CONFIG_LONG,
540
562
                true )
541
563
        change_autosave ()
542
 
        change_internal ()
 
564
        change_private ()
543
565
#ifdef WIN32
544
566
    add_bool( "skins2-systray", false, onSystrayChange, SKINS2_SYSTRAY,
545
567
              SKINS2_SYSTRAY_LONG, false );
546
568
    add_bool( "skins2-taskbar", true, onTaskBarChange, SKINS2_TASKBAR,
547
569
              SKINS2_TASKBAR_LONG, false );
 
570
#endif
548
571
    add_bool( "skins2-transparency", false, NULL, SKINS2_TRANSPARENCY,
549
572
              SKINS2_TRANSPARENCY_LONG, false );
550
 
#endif
551
573
 
552
574
    add_bool( "skinned-playlist", true, NULL, SKINS2_PLAYLIST,
553
575
              SKINS2_PLAYLIST_LONG, false );
560
582
    add_shortcut( "skins" )
561
583
 
562
584
    add_submodule ()
563
 
#ifndef WIN32
564
 
        set_capability( "xwindow", 51 )
 
585
#ifdef WIN32
 
586
        set_capability( "vout window hwnd", 51 )
565
587
#else
566
 
        set_capability( "hwnd", 51 )
 
588
        set_capability( "vout window xid", 51 )
567
589
#endif
568
590
        set_callbacks( WindowOpen, WindowClose )
569
591
 
570
592
    add_submodule ()
571
593
        set_description( N_("Skins loader demux") )
572
 
        set_capability( "demux", 5 )
 
594
        set_capability( "access_demux", 5 )
573
595
        set_callbacks( DemuxOpen, NULL )
574
596
        add_shortcut( "skins" )
575
597