root/src/servers/app/drawing/interface/remote/RemoteHWInterface.cpp
/*
 * Copyright 2009, Haiku, Inc.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Michael Lotz <mmlr@mlotz.ch>
 */

#include "RemoteHWInterface.h"
#include "RemoteDrawingEngine.h"
#include "RemoteEventStream.h"
#include "RemoteMessage.h"

#include "NetReceiver.h"
#include "NetSender.h"
#include "StreamingRingBuffer.h"

#include "SystemPalette.h"

#include <Autolock.h>
#include <NetEndpoint.h>

#include <new>
#include <string.h>


#define TRACE(x...)                             /*debug_printf("RemoteHWInterface: " x)*/
#define TRACE_ALWAYS(x...)              debug_printf("RemoteHWInterface: " x)
#define TRACE_ERROR(x...)               debug_printf("RemoteHWInterface: " x)


struct callback_info {
        uint32                          token;
        RemoteHWInterface::CallbackFunction     callback;
        void*                           cookie;
};


RemoteHWInterface::RemoteHWInterface(const char* target)
        :
        HWInterface(),
        fTarget(target),
        fIsConnected(false),
        fProtocolVersion(100),
        fConnectionSpeed(0),
        fListenPort(10901),
        fListenEndpoint(NULL),
        fSendBuffer(NULL),
        fReceiveBuffer(NULL),
        fSender(NULL),
        fReceiver(NULL),
        fEventThread(-1),
        fEventStream(NULL),
        fCallbackLocker("callback locker")
{
        memset(&fFallbackMode, 0, sizeof(fFallbackMode));
        fFallbackMode.virtual_width = 640;
        fFallbackMode.virtual_height = 480;
        fFallbackMode.space = B_RGB32;
        _FillDisplayModeTiming(fFallbackMode);

        fCurrentMode = fClientMode = fFallbackMode;

        if (sscanf(fTarget, "%" B_SCNu16, &fListenPort) != 1) {
                fInitStatus = B_BAD_VALUE;
                return;
        }

        fListenEndpoint.SetTo(new(std::nothrow) BNetEndpoint());
        if (!fListenEndpoint.IsSet()) {
                fInitStatus = B_NO_MEMORY;
                return;
        }

        fInitStatus = fListenEndpoint->Bind(fListenPort);
        if (fInitStatus != B_OK)
                return;

        fSendBuffer.SetTo(new(std::nothrow) StreamingRingBuffer(16 * 1024));
        if (!fSendBuffer.IsSet()) {
                fInitStatus = B_NO_MEMORY;
                return;
        }

        fInitStatus = fSendBuffer->InitCheck();
        if (fInitStatus != B_OK)
                return;

        fReceiveBuffer.SetTo(new(std::nothrow) StreamingRingBuffer(16 * 1024));
        if (!fReceiveBuffer.IsSet()) {
                fInitStatus = B_NO_MEMORY;
                return;
        }

        fInitStatus = fReceiveBuffer->InitCheck();
        if (fInitStatus != B_OK)
                return;

        fReceiver.SetTo(new(std::nothrow) NetReceiver(fListenEndpoint.Get(), fReceiveBuffer.Get(),
                _NewConnectionCallback, this));
        if (!fReceiver.IsSet()) {
                fInitStatus = B_NO_MEMORY;
                return;
        }

        fEventStream.SetTo(new(std::nothrow) RemoteEventStream());
        if (!fEventStream.IsSet()) {
                fInitStatus = B_NO_MEMORY;
                return;
        }

        fEventThread = spawn_thread(_EventThreadEntry, "remote event thread",
                B_NORMAL_PRIORITY, this);
        if (fEventThread < 0) {
                fInitStatus = fEventThread;
                return;
        }

        resume_thread(fEventThread);
}


RemoteHWInterface::~RemoteHWInterface()
{
        //TODO: check order
        fReceiver.Unset();
        fReceiveBuffer.Unset();

        fSendBuffer.Unset();
        fSender.Unset();

        fListenEndpoint.Unset();

        fEventStream.Unset();
}


