root/src/servers/app/drawing/interface/virtual/ViewHWInterface.cpp
/*
 * Copyright 2001-2009, Haiku.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              DarkWyrm <bpmagic@columbus.rr.com>
 *              Stephan Aßmus <superstippi@gmx.de>
 */


/*!     BView/BWindow combination HWInterface implementation */


#include "ViewHWInterface.h"

#include <new>
#include <stdio.h>

#include <Application.h>
#include <Bitmap.h>
#include <Cursor.h>
#include <Locker.h>
#include <Message.h>
#include <MessageFilter.h>
#include <MessageRunner.h>
#include <Region.h>
#include <Screen.h>
#include <String.h>
#include <View.h>
#include <Window.h>

#include <ServerProtocol.h>

#include "BBitmapBuffer.h"
#include "PortLink.h"
#include "ServerConfig.h"
#include "ServerCursor.h"


#ifdef DEBUG_DRIVER_MODULE
#       include <stdio.h>
#       define STRACE(x) printf x
#else
#       define STRACE(x) ;
#endif


const unsigned char kEmptyCursor[] = { 16, 1, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

enum {
        MSG_UPDATE = 'updt'
};


const char*
string_for_color_space(color_space format)
{
        const char* name = "<unkown format>";
        switch (format) {
                case B_RGBA64:
                        name = "B_RGBA64";
                        break;
                case B_RGBA64_BIG:
                        name = "B_RGBA64_BIG";
                        break;
                case B_RGB48:
                        name = "B_RGB48";
                        break;
                case B_RGB48_BIG:
                        name = "B_RGB48_BIG";
                        break;
                case B_RGB32:
                        name = "B_RGB32";
                        break;
                case B_RGBA32:
                        name = "B_RGBA32";
                        break;
                case B_RGB32_BIG:
                        name = "B_RGB32_BIG";
                        break;
                case B_RGBA32_BIG:
                        name = "B_RGBA32_BIG";
                        break;
                case B_RGB24:
                        name = "B_RGB24";
                        break;
                case B_RGB24_BIG:
                        name = "B_RGB24_BIG";
                        break;
                case B_CMAP8:
                        name = "B_CMAP8";
                        break;
                case B_GRAY8:
                        name = "B_GRAY8";
                        break;
                case B_GRAY1:
                        name = "B_GRAY1";
                        break;
                default:
                        break;
        }
        return name;
}


static int32
run_app_thread(void* cookie)
{
        if (BApplication* app = (BApplication*)cookie) {
                app->Lock();
                app->Run();
                delete app;
        }
        return 0;
}


//#define INPUTSERVER_TEST_MODE 1


class CardView : public BView {
public:
                                                                CardView(BRect bounds);
        virtual                                         ~CardView();

        virtual void                            AttachedToWindow();
        virtual void                            Draw(BRect updateRect);
        virtual void                            MessageReceived(BMessage* message);

                                                                // CardView
                        void                            SetBitmap(const BBitmap* bitmap);

                        void                            ForwardMessage(BMessage* message = NULL);

private:
                        port_id                         fInputPort;
                        const BBitmap*          fBitmap;
};

class CardWindow : public BWindow {
public:
                                                                CardWindow(BRect frame);
        virtual                                         ~CardWindow();

        virtual void                            MessageReceived(BMessage* message);
        virtual bool                            QuitRequested();

                                                                // CardWindow
                        void                            SetBitmap(const BBitmap* bitmap);
                        void                            Invalidate(const BRect& area);

private:
                        CardView*                       fView;
                        BRegion                         fUpdateRegion;
                        BLocker                         fUpdateLock;
};

class CardMessageFilter : public BMessageFilter {
public:
                                                                CardMessageFilter(CardView* view);

        virtual filter_result           Filter(BMessage* message, BHandler** _target);

private:
                        CardView*                       fView;
};


//      #pragma mark -


CardView::CardView(BRect bounds)
        :
        BView(bounds, "graphics card view", B_FOLLOW_ALL, B_WILL_DRAW),
        fBitmap(NULL)
{
        SetViewColor(B_TRANSPARENT_32_BIT);

#ifndef INPUTSERVER_TEST_MODE
        fInputPort = create_port(200, SERVER_INPUT_PORT);
#else
        fInputPort = create_port(100, "ViewInputDevice");
#endif

#ifdef ENABLE_INPUT_SERVER_EMULATION
        AddFilter(new CardMessageFilter(this));
#endif
}


CardView::~CardView()
{
}


void
CardView::AttachedToWindow()
{
}


void
CardView::Draw(BRect updateRect)
{
        if (fBitmap != NULL)
                DrawBitmapAsync(fBitmap, updateRect, updateRect);
}


/*!     These functions emulate the Input Server by sending the *exact* same kind of
        messages to the server's port. Being we're using a regular window, it would
        make little sense to do anything else.
*/
void
CardView::ForwardMessage(BMessage* message)
{
        if (message == NULL)
                message = Window()->CurrentMessage();
        if (message == NULL)
                return;

        // remove some fields that potentially mess up our own message processing
        BMessage copy = *message;
        copy.RemoveName("screen_where");
        copy.RemoveName("be:transit");
        copy.RemoveName("be:view_where");
        copy.RemoveName("be:cursor_needed");
        copy.RemoveName("_view_token");

        size_t length = copy.FlattenedSize();
        char stream[length];

        if (copy.Flatten(stream, length) == B_OK)
                write_port(fInputPort, 0, stream, length);
}


void
CardView::MessageReceived(BMessage* message)
{
        switch (message->what) {
                default:
                        BView::MessageReceived(message);
                        break;
        }
}


void
CardView::SetBitmap(const BBitmap* bitmap)
{
        if (bitmap != fBitmap) {
                fBitmap = bitmap;

                if (Parent())
                        Invalidate();
        }
}


//      #pragma mark -


CardMessageFilter::CardMessageFilter(CardView* view)
        :
        BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE),
        fView(view)
{
}


