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

#include "kernel.h"
#include "swis.h"
#include "inttypes.h"
#include "player.h"
#include "config.h"
#include "msg.h"
#include "mt.h"
#include "osd.h"
#include "osd_font.h"
#include "ka_mem.h"
#include "keyboard.h"
#include "display.h"
#include "timer1.h"
#include "ka_log.h"

typedef struct osditem_t
{
  char *title;
  int min, max; // min and max possible values
  int diff;     // delta to apply when changing value using left/right arrows
  int std;      // standard value, or < min if no standard value
  int *val;     // pointer to value or NULL for empty
  // null or function to apply value changes (pass cv->nul to read current value)
  int (* set)(player_t* vo, int val);
} osditem_t;

static int caudio_volume(player_t* player, int vol)
{
  audio_t* astream = (player->stream) ? player->stream->audio.astream : NULL;
  if (!astream) return (vol >= 0) ? vol : 0;
  return audio_volume(astream, vol);
}

static const osditem_t
  osditem_empty      = { "",           0, 100, 0,  -1, NULL,               NULL },
  osditem_brightness = { "Brightness", 0, 200, 2, 100, &config.video.brightness, display_brightness },
  osditem_contrast   = { "Contrast",   0, 200, 2, 100, &config.video.contrast,   display_contrast },
  osditem_colour     = { "Colour",     0, 200, 2, 100, &config.video.colour,     display_colour },
  osditem_volume     = { "Volume",     0, 127, 1,  -1, &config.audio.volume, caudio_volume };

struct osd_s
{
  uint32_t         state;
  int              zoom;
  int              currentItem;
  char*            bitmap;
  char*            emptyline;
  int              sizex;
  int              sizey;
  int              changed[4];
  const osditem_t* osditems[6];
  uint32_t         drawTimer;
  uint32_t         msgTimer;
  uint32_t         mouseTime;
  int              lastItem;
  int              lastItemValue;
  ka_demux_pos_t   lastReplayPos;
  wimp_mouse_t     lastMouse;
  char             timeCode[12];
  char             msg[256];
};

static uint8_t palette_8[8]   = { 0, 1, 2, 3, 4, 5, 6, 7};
static const uint8_t palette_y[8]   = { 0x00, 0x4c, 0x95, 0xe1, 0x1d, 0x69, 0xb2, 0xff};
//static const uint8_t palette_u[8]   = { 0x80, 0x00, 0x00, 0x00, 0x6f, 0x00, 0x25, 0x80};
//static const uint8_t palette_v[8]   = { 0x80, 0x9c, 0x00, 0x19, 0x00, 0x00, 0x00, 0x80};

static const uint16_t palette_12[8]   = { 0x0000, 0x000f, 0x00f0, 0x00ff
                                        , 0x0f00, 0x0444, 0x0ff0, 0x0fff };
static const uint16_t palette_12_r[8] = { 0x0000, 0x0f00, 0x00f0, 0x0ff0
                                        , 0x000f, 0x0444, 0x00ff, 0x0fff };
static const uint16_t palette_15[8]   = { 0x0000, 0x001f, 0x03e0, 0x03ff
                                        , 0x7c00, 0x2108, 0x7fe0, 0x7fff };
static const uint16_t palette_15_r[8] = { 0x0000, 0x7c00, 0x03e0, 0x7fe0
                                        , 0x001f, 0x2108, 0x03ff, 0x7fff };
static const uint16_t palette_16[8]   = { 0x0000, 0x001f, 0x07e0, 0x07ff
                                        , 0xf800, 0x4208, 0xffe0, 0xffff };
static const uint16_t palette_16_r[8] = { 0x0000, 0xf800, 0x07e0, 0xffe0
                                        , 0x001f, 0x4208, 0x07ff, 0xffff };
static const uint32_t palette_32[8]   = { 0x00000000, 0x000000ff, 0x0000ff00, 0x0000ffff
                                        , 0x00ff0000, 0x00404040, 0x00ffff00, 0x00ffffff };
static const uint32_t palette_32_r[8] = { 0x00000000, 0x00ff0000, 0x0000ff00, 0x00ffff00
                                        , 0x000000ff, 0x00404040, 0x0000ffff, 0x00ffffff };
static const uint16_t palette_a12[8]   = { 0xf000, 0xf00f, 0xf0f0, 0xf0ff
                                         , 0xff00, 0xf444, 0xfff0, 0xffff };
static const uint16_t palette_a12_r[8] = { 0xf000, 0xff00, 0xf0f0, 0xfff0
                                         , 0xf00f, 0xf444, 0xf0ff, 0xffff };
static const uint16_t palette_a15[8]   = { 0x8000, 0x801f, 0x83e0, 0x83ff
                                         , 0xfc00, 0xa108, 0xffe0, 0xffff };
static const uint16_t palette_a15_r[8] = { 0x8000, 0xfc00, 0x83e0, 0xffe0
                                         , 0x801f, 0xa108, 0x83ff, 0xffff };
static const uint32_t palette_a32[8]   = { 0xff000000, 0xff0000ff, 0xff00ff00, 0xff00ffff
                                         , 0xffff0000, 0xff404040, 0xffffff00, 0xffffffff };
static const uint32_t palette_a32_r[8] = { 0xff000000, 0xffff0000, 0xff00ff00, 0xffffff00
                                         , 0xff0000ff, 0xff404040, 0xff00ffff, 0xffffffff };
#define CHANGED_AREA_EMPTY    -123456

