root/src/servers/app/VirtualScreen.cpp
/*
 * Copyright 2005-2013, Haiku.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Axel Dörfler, axeld@pinc-software.de
 */


#include "VirtualScreen.h"

#include "HWInterface.h"
#include "Desktop.h"

#include <new>


VirtualScreen::VirtualScreen()
        :
        fScreenList(4),
        fDrawingEngine(NULL),
        fHWInterface(NULL)
{
}


VirtualScreen::~VirtualScreen()
{
        _Reset();
}


void
VirtualScreen::_Reset()
{
        ScreenList list;
        for (int32 i = 0; i < fScreenList.CountItems(); i++) {
                screen_item* item = fScreenList.ItemAt(i);

                list.AddItem(item->screen);
        }

        gScreenManager->ReleaseScreens(list);
        fScreenList.MakeEmpty();

        fFrame.Set(0, 0, 0, 0);
        fDrawingEngine = NULL;
        fHWInterface = NULL;
}


status_t
VirtualScreen::SetConfiguration(Desktop& desktop,
        ScreenConfigurations& configurations, uint32* _changedScreens)
{
        // Remember previous screen modes

        typedef std::map<Screen*, display_mode> ScreenModeMap;
        ScreenModeMap previousModes;
        bool previousModesFailed = false;

        if (_changedScreens != NULL) {
                *_changedScreens = 0;

                try {
                        for (int32 i = 0; i < fScreenList.CountItems(); i++) {
                                Screen* screen = fScreenList.ItemAt(i)->screen;

                                display_mode mode;
                                screen->GetMode(mode);

                                previousModes.insert(std::make_pair(screen, mode));
                        }
                } catch (...) {
                        previousModesFailed = true;
                        *_changedScreens = ~0L;
                }
        }

        _Reset();

        ScreenList list;
        status_t status = gScreenManager->AcquireScreens(&desktop, NULL, 0,
                desktop.TargetScreen(), false, list);
        if (status != B_OK) {
                // TODO: we would try again here with force == true
                return status;
        }

        for (int32 i = 0; i < list.CountItems(); i++) {
                Screen* screen = list.ItemAt(i);

                AddScreen(screen, configurations);

                if (!previousModesFailed && _changedScreens != NULL) {
                        // Figure out which screens have changed their mode
                        display_mode mode;
                        screen->GetMode(mode);

                        ScreenModeMap::const_iterator found = previousModes.find(screen);
                        if (found != previousModes.end()
                                && memcmp(&mode, &found->second, sizeof(display_mode)))
                                *_changedScreens |= 1 << i;
                }
        }

        UpdateFrame();
        return B_OK;
}


status_t
VirtualScreen::AddScreen(Screen* screen, ScreenConfigurations& configurations)
{
        screen_item* item = new(std::nothrow) screen_item;
        if (item == NULL)
                return B_NO_MEMORY;

        item->screen = screen;

        status_t status = B_ERROR;
        display_mode mode;
        if (_GetMode(screen, configurations, mode) == B_OK) {
                // we found settings for this screen, and try to apply them now
                status = screen->SetMode(mode);
        }

        if (status != B_OK) {
                // We found no configuration or it wasn't valid, try to fallback to
                // sane values
                status = screen->SetPreferredMode();
                if (status == B_OK) {
                        monitor_info info;
                        bool hasInfo = screen->GetMonitorInfo(info) == B_OK;
                        screen->GetMode(mode);
                        configurations.Set(screen->ID(), hasInfo ? &info : NULL, screen->Frame(), mode);
                }
                if (status != B_OK)
                        status = screen->SetBestMode(1024, 768, B_RGB32, 60.f);
                if (status != B_OK)
                        status = screen->SetBestMode(800, 600, B_RGB32, 60.f, false);
                if (status != B_OK) {
                        debug_printf("app_server: Failed to set mode: %s\n",
                                strerror(status));

                        delete item;
                        return status;
                }
        }

        // TODO: this works only for single screen configurations
        fDrawingEngine = screen->GetDrawingEngine();
        fHWInterface = screen->HWInterface();
        fFrame = screen->Frame();
        item->frame = fFrame;

        fScreenList.AddItem(item);

        return B_OK;
}


status_t
VirtualScreen::RemoveScreen(Screen* screen)
{
        // not implemented yet (config changes when running)
        return B_ERROR;
}


void
VirtualScreen::UpdateFrame()
{
        int32 virtualWidth = 0, virtualHeight = 0;

        for (int32 i = 0; i < fScreenList.CountItems(); i++) {
                Screen* screen = fScreenList.ItemAt(i)->screen;

                uint16 width, height;
                uint32 colorSpace;
                float frequency;
                screen->GetMode(width, height, colorSpace, frequency);

                // TODO: compute virtual size depending on the actual screen position!
                virtualWidth += width;
                virtualHeight = max_c(virtualHeight, height);
        }

        fFrame.Set(0, 0, virtualWidth - 1, virtualHeight - 1);
}


/*!     Returns the smallest frame that spans over all screens
*/
BRect
VirtualScreen::Frame() const
{
        return fFrame;
}


Screen*
VirtualScreen::ScreenAt(int32 index) const
{
        screen_item* item = fScreenList.ItemAt(index);
        if (item != NULL)
                return item->screen;

        return NULL;
}


Screen*
VirtualScreen::ScreenByID(int32 id) const
{
        for (int32 i = fScreenList.CountItems(); i-- > 0;) {
                screen_item* item = fScreenList.ItemAt(i);

                if (item->screen->ID() == id || id == B_MAIN_SCREEN_ID.id)
                        return item->screen;
        }

        return NULL;
}


BRect
VirtualScreen::ScreenFrameAt(int32 index) const
{
        screen_item* item = fScreenList.ItemAt(index);
        if (item != NULL)
                return item->frame;

        return BRect(0, 0, 0, 0);
}


int32
VirtualScreen::CountScreens() const
{
        return fScreenList.CountItems();
}


status_t
VirtualScreen::_GetMode(Screen* screen, ScreenConfigurations& configurations,
        display_mode& mode) const
{
        monitor_info info;
        bool hasInfo = screen->GetMonitorInfo(info) == B_OK;

        screen_configuration* configuration = configurations.BestFit(screen->ID(),
                hasInfo ? &info : NULL);
        if (configuration == NULL)
                return B_NAME_NOT_FOUND;

        mode = configuration->mode;
        configuration->is_current = true;

        return B_OK;
}