filter_result
CardMessageFilter::Filter(BMessage* message, BHandler** target)
{
        switch (message->what) {
                case B_KEY_DOWN:
                case B_UNMAPPED_KEY_DOWN:
                case B_KEY_UP:
                case B_UNMAPPED_KEY_UP:
                case B_MOUSE_DOWN:
                case B_MOUSE_UP:
                case B_MOUSE_WHEEL_CHANGED:
                        if (message->what == B_MOUSE_DOWN)
                                fView->SetMouseEventMask(B_POINTER_EVENTS);

                        fView->ForwardMessage(message);
                        return B_SKIP_MESSAGE;

                case B_MOUSE_MOVED:
                {
                        int32 transit;
                        if (message->FindInt32("be:transit", &transit) == B_OK
                                && transit == B_ENTERED_VIEW) {
                                // A bug in R5 prevents this call from having an effect if
                                // called elsewhere, and calling it here works, if we're lucky :-)
                                BCursor cursor(kEmptyCursor);
                                fView->SetViewCursor(&cursor, true);
                        }
                        fView->ForwardMessage(message);
                        return B_SKIP_MESSAGE;
                }
        }

        return B_DISPATCH_MESSAGE;
}


//      #pragma mark -


CardWindow::CardWindow(BRect frame)
        :
        BWindow(frame, "Haiku App Server", B_TITLED_WINDOW,
                B_NOT_ZOOMABLE | B_NOT_RESIZABLE | B_NO_SERVER_SIDE_WINDOW_MODIFIERS),
        fUpdateRegion(),
        fUpdateLock("update lock")
{
        fView = new CardView(Bounds());
        AddChild(fView);
        fView->MakeFocus();
                // make it receive key events
}


CardWindow::~CardWindow()
{
}