status_t
RemoteHWInterface::Initialize()
{
        return fInitStatus;
}


status_t
RemoteHWInterface::Shutdown()
{
        _Disconnect();
        return B_OK;
}


DrawingEngine*
RemoteHWInterface::CreateDrawingEngine()
{
        return new(std::nothrow) RemoteDrawingEngine(this);
}


EventStream*
RemoteHWInterface::CreateEventStream()
{
        return fEventStream.Get();
}


status_t
RemoteHWInterface::AddCallback(uint32 token, CallbackFunction callback,
        void* cookie)
{
        BAutolock lock(fCallbackLocker);
        int32 index = fCallbacks.BinarySearchIndexByKey(token, &_CallbackCompare);
        if (index >= 0)
                return B_NAME_IN_USE;

        callback_info* info = new(std::nothrow) callback_info;
        if (info == NULL)
                return B_NO_MEMORY;

        info->token = token;
        info->callback = callback;
        info->cookie = cookie;

        fCallbacks.AddItem(info, -index - 1);
        return B_OK;
}


bool
RemoteHWInterface::RemoveCallback(uint32 token)
{
        BAutolock lock(fCallbackLocker);
        int32 index = fCallbacks.BinarySearchIndexByKey(token, &_CallbackCompare);
        if (index < 0)
                return false;

        delete fCallbacks.RemoveItemAt(index);
        return true;
}


callback_info*
RemoteHWInterface::_FindCallback(uint32 token)
{
        BAutolock lock(fCallbackLocker);
        return fCallbacks.BinarySearchByKey(token, &_CallbackCompare);
}


int
RemoteHWInterface::_CallbackCompare(const uint32* key,
        const callback_info* info)
{
        if (info->token == *key)
                return 0;

        if (info->token < *key)
                return -1;

        return 1;
}


int32
RemoteHWInterface::_EventThreadEntry(void* data)
{
        return ((RemoteHWInterface*)data)->_EventThread();
}


status_t
RemoteHWInterface::_EventThread()
{
        RemoteMessage message(fReceiveBuffer.Get(), NULL);
        while (true) {
                uint16 code;
                status_t result = message.NextMessage(code);
                if (result != B_OK) {
                        TRACE_ERROR("failed to read message from receiver: %s\n",
                                strerror(result));
                        return result;
                }

                TRACE("got message code %" B_PRIu16 " with %" B_PRIu32 " bytes\n", code,
                        message.DataLeft());

                if (code >= RP_MOUSE_MOVED && code <= RP_MODIFIERS_CHANGED) {
                        // an input event, dispatch to the event stream
                        if (fEventStream->EventReceived(message))
                                continue;
                }

                switch (code) {
                        case RP_INIT_CONNECTION:
                        {
                                RemoteMessage reply(NULL, fSendBuffer.Get());
                                reply.Start(RP_INIT_CONNECTION);
                                status_t result = reply.Flush();
                                (void)result;
                                TRACE("init connection result: %s\n", strerror(result));
                                reply.Start(RP_SET_CURSOR);
                                reply.AddCursor(CursorAndDragBitmap().Get());
                                result = reply.Flush();
                                TRACE("init connection set cursor result: %s\n", strerror(result));
                                reply.Start(RP_SET_CURSOR_VISIBLE);
                                reply.Add(fCursorVisible);
                                result = reply.Flush();
                                TRACE("init connection set cursor visible result: %s\n", strerror(result));
                                BPoint position = CursorPosition();
                                reply.Start(RP_MOVE_CURSOR_TO);
                                reply.Add(position.x);
                                reply.Add(position.y);
                                result = reply.Flush();
                                TRACE("init connection set cursor position result: %s\n", strerror(result));
                                break;
                        }

                        case RP_UPDATE_DISPLAY_MODE:
                        {
                                int32 width, height;
                                message.Read(width);
                                result = message.Read(height);
                                if (result != B_OK) {
                                        TRACE_ERROR("failed to read display mode\n");
                                        break;
                                }

                                fIsConnected = true;
                                fClientMode.virtual_width = width;
                                fClientMode.virtual_height = height;
                                _FillDisplayModeTiming(fClientMode);
                                _NotifyScreenChanged();
                                break;
                        }

                        case RP_GET_SYSTEM_PALETTE:
                        {
                                RemoteMessage reply(NULL, fSendBuffer.Get());
                                reply.Start(RP_GET_SYSTEM_PALETTE_RESULT);

                                const color_map *map = SystemColorMap();
                                uint32 count = (uint32)B_COUNT_OF(map->color_list);

                                reply.Add(count);
                                for (size_t i = 0; i < count; i++) {
                                        const rgb_color &color = map->color_list[i];
                                        reply.Add(color.red);
                                        reply.Add(color.green);
                                        reply.Add(color.blue);
                                        reply.Add(color.alpha);
                                }

                                break;
                        }

                        default:
                        {
                                uint32 token;
                                if (message.Read(token) == B_OK) {
                                        callback_info* info = _FindCallback(token);
                                        if (info != NULL && info->callback(info->cookie, message))
                                                break;
                                }

                                TRACE_ERROR("unhandled remote event code %u\n", code);
                                break;
                        }
                }
        }
}