typedef enum
{ OSD_COLOUR_TRANSPARENT   = 255
, OSD_COLOUR_BLACK         = 0
, OSD_COLOUR_RED           = 1
, OSD_COLOUR_GREEN         = 2
, OSD_COLOUR_YELLOW        = 3
, OSD_COLOUR_BLUE          = 4
, OSD_COLOUR_GREY          = 5
, OSD_COLOUR_CYAN          = 6
, OSD_COLOUR_WHITE         = 7
} osd_colour;
/*
static void osd_save(const osd_t* osd, char* filename)
{
  _kernel_swi_regs regs;
  FILE* f;
  sprite_header_t h;
  int width, height;

  width = osd->sizex >> 2; // width is now always in words
  height = osd->sizey;

  if((f = fopen(filename,"wb")) == 0)
  {
    report_error(0, "error10:%s", filename); // (Cannot create Sprite file)
    return;
  }

  // write sprite file header
  h.num = 1;
  h.offset = 16;
  h.size_1 = 4 * width * height + 60;
  h.size_2 = h.size_1 - 16;
  h.name[10] = 0; // ensure it's padded with zeros,
  h.name[11] = 0; // should use \0 in sprintf but it didn't always work ?
  sprintf(h.name,"frameframe");
  h.width = width - 1;
  h.height = height - 1;
  h.first = 0;
  h.last = 31;
  h.s_offset = 44;
  h.m_offset = h.s_offset; // no mask
  h.mode = 28;
  fwrite(&h.num,sizeof(sprite_header_t)-4,1,f);

  // write sprite data
  fwrite(osd->bitmap,1,4*width*height,f);

  fclose(f);

  // set filetype
  regs.r[0] = WRITE_CATINFO;
  regs.r[1] = (int)filename;
  regs.r[2] = SPRITE;
  _kernel_swi(OS_File, &regs, &regs);
}
*/
static void get_char_min_max_x(unsigned char c, int *min, int *max)
{
  int y, x;
  const unsigned short *glyph;

  *max = 0;
  *min = FONTSIZE_X;
  glyph = font12x24[c];
  for (y = 0; y < FONTSIZE_Y; y++)
  {
    unsigned short mask;

    mask = glyph[y];

    for (x = 0; (mask & (1<<x)) == 0 && x < FONTSIZE_X; x++)
      ;
    if (x < *min)
      *min = x;
    for (x = FONTSIZE_X-1; (mask & (1<<x)) == 0 && x > 0; x--)
      ;
    if (x > *max)
      *max = x;
  }
  if (*min >= *max)
    *min = *max = 0;
  if (c == ' ')
    *max += 3;
}

#define DRAW_BITMAP_LINE(wr0, rd, x0, x1, type, palette) \
    { type* wr = (type*) wr0;                    \
      int x;                                     \
      for (x = x1-1; x >= x0; x--)               \
        if (rd[x] != OSD_COLOUR_TRANSPARENT)     \
          wr[x] = palette[rd[x] & 7];            \
    }

#define DRAW_BITMAP_LINEXY(wr0, rd, x0, x1, offset, step, palette) \
    { uint8_t* wr = ((uint8_t*) wr0) + offset;   \
      int x;                                     \
      for (x = x1-1; x >= x0; x--)               \
        if (rd[x] != OSD_COLOUR_TRANSPARENT)     \
          wr[x*step] = palette[rd[x] & 7];       \
    }

#define DRAW_BITMAP_LINE_SCALED(wr0, rd, x0, x1, type, palette) \
    { type* wr = (type*) wr0;                           \
      int x;                                            \
      for (x = x1-1; x >= x0; x--)                      \
        if (rd[x] != OSD_COLOUR_TRANSPARENT)            \
          wr[2*x+0] = wr[2*x+1] = palette[rd[x] & 7];   \
    }

#define DRAW_BITMAP_LINEXY_SCALED(wr0, rd, x0, x1, offset, step, palette) \
    { uint8_t* wr = ((uint8_t*) wr0) + offset;          \
      int x;                                            \
      for (x = x1-1; x >= x0; x--)                      \
        if (rd[x] != OSD_COLOUR_TRANSPARENT)            \
          wr[step*(2*x+0)] = wr[step*(2*x+1)] = palette[rd[x] & 7];   \
    }

#define DRAW_BITMAP_LINE_8(wr, rd, x0, x1)              \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint8_t,  palette_8)
#define DRAW_BITMAP_LINE_12(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint16_t, palette_12)
#define DRAW_BITMAP_LINE_12_R(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint16_t, palette_12_r)
#define DRAW_BITMAP_LINE_15(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint16_t, palette_15)
#define DRAW_BITMAP_LINE_15_R(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint16_t, palette_15_r)
#define DRAW_BITMAP_LINE_16(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint16_t, palette_16)
#define DRAW_BITMAP_LINE_16_R(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint16_t, palette_16_r)
#define DRAW_BITMAP_LINE_32(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint32_t, palette_32)
#define DRAW_BITMAP_LINE_32_R(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint32_t, palette_32_r)
#define DRAW_BITMAP_LINE_A12(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint16_t, palette_a12)
#define DRAW_BITMAP_LINE_A12_R(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint16_t, palette_a12_r)
#define DRAW_BITMAP_LINE_A15(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint16_t, palette_a15)
#define DRAW_BITMAP_LINE_A15_R(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint16_t, palette_a15_r)
#define DRAW_BITMAP_LINE_A32(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint32_t, palette_a32)
#define DRAW_BITMAP_LINE_A32_R(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint32_t, palette_a32_r)
#define DRAW_BITMAP_LINE_Y(wr, rd, x0, x1)              \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint8_t,  palette_y)

#define DRAW_BITMAP_LINE_8_SCALED(wr, rd, x0, x1)       \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint8_t,  palette_8)
#define DRAW_BITMAP_LINE_12_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint16_t, palette_12)
#define DRAW_BITMAP_LINE_12_R_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint16_t, palette_12_r)
#define DRAW_BITMAP_LINE_15_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint16_t, palette_15)
#define DRAW_BITMAP_LINE_15_R_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint16_t, palette_15_r)
#define DRAW_BITMAP_LINE_16_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint16_t, palette_16)
#define DRAW_BITMAP_LINE_16_R_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint16_t, palette_16_r)
#define DRAW_BITMAP_LINE_32_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint32_t, palette_32)
#define DRAW_BITMAP_LINE_32_R_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint32_t, palette_32_r)
#define DRAW_BITMAP_LINE_A12_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint16_t, palette_a12)
#define DRAW_BITMAP_LINE_A12_R_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint16_t, palette_a12_r)
#define DRAW_BITMAP_LINE_A15_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint16_t, palette_a15)
#define DRAW_BITMAP_LINE_A15_R_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint16_t, palette_a15_r)
#define DRAW_BITMAP_LINE_A32_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint32_t, palette_a32)
#define DRAW_BITMAP_LINE_A32_R_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint32_t, palette_a32_r)
#define DRAW_BITMAP_LINE_Y_SCALED(wr, rd, x0, x1)       \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint8_t,  palette_y)