void
CardWindow::MessageReceived(BMessage* msg)
{
        STRACE("CardWindow::MessageReceived()\n");
        switch (msg->what) {
                case MSG_UPDATE:
                        STRACE("MSG_UPDATE\n");
                        // invalidate all areas in the view that need redrawing
                        if (fUpdateLock.LockWithTimeout(2000LL) >= B_OK) {
/*                              int32 count = fUpdateRegion.CountRects();
                                for (int32 i = 0; i < count; i++) {
                                        fView->Invalidate(fUpdateRegion.RectAt(i));
                                }*/
                                BRect frame = fUpdateRegion.Frame();
                                if (frame.IsValid()) {
                                        fView->Invalidate(frame);
//                                      fView->Invalidate();
                                }
                                fUpdateRegion.MakeEmpty();
                                fUpdateLock.Unlock();
                        } else {
                                // see you next time
                        }
                        break;
                default:
                        BWindow::MessageReceived(msg);
                        break;
        }
        STRACE("CardWindow::MessageReceived() - exit\n");
}


bool
CardWindow::QuitRequested()
{
        port_id serverport = find_port(SERVER_PORT_NAME);

        if (serverport >= 0) {
                BPrivate::PortLink link(serverport);
                link.StartMessage(B_QUIT_REQUESTED);
                link.Flush();
        } else
                printf("ERROR: couldn't find the app_server's main port!");

        // we don't quit on ourself, we let us be Quit()!
        return false;
}


void
CardWindow::SetBitmap(const BBitmap* bitmap)
{
        fView->SetBitmap(bitmap);
}


void
CardWindow::Invalidate(const BRect& frame)
{
        if (LockWithTimeout(1000000) >= B_OK) {
                fView->Invalidate(frame);
                Unlock();
        }
}


//      #pragma mark -


ViewHWInterface::ViewHWInterface()
        :
        HWInterface(),
        fBackBuffer(NULL),
        fFrontBuffer(NULL),
        fWindow(NULL)
{
        fDisplayMode.virtual_width = 640;
        fDisplayMode.virtual_height = 480;
        fDisplayMode.space = B_RGBA32;
}


ViewHWInterface::~ViewHWInterface()
{
        if (fWindow) {
                fWindow->Lock();
                fWindow->Quit();
        }

        be_app->Lock();
        be_app->Quit();
}


status_t
ViewHWInterface::Initialize()
{
        return B_OK;
}


status_t
ViewHWInterface::Shutdown()
{
        return B_OK;
}


