root/trunk/libffado/src/bebob/bebob_avplug.cpp

Revision 632, 21.8 kB (checked in by ppalmers, 17 years ago)

fixes ticket:26 (test of trac post-commit hook)

Line 
1 /*
2  * Copyright (C) 2005-2007 by Daniel Wagner
3  *
4  * This file is part of FFADO
5  * FFADO = Free Firewire (pro-)audio drivers for linux
6  *
7  * FFADO is based upon FreeBoB
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License version 2.1, as published by the Free Software Foundation;
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21  * MA 02110-1301 USA
22  */
23
24 #include "bebob/bebob_avplug.h"
25 #include "bebob/bebob_avdevice.h"
26 #include "libieee1394/configrom.h"
27
28 #include "libieee1394/ieee1394service.h"
29 #include "libutil/cmd_serialize.h"
30
31 #include <sstream>
32
33 using namespace AVC;
34
35 namespace BeBoB {
36
37 Plug::Plug( AVC::Unit* unit,
38             AVC::Subunit* subunit,
39             AVC::function_block_type_t functionBlockType,
40             AVC::function_block_type_t functionBlockId,
41             AVC::Plug::EPlugAddressType plugAddressType,
42             AVC::Plug::EPlugDirection plugDirection,
43             AVC::plug_id_t plugId )
44     : AVC::Plug( unit,
45                  subunit,
46                  functionBlockType,
47                  functionBlockId,
48                  plugAddressType,
49                  plugDirection,
50                  plugId )
51 {
52     debugOutput( DEBUG_LEVEL_VERBOSE,
53                  "nodeId = %d, subunitType = %d, "
54                  "subunitId = %d, functionBlockType = %d, "
55                  "functionBlockId = %d, addressType = %d, "
56                  "direction = %d, id = %d\n",
57                  unit->getConfigRom().getNodeId(),
58                  getSubunitType(),
59                  getSubunitId(),
60                  functionBlockType,
61                  functionBlockId,
62                  plugAddressType,
63                  plugDirection,
64                  plugId );
65 }
66
67 Plug::Plug( const Plug& rhs )
68     : AVC::Plug( rhs )
69 {
70
71 }
72
73 Plug::Plug()
74     : AVC::Plug()
75 {
76 }
77
78 Plug::~Plug()
79 {
80
81 }
82
83 bool
84 Plug::discover()
85 {
86     if ( !discoverPlugType() ) {
87         debugError( "discover: Could not discover plug type (%d,%d,%d,%d,%d)\n",
88                     m_unit->getConfigRom().getNodeId(), getSubunitType(), getSubunitId(), m_direction, m_id );
89         return false;
90     }
91
92     if ( !discoverName() ) {
93         debugError( "Could not discover name (%d,%d,%d,%d,%d)\n",
94                     m_unit->getConfigRom().getNodeId(), getSubunitType(), getSubunitId(), m_direction, m_id );
95         return false;
96     }
97
98     if ( !discoverNoOfChannels() ) {
99         debugError( "Could not discover number of channels "
100                     "(%d,%d,%d,%d,%d)\n",
101                     m_unit->getConfigRom().getNodeId(), getSubunitType(), getSubunitId(), m_direction, m_id );
102         return false;
103     }
104
105     if ( !discoverChannelPosition() ) {
106         debugError( "Could not discover channel positions "
107                     "(%d,%d,%d,%d,%d)\n",
108                     m_unit->getConfigRom().getNodeId(), getSubunitType(), getSubunitId(), m_direction, m_id );
109         return false;
110     }
111
112     if ( !discoverChannelName() ) {
113         debugError( "Could not discover channel name "
114                     "(%d,%d,%d,%d,%d)\n",
115                     m_unit->getConfigRom().getNodeId(), getSubunitType(), getSubunitId(), m_direction, m_id );
116         return false;
117     }
118
119     if ( !discoverClusterInfo() ) {
120         debugError( "Could not discover channel name "
121                     "(%d,%d,%d,%d,%d)\n",
122                     m_unit->getConfigRom().getNodeId(), getSubunitType(), getSubunitId(), m_direction, m_id );
123         return false;
124     }
125
126     if ( !discoverStreamFormat() ) {
127         debugError( "Could not discover stream format "
128                     "(%d,%d,%d,%d,%d)\n",
129                     m_unit->getConfigRom().getNodeId(), getSubunitType(), getSubunitId(), m_direction, m_id );
130         return false;
131     }
132
133     if ( !discoverSupportedStreamFormats() ) {
134         debugError( "Could not discover supported stream formats "
135                     "(%d,%d,%d,%d,%d)\n",
136                     m_unit->getConfigRom().getNodeId(), getSubunitType(), getSubunitId(), m_direction, m_id );
137         return false;
138     }
139
140     return m_unit->getPlugManager().addPlug( *this );
141 }
142
143 bool
144 Plug::discoverConnections()
145 {
146     return discoverConnectionsInput() && discoverConnectionsOutput();
147 }
148
149 bool
150 Plug::discoverPlugType()
151 {
152     ExtendedPlugInfoCmd extPlugInfoCmd = setPlugAddrToPlugInfoCmd();
153     ExtendedPlugInfoInfoType extendedPlugInfoInfoType(
154         ExtendedPlugInfoInfoType::eIT_PlugType );
155     extendedPlugInfoInfoType.initialize();
156     extPlugInfoCmd.setInfoType( extendedPlugInfoInfoType );
157     extPlugInfoCmd.setVerbose( getDebugLevel() );
158
159     if ( !extPlugInfoCmd.fire() ) {
160         debugError( "plug type command failed\n" );
161         return false;
162     }
163
164     m_infoPlugType = eAPT_Unknown;
165
166     if ( extPlugInfoCmd.getResponse() == AVCCommand::eR_Implemented ) {
167
168         ExtendedPlugInfoInfoType* infoType = extPlugInfoCmd.getInfoType();
169         if ( infoType
170              && infoType->m_plugType )
171         {
172             plug_type_t plugType = infoType->m_plugType->m_plugType;
173
174             debugOutput( DEBUG_LEVEL_VERBOSE,
175                          "plug %d is of type %d (%s)\n",
176                          m_id,
177                          plugType,
178                          extendedPlugInfoPlugTypeToString( plugType ) );
179             switch ( plugType ) {
180             case ExtendedPlugInfoPlugTypeSpecificData::eEPIPT_IsoStream:
181                 m_infoPlugType = eAPT_IsoStream;
182                 break;
183             case ExtendedPlugInfoPlugTypeSpecificData::eEPIPT_AsyncStream:
184                 m_infoPlugType = eAPT_AsyncStream;
185                 break;
186             case ExtendedPlugInfoPlugTypeSpecificData::eEPIPT_Midi:
187                 m_infoPlugType = eAPT_Midi;
188                 break;
189             case ExtendedPlugInfoPlugTypeSpecificData::eEPIPT_Sync:
190                 m_infoPlugType = eAPT_Sync;
191                 break;
192             case ExtendedPlugInfoPlugTypeSpecificData::eEPIPT_Analog:
193                 m_infoPlugType = eAPT_Analog;
194                 break;
195             case ExtendedPlugInfoPlugTypeSpecificData::eEPIPT_Digital:
196                 m_infoPlugType = eAPT_Digital;
197                 break;
198             default:
199                 m_infoPlugType = eAPT_Unknown;
200
201             }
202         }
203     } else {
204         debugError( "Plug does not implement extended plug info plug "
205                     "type info command\n" );
206         return false;
207     }
208
209    return true;
210 }
211
212 bool
213 Plug::discoverName()
214 {
215     ExtendedPlugInfoCmd extPlugInfoCmd = setPlugAddrToPlugInfoCmd();
216     ExtendedPlugInfoInfoType extendedPlugInfoInfoType(
217         ExtendedPlugInfoInfoType::eIT_PlugName );
218     extendedPlugInfoInfoType.initialize();
219     extPlugInfoCmd.setInfoType( extendedPlugInfoInfoType );
220     extPlugInfoCmd.setVerbose( getDebugLevel() );
221
222     if ( !extPlugInfoCmd.fire() ) {
223         debugError( "name command failed\n" );
224         return false;
225     }
226
227     ExtendedPlugInfoInfoType* infoType = extPlugInfoCmd.getInfoType();
228     if ( infoType
229          && infoType->m_plugName )
230     {
231         std::string name =
232             infoType->m_plugName->m_name;
233
234         debugOutput( DEBUG_LEVEL_VERBOSE,
235                      "plug %d has name '%s'\n",
236                      m_id,
237                      name.c_str() );
238
239         m_name = name;
240     }
241     return true;
242 }
243
244 bool
245 Plug::discoverNoOfChannels()
246 {
247     ExtendedPlugInfoCmd extPlugInfoCmd = setPlugAddrToPlugInfoCmd();
248     //extPlugInfoCmd.setVerbose( true );
249     ExtendedPlugInfoInfoType extendedPlugInfoInfoType(
250         ExtendedPlugInfoInfoType::eIT_NoOfChannels );
251     extendedPlugInfoInfoType.initialize();
252     extPlugInfoCmd.setInfoType( extendedPlugInfoInfoType );
253     extPlugInfoCmd.setVerbose( getDebugLevel() );
254
255     if ( !extPlugInfoCmd.fire() ) {
256         debugError( "number of channels command failed\n" );
257         return false;
258     }
259
260     ExtendedPlugInfoInfoType* infoType = extPlugInfoCmd.getInfoType();
261     if ( infoType
262          && infoType->m_plugNrOfChns )
263     {
264         nr_of_channels_t nrOfChannels
265             = infoType->m_plugNrOfChns->m_nrOfChannels;
266
267         debugOutput( DEBUG_LEVEL_VERBOSE,
268                      "plug %d has %d channels\n",
269                      m_id,
270                      nrOfChannels );
271
272         m_nrOfChannels = nrOfChannels;
273     }
274     return true;
275 }
276
277 bool
278 Plug::discoverChannelPosition()
279 {
280     ExtendedPlugInfoCmd extPlugInfoCmd = setPlugAddrToPlugInfoCmd();
281     ExtendedPlugInfoInfoType extendedPlugInfoInfoType(
282         ExtendedPlugInfoInfoType::eIT_ChannelPosition );
283     extendedPlugInfoInfoType.initialize();
284     extPlugInfoCmd.setInfoType( extendedPlugInfoInfoType );
285     extPlugInfoCmd.setVerbose( getDebugLevel() );
286
287     if ( !extPlugInfoCmd.fire() ) {
288         debugError( "channel position command failed\n" );
289         return false;
290     }
291
292     ExtendedPlugInfoInfoType* infoType = extPlugInfoCmd.getInfoType();
293     if ( infoType
294          && infoType->m_plugChannelPosition )
295     {
296         if ( !copyClusterInfo( *( infoType->m_plugChannelPosition ) ) ) {
297             debugError( "Could not copy channel position "
298                         "information\n" );
299             return false;
300         }
301
302         debugOutput( DEBUG_LEVEL_VERBOSE,
303                      "plug %d: channel position information "
304                      "retrieved\n",
305                      m_id );
306
307         debugOutputClusterInfos( DEBUG_LEVEL_VERBOSE );
308     }
309
310     return true;
311 }
312
313 bool
314 Plug::copyClusterInfo(ExtendedPlugInfoPlugChannelPositionSpecificData&
315                         channelPositionData )
316 {
317     int index = 1;
318     for ( ExtendedPlugInfoPlugChannelPositionSpecificData::ClusterInfoVector::const_iterator it
319               = channelPositionData.m_clusterInfos.begin();
320           it != channelPositionData.m_clusterInfos.end();
321           ++it )
322     {
323         const ExtendedPlugInfoPlugChannelPositionSpecificData::ClusterInfo*
324             extPlugSpClusterInfo = &( *it );
325
326         ClusterInfo clusterInfo;
327         clusterInfo.m_nrOfChannels = extPlugSpClusterInfo->m_nrOfChannels;
328         clusterInfo.m_index = index;
329         index++;
330
331         for (  ExtendedPlugInfoPlugChannelPositionSpecificData::ChannelInfoVector::const_iterator cit
332                   = extPlugSpClusterInfo->m_channelInfos.begin();
333               cit != extPlugSpClusterInfo->m_channelInfos.end();
334               ++cit )
335         {
336             const ExtendedPlugInfoPlugChannelPositionSpecificData::ChannelInfo*
337                 extPlugSpChannelInfo = &( *cit );
338
339             ChannelInfo channelInfo;
340             channelInfo.m_streamPosition =
341                 extPlugSpChannelInfo->m_streamPosition-1;
342             // FIXME: this can only become a mess with the two meanings
343             //        of the location parameter. the audio style meaning
344             //        starts from 1, the midi style meaning from 0
345             //        lucky for us we recalculate this for the midi channels
346             //        and don't use this value.
347             channelInfo.m_location =
348                 extPlugSpChannelInfo->m_location;
349
350             clusterInfo.m_channelInfos.push_back( channelInfo );
351         }
352         m_clusterInfos.push_back( clusterInfo );
353     }
354
355     return true;
356 }
357
358 bool
359 Plug::discoverChannelName()
360 {
361     for ( ClusterInfoVector::iterator clit = m_clusterInfos.begin();
362           clit != m_clusterInfos.end();
363           ++clit )
364     {
365         ClusterInfo* clitInfo = &*clit;
366
367         for ( ChannelInfoVector::iterator pit =  clitInfo->m_channelInfos.begin();
368               pit != clitInfo->m_channelInfos.end();
369               ++pit )
370         {
371             ChannelInfo* channelInfo = &*pit;
372
373             ExtendedPlugInfoCmd extPlugInfoCmd = setPlugAddrToPlugInfoCmd();
374             ExtendedPlugInfoInfoType extendedPlugInfoInfoType(
375                 ExtendedPlugInfoInfoType::eIT_ChannelName );
376             extendedPlugInfoInfoType.initialize();
377             extPlugInfoCmd.setInfoType( extendedPlugInfoInfoType );
378             extPlugInfoCmd.setVerbose( getDebugLevel() );
379
380             ExtendedPlugInfoInfoType* infoType =
381                 extPlugInfoCmd.getInfoType();
382             if ( infoType ) {
383                 infoType->m_plugChannelName->m_streamPosition =
384                    
385                     channelInfo->m_streamPosition + 1;
386             }
387             if ( !extPlugInfoCmd.fire() ) {
388                 debugError( "channel name command failed\n" );
389                 return false;
390             }
391             infoType = extPlugInfoCmd.getInfoType();
392             if ( infoType
393                  && infoType->m_plugChannelName )
394             {
395                 debugOutput( DEBUG_LEVEL_VERBOSE,
396                              "plug %d stream "
397                              "position %d: channel name = %s\n",
398                              m_id,
399                              channelInfo->m_streamPosition,
400                              infoType->m_plugChannelName->m_plugChannelName.c_str() );
401                 channelInfo->m_name =
402                     infoType->m_plugChannelName->m_plugChannelName;
403             }
404
405         }
406     }
407
408     return true;
409 }
410
411 bool
412 Plug::discoverClusterInfo()
413 {
414     if ( m_infoPlugType == eAPT_Sync )
415     {
416         // If the plug is of type sync it is either a normal 2 channel
417         // stream (not compound stream) or it is a compound stream
418         // with exactly one cluster. This depends on the
419         // extended stream format command version which is used.
420         // We are not interested in this plug so we skip it.
421         debugOutput( DEBUG_LEVEL_VERBOSE,
422                      "%s plug %d is of type sync -> skip\n",
423                      getName(),
424                      m_id );
425         return true;
426     }
427
428     for ( ClusterInfoVector::iterator clit = m_clusterInfos.begin();
429           clit != m_clusterInfos.end();
430           ++clit )
431     {
432         ClusterInfo* clusterInfo = &*clit;
433
434         ExtendedPlugInfoCmd extPlugInfoCmd = setPlugAddrToPlugInfoCmd();
435         ExtendedPlugInfoInfoType extendedPlugInfoInfoType(
436             ExtendedPlugInfoInfoType::eIT_ClusterInfo );
437         extendedPlugInfoInfoType.initialize();
438         extPlugInfoCmd.setInfoType( extendedPlugInfoInfoType );
439         extPlugInfoCmd.setVerbose( getDebugLevel() );
440
441         extPlugInfoCmd.getInfoType()->m_plugClusterInfo->m_clusterIndex =
442             clusterInfo->m_index;
443
444         if ( !extPlugInfoCmd.fire() ) {
445             debugError( "cluster info command failed\n" );
446             return false;
447         }
448
449         ExtendedPlugInfoInfoType* infoType = extPlugInfoCmd.getInfoType();
450         if ( infoType
451              && infoType->m_plugClusterInfo )
452         {
453             debugOutput( DEBUG_LEVEL_VERBOSE,
454                          "%s plug %d: cluster index = %d, "
455                          "portType %s, cluster name = %s\n",
456                          getName(),
457                          m_id,
458                          infoType->m_plugClusterInfo->m_clusterIndex,
459                          extendedPlugInfoClusterInfoPortTypeToString(
460                              infoType->m_plugClusterInfo->m_portType ),
461                          infoType->m_plugClusterInfo->m_clusterName.c_str() );
462
463             clusterInfo->m_portType = infoType->m_plugClusterInfo->m_portType;
464             clusterInfo->m_name = infoType->m_plugClusterInfo->m_clusterName;
465         }
466     }
467
468     return true;
469 }
470
471 bool
472 Plug::discoverConnectionsInput()
473 {
474     ExtendedPlugInfoCmd extPlugInfoCmd = setPlugAddrToPlugInfoCmd();
475     ExtendedPlugInfoInfoType extendedPlugInfoInfoType(
476         ExtendedPlugInfoInfoType::eIT_PlugInput );
477     extendedPlugInfoInfoType.initialize();
478     extPlugInfoCmd.setInfoType( extendedPlugInfoInfoType );
479     extPlugInfoCmd.setVerbose( getDebugLevel() );
480
481     if ( !extPlugInfoCmd.fire() ) {
482         debugError( "plug type command failed\n" );
483         return false;
484     }
485
486     if ( extPlugInfoCmd.getResponse() == AVCCommand::eR_Rejected ) {
487         // Plugs does not like to be asked about connections
488         debugOutput( DEBUG_LEVEL_VERBOSE, "Plug '%s' rejects "
489                      "connections command\n",
490                      getName() );
491         return true;
492     }
493
494     ExtendedPlugInfoInfoType* infoType = extPlugInfoCmd.getInfoType();
495     if ( infoType
496          && infoType->m_plugInput )
497     {
498         PlugAddressSpecificData* plugAddress
499             = infoType->m_plugInput->m_plugAddress;
500
501         if ( plugAddress->m_addressMode ==
502              PlugAddressSpecificData::ePAM_Undefined )
503         {
504             // This plug has no input connection
505             return true;
506         }
507
508         if ( !discoverConnectionsFromSpecificData( eAPD_Input,
509                                                    plugAddress,
510                                                    m_inputConnections ) )
511         {
512             debugWarning( "Could not discover connnections for plug '%s'\n",
513                           getName() );
514         }
515     } else {
516         debugError( "no valid info type for plug '%s'\n", getName() );
517         return false;
518     }
519
520     return true;
521 }
522
523 bool
524 Plug::discoverConnectionsOutput()
525 {
526     ExtendedPlugInfoCmd extPlugInfoCmd = setPlugAddrToPlugInfoCmd();
527     ExtendedPlugInfoInfoType extendedPlugInfoInfoType(
528         ExtendedPlugInfoInfoType::eIT_PlugOutput );
529     extendedPlugInfoInfoType.initialize();
530     extPlugInfoCmd.setInfoType( extendedPlugInfoInfoType );
531     extPlugInfoCmd.setVerbose( getDebugLevel() );
532
533     if ( !extPlugInfoCmd.fire() ) {
534         debugError( "plug type command failed\n" );
535         return false;
536     }
537
538     if ( extPlugInfoCmd.getResponse() == AVCCommand::eR_Rejected ) {
539         // Plugs does not like to be asked about connections
540         debugOutput( DEBUG_LEVEL_VERBOSE, "Plug '%s' rejects "
541                      "connections command\n",
542                      getName() );
543         return true;
544     }
545
546     ExtendedPlugInfoInfoType* infoType = extPlugInfoCmd.getInfoType();
547     if ( infoType
548          && infoType->m_plugOutput )
549     {
550         if ( infoType->m_plugOutput->m_nrOfOutputPlugs
551              != infoType->m_plugOutput->m_outputPlugAddresses.size() )
552         {
553             debugError( "number of output plugs (%d) disagree with "
554                         "number of elements in plug address vector (%d)\n",
555                         infoType->m_plugOutput->m_nrOfOutputPlugs,
556                         infoType->m_plugOutput->m_outputPlugAddresses.size());
557         }
558
559         if ( infoType->m_plugOutput->m_nrOfOutputPlugs == 0 ) {
560             // This plug has no output connections
561             return true;
562         }
563
564         for ( unsigned int i = 0;
565               i < infoType->m_plugOutput->m_outputPlugAddresses.size();
566               ++i )
567         {
568             PlugAddressSpecificData* plugAddress
569                 = infoType->m_plugOutput->m_outputPlugAddresses[i];
570
571             if ( !discoverConnectionsFromSpecificData( eAPD_Output,
572                                                        plugAddress,
573                                                        m_outputConnections ) )
574             {
575                 debugWarning( "Could not discover connnections for "
576                               "plug '%s'\n", getName() );
577             }
578         }
579     } else {
580         debugError( "no valid info type for plug '%s'\n", getName() );
581         return false;
582     }
583
584     return true;
585 }
586
587 ExtendedPlugInfoCmd
588 Plug::setPlugAddrToPlugInfoCmd()
589 {
590     ExtendedPlugInfoCmd extPlugInfoCmd( m_unit->get1394Service() );
591
592     switch( getSubunitType() ) {
593     case eST_Unit:
594         {
595             UnitPlugAddress::EPlugType ePlugType =
596                 UnitPlugAddress::ePT_Unknown;
597             switch ( m_addressType ) {
598                 case eAPA_PCR:
599                     ePlugType = UnitPlugAddress::ePT_PCR;
600                     break;
601                 case eAPA_ExternalPlug:
602                     ePlugType = UnitPlugAddress::ePT_ExternalPlug;
603                     break;
604                 case eAPA_AsynchronousPlug:
605                     ePlugType = UnitPlugAddress::ePT_AsynchronousPlug;
606                     break;
607                 default:
608                     ePlugType = UnitPlugAddress::ePT_Unknown;
609             }
610             UnitPlugAddress unitPlugAddress( ePlugType,
611                                              m_id );
612             extPlugInfoCmd.setPlugAddress(
613                 PlugAddress( convertPlugDirection( getPlugDirection() ),
614                              PlugAddress::ePAM_Unit,
615                              unitPlugAddress ) );
616         }
617         break;
618     case eST_Music:
619     case eST_Audio:
620         {
621             switch( m_addressType ) {
622             case eAPA_SubunitPlug:
623             {
624                 SubunitPlugAddress subunitPlugAddress( m_id );
625                 extPlugInfoCmd.setPlugAddress(
626                     PlugAddress(
627                         convertPlugDirection( getPlugDirection() ),
628                         PlugAddress::ePAM_Subunit,
629                         subunitPlugAddress ) );
630             }
631             break;
632             case eAPA_FunctionBlockPlug:
633             {
634                 FunctionBlockPlugAddress functionBlockPlugAddress(
635                     m_functionBlockType,
636                     m_functionBlockId,
637                     m_id );
638                 extPlugInfoCmd.setPlugAddress(
639                     PlugAddress(
640                         convertPlugDirection( getPlugDirection() ),
641                         PlugAddress::ePAM_FunctionBlock,
642                         functionBlockPlugAddress ) );
643             }
644             break;
645             default:
646                 extPlugInfoCmd.setPlugAddress(PlugAddress());
647             }
648         }
649         break;
650     default:
651         debugError( "Unknown subunit type\n" );
652     }
653
654     extPlugInfoCmd.setNodeId( m_unit->getConfigRom().getNodeId() );
655     extPlugInfoCmd.setCommandType( AVCCommand::eCT_Status );
656     extPlugInfoCmd.setSubunitId( getSubunitId() );
657     extPlugInfoCmd.setSubunitType( getSubunitType() );
658
659     return extPlugInfoCmd;
660 }
661
662 }
Note: See TracBrowser for help on using the browser.