/**
 * Plots a rectangle from the OSD bitmap to the rgb screen or buffer.
 *
 * @param osd
 * @param player
 * @param x0     Horizontal start of the rectangle.
 * @param y0     Vertical start of the rectangle.
 * @param x1     Horizontal end the rectangle.
 * @param y1     Vertical end of the rectangle.
 */
static void draw_bitmap(osd_t* osd, player_t* player, int x0, int y0, int x1, int y1)
{
  int y;
  uint8_t *scr, *scr_u, *scr_v;
  char *bm;

  scr = player->paint.dst;
  if (scr == NULL)
    return;

  scr_u = player->paint.dst_u;
  scr_v = player->paint.dst_v;
  bm = osd->bitmap;

  for (y = y0; y < y1; y++)
  {
    if (!osd->emptyline[y])
    {
      if (osd->zoom > 100)
      {
        switch (player->screen_paint.col_depth)
        {
          case ka_col_depth_256:
            DRAW_BITMAP_LINE_8_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_8_SCALED(scr, bm, x0, x1);
          break;

          case ka_col_depth_NV12:
          case ka_col_depth_NV21:
          case ka_col_depth_YV12:
          case ka_col_depth_YV16:
            // Y
            DRAW_BITMAP_LINE_Y_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_Y_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_UYVY:
            // Y
            DRAW_BITMAP_LINEXY_SCALED(scr, bm, x0, x1, 1, 2, palette_y);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINEXY_SCALED(scr, bm, x0, x1, 1, 2, palette_y);
          break;
          case ka_col_depth_YUY2:
            // Y
            DRAW_BITMAP_LINEXY_SCALED(scr, bm, x0, x1, 0, 2, palette_y);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINEXY_SCALED(scr, bm, x0, x1, 0, 2, palette_y);
          break;

          case ka_col_depth_TBGR12:
            DRAW_BITMAP_LINE_12_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_12_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_TBGR15:
            DRAW_BITMAP_LINE_15_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_15_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_TBGR16:
            DRAW_BITMAP_LINE_16_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_16_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_TBGR32:
            DRAW_BITMAP_LINE_32_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_32_SCALED(scr, bm, x0, x1);
          break;

          case ka_col_depth_TRGB12:
            DRAW_BITMAP_LINE_12_R_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_12_R_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_TRGB15:
            DRAW_BITMAP_LINE_15_R_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_15_R_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_TRGB16:
            DRAW_BITMAP_LINE_16_R_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_16_R_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_TRGB32:
            DRAW_BITMAP_LINE_32_R_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_32_R_SCALED(scr, bm, x0, x1);
          break;

          case ka_col_depth_ABGR12:
            DRAW_BITMAP_LINE_A12_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_A12_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_ABGR15:
            DRAW_BITMAP_LINE_A15_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_A15_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_ABGR16:
            DRAW_BITMAP_LINE_16_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_16_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_ABGR32:
            DRAW_BITMAP_LINE_A32_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_A32_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_ARGB12:
            DRAW_BITMAP_LINE_A12_R_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_A12_R_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_ARGB15:
            DRAW_BITMAP_LINE_A15_R_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_A15_R_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_ARGB16:
            DRAW_BITMAP_LINE_16_R_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_16_R_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_ARGB32:
            DRAW_BITMAP_LINE_A32_R_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_A32_R_SCALED(scr, bm, x0, x1);
          break;
        }
      }
      else
      {
        switch (player->screen_paint.col_depth)
        {
          case ka_col_depth_256:
            DRAW_BITMAP_LINE_8(scr, bm, x0, x1);
          break;
          case ka_col_depth_NV12:
          case ka_col_depth_NV21:
          case ka_col_depth_YV12:
          case ka_col_depth_YV16:
            // Y
            DRAW_BITMAP_LINE_Y(scr, bm, x0, x1);
          break;
          case ka_col_depth_UYVY:
            // Y
            DRAW_BITMAP_LINEXY(scr, bm, x0, x1, 1, 2, palette_y);
          break;
          case ka_col_depth_YUY2:
            // Y
            DRAW_BITMAP_LINEXY(scr, bm, x0, x1, 0, 2, palette_y);
          break;

          case ka_col_depth_TBGR12: DRAW_BITMAP_LINE_12(scr, bm, x0, x1);  break;
          case ka_col_depth_TBGR15: DRAW_BITMAP_LINE_15(scr, bm, x0, x1);  break;
          case ka_col_depth_TBGR16: DRAW_BITMAP_LINE_16(scr, bm, x0, x1);  break;
          case ka_col_depth_TBGR32: DRAW_BITMAP_LINE_32(scr, bm, x0, x1);  break;
          case ka_col_depth_TRGB12: DRAW_BITMAP_LINE_12_R(scr, bm, x0, x1);  break;
          case ka_col_depth_TRGB15: DRAW_BITMAP_LINE_15_R(scr, bm, x0, x1);  break;
          case ka_col_depth_TRGB16: DRAW_BITMAP_LINE_16_R(scr, bm, x0, x1);  break;
          case ka_col_depth_TRGB32: DRAW_BITMAP_LINE_32_R(scr, bm, x0, x1);  break;

          case ka_col_depth_ABGR12: DRAW_BITMAP_LINE_A12(scr, bm, x0, x1);  break;
          case ka_col_depth_ABGR15: DRAW_BITMAP_LINE_A15(scr, bm, x0, x1);  break;
          case ka_col_depth_ABGR16: DRAW_BITMAP_LINE_16(scr, bm, x0, x1);  break;
          case ka_col_depth_ABGR32: DRAW_BITMAP_LINE_A32(scr, bm, x0, x1);  break;
          case ka_col_depth_ARGB12: DRAW_BITMAP_LINE_A12_R(scr, bm, x0, x1);  break;
          case ka_col_depth_ARGB15: DRAW_BITMAP_LINE_A15_R(scr, bm, x0, x1);  break;
          case ka_col_depth_ARGB16: DRAW_BITMAP_LINE_16_R(scr, bm, x0, x1);  break;
          case ka_col_depth_ARGB32: DRAW_BITMAP_LINE_A32_R(scr, bm, x0, x1);  break;
        }
      }
    }
    else if (osd->zoom > 100)
    {
      scr += player->paint.dst_bpr;
      scr_u += player->paint.dst_u_bpr;
      scr_v += player->paint.dst_v_bpr;
    }

    scr += player->paint.dst_bpr;
    scr_u += player->paint.dst_u_bpr;
    scr_v += player->paint.dst_v_bpr;
    bm += osd->sizex;
  }
}