status_t
ViewHWInterface::SetMode(const display_mode& mode)
{
        AutoWriteLocker _(this);

        status_t ret = B_OK;
        // prevent from doing the unnecessary
        if (fBackBuffer.IsSet() && fFrontBuffer.IsSet()
                && fDisplayMode.virtual_width == mode.virtual_width
                && fDisplayMode.virtual_height == mode.virtual_height
                && fDisplayMode.space == mode.space)
                return ret;

        // check if we support the mode

        display_mode* modes;
        uint32 modeCount, i;
        if (GetModeList(&modes, &modeCount) != B_OK)
                return B_NO_MEMORY;

        for (i = 0; i < modeCount; i++) {
                // we only care for the bare minimum
                if (modes[i].virtual_width == mode.virtual_width
                        && modes[i].virtual_height == mode.virtual_height
                        && modes[i].space == mode.space) {
                        // take over settings
                        fDisplayMode = modes[i];
                        break;
                }
        }

        delete[] modes;

        if (i == modeCount)
                return B_BAD_VALUE;

        BRect frame(0.0, 0.0, fDisplayMode.virtual_width - 1,
                fDisplayMode.virtual_height - 1);

        // create the window if we don't have one already
        if (!fWindow) {
                // if the window has not been created yet, the BApplication
                // has not been created either, but we need one to display
                // a real BWindow in the test environment.
                // be_app->Run() needs to be called in another thread

                if (be_app == NULL) {
                        BApplication* app = new BApplication(
                                "application/x-vnd.Haiku-test-app_server");
                        app->Unlock();

                        thread_id appThread = spawn_thread(run_app_thread, "app thread",
                                B_NORMAL_PRIORITY, app);
                        if (appThread >= B_OK)
                                ret = resume_thread(appThread);
                        else
                                ret = appThread;

                        if (ret < B_OK)
                                return ret;
                }

                fWindow = new CardWindow(frame.OffsetToCopy(BPoint(50.0, 50.0)));

                // fire up the window thread but don't show it on screen yet
                fWindow->Hide();
                fWindow->Show();
        }

        if (fWindow->Lock()) {
                // just to be save
                fWindow->SetBitmap(NULL);

                // free and reallocate the bitmaps while the window is locked,
                // so that the view does not accidentally draw a freed bitmap
                fBackBuffer.Unset();
                fFrontBuffer.Unset();

                // NOTE: backbuffer is always B_RGBA32, this simplifies the
                // drawing backend implementation tremendously for the time
                // being. The color space conversion is handled in CopyBackToFront()

                // TODO: Above not true anymore for single buffered mode!!!
                // -> fall back to double buffer for fDisplayMode.space != B_RGB32
                // as intermediate solution...
                bool doubleBuffered = true;
                if ((color_space)fDisplayMode.space != B_RGB32
                        && (color_space)fDisplayMode.space != B_RGBA32)
                        doubleBuffered = true;

                BBitmap* frontBitmap
                        = new BBitmap(frame, 0, (color_space)fDisplayMode.space);
                fFrontBuffer.SetTo(new BBitmapBuffer(frontBitmap));

                status_t err = fFrontBuffer->InitCheck();
                if (err < B_OK) {
                        fFrontBuffer.Unset();
                        ret = err;
                }

                if (err >= B_OK && doubleBuffered) {
                        // backbuffer is always B_RGBA32
                        // since we override IsDoubleBuffered(), the drawing buffer
                        // is in effect also always B_RGBA32.
                        BBitmap* backBitmap = new BBitmap(frame, 0, B_RGBA32);
                        fBackBuffer.SetTo(new BBitmapBuffer(backBitmap));

                        err = fBackBuffer->InitCheck();
                        if (err < B_OK) {
                                fBackBuffer.Unset();
                                ret = err;
                        }
                }

                _NotifyFrameBufferChanged();

                if (ret >= B_OK) {
                        // clear out buffers, alpha is 255 this way
                        // TODO: maybe this should handle different color spaces in different ways
                        if (fBackBuffer.IsSet())
                                memset(fBackBuffer->Bits(), 255, fBackBuffer->BitsLength());
                        memset(fFrontBuffer->Bits(), 255, fFrontBuffer->BitsLength());

                        // change the window size and update the bitmap used for drawing
                        fWindow->ResizeTo(frame.Width(), frame.Height());
                        fWindow->SetBitmap(fFrontBuffer->Bitmap());
                }

                // window is hidden when this function is called the first time
                if (fWindow->IsHidden())
                        fWindow->Show();

                fWindow->Unlock();
        } else {
                ret = B_ERROR;
        }
        return ret;
}


void
ViewHWInterface::GetMode(display_mode* mode)
{
        if (mode && ReadLock()) {
                *mode = fDisplayMode;
                ReadUnlock();
        }
}


status_t
ViewHWInterface::GetDeviceInfo(accelerant_device_info* info)
{
        // We really don't have to provide anything here because this is strictly
        // a software-only driver, but we'll have some fun, anyway.
        if (ReadLock()) {
                info->version = 100;
                sprintf(info->name, "Haiku, Inc. ViewHWInterface");
                sprintf(info->chipset, "Haiku, Inc. Chipset");
                sprintf(info->serial_no, "3.14159265358979323846");
                info->memory = 134217728;       // 128 MB, not that we really have that much. :)
                info->dac_speed = 0xFFFFFFFF;   // *heh*

                ReadUnlock();
        }

        return B_OK;
}