status_t
RemoteHWInterface::_NewConnectionCallback(void *cookie, BNetEndpoint &endpoint)
{
        return ((RemoteHWInterface *)cookie)->_NewConnection(endpoint);
}


status_t
RemoteHWInterface::_NewConnection(BNetEndpoint &endpoint)
{
        fSender.Unset();

        fSendBuffer->MakeEmpty();

        BNetEndpoint *sendEndpoint = new(std::nothrow) BNetEndpoint(endpoint);
        if (sendEndpoint == NULL)
                return B_NO_MEMORY;

        fSender.SetTo(new(std::nothrow) NetSender(sendEndpoint, fSendBuffer.Get()));
        if (!fSender.IsSet()) {
                delete sendEndpoint;
                return B_NO_MEMORY;
        }

        return B_OK;
}


void
RemoteHWInterface::_Disconnect()
{
        if (fIsConnected) {
                RemoteMessage message(NULL, fSendBuffer.Get());
                message.Start(RP_CLOSE_CONNECTION);
                message.Flush();
                fIsConnected = false;
        }

        if (fListenEndpoint.IsSet())
                fListenEndpoint->Close();
}


status_t
RemoteHWInterface::SetMode(const display_mode& mode)
{
        TRACE("set mode: %" B_PRIu16 " %" B_PRIu16 "\n", mode.virtual_width,
                mode.virtual_height);
        fCurrentMode = mode;
        return B_OK;
}


void
RemoteHWInterface::GetMode(display_mode* mode)
{
        if (mode == NULL || !ReadLock())
                return;

        *mode = fCurrentMode;
        ReadUnlock();

        TRACE("get mode: %" B_PRIu16 " %" B_PRIu16 "\n", mode->virtual_width,
                mode->virtual_height);
}


status_t
RemoteHWInterface::GetPreferredMode(display_mode* mode)
{
        *mode = fClientMode;
        return B_OK;
}


status_t
RemoteHWInterface::GetDeviceInfo(accelerant_device_info* info)
{
        if (!ReadLock())
                return B_ERROR;

        info->version = fProtocolVersion;
        info->dac_speed = fConnectionSpeed;
        info->memory = 33554432; // 32MB
        strlcpy(info->name, "Haiku, Inc. RemoteHWInterface", sizeof(info->name));
        strlcpy(info->chipset, "Haiku, Inc. Chipset", sizeof(info->chipset));
        strlcpy(info->serial_no, fTarget, sizeof(info->serial_no));

        ReadUnlock();
        return B_OK;
}


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

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

        modes[0] = fFallbackMode;
        modes[1] = fClientMode;
        *_modes = modes;
        *_count = 2;

        return B_OK;
}