/**
 * Mark a rectangle in the OSD bitmap as update and in need to redraw.
 *
 * @param osd
 * @param x0     Horizontal start of the rectangle.
 * @param y0     Vertical start of the rectangle.
 * @param x1     Horizontal end the rectangle.
 * @param y1     Vertical end of the rectangle.
 */
static void add_changed_area(osd_t* osd, int x0, int y0, int x1, int y1)
{
  if (osd->changed[0] == CHANGED_AREA_EMPTY)
  {
    osd->changed[0] = x0;
    osd->changed[1] = y0;
    osd->changed[2] = x1;
    osd->changed[3] = y1;
  }
  else
  {
    if (x0 < osd->changed[0])    osd->changed[0] = x0;
    if (y0 < osd->changed[1])    osd->changed[1] = y0;
    if (x1 > osd->changed[2])    osd->changed[2] = x1;
    if (y1 > osd->changed[3])    osd->changed[3] = y1;
  }
}

/**
 * Fills a rectangle in the OSD bitmap.
 *
 * @param osd
 * @param x0     Horizontal starting position of the rectangle.
 * @param y0     Vertical starting position of the rectangle.
 * @param w      Width of the rectangle.
 * @param h      Height of the rectangle.
 * @param colour Colour to give to the rectangle.
 */
static void osd_drawRectangle(osd_t* osd, int x0, int y0, int w, int h, osd_colour colour)
{
  int y;

  y0 = osd->sizey - y0 - h;
  if (x0 < 0)              w += x0, x0 = 0;
  if (y0 < 0)              h += y0, y0 = 0;
  if (x0+w > osd->sizex)   w = osd->sizex - x0;
  if (y0+h > osd->sizey)   h = osd->sizey - y0;
  if (w <= 0 || h <= 0)    return;

  for (y = y0; y < y0+h; y++)
  {
    memset(osd->bitmap + y*osd->sizex + x0, colour, w);
    osd->emptyline[y] = 0;
  }
  add_changed_area(osd, x0, y0, x0+w, y0+h);
}

/*
static void osd_getStringSize(const char *text, int *w, int *h)
{
  unsigned int i, width;

  width = 0;
  for (i = 0; i < strlen(text); i++)
  {
    int min, max;
    get_char_min_max_x(text[i], &min, &max);
    width += max-min + 2;
  }
  *w = width;
  *h = FONTSIZE_Y;
}
*/

/**
 * Push a single character in the OSD bitmap.
 *
 * @param osd
 * @param c       Character to display.
 * @param x       Horizontal starting position in the bitmap.
 * @param y       Vertical starting position in the bitmap.
 * @param colour  Colour to give to the text.
 */
static void draw_char(osd_t* osd, unsigned char c, int x, int y, osd_colour colour)
{
  const unsigned short *glyph;
  int px, py;

  y = osd->sizey -y - FONTSIZE_Y;
  glyph = font12x24[c];
  for (py = 0; py < FONTSIZE_Y; py++)
  {
    int yy;

    yy = y + py;

    if (yy >= 0 && yy < osd->sizey)
    {
      unsigned short mask;
      char *bm;

      bm = osd->bitmap + yy*osd->sizex;
      osd->emptyline[yy] = 0;

      mask = glyph[py];
      for (px = 0; px < FONTSIZE_X; px++)
        if (x+px >= 0 && x+px < osd->sizex)
          if (mask & (1<<px))
            bm[x+px] = colour;
    }
  }
}

/**
 * Push some text in the OSD bitmap.
 *
 * @param osd
 * @param text    Text to display.
 * @param x       Horizontal starting position in the bitmap.
 * @param y       Vertical starting position in the bitmap.
 * @param colour  Colour to give to the text.
 */
static void osd_drawText(osd_t* osd, const char *text, int x, int y, osd_colour colour)
{
  int i;

  for (i = 0; text[i]; i++)
  {
    int min, max;
    get_char_min_max_x(text[i], &min, &max);
    add_changed_area(osd, x-min, y, x-min+(max-min), y+FONTSIZE_Y);
    draw_char(osd, text[i], x-min, y, colour);
    x += max-min+2;
  }
}

/*
static void osd_drawTextRightAligned(osd_t* osd, const char *text, int x, int y, osd_colour colour)
{
  int w, h;

  osd_getStringSize(text, &w, &h);
  osd_drawText(osd, text, x-w, y, colour);
}
*/

/**
 * Updates pointer.
 */
static void osd_setPointer(player_t* player, int on)
{
  if ((player->mode == KA_MODE_AUTO)
  ||  (player->mode == KA_MODE_MANUAL))
  {
    display_showPointer(on);
  }
}

/**
 * Initialises the OSD to the player's parameters.
 *
 * @param osd
 * @param player
 */
static int osd_enable(osd_t* osd, player_t* player)
{
  if (osd->bitmap) return 0;
  if (!player->paint.dst_pix_width) return 1;

  // use dst sizes so osd is visible when the picture is larger than the screen
  osd->sizex = player->paint.dst_pix_width;
  osd->sizey = player->paint.dst_height;

  // to use the scaled display above 100% zoom
  if (osd->sizex >= 960)
  {
    osd->sizex >>= 1;
    osd->sizey >>= 1;
    osd->zoom = 200;
  }
  else osd->zoom = 100;

  osd_setPointer(player, 0);

  osd->bitmap = ka_mem_alloc(osd->sizex*osd->sizey + osd->sizey);
  if (!osd->bitmap) return 1;
  osd->emptyline = osd->bitmap + osd->sizex*osd->sizey;

  memset(osd->bitmap, OSD_COLOUR_TRANSPARENT, osd->sizex*osd->sizey);
  for (int i = 0; i < osd->sizey; i++)
    osd->emptyline[i] = 1;
  osd->changed[0] = 0;
  osd->changed[1] = 0;
  osd->changed[2] = osd->sizex;
  osd->changed[3] = osd->sizey;

  if (player->screen_paint.col_depth == ka_col_depth_256)
  {
    for (int i = 0; i < 8; i++)
    {
      _kernel_swi_regs regs;

      regs.r[0] = palette_32[i] << 8;
      _kernel_swi(ColourTrans_ReturnColourNumber, &regs, &regs);
      palette_8[i] = regs.r[0];
    }
  }
  osd->changed[0] = CHANGED_AREA_EMPTY;

  osd->osditems[0] = &osditem_empty;
  osd->osditems[1] = &osditem_brightness;
  osd->osditems[2] = &osditem_contrast;

  int n = 3;
  if (!(config.video.modes[player->mode].cfg & cfg_video_mono))
    osd->osditems[n++] = &osditem_colour;
  if (config.audio.cfg & cfg_audio_play)
    osd->osditems[n++] = &osditem_volume;

  osd->osditems[n++] = NULL;
  if (osd->currentItem >= (n - 1))
    osd->currentItem = 0;

  return 0;
}

