Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/core5/src/libraries/core/input/InputManager.cc @ 5869

Last change on this file since 5869 was 5869, checked in by rgrieder, 15 years ago

Applied ScopedSingletonManager to KeyBinderManager and KeyDetector.

  • Property svn:eol-style set to native
File size: 25.5 KB
Line 
1/*
2 *   ORXONOX - the hottest 3D action shooter ever to exist
3 *                    > www.orxonox.net <
4 *
5 *
6 *   License notice:
7 *
8 *   This program is free software; you can redistribute it and/or
9 *   modify it under the terms of the GNU General Public License
10 *   as published by the Free Software Foundation; either version 2
11 *   of the License, or (at your option) any later version.
12 *
13 *   This program 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
16 *   GNU General Public License for more details.
17 *
18 *   You should have received a copy of the GNU General Public License
19 *   along with this program; if not, write to the Free Software
20 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 *
22 *   Author:
23 *      Reto Grieder
24 *   Co-authors:
25 *      ...
26 *
27 */
28
29/**
30@file
31@brief
32    Implementation of the InputManager and a static variable from the InputHandler.
33*/
34
35#include "InputManager.h"
36
37#include <cassert>
38#include <climits>
39#include <ois/OISException.h>
40#include <ois/OISInputManager.h>
41#include <boost/foreach.hpp>
42
43#include "util/Clock.h"
44#include "util/Convert.h"
45#include "util/Exception.h"
46#include "util/ScopeGuard.h"
47#include "core/CoreIncludes.h"
48#include "core/ConfigValueIncludes.h"
49#include "core/ConsoleCommand.h"
50#include "core/CommandLine.h"
51#include "core/Functor.h"
52#include "core/GraphicsManager.h"
53
54#include "InputBuffer.h"
55#include "JoyStick.h"
56#include "JoyStickQuantityListener.h"
57#include "Mouse.h"
58#include "Keyboard.h"
59
60namespace orxonox
61{
62    SetCommandLineSwitch(keyboard_no_grab).information("Whether not to exclusively grab the keyboard");
63
64    // Abuse of this source file for the InputHandler
65    InputHandler InputHandler::EMPTY;
66
67    InputManager* InputManager::singletonPtr_s = 0;
68
69    //! Defines the |= operator for easier use.
70    inline InputManager::State operator|=(InputManager::State& lval, InputManager::State rval)
71    {
72        return (lval = (InputManager::State)(lval | rval));
73    }
74
75    //! Defines the &= operator for easier use.
76    inline InputManager::State operator&=(InputManager::State& lval, int rval)
77    {
78        return (lval = (InputManager::State)(lval & rval));
79    }
80
81    // ############################################################
82    // #####                  Initialisation                  #####
83    // ##########                                        ##########
84    // ############################################################
85    InputManager::InputManager()
86        : internalState_(Bad)
87        , oisInputManager_(0)
88        , devices_(2)
89        , bExclusiveMouse_(false)
90        , emptyState_(0)
91        , calibratorCallbackHandler_(0)
92    {
93        RegisterRootObject(InputManager);
94
95        CCOUT(4) << "Constructing..." << std::endl;
96
97        this->setConfigValues();
98
99        this->loadDevices();
100
101        // Lowest priority empty InputState
102        emptyState_ = createInputState("empty", false, false, InputStatePriority::Empty);
103        emptyState_->setHandler(&InputHandler::EMPTY);
104        activeStates_[emptyState_->getPriority()] = emptyState_;
105
106        // Joy stick calibration helper callback
107        InputState* calibrator = createInputState("calibrator", false, false, InputStatePriority::Calibrator);
108        calibrator->setHandler(&InputHandler::EMPTY);
109        calibratorCallbackHandler_ = new InputBuffer();
110        calibratorCallbackHandler_->registerListener(this, &InputManager::stopCalibration, '\r', true);
111        calibrator->setKeyHandler(calibratorCallbackHandler_);
112
113        this->updateActiveStates();
114
115        // calibrate console command
116        this->getIdentifier()->addConsoleCommand(createConsoleCommand(createFunctor(&InputManager::calibrate, this), "calibrate"), true);
117        // reload console command
118        this->getIdentifier()->addConsoleCommand(createConsoleCommand(createFunctor(&InputManager::reload, this), "reload"), false);
119
120        CCOUT(4) << "Construction complete." << std::endl;
121        internalState_ = Nothing;
122    }
123
124    void InputManager::setConfigValues()
125    {
126    }
127
128    /**
129    @brief
130        Creates the OIS::InputMananger, the keyboard, the mouse and
131        the joys ticks. If either of the first two fail, this method throws an exception.
132    @param windowWidth
133        The width of the render window
134    @param windowHeight
135        The height of the render window
136    */
137    void InputManager::loadDevices()
138    {
139        CCOUT(4) << "Loading input devices..." << std::endl;
140
141        // When loading the devices they should not already be loaded
142        assert(internalState_ & Bad);
143        assert(devices_[InputDeviceEnumerator::Mouse] == 0);
144        assert(devices_[InputDeviceEnumerator::Keyboard] == 0);
145        assert(devices_.size() == InputDeviceEnumerator::FirstJoyStick);
146
147        // Fill parameter list
148        OIS::ParamList paramList;
149        size_t windowHnd = GraphicsManager::getInstance().getRenderWindowHandle();
150        paramList.insert(std::make_pair("WINDOW", multi_cast<std::string>(windowHnd)));
151#if defined(ORXONOX_PLATFORM_WINDOWS)
152        paramList.insert(std::make_pair("w32_keyboard", "DISCL_NONEXCLUSIVE"));
153        paramList.insert(std::make_pair("w32_keyboard", "DISCL_FOREGROUND"));
154        paramList.insert(std::make_pair("w32_mouse", "DISCL_FOREGROUND"));
155        if (bExclusiveMouse_ || GraphicsManager::getInstance().isFullScreen())
156        {
157            // Disable Windows key plus special keys (like play, stop, next, etc.)
158            paramList.insert(std::make_pair("w32_keyboard", "DISCL_NOWINKEY"));
159            paramList.insert(std::make_pair("w32_mouse", "DISCL_EXCLUSIVE"));
160        }
161        else
162            paramList.insert(std::make_pair("w32_mouse", "DISCL_NONEXCLUSIVE"));
163#elif defined(ORXONOX_PLATFORM_LINUX)
164        // Enabling this is probably a bad idea, but whenever orxonox crashes, the setting stays on
165        // Trouble might be that the Pressed event occurs a bit too often...
166        paramList.insert(std::make_pair("XAutoRepeatOn", "true"));
167
168        if (bExclusiveMouse_ || GraphicsManager::getInstance().isFullScreen())
169        {
170            if (CommandLine::getValue("keyboard_no_grab").getBool())
171                paramList.insert(std::make_pair("x11_keyboard_grab", "false"));
172            else
173                paramList.insert(std::make_pair("x11_keyboard_grab", "true"));
174            paramList.insert(std::make_pair("x11_mouse_grab",  "true"));
175            paramList.insert(std::make_pair("x11_mouse_hide", "true"));
176        }
177        else
178        {
179            paramList.insert(std::make_pair("x11_keyboard_grab", "false"));
180            paramList.insert(std::make_pair("x11_mouse_grab",  "false"));
181            paramList.insert(std::make_pair("x11_mouse_hide", "false"));
182        }
183#endif
184
185        try
186        {
187            oisInputManager_ = OIS::InputManager::createInputSystem(paramList);
188            // Exception-safety
189            Loki::ScopeGuard guard = Loki::MakeGuard(OIS::InputManager::destroyInputSystem, oisInputManager_);
190            CCOUT(ORX_DEBUG) << "Created OIS input manager." << std::endl;
191
192            if (oisInputManager_->getNumberOfDevices(OIS::OISKeyboard) > 0)
193                devices_[InputDeviceEnumerator::Keyboard] = new Keyboard(InputDeviceEnumerator::Keyboard, oisInputManager_);
194            else
195                ThrowException(InitialisationFailed, "InputManager: No keyboard found, cannot proceed!");
196
197            // Successful initialisation
198            guard.Dismiss();
199        }
200        catch (const std::exception& ex)
201        {
202            oisInputManager_ = NULL;
203            internalState_ |= Bad;
204            ThrowException(InitialisationFailed, "Could not initialise the input system: " << ex.what());
205        }
206
207        this->loadMouse();
208        this->loadJoySticks();
209
210        // Reorder states in case some joy sticks were added/removed
211        this->updateActiveStates();
212
213        CCOUT(4) << "Input devices loaded." << std::endl;
214    }
215
216    //! Creates a new orxonox::Mouse
217    void InputManager::loadMouse()
218    {
219        if (oisInputManager_->getNumberOfDevices(OIS::OISMouse) > 0)
220        {
221            try
222            {
223                devices_[InputDeviceEnumerator::Mouse] = new Mouse(InputDeviceEnumerator::Mouse, oisInputManager_);
224            }
225            catch (const std::exception& ex)
226            {
227                CCOUT(2) << "Warning: Failed to create Mouse:" << ex.what() << std::endl
228                         << "Proceeding without mouse support." << std::endl;
229            }
230        }
231        else
232            CCOUT(ORX_WARNING) << "Warning: No mouse found! Proceeding without mouse support." << std::endl;
233    }
234
235    //! Creates as many joy sticks as are available.
236    void InputManager::loadJoySticks()
237    {
238        for (int i = 0; i < oisInputManager_->getNumberOfDevices(OIS::OISJoyStick); i++)
239        {
240            try
241            {
242                devices_.push_back(new JoyStick(InputDeviceEnumerator::FirstJoyStick + i, oisInputManager_));
243            }
244            catch (const std::exception& ex)
245            {
246                CCOUT(2) << "Warning: Failed to create joy stick: " << ex.what() << std::endl;
247            }
248        }
249
250        // inform all JoyStick Device Number Listeners
251        std::vector<JoyStick*> joyStickList;
252        for (unsigned int i = InputDeviceEnumerator::FirstJoyStick; i < devices_.size(); ++i)
253            joyStickList.push_back(static_cast<JoyStick*>(devices_[i]));
254        JoyStickQuantityListener::changeJoyStickQuantity(joyStickList);
255    }
256
257    // ############################################################
258    // #####                    Destruction                   #####
259    // ##########                                        ##########
260    // ############################################################
261
262    InputManager::~InputManager()
263    {
264        CCOUT(3) << "Destroying..." << std::endl;
265
266        // Destroy calibrator helper handler and state
267        this->destroyState("calibrator");
268        // Destroy KeyDetector and state
269        calibratorCallbackHandler_->destroy();
270        // destroy the empty InputState
271        this->destroyStateInternal(this->emptyState_);
272
273        // destroy all user InputStates
274        while (statesByName_.size() > 0)
275            this->destroyStateInternal((*statesByName_.rbegin()).second);
276
277        if (!(internalState_ & Bad))
278            this->destroyDevices();
279
280        CCOUT(3) << "Destruction complete." << std::endl;
281    }
282
283    /**
284    @brief
285        Destoys all input devices (joy sticks, mouse, keyboard and OIS::InputManager)
286    @throw
287        Method does not throw
288    */
289    void InputManager::destroyDevices()
290    {
291        CCOUT(4) << "Destroying devices..." << std::endl;
292
293        BOOST_FOREACH(InputDevice*& device, devices_)
294        {
295            if (device == NULL)
296                continue;
297            std::string className = device->getClassName();
298            try
299            {
300                delete device;
301                device = 0;
302                CCOUT(4) << className << " destroyed." << std::endl;
303            }
304            catch (...)
305            {
306                COUT(1) << className << " destruction failed: " << Exception::handleMessage() << std::endl
307                        << "    Potential resource leak!" << std::endl;
308            }
309        }
310        devices_.resize(InputDeviceEnumerator::FirstJoyStick);
311
312        assert(oisInputManager_ != NULL);
313        try
314        {
315            OIS::InputManager::destroyInputSystem(oisInputManager_);
316        }
317        catch (...)
318        {
319            COUT(1) << "OIS::InputManager destruction failed" << Exception::handleMessage() << std::endl
320                    << "    Potential resource leak!" << std::endl;
321        }
322        oisInputManager_ = NULL;
323
324        internalState_ |= Bad;
325        CCOUT(4) << "Destroyed devices." << std::endl;
326    }
327
328    // ############################################################
329    // #####                     Reloading                    #####
330    // ##########                                        ##########
331    // ############################################################
332
333    void InputManager::reload()
334    {
335        if (internalState_ & Ticking)
336        {
337            // We cannot destroy OIS right now, because reload was probably
338            // caused by a user clicking on a GUI item. The stack trace would then
339            // include an OIS method. So it would be a very bad thing to destroy it..
340            internalState_ |= ReloadRequest;
341        }
342        else if (internalState_ & Calibrating)
343            CCOUT(2) << "Warning: Cannot reload input system. Joy sticks are currently being calibrated." << std::endl;
344        else
345            reloadInternal();
346    }
347
348    //! Internal reload method. Destroys the OIS devices and loads them again.
349    void InputManager::reloadInternal()
350    {
351        CCOUT(3) << "Reloading ..." << std::endl;
352
353        this->destroyDevices();
354        this->loadDevices();
355
356        internalState_ &= ~Bad;
357        internalState_ &= ~ReloadRequest;
358        CCOUT(4) << "Reloading complete." << std::endl;
359    }
360
361    // ############################################################
362    // #####                  Runtime Methods                 #####
363    // ##########                                        ##########
364    // ############################################################
365
366    void InputManager::update(const Clock& time)
367    {
368        if (internalState_ & Bad)
369            ThrowException(General, "InputManager was not correctly reloaded.");
370
371        else if (internalState_ & ReloadRequest)
372            reloadInternal();
373
374        // check for states to leave
375        if (!stateLeaveRequests_.empty())
376        {
377            for (std::set<InputState*>::iterator it = stateLeaveRequests_.begin();
378                it != stateLeaveRequests_.end(); ++it)
379            {
380                (*it)->left();
381                // just to be sure that the state actually is registered
382                assert(statesByName_.find((*it)->getName()) != statesByName_.end());
383
384                activeStates_.erase((*it)->getPriority());
385                if ((*it)->getPriority() < InputStatePriority::HighPriority)
386                    (*it)->setPriority(0);
387                updateActiveStates();
388            }
389            stateLeaveRequests_.clear();
390        }
391
392        // check for states to enter
393        if (!stateEnterRequests_.empty())
394        {
395            for (std::set<InputState*>::const_iterator it = stateEnterRequests_.begin();
396                it != stateEnterRequests_.end(); ++it)
397            {
398                // just to be sure that the state actually is registered
399                assert(statesByName_.find((*it)->getName()) != statesByName_.end());
400
401                if ((*it)->getPriority() == 0)
402                {
403                    // Get smallest possible priority between 1 and maxStateStackSize_s
404                    for(std::map<int, InputState*>::reverse_iterator rit = activeStates_.rbegin();
405                        rit != activeStates_.rend(); ++rit)
406                    {
407                        if (rit->first < InputStatePriority::HighPriority)
408                        {
409                            (*it)->setPriority(rit->first + 1);
410                            break;
411                        }
412                    }
413                    // In case no normal handler was on the stack
414                    if ((*it)->getPriority() == 0)
415                        (*it)->setPriority(1);
416                }
417                activeStates_[(*it)->getPriority()] = (*it);
418                updateActiveStates();
419                (*it)->entered();
420            }
421            stateEnterRequests_.clear();
422        }
423
424        // check for states to destroy
425        if (!stateDestroyRequests_.empty())
426        {
427            for (std::set<InputState*>::iterator it = stateDestroyRequests_.begin();
428                it != stateDestroyRequests_.end(); ++it)
429            {
430                destroyStateInternal((*it));
431            }
432            stateDestroyRequests_.clear();
433        }
434
435        // check whether a state has changed its EMPTY situation
436        bool bUpdateRequired = false;
437        for (std::map<int, InputState*>::iterator it = activeStates_.begin(); it != activeStates_.end(); ++it)
438        {
439            if (it->second->hasExpired())
440            {
441                it->second->resetExpiration();
442                bUpdateRequired = true;
443            }
444        }
445        if (bUpdateRequired)
446            updateActiveStates();
447
448        // mark that we now start capturing and distributing input
449        internalState_ |= Ticking;
450
451        // Capture all the input and handle it
452        BOOST_FOREACH(InputDevice* device, devices_)
453            if (device != NULL)
454                device->update(time);
455
456        // Update the states
457        for (unsigned int i = 0; i < activeStatesTicked_.size(); ++i)
458            activeStatesTicked_[i]->update(time.getDeltaTime());
459
460        internalState_ &= ~Ticking;
461    }
462
463    /**
464    @brief
465        Updates the currently active states (according to activeStates_) for each device.
466        Also, a list of all active states (no duplicates!) is compiled for the general update().
467    */
468    void InputManager::updateActiveStates()
469    {
470        assert((internalState_ & InputManager::Ticking) == 0);
471        // temporary resize
472        for (unsigned int i = 0; i < devices_.size(); ++i)
473        {
474            if (devices_[i] == NULL)
475                continue;
476            std::vector<InputState*>& states = devices_[i]->getStateListRef();
477            bool occupied = false;
478            states.clear();
479            for (std::map<int, InputState*>::reverse_iterator rit = activeStates_.rbegin(); rit != activeStates_.rend(); ++rit)
480            {
481                if (rit->second->isInputDeviceEnabled(i) && (!occupied || rit->second->bAlwaysGetsInput_))
482                {
483                    states.push_back(rit->second);
484                    if (!rit->second->bTransparent_)
485                        occupied = true;
486                }
487            }
488        }
489
490        // update tickables (every state will only appear once)
491        // Using a std::set to avoid duplicates
492        std::set<InputState*> tempSet;
493        for (unsigned int i = 0; i < devices_.size(); ++i)
494            if (devices_[i] != NULL)
495                for (unsigned int iState = 0; iState < devices_[i]->getStateListRef().size(); ++iState)
496                    tempSet.insert(devices_[i]->getStateListRef()[iState]);
497
498        // copy the content of the std::set back to the actual vector
499        activeStatesTicked_.clear();
500        for (std::set<InputState*>::const_iterator it = tempSet.begin();it != tempSet.end(); ++it)
501            activeStatesTicked_.push_back(*it);
502
503        // Check whether we have to change the mouse mode
504        std::vector<InputState*>& mouseStates = devices_[InputDeviceEnumerator::Mouse]->getStateListRef();
505        if (mouseStates.empty() && bExclusiveMouse_ ||
506            !mouseStates.empty() && mouseStates.front()->getIsExclusiveMouse() != bExclusiveMouse_)
507        {
508            bExclusiveMouse_ = !bExclusiveMouse_;
509            if (!GraphicsManager::getInstance().isFullScreen())
510                this->reloadInternal();
511        }
512    }
513
514    void InputManager::clearBuffers()
515    {
516        BOOST_FOREACH(InputDevice* device, devices_)
517            if (device != NULL)
518                device->clearBuffers();
519    }
520
521    void InputManager::calibrate()
522    {
523        COUT(0) << "Move all joy stick axes fully in all directions." << std::endl
524                << "When done, put the axex in the middle position and press enter." << std::endl;
525
526        BOOST_FOREACH(InputDevice* device, devices_)
527            if (device != NULL)
528                device->startCalibration();
529
530        internalState_ |= Calibrating;
531        enterState("calibrator");
532    }
533
534    //! Tells all devices to stop the calibration and evaluate it. Buffers are being cleared as well!
535    void InputManager::stopCalibration()
536    {
537        BOOST_FOREACH(InputDevice* device, devices_)
538            if (device != NULL)
539                device->stopCalibration();
540
541        // restore old input state
542        leaveState("calibrator");
543        internalState_ &= ~Calibrating;
544        // Clear buffers to prevent button hold events
545        this->clearBuffers();
546
547        COUT(0) << "Calibration has been stored." << std::endl;
548    }
549
550    //! Gets called by WindowEventListener upon focus change --> clear buffers
551    void InputManager::windowFocusChanged()
552    {
553        this->clearBuffers();
554    }
555
556    std::pair<int, int> InputManager::getMousePosition() const
557    {
558        Mouse* mouse = static_cast<Mouse*>(devices_[InputDeviceEnumerator::Mouse]);
559        if (mouse != NULL)
560        {
561            const OIS::MouseState state = mouse->getOISDevice()->getMouseState();
562            return std::make_pair(state.X.abs, state.Y.abs);
563        }
564        else
565            return std::make_pair(0, 0);
566    }
567
568    // ############################################################
569    // #####                    Input States                  #####
570    // ##########                                        ##########
571    // ############################################################
572
573    InputState* InputManager::createInputState(const std::string& name, bool bAlwaysGetsInput, bool bTransparent, InputStatePriority priority)
574    {
575        if (name == "")
576            return 0;
577        if (statesByName_.find(name) == statesByName_.end())
578        {
579            if (priority >= InputStatePriority::HighPriority || priority == InputStatePriority::Empty)
580            {
581                // Make sure we don't add two high priority states with the same priority
582                for (std::map<std::string, InputState*>::const_iterator it = this->statesByName_.begin();
583                    it != this->statesByName_.end(); ++it)
584                {
585                    if (it->second->getPriority() == priority)
586                    {
587                        COUT(2) << "Warning: Could not add an InputState with the same priority '"
588                            << static_cast<int>(priority) << "' != 0." << std::endl;
589                        return 0;
590                    }
591                }
592            }
593            InputState* state = new InputState(name, bAlwaysGetsInput, bTransparent, priority);
594            statesByName_[name] = state;
595
596            return state;
597        }
598        else
599        {
600            COUT(2) << "Warning: Could not add an InputState with the same name '" << name << "'." << std::endl;
601            return 0;
602        }
603    }
604
605    InputState* InputManager::getState(const std::string& name)
606    {
607        std::map<std::string, InputState*>::iterator it = statesByName_.find(name);
608        if (it != statesByName_.end())
609            return it->second;
610        else
611            return 0;
612    }
613
614    bool InputManager::enterState(const std::string& name)
615    {
616        // get pointer from the map with all stored handlers
617        std::map<std::string, InputState*>::const_iterator it = statesByName_.find(name);
618        if (it != statesByName_.end())
619        {
620            // exists
621            if (activeStates_.find(it->second->getPriority()) == activeStates_.end())
622            {
623                // not active
624                if (stateDestroyRequests_.find(it->second) == stateDestroyRequests_.end())
625                {
626                    // not scheduled for destruction
627                    // prevents a state being added multiple times
628                    stateEnterRequests_.insert(it->second);
629                    return true;
630                }
631            }
632        }
633        return false;
634    }
635
636    bool InputManager::leaveState(const std::string& name)
637    {
638        if (name == "empty")
639        {
640            COUT(2) << "InputManager: Leaving the empty state is not allowed!" << std::endl;
641            return false;
642        }
643        // get pointer from the map with all stored handlers
644        std::map<std::string, InputState*>::const_iterator it = statesByName_.find(name);
645        if (it != statesByName_.end())
646        {
647            // exists
648            if (activeStates_.find(it->second->getPriority()) != activeStates_.end())
649            {
650                // active
651                stateLeaveRequests_.insert(it->second);
652                return true;
653            }
654        }
655        return false;
656    }
657
658    bool InputManager::destroyState(const std::string& name)
659    {
660        if (name == "empty")
661        {
662            COUT(2) << "InputManager: Removing the empty state is not allowed!" << std::endl;
663            return false;
664        }
665        std::map<std::string, InputState*>::iterator it = statesByName_.find(name);
666        if (it != statesByName_.end())
667        {
668            if (activeStates_.find(it->second->getPriority()) != activeStates_.end())
669            {
670                // The state is still active. We have to postpone
671                stateLeaveRequests_.insert(it->second);
672                stateDestroyRequests_.insert(it->second);
673            }
674            else if (this->internalState_ & Ticking)
675            {
676                // cannot remove state while ticking
677                stateDestroyRequests_.insert(it->second);
678            }
679            else
680                destroyStateInternal(it->second);
681
682            return true;
683        }
684        return false;
685    }
686
687    //! Destroys an InputState internally.
688    void InputManager::destroyStateInternal(InputState* state)
689    {
690        assert(state && !(this->internalState_ & Ticking));
691        std::map<int, InputState*>::iterator it = this->activeStates_.find(state->getPriority());
692        if (it != this->activeStates_.end())
693        {
694            this->activeStates_.erase(it);
695            updateActiveStates();
696        }
697        statesByName_.erase(state->getName());
698        state->destroy();
699    }
700}
Note: See TracBrowser for help on using the repository browser.