status_t
RemoteHWInterface::GetPixelClockLimits(display_mode* mode, uint32* low,
        uint32* high)
{
        TRACE("get pixel clock limits unsupported\n");
        return B_UNSUPPORTED;
}


status_t
RemoteHWInterface::GetTimingConstraints(display_timing_constraints* constraints)
{
        TRACE("get timing constraints unsupported\n");
        return B_UNSUPPORTED;
}


status_t
RemoteHWInterface::ProposeMode(display_mode* candidate, const display_mode* low,
        const display_mode* high)
{
        TRACE("propose mode: %" B_PRIu16 " %" B_PRIu16 "\n",
                candidate->virtual_width, candidate->virtual_height);
        return B_OK;
}


status_t
RemoteHWInterface::SetDPMSMode(uint32 state)
{
        return B_UNSUPPORTED;
}


uint32
RemoteHWInterface::DPMSMode()
{
        return B_UNSUPPORTED;
}


uint32
RemoteHWInterface::DPMSCapabilities()
{
        return 0;
}


status_t
RemoteHWInterface::SetBrightness(float)
{
        return B_UNSUPPORTED;
}


status_t
RemoteHWInterface::GetBrightness(float*)
{
        return B_UNSUPPORTED;
}


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


status_t
RemoteHWInterface::WaitForRetrace(bigtime_t timeout)
{
        return B_UNSUPPORTED;
}


void
RemoteHWInterface::SetCursor(ServerCursor* cursor)
{
        HWInterface::SetCursor(cursor);
        RemoteMessage message(NULL, fSendBuffer.Get());
        message.Start(RP_SET_CURSOR);
        message.AddCursor(CursorAndDragBitmap().Get());
}


void
RemoteHWInterface::SetCursorVisible(bool visible)
{
        HWInterface::SetCursorVisible(visible);
        RemoteMessage message(NULL, fSendBuffer.Get());
        message.Start(RP_SET_CURSOR_VISIBLE);
        message.Add(visible);
}


void
RemoteHWInterface::MoveCursorTo(float x, float y)
{
        HWInterface::MoveCursorTo(x, y);
        RemoteMessage message(NULL, fSendBuffer.Get());
        message.Start(RP_MOVE_CURSOR_TO);
        message.Add(x);
        message.Add(y);
}


void
RemoteHWInterface::SetDragBitmap(const ServerBitmap* bitmap,
        const BPoint& offsetFromCursor)
{
        HWInterface::SetDragBitmap(bitmap, offsetFromCursor);
        RemoteMessage message(NULL, fSendBuffer.Get());
        message.Start(RP_SET_CURSOR);
        message.AddCursor(CursorAndDragBitmap().Get());
}


RenderingBuffer*
RemoteHWInterface::FrontBuffer() const
{
        return NULL;
}


RenderingBuffer*
RemoteHWInterface::BackBuffer() const
{
        return NULL;
}


bool
RemoteHWInterface::IsDoubleBuffered() const
{
        return false;
}


status_t
RemoteHWInterface::InvalidateRegion(const BRegion& region)
{
        RemoteMessage message(NULL, fSendBuffer.Get());
        message.Start(RP_INVALIDATE_REGION);
        message.AddRegion(region);
        return B_OK;
}


status_t
RemoteHWInterface::Invalidate(const BRect& frame)
{
        RemoteMessage message(NULL, fSendBuffer.Get());
        message.Start(RP_INVALIDATE_RECT);
        message.Add(frame);
        return B_OK;
}


status_t
RemoteHWInterface::CopyBackToFront(const BRect& frame)
{
        return B_OK;
}


void
RemoteHWInterface::_FillDisplayModeTiming(display_mode &mode)
{
        mode.timing.pixel_clock
                = (uint64_t)mode.virtual_width * mode.virtual_height * 60 / 1000;
        mode.timing.h_display = mode.timing.h_sync_start = mode.timing.h_sync_end
                = mode.timing.h_total = mode.virtual_width;
        mode.timing.v_display = mode.timing.v_sync_start = mode.timing.v_sync_end
                = mode.timing.v_total = mode.virtual_height;
}