/**
 * Loose the OSD content and parameters.
 *
 * @param player
 */
static void osd_disable(player_t* player)
{
  osd_t* osd = player->osd;

  osd->state = 0;
  osd->lastReplayPos.curPos = osd->lastReplayPos.endPos = 0;
  osd->lastItem = 0;
  osd_setPointer(player, 0);

  if (osd->bitmap)
  {
    ka_mem_free(osd->bitmap);
    osd->bitmap = NULL;
    osd->emptyline = NULL;
  }
}

#define BAR_HEIGHT 6

/**
 * Updates parts of the OSD.
 */
static void osd_update(player_t* player, uint32_t update)
{
  int width, height;
  osd_t* osd = player->osd;

  if (!osd) return;

  if (osd->state)
  {
    // Allocate bitmap and init
    if (!osd->bitmap)
      osd_enable(osd, player);
    // Could not allocate bitmap? Disable OSD.
    if (!osd->bitmap)
    {
      osd_disable(player);
      return;
    }
  }

  if (!osd->bitmap)
    return;

  width = osd->sizex - 20;
  height = osd->sizey;

  if (update & (OSD_STATE_POS | OSD_STATE_NAV))
  {
    int clean = 0;

    // Position bar is enabled? Show pointer.
    if ((update & OSD_STATE_POS) && (osd->state & OSD_STATE_POS))
    {
      osd_setPointer(player, 1);
    }
    // Position bar is disabled? Clear it and hide pointer.
    if ((update & OSD_STATE_POS) && !(osd->state & OSD_STATE_POS))
    {
      osd_setPointer(player, 0);
      osd->lastReplayPos.endPos = 0;
      clean = 1;
    }

    // If position bar was shown and we will display a property, clear position bar
    if (osd->lastReplayPos.endPos && osd->currentItem)
    {
      osd->lastReplayPos.endPos = 0;
      clean = 1;
    }
    // Property is disabled or we switched to another one? Clear it.
    if (((update & OSD_STATE_NAV) && !(osd->state & OSD_STATE_NAV) && osd->lastItem)
    ||  (osd->lastItem != osd->currentItem))
    {
      osd->lastItem = 0;
      clean = 1;
    }

    if (clean)
      osd_drawRectangle(osd, 0, 4, osd->sizex, 32 + FONTSIZE_Y - 4, OSD_COLOUR_TRANSPARENT);

    // Show a property title and value
    if ((osd->state & OSD_STATE_NAV) && (osd->currentItem))
    {
      const osditem_t* cv = osd->osditems[osd->currentItem];

      int newVal = (width * (*cv->val)) / (cv->max - cv->min);
      int oldVal = (width * osd->lastItemValue) / (cv->max - cv->min);
      int stdVal = (width * cv->std) / (cv->max - cv->min);

      if (!osd->lastItem)
      {
        // Redraw all
        osd_drawText(osd, cv->title, 10, 32, OSD_COLOUR_WHITE);

        osd_drawRectangle(osd, 10         , 20, newVal        , BAR_HEIGHT, OSD_COLOUR_WHITE);
        osd_drawRectangle(osd, 10 + newVal, 20, width - newVal, BAR_HEIGHT, OSD_COLOUR_GREY);
      }
      else
      {
        // Redraw value change
        if (newVal < oldVal)
          osd_drawRectangle(osd, 10 + newVal, 20, oldVal - newVal, BAR_HEIGHT, OSD_COLOUR_GREY);
        else if (newVal > oldVal)
          osd_drawRectangle(osd, 10 + oldVal, 20, newVal - oldVal, BAR_HEIGHT, OSD_COLOUR_WHITE);
      }

      // Redraw std value
      if (cv->std >= cv->min)
        osd_drawRectangle(osd, 10 + stdVal, 16, 1, BAR_HEIGHT + 8, OSD_COLOUR_WHITE);

      osd->lastItem = osd->currentItem;
      osd->lastItemValue = *cv->val;
    }
    // Show position bar
    else if ((osd->state & OSD_STATE_POS) && (player->stream != NULL))
    {
      ka_demux_pos_t pos;

      ka_stream_getPosInfo(player->stream, &pos);

      if (pos.endPos)
      {
        int newPos = (int)((pos.curPos * width) / pos.endPos);
        int oldPos = (int)((osd->lastReplayPos.curPos * width) / pos.endPos);
        int posx = (osd->sizex / 2) - 90;

        if ((osd->lastReplayPos.titleNr != pos.titleNr)
        ||  (osd->lastReplayPos.endPos != pos.endPos))
        {
          // Redraw all
          osd_drawRectangle(osd, 10         , 20, newPos        , BAR_HEIGHT, OSD_COLOUR_WHITE);
          osd_drawRectangle(osd, 10 + newPos, 20, width - newPos, BAR_HEIGHT, OSD_COLOUR_GREY);
        }
        else
        {
          // Redraw change
          if (newPos < oldPos)
            osd_drawRectangle(osd, 10 + newPos, 20, oldPos - newPos, BAR_HEIGHT, OSD_COLOUR_GREY);
          else if (newPos > oldPos)
            osd_drawRectangle(osd, 10 + oldPos, 20, newPos - oldPos, BAR_HEIGHT, OSD_COLOUR_WHITE);
        }


        // Redraw chapters
        for(int i = 1; i < KA_PROGRAM_MAX_CHAPTERS; i++)
        {
          int64_t chPos = ka_stream_getChapterPos(player->stream, pos.titleNr, i);

          // No or no more chapters
          if (chPos < 0)
            break;

          int curPos = (int)((chPos * width) / pos.endPos);
          osd_drawRectangle(osd, 10 + curPos, 16, 1, BAR_HEIGHT + 8, OSD_COLOUR_WHITE);
        }

        osd_colour color;

        // Draw play
        color = (player_getPlayStatus(player) == play_status_play) ? OSD_COLOUR_RED : OSD_COLOUR_WHITE;
        draw_char(osd, 1, posx +   0, 4, color);
        // Draw step
        color = (player_getPlayStatus(player) == play_status_advance) ? OSD_COLOUR_RED : OSD_COLOUR_WHITE;
        draw_char(osd, 2, posx +  20, 4, color);
        // Draw pause
        color = (player_getPlayStatus(player) == play_status_pause) ? OSD_COLOUR_RED : OSD_COLOUR_WHITE;
        draw_char(osd, 3, posx +  40, 4, color);
        // Draw stop
        color = (player_getPlayStatus(player) == play_status_stop) ? OSD_COLOUR_RED : OSD_COLOUR_WHITE;
        draw_char(osd, 4, posx +  60, 4, color);
        // Draw rewind
        color = OSD_COLOUR_WHITE;
        draw_char(osd, 5, posx +  80, 4, color);
        // Draw forward
        color = OSD_COLOUR_WHITE;
        draw_char(osd, 6, posx + 100, 4, color);
        // Draw prev
        color = OSD_COLOUR_WHITE;
        draw_char(osd, 7, posx + 120, 4, color);
        // Draw next
        color = OSD_COLOUR_WHITE;
        draw_char(osd, 8, posx + 140, 4, color);
        // Draw mute
        if (config.audio.cfg & cfg_audio_play)
        {
          // Two different shapes so need to clear it first.
          osd_drawRectangle(osd, posx + 160, 4, BUTTONSIZE_X, BUTTONSIZE_Y, OSD_COLOUR_TRANSPARENT);
          color = (config.audio.cfg & cfg_audio_mute) ? OSD_COLOUR_RED : OSD_COLOUR_WHITE;
          draw_char(osd, (config.audio.cfg & cfg_audio_mute) ? 10 : 9, posx + 160, 4, color);
        }
      }

      osd->lastReplayPos = pos;
    }
  }

  if (update & (OSD_STATE_MSG | OSD_STATE_TC))
  {
    // Clean previous text
    osd_drawRectangle(osd, 0, osd->sizey - FONTSIZE_Y, osd->sizex, FONTSIZE_Y, OSD_COLOUR_TRANSPARENT);

    // Update message
    if (osd->state & OSD_STATE_MSG)
      osd_drawText(osd, osd->msg, 10, osd->sizey - FONTSIZE_Y, OSD_COLOUR_RED);
    // Update timecode
    else if (osd->state & OSD_STATE_TC)
    {
      uint32_t rtc = timer_getrtc();

      snprintf( osd->timeCode, sizeof(osd->timeCode), "%02d:%02d:%02d"
              , rtc/(timer_getrate()*60*60)
              , (rtc/(timer_getrate()*60))%60
              , (rtc/timer_getrate())%60
              );
      osd_drawText(osd, osd->timeCode, 10, osd->sizey - FONTSIZE_Y, OSD_COLOUR_GREEN);
      if (player->curr_frame != NULL)
      {
        snprintf(osd->timeCode, sizeof(osd->timeCode), "%05d", player->draw.picturenr);
        osd_drawText(osd, osd->timeCode, 90, osd->sizey - FONTSIZE_Y, OSD_COLOUR_CYAN);
        static const char* stype[3] = {"I", "P", "B"};
        osd_drawText(osd, stype[player->draw.frame.type], 140, osd->sizey - FONTSIZE_Y, OSD_COLOUR_GREEN);
        snprintf(osd->timeCode, sizeof(osd->timeCode), "%02d Fps"
          , (player->time.fps_delta > 0)
            ? (timer_getrate() + (player->time.fps_delta >> 1))/player->time.fps_delta
            : 0);
        osd_drawText(osd, osd->timeCode, osd->sizex-70, osd->sizey - FONTSIZE_Y, OSD_COLOUR_GREEN);
      }
    }
  }

  if (!osd->state)
  {
    // Disable OSD
    osd_disable(player);
  }
}

