/*
 * OS/2 video output driver
 *
 * Copyright (c) 2007-2009 by KO Myung-Hun (komh@chollian.net)
 *
 * This file is part of MPlayer.
 *
 * MPlayer is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * MPlayer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with MPlayer; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#define INCL_WIN
#define INCL_GPI
#define INCL_DOS
#include <os2.h>

#include <mmioos2.h>
#include <fourcc.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <float.h>

#include <kva.h>

#include "config.h"
#include "mp_msg.h"
#include "help_mp.h"
#include "video_out.h"
#include "video_out_internal.h"
#include "libmpcodecs/vf.h"
#include "aspect.h"

#include "fastmemcpy.h"
#include "mp_fifo.h"
#include "osdep/keycodes.h"
#include "input/input.h"
#include "input/mouse.h"
#include "subopt-helper.h"
#include "sub/sub.h"

#include "cpudetect.h"
#include "libswscale/swscale.h"
#include "libmpcodecs/vf_scale.h"

static const vo_info_t info = {
    "SNAP/WarpOverlay!/DIVE video output",
    "kva",
    "KO Myung-Hun <komh@chollian.net>",
    ""
};

const LIBVO_EXTERN(kva)

#define WC_MPLAYER  "WC_MPLAYER"

#define SRC_WIDTH   m_int.kvas.szlSrcSize.cx
#define SRC_HEIGHT  m_int.kvas.szlSrcSize.cy

#define HWNDFROMWINID(wid)    ((wid) + 0x80000000UL)

static const struct mp_keymap m_vk_map[] = {
    {VK_NEWLINE, KEY_ENTER}, {VK_TAB, KEY_TAB}, {VK_SPACE, ' '},

    // control keys
    {VK_CTRL,   KEY_CTRL},    {VK_BACKSPACE, KEY_BS},
    {VK_DELETE, KEY_DELETE},  {VK_INSERT,    KEY_INSERT},
    {VK_HOME,   KEY_HOME},    {VK_END,       KEY_END},
    {VK_PAGEUP, KEY_PAGE_UP}, {VK_PAGEDOWN,  KEY_PAGE_DOWN},
    {VK_ESC,    KEY_ESC},

    // cursor keys
    {VK_RIGHT, KEY_RIGHT}, {VK_LEFT, KEY_LEFT},
    {VK_DOWN,  KEY_DOWN},  {VK_UP,   KEY_UP},

    // function keys
    {VK_F1, KEY_F+1}, {VK_F2,  KEY_F+2},  {VK_F3,  KEY_F+3},  {VK_F4,  KEY_F+4},
    {VK_F5, KEY_F+5}, {VK_F6,  KEY_F+6},  {VK_F7,  KEY_F+7},  {VK_F8,  KEY_F+8},
    {VK_F9, KEY_F+9}, {VK_F10, KEY_F+10}, {VK_F11, KEY_F+11}, {VK_F12, KEY_F+12},

    {0, 0}
};

static const struct mp_keymap m_keypad_map[] = {
    // keypad keys
    {0x52, KEY_KP0}, {0x4F, KEY_KP1}, {0x50, KEY_KP2},   {0x51, KEY_KP3},
    {0x4B, KEY_KP4}, {0x4C, KEY_KP5}, {0x4D, KEY_KP6},   {0x47, KEY_KP7},
    {0x48, KEY_KP8}, {0x49, KEY_KP9}, {0x53, KEY_KPDEC}, {0x5A, KEY_KPENTER},

    {0, 0}
};

static const struct mp_keymap m_mouse_map[] = {
    {WM_BUTTON1DOWN,   MOUSE_BTN0},
    {WM_BUTTON3DOWN,   MOUSE_BTN1},
    {WM_BUTTON2DOWN,   MOUSE_BTN2},
    {WM_BUTTON1DBLCLK, MOUSE_BTN0_DBL},
    {WM_BUTTON3DBLCLK, MOUSE_BTN1_DBL},
    {WM_BUTTON2DBLCLK, MOUSE_BTN2_DBL},

    {0, 0}
};

struct {
    HAB         hab;
    HMQ         hmq;
    HWND        hwndFrame;
    HWND        hwndClient;
    HWND        hwndSysMenu;
    HWND        hwndTitleBar;
    HWND        hwndMinMax;
    FOURCC      fcc;
    int         iImageFormat;
    int         nChromaShift;
    KVASETUP    kvas;
    KVACAPS     kvac;
    RECTL       rclDst;
    int         bpp;
    LONG        lStride;
    PBYTE       pbImage;
    BOOL        fFixT23;
    PFNWP       pfnwpOldFrame;
    uint8_t    *planes[MP_MAX_PLANES];     // y = 0, u = 1, v = 2
    int         stride[MP_MAX_PLANES];
    BOOL        fHWAccel;
    RECTL       rclParent;
    struct SwsContext *sws;
} m_int;

static inline void setAspectRatio(ULONG ulRatio)
{
    ULONG ulValue;
    int   i;

    m_int.kvas.ulRatio = ulRatio;
    kvaSetup(&m_int.kvas);

    // Setup initializes all attributes, so need to restore them.
    for (i = 0; i < KVAA_LAST; i++) {
        kvaQueryAttr(i, &ulValue);
        kvaSetAttr(i, &ulValue);
    }
}

static int query_format_info(int format, PBOOL pfHWAccel, PFOURCC pfcc,
                             int *pbpp, int *pnChromaShift)
{
    BOOL    fHWAccel;
    FOURCC  fcc;
    INT     bpp;
    INT     nChromaShift;

    switch (format) {
    case IMGFMT_YV12:
        fHWAccel        = m_int.kvac.ulInputFormatFlags & KVAF_YV12;
        fcc             = FOURCC_YV12;
        bpp             = 1;
        nChromaShift    = 1;
        break;

    case IMGFMT_YUY2:
        fHWAccel        = m_int.kvac.ulInputFormatFlags & KVAF_YUY2;
        fcc             = FOURCC_Y422;
        bpp             = 2;
        nChromaShift    = 0;
        break;

    case IMGFMT_YVU9:
        fHWAccel        = m_int.kvac.ulInputFormatFlags & KVAF_YVU9;
        fcc             = FOURCC_YVU9;
        bpp             = 1;
        nChromaShift    = 2;
        break;

    case IMGFMT_BGR24:
        fHWAccel        = m_int.kvac.ulInputFormatFlags & KVAF_BGR24;
        fcc             = FOURCC_BGR3;
        bpp             = 3;
        nChromaShift    = 0;
        break;

    case IMGFMT_BGR16:
        fHWAccel        = m_int.kvac.ulInputFormatFlags & KVAF_BGR16;
        fcc             = FOURCC_R565;
        bpp             = 2;
        nChromaShift    = 0;
        break;

    case IMGFMT_BGR15:
        fHWAccel        = m_int.kvac.ulInputFormatFlags & KVAF_BGR15;
        fcc             = FOURCC_R555;
        bpp             = 2;
        nChromaShift    = 0;
        break;

    default:
        return 1;
    }

    if (pfHWAccel)
        *pfHWAccel = fHWAccel;

    if (pfcc)
        *pfcc = fcc;

    if (pbpp)
        *pbpp = bpp;

    if (pnChromaShift)
        *pnChromaShift = nChromaShift;

    return 0;
}

static void imgCreate(void)
{
    int size = SRC_HEIGHT * m_int.lStride;;

    switch (m_int.iImageFormat) {
    case IMGFMT_YV12:
        size += size / 2;
        break;

    case IMGFMT_YVU9:
        size += size / 8;
        break;
    }

    m_int.pbImage = malloc(size);

    memset(m_int.planes, 0, sizeof(m_int.planes));
    memset(m_int.stride, 0, sizeof(m_int.stride));
    m_int.planes[0] = m_int.pbImage;
    m_int.stride[0] = m_int.lStride;

    // YV12 or YVU9 ?
    if (m_int.nChromaShift) {
        m_int.planes[1] = m_int.planes[0] + SRC_HEIGHT * m_int.stride[0];
        m_int.stride[1] = m_int.stride[0] >> m_int.nChromaShift;

        m_int.planes[2] = m_int.planes[1] +
                          (SRC_HEIGHT >> m_int.nChromaShift) * m_int.stride[1];
        m_int.stride[2] = m_int.stride[1];
    }
}

static void imgFree(void)
{
    free(m_int.pbImage);

    m_int.pbImage = NULL;
}

static void imgDisplay(void)
{
    PVOID pBuffer;
    ULONG ulBPL;

    if (!kvaLockBuffer(&pBuffer, &ulBPL)) {
        uint8_t *dst[MP_MAX_PLANES] = {NULL};
        int      dstStride[MP_MAX_PLANES] = {0};

        // Get packed or Y
        dst[0]       = pBuffer;
        dstStride[0] = ulBPL;

        // YV12 or YVU9 ?
        if (m_int.nChromaShift) {
            // Get V
            dst[2]       = dst[0] + SRC_HEIGHT * dstStride[0];
            dstStride[2] = dstStride[0] >> m_int.nChromaShift;

            // Get U
            dst[1]       = dst[2] +
                           (SRC_HEIGHT >> m_int.nChromaShift ) * dstStride[2];
            dstStride[1] = dstStride[2];
        }

        if (m_int.fHWAccel) {
            int w, h;

            w = m_int.stride[0];
            h = SRC_HEIGHT;

            // Copy packed or Y
            mem2agpcpy_pic(dst[0], m_int.planes[0], w, h,
                           dstStride[0], m_int.stride[0]);

            // YV12 or YVU9 ?
            if (m_int.nChromaShift) {
                w >>= m_int.nChromaShift; h >>= m_int.nChromaShift;

                // Copy U
                mem2agpcpy_pic(dst[1], m_int.planes[1], w, h,
                               dstStride[1], m_int.stride[1]);

                // Copy V
                mem2agpcpy_pic(dst[2], m_int.planes[2], w, h,
                               dstStride[2], m_int.stride[2]);
            }
        } else {
            sws_scale(m_int.sws, m_int.planes, m_int.stride, 0, SRC_HEIGHT,
                      dst, dstStride);
        }

        kvaUnlockBuffer();
    }
}

// Frame window procedure to work around T23 laptop with S3 video card,
// which supports upscaling only.
static MRESULT EXPENTRY NewFrameWndProc(HWND hwnd, ULONG msg, MPARAM mp1,
                                        MPARAM mp2)
{
    switch (msg) {
    case WM_QUERYTRACKINFO:
        {
        PTRACKINFO  pti = (PTRACKINFO)mp2;
        RECTL       rcl;

        if (vo_fs)
            break;

        m_int.pfnwpOldFrame(hwnd, msg, mp1, mp2);

        rcl.xLeft   = 0;
        rcl.yBottom = 0;
        rcl.xRight  = SRC_WIDTH  + 1;
        rcl.yTop    = SRC_HEIGHT + 1;

        WinCalcFrameRect(hwnd, &rcl, FALSE);

        pti->ptlMinTrackSize.x = rcl.xRight - rcl.xLeft;
        pti->ptlMinTrackSize.y = rcl.yTop   - rcl.yBottom;

        pti->ptlMaxTrackSize.x = vo_screenwidth;
        pti->ptlMaxTrackSize.y = vo_screenheight;

        return (MRESULT)TRUE;
        }

    case WM_ADJUSTWINDOWPOS:
        {
        PSWP    pswp = (PSWP)mp1;
        RECTL   rcl;

        if (vo_fs)
            break;

        if (pswp->fl & SWP_SIZE) {
            rcl.xLeft   = pswp->x;
            rcl.yBottom = pswp->y;
            rcl.xRight  = rcl.xLeft   + pswp->cx;
            rcl.yTop    = rcl.yBottom + pswp->cy;

            WinCalcFrameRect(hwnd, &rcl, TRUE);

            if (rcl.xRight - rcl.xLeft <= SRC_WIDTH)
                rcl.xRight = rcl.xLeft + (SRC_WIDTH + 1);

            if (rcl.yTop - rcl.yBottom <= SRC_HEIGHT)
                rcl.yTop = rcl.yBottom + (SRC_HEIGHT + 1);

            WinCalcFrameRect(hwnd, &rcl, FALSE);

            if (rcl.xRight - rcl.xLeft > vo_screenwidth) {
                rcl.xLeft  = 0;
                rcl.xRight = vo_screenwidth;
            }

            if (rcl.yTop - rcl.yBottom > vo_screenheight) {
                rcl.yBottom = 0;
                rcl.yTop    = vo_screenheight;
            }

            pswp->fl |= SWP_MOVE;
            pswp->x   = rcl.xLeft;
            pswp->y   = rcl.yBottom;
            pswp->cx  = rcl.xRight - rcl.xLeft;
            pswp->cy  = rcl.yTop   - rcl.yBottom;
        }
        break;
        }
    }

    return m_int.pfnwpOldFrame(hwnd, msg, mp1, mp2);
}

static MRESULT EXPENTRY WndProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    // if slave mode, ignore mouse events and deliver them to a parent window
    if (WinID != -1 &&
        ((msg >= WM_MOUSEFIRST    && msg <= WM_MOUSELAST) ||
         (msg >= WM_EXTMOUSEFIRST && msg <= WM_EXTMOUSELAST))) {
        WinPostMsg(HWNDFROMWINID(WinID), msg, mp1, mp2);

        return (MRESULT)TRUE;
    }

    switch (msg) {
    case WM_CLOSE:
        mplayer_put_key(KEY_CLOSE_WIN);

        return 0;

    case WM_CHAR:
        {
        USHORT fsFlags = SHORT1FROMMP(mp1);
        UCHAR  uchScan =  CHAR4FROMMP(mp1);
        USHORT usCh    = SHORT1FROMMP(mp2);
        USHORT usVk    = SHORT2FROMMP(mp2);
        int    mpkey;

        if (fsFlags & KC_KEYUP)
            break;

        if (fsFlags & KC_SCANCODE) {
            mpkey = lookup_keymap_table(m_keypad_map, uchScan);
            if (mpkey) {
                // distinguish KEY_KP0 and KEY_KPINS
                if (mpkey == KEY_KP0 && usCh != '0')
                    mpkey = KEY_KPINS;

                // distinguish KEY_KPDEC and KEY_KPDEL
                if (mpkey == KEY_KPDEC && usCh != '.')
                    mpkey = KEY_KPDEL;

                mplayer_put_key(mpkey);

                return (MRESULT)TRUE;
            }
        }

        if (fsFlags & KC_VIRTUALKEY) {
            mpkey = lookup_keymap_table(m_vk_map, usVk);
            if (mpkey) {
                mplayer_put_key(mpkey);

                return (MRESULT)TRUE;
            }
        }

        if ((fsFlags & KC_CHAR) && !HIBYTE(usCh))
            mplayer_put_key(usCh);

        return (MRESULT)TRUE;
        }

    case WM_BUTTON1DOWN:
    case WM_BUTTON3DOWN:
    case WM_BUTTON2DOWN:
    case WM_BUTTON1DBLCLK:
    case WM_BUTTON3DBLCLK:
    case WM_BUTTON2DBLCLK:
        if (WinQueryFocus(HWND_DESKTOP) != hwnd)
            WinSetFocus(HWND_DESKTOP, hwnd);
        else if (!vo_nomouse_input)
            mplayer_put_key(lookup_keymap_table(m_mouse_map, msg));

        return (MRESULT)TRUE;

    case WM_PAINT:
        {
        HPS     hps;
        RECTL   rcl, rclDst;
        PRECTL  prcl = NULL;
        HRGN    hrgn, hrgnDst;
        RGNRECT rgnCtl;

        // get a current movie area
        kvaAdjustDstRect(&m_int.kvas.rclSrcRect, &rclDst);

        // get a current invalidated area
        hps = WinBeginPaint(hwnd, NULLHANDLE, &rcl);

        // create a region for an invalidated area
        hrgn    = GpiCreateRegion(hps, 1, &rcl);
        // create a region for a movie area
        hrgnDst = GpiCreateRegion(hps, 1, &rclDst);

        // exclude a movie area from an invalidated area
        GpiCombineRegion(hps, hrgn, hrgn, hrgnDst, CRGN_DIFF);

        // get rectangles from the region
        rgnCtl.ircStart     = 1;
        rgnCtl.ulDirection  = RECTDIR_LFRT_TOPBOT;
        GpiQueryRegionRects(hps, hrgn, NULL, &rgnCtl, NULL);

        if (rgnCtl.crcReturned > 0) {
            rgnCtl.crc = rgnCtl.crcReturned;
            prcl       = malloc(sizeof(RECTL) * rgnCtl.crcReturned);
        }

        // draw black bar if needed
        if (prcl && GpiQueryRegionRects(hps, hrgn, NULL, &rgnCtl, prcl)) {
            int i;

            for (i = 0; i < rgnCtl.crcReturned; i++)
                WinFillRect(hps, &prcl[i], CLR_BLACK);
        }

        free(prcl);

        GpiDestroyRegion(hps, hrgnDst);
        GpiDestroyRegion(hps, hrgn);

        WinEndPaint(hps);

        return 0;
        }
    }

    return WinDefWindowProc(hwnd, msg, mp1, mp2);
}

// Change process type from VIO to PM to use PM APIs.
static void morphToPM(void)
{
    PPIB pib;

    DosGetInfoBlocks(NULL, &pib);

    // Change flag from VIO to PM:
    if (pib->pib_ultype == 2)
        pib->pib_ultype = 3;
}

static int preinit(const char *arg)
{
    HWND    hwndParent;
    ULONG   flFrameFlags;
    ULONG   kvaMode = 0;

    int     fUseSnap = 0;
    int     fUseWO   = 0;
    int     fUseDive = 0;
    int     fFixT23  = 0;

    const opt_t subopts[] = {
        {"snap", OPT_ARG_BOOL, &fUseSnap, NULL},
        {"wo",   OPT_ARG_BOOL, &fUseWO,   NULL},
        {"dive", OPT_ARG_BOOL, &fUseDive, NULL},
        {"t23",  OPT_ARG_BOOL, &fFixT23,  NULL},
        {NULL,              0, NULL,      NULL}
    };

    PCSZ pcszVideoModeStr[3] = {"DIVE", "WarpOverlay!", "SNAP"};

    if (subopt_parse(arg, subopts) != 0)
        return -1;

    morphToPM();

    memset(&m_int, 0, sizeof(m_int));

    m_int.hab = WinInitialize(0);
    m_int.hmq = WinCreateMsgQueue(m_int.hab, 0);

    WinRegisterClass(m_int.hab,
                     WC_MPLAYER,
                     WndProc,
                     CS_SIZEREDRAW | CS_MOVENOTIFY,
                     sizeof(PVOID));

    if (WinID == -1) {
        hwndParent   = HWND_DESKTOP;
        flFrameFlags = FCF_SYSMENU    | FCF_TITLEBAR | FCF_MINMAX |
                       FCF_SIZEBORDER | FCF_TASKLIST;
    } else {
        ULONG ulStyle;

        hwndParent   = HWNDFROMWINID(WinID);
        flFrameFlags = 0;

        // Prevent a parent window from painting over our window
        ulStyle = WinQueryWindowULong(hwndParent, QWL_STYLE);
        WinSetWindowULong(hwndParent, QWL_STYLE, ulStyle | WS_CLIPCHILDREN);
    }

    m_int.hwndFrame =
        WinCreateStdWindow(hwndParent,          // parent window handle
                           WS_VISIBLE,          // frame window style
                           &flFrameFlags,       // window style
                           WC_MPLAYER,          // class name
                           "",                  // window title
                           0L,                  // default client style
                           NULLHANDLE,          // resource in exe file
                           1,                   // frame window id
                           &m_int.hwndClient);  // client window handle

    if (m_int.hwndFrame == NULLHANDLE)
        return -1;

    m_int.hwndSysMenu  = WinWindowFromID(m_int.hwndFrame, FID_SYSMENU);
    m_int.hwndTitleBar = WinWindowFromID(m_int.hwndFrame, FID_TITLEBAR);
    m_int.hwndMinMax   = WinWindowFromID(m_int.hwndFrame, FID_MINMAX);

    m_int.fFixT23 = fFixT23;

    if (m_int.fFixT23)
        m_int.pfnwpOldFrame = WinSubclassWindow(m_int.hwndFrame,
                                                NewFrameWndProc);

    if (!!fUseSnap + !!fUseWO + !!fUseDive > 1)
        mp_msg(MSGT_VO, MSGL_WARN,"KVA: Multiple mode specified!!!\n");

    if (fUseSnap)
        kvaMode = KVAM_SNAP;
    else if (fUseWO)
        kvaMode = KVAM_WO;
    else if (fUseDive)
        kvaMode = KVAM_DIVE;
    else
        kvaMode = KVAM_AUTO;

    if (kvaInit(kvaMode, m_int.hwndClient, vo_colorkey)) {
        mp_msg(MSGT_VO, MSGL_ERR, "KVA: Init failed!!!\n");

        return -1;
    }

    kvaCaps(&m_int.kvac);

    mp_msg(MSGT_VO, MSGL_V, "KVA: Selected video mode = %s\n",
           pcszVideoModeStr[m_int.kvac.ulMode - 1]);

    kvaDisableScreenSaver();

    // Might cause PM DLLs to be loaded which incorrectly enable SIG_FPE,
    // so mask off all floating-point exceptions.
    _control87(MCW_EM, MCW_EM);

    return 0;
}

static void uninit(void)
{
    kvaEnableScreenSaver();

    imgFree();

    sws_freeContext(m_int.sws);

    if (m_int.hwndFrame != NULLHANDLE) {
        kvaResetAttr();
        kvaDone();

        if (m_int.fFixT23)
            WinSubclassWindow(m_int.hwndFrame, m_int.pfnwpOldFrame);

        WinDestroyWindow(m_int.hwndFrame);
    }

    WinDestroyMsgQueue(m_int.hmq);
    WinTerminate(m_int.hab);
}

static int config(uint32_t width, uint32_t height,
                  uint32_t d_width, uint32_t d_height,
                  uint32_t flags, char *title, uint32_t format)
{
    RECTL   rcl;

    mp_msg(MSGT_VO, MSGL_V,
           "KVA: Using 0x%X (%s) image format, vo_config_count = %d\n",
           format, vo_format_name(format), vo_config_count);

    imgFree();

    if (query_format_info(format, &m_int.fHWAccel, &m_int.fcc, &m_int.bpp,
                          &m_int.nChromaShift))
        return 1;

    m_int.iImageFormat = format;

    // if there is no hw accel for given format,
    // try any format supported by hw accel
    if (!m_int.fHWAccel) {
        int dstFormat = 0;

        sws_freeContext(m_int.sws);

        if (m_int.kvac.ulInputFormatFlags & KVAF_YV12)
            dstFormat = IMGFMT_YV12;
        else if (m_int.kvac.ulInputFormatFlags & KVAF_YUY2)
            dstFormat = IMGFMT_YUY2;
        else if (m_int.kvac.ulInputFormatFlags & KVAF_YVU9)
            dstFormat = IMGFMT_YVU9;
        else if (m_int.kvac.ulInputFormatFlags & KVAF_BGR24)
            dstFormat = IMGFMT_BGR24;
        else if (m_int.kvac.ulInputFormatFlags & KVAF_BGR16)
            dstFormat = IMGFMT_BGR16;
        else if (m_int.kvac.ulInputFormatFlags & KVAF_BGR15)
            dstFormat = IMGFMT_BGR15;

        if (query_format_info(dstFormat, NULL, &m_int.fcc, NULL, NULL))
            return 1;

        m_int.sws = sws_getContextFromCmdLine(width, height, format,
                                              width, height, dstFormat);
    }

    mp_msg(MSGT_VO, MSGL_V, "KVA: Selected FOURCC = %.4s\n", (char *)&m_int.fcc);

    m_int.kvas.ulLength           = sizeof(KVASETUP);
    m_int.kvas.szlSrcSize.cx      = width;
    m_int.kvas.szlSrcSize.cy      = height;
    m_int.kvas.rclSrcRect.xLeft   = 0;
    m_int.kvas.rclSrcRect.yTop    = 0;
    m_int.kvas.rclSrcRect.xRight  = width;
    m_int.kvas.rclSrcRect.yBottom = height;
    m_int.kvas.ulRatio            = vo_keepaspect ? KVAR_FORCEANY : KVAR_NONE;
    m_int.kvas.ulAspectWidth      = d_width;
    m_int.kvas.ulAspectHeight     = d_height;
    m_int.kvas.fccSrcColor        = m_int.fcc;
    m_int.kvas.fDither            = TRUE;

    if (kvaSetup(&m_int.kvas)) {
        mp_msg(MSGT_VO, MSGL_ERR, "KVA: Setup failed!!!\n");

        return 1;
    }

    m_int.lStride = width * m_int.bpp;

    imgCreate();

    if (WinID == -1) {
        WinSetWindowText(m_int.hwndFrame, title);

        // initialize 'vo_fs' only once at first config() call
        if (vo_config_count == 0)
            vo_fs = flags & VOFLAG_FULLSCREEN;

        // workaround for T23 laptop with S3 Video by Franz Bakan
        if (!vo_fs && m_int.fFixT23) {
            d_width++;
            d_height++;
        }

        m_int.rclDst.xLeft   = ((LONG)vo_screenwidth  - (LONG)d_width)  / 2;
        m_int.rclDst.yBottom = ((LONG)vo_screenheight - (LONG)d_height) / 2;
        m_int.rclDst.xRight  = m_int.rclDst.xLeft   + d_width;
        m_int.rclDst.yTop    = m_int.rclDst.yBottom + d_height;

        if (vo_fs) {
            d_width  = vo_screenwidth;
            d_height = vo_screenheight;

            // when -fs option is used without this, title bar is not highlighted
            WinSetActiveWindow(HWND_DESKTOP, m_int.hwndFrame);

            WinSetParent(m_int.hwndSysMenu,  HWND_OBJECT, FALSE);
            WinSetParent(m_int.hwndTitleBar, HWND_OBJECT, FALSE);
            WinSetParent(m_int.hwndMinMax,   HWND_OBJECT, FALSE);

            setAspectRatio(KVAR_FORCEANY);
        }

        rcl.xLeft   = ((LONG)vo_screenwidth  - (LONG)d_width) / 2;
        rcl.yBottom = ((LONG)vo_screenheight - (LONG)d_height) /2 ;
        rcl.xRight  = rcl.xLeft              + d_width;
        rcl.yTop    = rcl.yBottom            + d_height;
    } else {
        vo_fs = 0;

        WinQueryWindowRect(HWNDFROMWINID(WinID), &m_int.rclDst);
        rcl = m_int.rclDst;
    }

    WinCalcFrameRect(m_int.hwndFrame, &rcl, FALSE);

    WinSetWindowPos(m_int.hwndFrame, HWND_TOP,
                    rcl.xLeft, rcl.yBottom,
                    rcl.xRight - rcl.xLeft, rcl.yTop - rcl.yBottom,
                    SWP_SIZE | SWP_MOVE | SWP_ZORDER | SWP_SHOW |
                    (WinID == -1 ? SWP_ACTIVATE : 0));

    WinInvalidateRect(m_int.hwndFrame, NULL, TRUE);

    return 0;
}

static uint32_t get_image(mp_image_t *mpi)
{
    if (m_int.iImageFormat != mpi->imgfmt)
        return VO_FALSE;

    if (mpi->type == MP_IMGTYPE_STATIC || mpi->type == MP_IMGTYPE_TEMP) {
        if (mpi->flags & MP_IMGFLAG_PLANAR) {
            mpi->planes[1] = m_int.planes[1];
            mpi->planes[2] = m_int.planes[2];

            mpi->stride[1] = m_int.stride[1];
            mpi->stride[2] = m_int.stride[2];
        }

        mpi->planes[0] = m_int.planes[0];
        mpi->stride[0] = m_int.stride[0];
        mpi->flags    |= MP_IMGFLAG_DIRECT;

        return VO_TRUE;
    }

    return VO_FALSE;
}

static uint32_t draw_image(mp_image_t *mpi)
{
    // if -dr or -slices then do nothing:
    if (mpi->flags & (MP_IMGFLAG_DIRECT | MP_IMGFLAG_DRAW_CALLBACK))
        return VO_TRUE;

    draw_slice(mpi->planes, mpi->stride, mpi->w, mpi->h, mpi->x, mpi->y);

    return VO_TRUE;
}

static int query_format(uint32_t format)
{
    BOOL fHWAccel;
    int  res;

    if (query_format_info(format, &fHWAccel, NULL, NULL, NULL))
        return 0;

    res = VFCAP_CSP_SUPPORTED | VFCAP_OSD;
    if (fHWAccel) {
        res |= VFCAP_CSP_SUPPORTED_BY_HW | VFCAP_HWSCALE_UP;

        if (!m_int.fFixT23)
            res |= VFCAP_HWSCALE_DOWN;
    }

    return res;
}

static int fs_toggle(void)
{
    RECTL   rcl;

    vo_fs = !vo_fs;

    if (vo_fs) {
        SWP swp;

        WinQueryWindowPos(m_int.hwndFrame, &swp);
        m_int.rclDst.xLeft   = swp.x;
        m_int.rclDst.yBottom = swp.y;
        m_int.rclDst.xRight  = m_int.rclDst.xLeft   + swp.cx;
        m_int.rclDst.yTop    = m_int.rclDst.yBottom + swp.cy;
        WinCalcFrameRect(m_int.hwndFrame, &m_int.rclDst, TRUE);

        if (WinID != -1)
            WinSetParent(m_int.hwndFrame, HWND_DESKTOP, FALSE);

        WinSetParent(m_int.hwndSysMenu,  HWND_OBJECT, FALSE);
        WinSetParent(m_int.hwndTitleBar, HWND_OBJECT, FALSE);
        WinSetParent(m_int.hwndMinMax,   HWND_OBJECT, FALSE);

        rcl.xLeft   = 0;
        rcl.yBottom = 0;
        rcl.xRight  = vo_screenwidth;
        rcl.yTop    = vo_screenheight;

        setAspectRatio(KVAR_FORCEANY);
    } else {
        if (WinID != -1)
            WinSetParent(m_int.hwndFrame, HWNDFROMWINID(WinID), TRUE);

        WinSetParent(m_int.hwndSysMenu,  m_int.hwndFrame, FALSE);
        WinSetParent(m_int.hwndTitleBar, m_int.hwndFrame, FALSE);
        WinSetParent(m_int.hwndMinMax,   m_int.hwndFrame, FALSE);

        rcl = m_int.rclDst;

        setAspectRatio(vo_keepaspect ? KVAR_FORCEANY : KVAR_NONE);
    }

    WinCalcFrameRect(m_int.hwndFrame, &rcl, FALSE);

    WinSetWindowPos(m_int.hwndFrame, HWND_TOP,
                    rcl.xLeft, rcl.yBottom,
                    rcl.xRight - rcl.xLeft, rcl.yTop - rcl.yBottom,
                    SWP_SIZE | SWP_MOVE | SWP_ZORDER | SWP_SHOW |
                    (WinID == -1 ? SWP_ACTIVATE : 0));

    return VO_TRUE;
}

static int color_ctrl_set(char *what, int value)
{
    ULONG   ulAttr;
    ULONG   ulValue;

    if (!strcmp(what, "brightness"))
        ulAttr = KVAA_BRIGHTNESS;
    else if (!strcmp(what, "contrast"))
        ulAttr = KVAA_CONTRAST;
    else if (!strcmp(what, "hue"))
        ulAttr = KVAA_HUE;
    else if (!strcmp(what, "saturation"))
        ulAttr = KVAA_SATURATION;
    else
        return VO_NOTIMPL;

    ulValue = (value + 100) * 255 / 200;

    if (kvaSetAttr(ulAttr, &ulValue))
        return VO_NOTIMPL;

    return VO_TRUE;
}

static int color_ctrl_get(char *what, int *value)
{
    ULONG   ulAttr;
    ULONG   ulValue;

    if (!strcmp(what, "brightness"))
        ulAttr = KVAA_BRIGHTNESS;
    else if (!strcmp(what, "contrast"))
        ulAttr = KVAA_CONTRAST;
    else if (!strcmp(what, "hue"))
        ulAttr = KVAA_HUE;
    else if (!strcmp(what, "saturation"))
        ulAttr = KVAA_SATURATION;
    else
        return VO_NOTIMPL;

    if (kvaQueryAttr(ulAttr, &ulValue))
        return VO_NOTIMPL;

    // add 1 to adjust range
    *value = ((ulValue + 1) * 200 / 255) - 100;

    return VO_TRUE;
}

static int control(uint32_t request, void *data)
{
    switch (request) {
    case VOCTRL_GET_IMAGE:
        return get_image(data);

    case VOCTRL_DRAW_IMAGE:
        return draw_image(data);

    case VOCTRL_QUERY_FORMAT:
        return query_format(*(uint32_t *)data);

    case VOCTRL_FULLSCREEN:
        return fs_toggle();

    case VOCTRL_SET_EQUALIZER:
        {
        vf_equalizer_t *eq=data;
        return color_ctrl_set(eq->item, eq->value);
        }

    case VOCTRL_GET_EQUALIZER:
        {
        vf_equalizer_t *eq=data;
        return color_ctrl_get(eq->item, &eq->value);
        }

    case VOCTRL_UPDATE_SCREENINFO:
        vo_screenwidth  = m_int.kvac.cxScreen;
        vo_screenheight = m_int.kvac.cyScreen;

        aspect_save_screenres(vo_screenwidth, vo_screenheight);

        return VO_TRUE;
    }

    return VO_NOTIMPL;
}

static int draw_frame(uint8_t *src[])
{
    return VO_ERROR;
}

static int draw_slice(uint8_t *src[], int stride[], int w, int h, int x, int y)
{
    uint8_t *s;
    uint8_t *d;

    // copy packed or Y
    d = m_int.planes[0] + m_int.stride[0] * y + x;
    s = src[0];
    mem2agpcpy_pic(d, s, w * m_int.bpp, h, m_int.stride[0], stride[0]);

    // YV12 or YVU9
    if (m_int.nChromaShift) {
        w >>= m_int.nChromaShift; h >>= m_int.nChromaShift;
        x >>= m_int.nChromaShift; y >>= m_int.nChromaShift;

        // copy U
        d = m_int.planes[1] + m_int.stride[1] * y + x;
        s = src[1];
        mem2agpcpy_pic(d, s, w, h, m_int.stride[1], stride[1]);

        // copy V
        d = m_int.planes[2] + m_int.stride[2] * y + x;
        s = src[2];
        mem2agpcpy_pic(d, s, w, h, m_int.stride[2], stride[2]);
    }

    return 0;
}

#define vo_draw_alpha(imgfmt) \
    vo_draw_alpha_##imgfmt(w, h, src, srca, stride, \
                           m_int.planes[0] + m_int.stride[0] * y0 + m_int.bpp * x0, \
                           m_int.stride[0])

static void draw_alpha(int x0, int y0, int w, int h,
                       unsigned char *src, unsigned char *srca, int stride)
{
    switch (m_int.iImageFormat) {
    case IMGFMT_YV12:
    case IMGFMT_YVU9:
        vo_draw_alpha(yv12);
        break;

    case IMGFMT_YUY2:
        vo_draw_alpha(yuy2);
        break;

    case IMGFMT_BGR24:
        vo_draw_alpha(rgb24);
        break;

    case IMGFMT_BGR16:
        vo_draw_alpha(rgb16);
        break;

    case IMGFMT_BGR15:
        vo_draw_alpha(rgb15);
        break;
    }
}

static void draw_osd(void)
{
    vo_draw_text(SRC_WIDTH, SRC_HEIGHT, draw_alpha);
}

static void flip_page(void)
{
    imgDisplay();
}

static void check_events(void)
{
    QMSG    qm;

    // On slave mode, we need to change our window size according to a
    // parent window size
    if (WinID != -1) {
        RECTL rcl;

        WinQueryWindowRect(HWNDFROMWINID(WinID), &rcl);

        if (rcl.xLeft   != m_int.rclParent.xLeft   ||
            rcl.yBottom != m_int.rclParent.yBottom ||
            rcl.xRight  != m_int.rclParent.xRight  ||
            rcl.yTop    != m_int.rclParent.yTop) {
            WinSetWindowPos(m_int.hwndFrame, NULLHANDLE,
                            rcl.xLeft, rcl.yBottom,
                            rcl.xRight - rcl.xLeft, rcl.yTop - rcl.yBottom,
                            SWP_SIZE | SWP_MOVE);

            m_int.rclParent = rcl;
        }
    }

    while (WinPeekMsg(m_int.hab, &qm, NULLHANDLE, 0, 0, PM_REMOVE))
        WinDispatchMsg(m_int.hab, &qm);
}