status_t
ViewHWInterface::GetModeList(display_mode** _modes, uint32* _count)
{
        AutoReadLocker _(this);

#if 1
        // setup a whole bunch of different modes
        const struct resolution { int32 width, height; } resolutions[] = {
                {640, 480}, {800, 600}, {1024, 768}, {1152, 864}, {1280, 960},
                {1280, 1024}, {1400, 1050}, {1600, 1200}
        };
        uint32 resolutionCount = sizeof(resolutions) / sizeof(resolutions[0]);
        const uint32 colors[] = {B_CMAP8, B_RGB15, B_RGB16, B_RGB32};
        uint32 count = resolutionCount * 4;

        display_mode* modes = new(std::nothrow) display_mode[count];
        if (modes == NULL)
                return B_NO_MEMORY;

        *_modes = modes;
        *_count = count;

        int32 index = 0;
        for (uint32 i = 0; i < resolutionCount; i++) {
                for (uint32 c = 0; c < 4; c++) {
                        modes[index].virtual_width = resolutions[i].width;
                        modes[index].virtual_height = resolutions[i].height;
                        modes[index].space = colors[c];

                        modes[index].h_display_start = 0;
                        modes[index].v_display_start = 0;
                        modes[index].timing.h_display = resolutions[i].width;
                        modes[index].timing.v_display = resolutions[i].height;
                        modes[index].timing.h_total = 22000;
                        modes[index].timing.v_total = 22000;
                        modes[index].timing.pixel_clock = ((uint32)modes[index].timing.h_total
                                * modes[index].timing.v_total * 60) / 1000;
                        modes[index].flags = B_PARALLEL_ACCESS;

                        index++;
                }
        }
#else
        // support only a single mode, useful
        // for testing a specific mode
        display_mode *modes = new(nothrow) display_mode[1];
        modes[0].virtual_width = 640;
        modes[0].virtual_height = 480;
        modes[0].space = B_CMAP8;

        *_modes = modes;
        *_count = 1;
#endif

        return B_OK;
}


status_t
ViewHWInterface::GetPixelClockLimits(display_mode* mode, uint32* low,
        uint32* high)
{
        return B_ERROR;
}


status_t
ViewHWInterface::GetTimingConstraints(display_timing_constraints* constraints)
{
        return B_ERROR;
}


status_t
ViewHWInterface::ProposeMode(display_mode* candidate, const display_mode* low,
        const display_mode* high)
{
        // We should be able to get away with this because we're not dealing with
        // any specific hardware. This is a Good Thing(TM) because we can support
        // any hardware we wish within reasonable expectaions and programmer
        // laziness. :P
        return B_OK;
}


status_t
ViewHWInterface::SetDPMSMode(uint32 state)
{
        AutoWriteLocker _(this);

        return BScreen().SetDPMS(state);
}


uint32
ViewHWInterface::DPMSMode()
{
        AutoReadLocker _(this);

        return BScreen().DPMSState();
}


uint32
ViewHWInterface::DPMSCapabilities()
{
        AutoReadLocker _(this);

        return BScreen().DPMSCapabilites();
}


status_t
ViewHWInterface::SetBrightness(float brightness)
{
        AutoReadLocker _(this);

        return BScreen().SetBrightness(brightness);
}


status_t
ViewHWInterface::GetBrightness(float* brightness)
{
        AutoReadLocker _(this);

        return BScreen().GetBrightness(brightness);
}


sem_id
ViewHWInterface::RetraceSemaphore()
{
        return -1;
}


status_t
ViewHWInterface::WaitForRetrace(bigtime_t timeout)
{
        // Locking shouldn't be necessary here - R5 should handle this for us. :)
        BScreen screen;
        return screen.WaitForRetrace(timeout);
}


RenderingBuffer*
ViewHWInterface::FrontBuffer() const
{
        return fFrontBuffer.Get();
}


RenderingBuffer*
ViewHWInterface::BackBuffer() const
{
        return fBackBuffer.Get();
}


bool
ViewHWInterface::IsDoubleBuffered() const
{
        if (fFrontBuffer.IsSet())
                return fBackBuffer.IsSet();

        return false;
}


status_t
ViewHWInterface::Invalidate(const BRect& frame)
{
        status_t ret = HWInterface::Invalidate(frame);

        if (ret >= B_OK && fWindow && !IsDoubleBuffered())
                fWindow->Invalidate(frame);
        return ret;
}


status_t
ViewHWInterface::CopyBackToFront(const BRect& frame)
{
        status_t ret = HWInterface::CopyBackToFront(frame);

        if (ret >= B_OK && fWindow)
                fWindow->Invalidate(frame);
        return ret;
}