/**
 * Creates a new OSD an attaches it to the player.
 *
 * @param player Player.
 */
void osd_attach(player_t* player)
{
  if (player->osd) return;

  osd_t* osd = player->osd = ka_mem_calloc(sizeof(*osd));
  mouse_get(player, &osd->lastMouse);
  osd->mouseTime = timer_gettime();
  osd->zoom = 100;
}

/**
 * Releases the OSD and all its memory.
 *
 * @param player Player.
 */
void osd_detach(player_t* player)
{
  if (player->osd)
  {
    osd_disable(player);
    ka_mem_free(player->osd);
    player->osd = NULL;
  }
}

void osd_reset(player_t* player)
{
  osd_t* osd = player->osd;

  if (!osd) return;

  osd->state &= ~OSD_STATE_MSG;
  osd->lastReplayPos.curPos = osd->lastReplayPos.endPos = 0;
  osd_update(player, OSD_STATE_MSG);
}

/**
 * Forces a redraw for time dependent info.
 */
void osd_refresh(player_t* player)
{
  osd_t* osd = player->osd;

  if (!osd) return;

  uint32_t time = timer_gettime();

  if ((osd->state & OSD_STATE_MSG)
  &&  (((int32_t)(osd->msgTimer - time)) < 0))
  {
    player->status_flags |= player_status_redrawframe;
  }

  if ((osd_getState(player) & (OSD_STATE_TC | OSD_STATE_POS))
  &&  (((int32_t)(osd->drawTimer - time)) < 0))
  {
    player->status_flags |= player_status_redrawframe;
  }
}

/**
 * Plots the OSD bitmap to rgb screen or buffer.
 *
 * @param player Player.
 */
