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

Revision 864, 21.7 kB (checked in by ppalmers, 13 years ago)

update license to GPLv2 or GPLv3 instead of GPLv2 or any later version. Update copyrights to reflect the new year

Line 
1 /*
2  * Copyright (C) 2005-2008 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 program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 2 of the License, or
12  * (at your option) version 3 of the License.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  *
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                     channelInfo->m_streamPosition + 1;
385             }
386             if ( !extPlugInfoCmd.fire() ) {
387                 debugError( "channel name command failed\n" );
388                 return false;
389             }
390             infoType = extPlugInfoCmd.getInfoType();
391             if ( infoType
392                  && infoType->m_plugChannelName )
393             {
394                 debugOutput( DEBUG_LEVEL_VERBOSE,
395                              "plug %d stream "
396                              "position %d: channel name = %s\n",
397                              m_id,
398                              channelInfo->m_streamPosition,
399                              infoType->m_plugChannelName->m_plugChannelName.c_str() );
400                 channelInfo->m_name =
401                     infoType->m_plugChannelName->m_plugChannelName;
402             }
403
404         }
405     }
406
407     return true;
408 }
409
410 bool
411 Plug::discoverClusterInfo()
412 {
413     if ( m_infoPlugType == eAPT_Sync )
414     {
415         // If the plug is of type sync it is either a normal 2 channel
416         // stream (not compound stream) or it is a compound stream
417         // with exactly one cluster. This depends on the
418         // extended stream format command version which is used.
419         // We are not interested in this plug so we skip it.
420         debugOutput( DEBUG_LEVEL_VERBOSE,
421                      "%s plug %d is of type sync -> skip\n",
422                      getName(),
423                      m_id );
424         return true;
425     }
426
427     for ( ClusterInfoVector::iterator clit = m_clusterInfos.begin();
428           clit != m_clusterInfos.end();
429           ++clit )
430     {
431         ClusterInfo* clusterInfo = &*clit;
432
433         ExtendedPlugInfoCmd extPlugInfoCmd = setPlugAddrToPlugInfoCmd();
434         ExtendedPlugInfoInfoType extendedPlugInfoInfoType(
435             ExtendedPlugInfoInfoType::eIT_ClusterInfo );
436         extendedPlugInfoInfoType.initialize();
437         extPlugInfoCmd.setInfoType( extendedPlugInfoInfoType );
438         extPlugInfoCmd.setVerbose( getDebugLevel() );
439
440         extPlugInfoCmd.getInfoType()->m_plugClusterInfo->m_clusterIndex =
441             clusterInfo->m_index;
442
443         if ( !extPlugInfoCmd.fire() ) {
444             debugError( "cluster info command failed\n" );
445             return false;
446         }
447
448         ExtendedPlugInfoInfoType* infoType = extPlugInfoCmd.getInfoType();
449         if ( infoType
450              && infoType->m_plugClusterInfo )
451         {
452             debugOutput( DEBUG_LEVEL_VERBOSE,
453                          "%s plug %d: cluster index = %d, "
454                          "portType %s, cluster name = %s\n",
455                          getName(),
456                          m_id,
457                          infoType->m_plugClusterInfo->m_clusterIndex,
458                          extendedPlugInfoClusterInfoPortTypeToString(
459                              infoType->m_plugClusterInfo->m_portType ),
460                          infoType->m_plugClusterInfo->m_clusterName.c_str() );
461
462             clusterInfo->m_portType = infoType->m_plugClusterInfo->m_portType;
463             clusterInfo->m_name = infoType->m_plugClusterInfo->m_clusterName;
464         }
465     }
466
467     return true;
468 }
469
470 bool
471 Plug::discoverConnectionsInput()
472 {
473     ExtendedPlugInfoCmd extPlugInfoCmd = setPlugAddrToPlugInfoCmd();
474     ExtendedPlugInfoInfoType extendedPlugInfoInfoType(
475         ExtendedPlugInfoInfoType::eIT_PlugInput );
476     extendedPlugInfoInfoType.initialize();
477     extPlugInfoCmd.setInfoType( extendedPlugInfoInfoType );
478     extPlugInfoCmd.setVerbose( getDebugLevel() );
479
480     if ( !extPlugInfoCmd.fire() ) {
481         debugError( "plug type command failed\n" );
482         return false;
483     }
484
485     if ( extPlugInfoCmd.getResponse() == AVCCommand::eR_Rejected ) {
486         // Plugs does not like to be asked about connections
487         debugOutput( DEBUG_LEVEL_VERBOSE, "Plug '%s' rejects "
488                      "connections command\n",
489                      getName() );
490         return true;
491     }
492
493     ExtendedPlugInfoInfoType* infoType = extPlugInfoCmd.getInfoType();
494     if ( infoType
495          && infoType->m_plugInput )
496     {
497         PlugAddressSpecificData* plugAddress
498             = infoType->m_plugInput->m_plugAddress;
499
500         if ( plugAddress->m_addressMode ==
501              PlugAddressSpecificData::ePAM_Undefined )
502         {
503             // This plug has no input connection
504             return true;
505         }
506
507         if ( !discoverConnectionsFromSpecificData( eAPD_Input,
508                                                    plugAddress,
509                                                    m_inputConnections ) )
510         {
511             debugWarning( "Could not discover connnections for plug '%s'\n",
512                           getName() );
513         }
514     } else {
515         debugError( "no valid info type for plug '%s'\n", getName() );
516         return false;
517     }
518
519     return true;
520 }
521
522 bool
523 Plug::discoverConnectionsOutput()
524 {
525     ExtendedPlugInfoCmd extPlugInfoCmd = setPlugAddrToPlugInfoCmd();
526     ExtendedPlugInfoInfoType extendedPlugInfoInfoType(
527         ExtendedPlugInfoInfoType::eIT_PlugOutput );
528     extendedPlugInfoInfoType.initialize();
529     extPlugInfoCmd.setInfoType( extendedPlugInfoInfoType );
530     extPlugInfoCmd.setVerbose( getDebugLevel() );
531
532     if ( !extPlugInfoCmd.fire() ) {
533         debugError( "plug type command failed\n" );
534         return false;
535     }
536
537     if ( extPlugInfoCmd.getResponse() == AVCCommand::eR_Rejected ) {
538         // Plugs does not like to be asked about connections
539         debugOutput( DEBUG_LEVEL_VERBOSE, "Plug '%s' rejects "
540                      "connections command\n",
541                      getName() );
542         return true;
543     }
544
545     ExtendedPlugInfoInfoType* infoType = extPlugInfoCmd.getInfoType();
546     if ( infoType
547          && infoType->m_plugOutput )
548     {
549         if ( infoType->m_plugOutput->m_nrOfOutputPlugs
550              != infoType->m_plugOutput->m_outputPlugAddresses.size() )
551         {
552             debugError( "number of output plugs (%d) disagree with "
553                         "number of elements in plug address vector (%d)\n",
554                         infoType->m_plugOutput->m_nrOfOutputPlugs,
555                         infoType->m_plugOutput->m_outputPlugAddresses.size());
556         }
557
558         if ( infoType->m_plugOutput->m_nrOfOutputPlugs == 0 ) {
559             // This plug has no output connections
560             return true;
561         }
562
563         for ( unsigned int i = 0;
564               i < infoType->m_plugOutput->m_outputPlugAddresses.size();
565               ++i )
566         {
567             PlugAddressSpecificData* plugAddress
568                 = infoType->m_plugOutput->m_outputPlugAddresses[i];
569
570             if ( !discoverConnectionsFromSpecificData( eAPD_Output,
571                                                        plugAddress,
572                                                        m_outputConnections ) )
573             {
574                 debugWarning( "Could not discover connnections for "
575                               "plug '%s'\n", getName() );
576             }
577         }
578     } else {
579         debugError( "no valid info type for plug '%s'\n", getName() );
580         return false;
581     }
582
583     return true;
584 }
585
586 ExtendedPlugInfoCmd
587 Plug::setPlugAddrToPlugInfoCmd()
588 {
589     ExtendedPlugInfoCmd extPlugInfoCmd( m_unit->get1394Service() );
590
591     switch( getSubunitType() ) {
592     case eST_Unit:
593         {
594             UnitPlugAddress::EPlugType ePlugType =
595                 UnitPlugAddress::ePT_Unknown;
596             switch ( m_addressType ) {
597                 case eAPA_PCR:
598                     ePlugType = UnitPlugAddress::ePT_PCR;
599                     break;
600                 case eAPA_ExternalPlug:
601                     ePlugType = UnitPlugAddress::ePT_ExternalPlug;
602                     break;
603                 case eAPA_AsynchronousPlug:
604                     ePlugType = UnitPlugAddress::ePT_AsynchronousPlug;
605                     break;
606                 default:
607                     ePlugType = UnitPlugAddress::ePT_Unknown;
608             }
609             UnitPlugAddress unitPlugAddress( ePlugType,
610                                              m_id );
611             extPlugInfoCmd.setPlugAddress(
612                 PlugAddress( convertPlugDirection( getPlugDirection() ),
613                              PlugAddress::ePAM_Unit,
614                              unitPlugAddress ) );
615         }
616         break;
617     case eST_Music:
618     case eST_Audio:
619         {
620             switch( m_addressType ) {
621             case eAPA_SubunitPlug:
622             {
623                 SubunitPlugAddress subunitPlugAddress( m_id );
624                 extPlugInfoCmd.setPlugAddress(
625                     PlugAddress(
626                         convertPlugDirection( getPlugDirection() ),
627                         PlugAddress::ePAM_Subunit,
628                         subunitPlugAddress ) );
629             }
630             break;
631             case eAPA_FunctionBlockPlug:
632             {
633                 FunctionBlockPlugAddress functionBlockPlugAddress(
634                     m_functionBlockType,
635                     m_functionBlockId,
636                     m_id );
637                 extPlugInfoCmd.setPlugAddress(
638                     PlugAddress(
639                         convertPlugDirection( getPlugDirection() ),
640                         PlugAddress::ePAM_FunctionBlock,
641                         functionBlockPlugAddress ) );
642             }
643             break;
644             default:
645                 extPlugInfoCmd.setPlugAddress(PlugAddress());
646             }
647         }
648         break;
649     default:
650         debugError( "Unknown subunit type\n" );
651     }
652
653     extPlugInfoCmd.setNodeId( m_unit->getConfigRom().getNodeId() );
654     extPlugInfoCmd.setCommandType( AVCCommand::eCT_Status );
655     extPlugInfoCmd.setSubunitId( getSubunitId() );
656     extPlugInfoCmd.setSubunitType( getSubunitType() );
657
658     return extPlugInfoCmd;
659 }
660
661 }
Note: See TracBrowser for help on using the browser.