void osd_draw(player_t* player)
{
  osd_t* osd = player->osd;
  uint32_t update = 0;

  if (!osd) return;

  uint32_t time = timer_gettime();
  osd->drawTimer = time + timer_getrate();

  if ((osd->state & OSD_STATE_MSG)
  &&  (((int32_t)(osd->msgTimer - time)) < 0))
  {
    osd->state &= ~OSD_STATE_MSG;
    update |= OSD_STATE_MSG;
  }

  update |= osd->state & (OSD_STATE_TC | OSD_STATE_POS);
  osd_update(player, update);

  if (!osd->bitmap || !player->paint.dst) return;

  draw_bitmap(osd, player, 0, 0, osd->sizex, osd->sizey);
}

/**
 * Returns the current state of the OSD.
 *
 * @param player Player.
 */
uint32_t osd_getState(player_t* player)
{
  osd_t* osd = player->osd;

  if (!osd || !osd->bitmap) return 0;

  return osd->state;
}

/**
 * Handles OSD display and navigation commands.
 *
 * @param player Player.
 * @param nav    Navigation command.
 */
void osd_navigate(player_t* player, osd_nav nav)
{
  osd_t* osd = player->osd;
  uint32_t state;
  uint32_t update = 0;

  if (!osd) return;

  state = osd->state;

  // Updates that will force a full display refresh
  switch (nav)
  {
    case OSD_NAV_PAUSE_ON:
    {
      if (!(osd->state & OSD_STATE_NAV))
      {
        osd->state |= OSD_STATE_NAV;
        osd->currentItem = 0;
      }
    }
    break;
    case OSD_NAV_PAUSE_OFF:
    {
      if (osd->state & OSD_STATE_NAV)
      {
        osd->state &= ~OSD_STATE_NAV;
        osd->currentItem = 0;
      }
    }
    break;
    case OSD_NAV_TIME_ON:
    {
      osd->state |= OSD_STATE_TC;
    }
    break;
    case OSD_NAV_TIME_OFF:
    {
      osd->state &= ~OSD_STATE_TC;
    }
    break;
    case OSD_NAV_UPDATE:
    {
      if (!osd->state)
        return;

      // Disable current version which may be of incorrect size
      osd_disable(player);
      osd->state = state;
      mouse_get(player, &osd->lastMouse);
      osd->mouseTime = timer_gettime();
      state = 0;
    }
    break;
    case OSD_NAV_UP:
    {
      if (osd->state & OSD_STATE_NAV)
      {
        osd->currentItem++;
        if (!osd->osditems[osd->currentItem])
          osd->currentItem = 0;
        update = OSD_STATE_NAV;
      }
    }
    break;
    case OSD_NAV_DOWN:
    {
      if (osd->state & OSD_STATE_NAV)
      {
        osd->currentItem--;
        if (osd->currentItem < 0)
        {
          osd->currentItem = 0;
          while (osd->osditems[osd->currentItem+1])
            osd->currentItem++;
        }
        update = OSD_STATE_NAV;
      }
    }
    break;
    case OSD_NAV_LEFT:
    {
      const osditem_t* cv = osd->osditems[osd->currentItem];

      if ((osd->state & OSD_STATE_NAV)
      &&  osd->currentItem
      &&  cv->set)
      {
        *cv->val = *cv->val - cv->diff;
        if (*cv->val < cv->min)    *cv->val = cv->min;
        if (*cv->val > cv->max)    *cv->val = cv->max;
        cv->set(player, *cv->val);

        update |= OSD_STATE_NAV;
      }
    }
    break;
    case OSD_NAV_RIGHT:
    {
      const osditem_t* cv = osd->osditems[osd->currentItem];

      if ((osd->state & OSD_STATE_NAV)
      &&  osd->currentItem
      &&  cv->set)
      {
        *cv->val = *cv->val + cv->diff;
        if (*cv->val < cv->min)    *cv->val = cv->min;
        if (*cv->val > cv->max)    *cv->val = cv->max;
        cv->set(player, *cv->val);

        update |= OSD_STATE_NAV;
      }
    }
    break;
    case OSD_NAV_FRAME_STEP:
    {
      if (osd->state & OSD_STATE_NAV)
      {
        update |= OSD_STATE_TC;
      }
    }
    break;
    case OSD_NAV_POS_ON:
    {
      osd->state |= OSD_STATE_POS;
    }
    break;
    case OSD_NAV_POS_OFF:
    {
      osd->state &= ~OSD_STATE_POS;
    }
    break;
  }

  update |= (state ^ osd->state);

  osd_update(player, update);
  if (update)
    player->status_flags |= player_status_redrawframe;
}

/**
 * Fills a message in a string.
 *
 * @param  player  Player.
 * @param  format  printf like format.
 * @param  ...     Additional parameters, format dependant.
 */
void osd_report(player_t* player, const char *format, ...)
{
  va_list arg;

  va_start(arg, format);
  osd_vreport(player, format, arg);
  va_end(arg);
}

void osd_vreport(player_t* player, const char *format, va_list arg)
{
  osd_t* osd = player->osd;

  if (!osd) return;

  if (format != NULL)
  {
    msg_lookup(format, -1, player->error_block.errmess, sizeof(player->error_block.errmess));
    vsnprintf(osd->msg, sizeof(osd->msg), player->error_block.errmess, arg);
    snprintf(player->error_block.errmess, sizeof(player->error_block.errmess), "%s", osd->msg);
  }
  else
  {
    snprintf(osd->msg, sizeof(osd->msg), "%s", player->error_block.errmess);
  }

  // Show new value
  osd->msgTimer = timer_gettime() + 7*timer_getrate();
  osd->state |= OSD_STATE_MSG;
  osd_update(player, OSD_STATE_MSG);

  player->status_flags |= player_status_redrawframe;
}

/**
 * Check mouse movements and actions.
 */
int osd_mouseAction(player_t* player, const wimp_mouse_t* m)
{
  osd_t* osd = player->osd;
  int osdx, osdy;

  if (!osd) return 0;

  // to OSD coordinates
  if (player->mode == KA_MODE_MULTITASK)
  {
    window_state_t win;

    win.window = win_handle.player;
    _swix(Wimp_GetWindowState, _IN(1), &win);

    osdx = (m->pos.x - win.vis.x0 + win.scroll.x) >> player->screen_plot.xeig;
    osdy = (m->pos.y - win.vis.y1 + win.scroll.y) >> player->screen_plot.yeig;
    osdx = (osdx * player->osd_mag.x) >> FRAC;
    osdy = (osdy * player->osd_mag.y) >> FRAC;
    osdy = player->paint.dst_height + osdy;
    osdx = (osdx * 100) / osd->zoom;
    osdy = (osdy * 100) / osd->zoom;
  }
  else
  {
    osdx = (m->pos.x - player->pos.x) >> player->screen_plot.xeig;
    osdy = (m->pos.y - player->pos.y) >> player->screen_plot.yeig;
    osdx = (osdx * player->osd_mag.x) >> FRAC;
    osdy = (osdy * player->osd_mag.y) >> FRAC;
    osdx = (osdx * 100) / osd->zoom;
    osdy = (osdy * 100) / osd->zoom;
  }

  // osd_report(player, "%d x %d - %d", osdx, osdy, (osd->sizex / 2) - 90);
  {
    osd_nav action = OSD_NAV_NONE;

    if ((m->window == win_handle.player)
    &&  (m->icon == -1)
    &&  (  (abs(m->pos.x - osd->lastMouse.pos.x) > 8)
        || (abs(m->pos.y - osd->lastMouse.pos.y) > 8)
        || m->button))
    {
      osd->lastMouse = *m;
      osd->mouseTime = timer_gettime();

      // Turn position bar on, show pointer
      if (!(osd->state & OSD_STATE_NAV))
      {
        // Only if no button pressed
        if (!m->button)
          action = OSD_NAV_POS_ON;
      }
    }
    else
    {
      osd->lastMouse = *m;

      // After a few seconds delay, turn position bar off and hide pointer
      if ((osd->state & OSD_STATE_POS)
      &&  (  (osdy > 32)
          || (m->window != win_handle.player) || (m->icon != -1)))
      {
        if ((int32_t)(timer_gettime() - osd->mouseTime) > (int32_t) timer_getcs(500))
        {
          action = OSD_NAV_POS_OFF;
        }
      }
    }

    // Position bar must remains hidden if control window is shown
    if ((player->mode == KA_MODE_MULTITASK)
    &&  (config.control & cfg_ctrl_controls))
    {
      if (action == OSD_NAV_POS_ON)
        action = OSD_NAV_NONE;
      if (osd->state & OSD_STATE_POS)
        action = OSD_NAV_POS_OFF;
    }

    if (action != OSD_NAV_NONE)
      osd_navigate(player, action);
  }

  int width = osd->sizex - 20;

  if ((m->window != win_handle.player)
  ||  (m->icon != -1)
  ||  !(m->button & (5*256 | 5)) // handle dbl-click as click for step button
  ||  !(osd->state & (OSD_STATE_NAV | OSD_STATE_POS))
  ||  (osdy >= (20 + BAR_HEIGHT)))
    return 0;

  // update a property value ?
  if ((osd->state & OSD_STATE_NAV) && (osd->currentItem))
  {
    if ((20 <= osdy)
    &&  (osdy < (20 + BAR_HEIGHT))
    &&  (10 <= osdx)
    &&  (osdx <= 10 + width))
    {
      const osditem_t* cv = osd->osditems[osd->currentItem];
      if (cv->set)
      {
        int frac = width / (2 * (cv->max - cv->min));
        *cv->val = cv->min + ((cv->max - cv->min) * (osdx + frac - 10)) / width;
        if (*cv->val < cv->min)    *cv->val = cv->min;
        if (*cv->val > cv->max)    *cv->val = cv->max;
        cv->set(player, *cv->val);

        osd_update(player, OSD_STATE_NAV);
        player->status_flags |= player_status_redrawframe;
      }
    }

    return 1;
  }
  // update position bar or buttons
  else if ((osd->state & OSD_STATE_POS) && (player->stream != NULL))
  {
    if ((20 <= osdy)
    &&  (osdy < (20 + BAR_HEIGHT))
    &&  (10 <= osdx)
    &&  (osdx < 10 + width))
    {
      player_seek(player, osdx - 10, width, 0);
      return 1;
    }

    if ((4 <= osdy) && (osdy < 4 + BUTTONSIZE_Y))
    {
      int posx = (osd->sizex / 2) - 90;

      // play ?
      posx = osdx - posx;
      if ((0 <= posx) && (BUTTONSIZE_X > posx))
      {
        do_command(player, CMD_REPLAY_TOGGLE);
        return 1;
      }
      // step ?
      posx -= 20;
      if ((0 <= posx) && (BUTTONSIZE_X > posx))
      {
        if (player_getPlayStatus(player) != play_status_pause)
          do_command(player, CMD_REPLAY_TOGGLE);
        do_command(player, CMD_REPLAY_STEP);
        return 1;
      }
      // pause ?
      posx -= 20;
      if ((0 <= posx) && (BUTTONSIZE_X > posx))
      {
        do_command(player, CMD_REPLAY_TOGGLE);
        return 1;
      }
      // stop ?
      posx -= 20;
      if ((0 <= posx) && (BUTTONSIZE_X > posx))
      {
        do_command(player, CMD_REPLAY_STOP);
        return 1;
      }
      // rewind ?
      posx -= 20;
      if ((0 <= posx) && (BUTTONSIZE_X > posx))
      {
        do_command(player, (m->button & 0x444) ? CMD_REPLAY_REWIND : CMD_REPLAY_FORWARD);
        return 1;
      }
      // forward ?
      posx -= 20;
      if ((0 <= posx) && (BUTTONSIZE_X > posx))
      {
        do_command(player, (m->button & 0x444) ? CMD_REPLAY_FORWARD : CMD_REPLAY_REWIND);
        return 1;
      }
      // prev ?
      posx -= 20;
      if ((0 <= posx) && (BUTTONSIZE_X > posx))
      {
        do_command(player, (m->button & 0x444) ? CMD_REPLAY_PREVIOUS : CMD_REPLAY_RND_PREV);
        return 1;
      }
      // next ?
      posx -= 20;
      if ((0 <= posx) && (BUTTONSIZE_X > posx))
      {
        do_command(player, (m->button & 0x444) ? CMD_REPLAY_NEXT : CMD_REPLAY_RND_NEXT);
        return 1;
      }
      // mute ?
      if (config.audio.cfg & cfg_audio_play)
      {
        posx -= 20;
        if ((0 <= posx) && (BUTTONSIZE_X > posx))
        {
          do_command(player, CMD_AUDIO_ON_OFF);
          return 1;
        }
      }
    }

    return 1;
  }

  return 0;
}
