/* dreamcast.c
 * DreamCast console ( Linux framebuffer + Joystick ) support code
 * based on framebuffer.c, a part of the Links program, released under GPL (c) 2002 Petr 'Brain' Kulhavy
 *
 * Joystick support added by VINCENT Cédric, based on Js2mouse Copyright (C) 2003  VINCENT Cédric
 * http://www.freshmeat.net/projects/js2mouse
 */

#include "cfg.h"

#ifdef GRDRV_DREAMCAST

/* #define FB_DEBUG */
/* #define SC_DEBUG */

#if defined(FB_DEBUG) || defined(SC_DEBUG)
#define MESSAGE(a) fprintf(stderr,"%s",a);
#endif

#ifdef TEXT
#undef TEXT
#endif

#include "links.h"


#include <sys/mman.h>
#include <sys/ioctl.h>

#include <linux/fb.h>
#include <linux/kd.h>
#include <linux/vt.h>
#include <signal.h>

#include "arrow.inc"

#define TTY 0

#include <stdio.h> //snprintf, perror, fprintf
#include <sys/poll.h> //struct pollfd
#include <linux/joystick.h> //;-)
#include <string.h> //memset
#include <stdlib.h> //strncpy
#include <fcntl.h> //open
#include <sys/ioctl.h> //ioctl
#include <errno.h> //errno
#include <unistd.h>// read

#define JS_CLICK_LEFT 0
#define JS_CLICK_RIGHT 1
#define JS_CLICK_MIDDLE 2
#define JS_WHEEL_UP 3
#define JS_WHEEL_DOWN  4
#define JS_MOVE_HORIZONTAL 5
#define JS_MOVE_VERTICAL 6
#define JS_CLICK_EXTRA1 7
#define JS_CLICK_EXTRA2 8
#define JS_UNUSED 10

#define GO 2
#define NONE -1
#define ACTIVED 1
#define DESACTIVED 0

#define DEFAULT_DEV "/dev/input/js0"
#define DEFAULT_POLLTIMEOUT 20

#define MAX(a,b) (a>b?a:b)

#define AXES_ACTIVE ((state & (1<<JS_MOVE_HORIZONTAL)) || (state & (1<<JS_MOVE_VERTICAL)) )
#define TRUE 1
#define FALSE 0

#define X 1
#define Y 2

struct map_s
{
        unsigned char js_click_left;
        unsigned char js_click_right;
        unsigned char js_click_middle;
        unsigned char js_click_extra1;
        unsigned char js_click_extra2;
        unsigned char js_wheel_up;
        unsigned char js_wheel_down;
        unsigned char js_move_vertical;
        unsigned char js_move_horizontal;
};

struct ext_s
{
        char path_joydev[128+1];
        unsigned char poll_timeout;
        unsigned char remap;
        struct map_s wanted_map;
        int fd_joydev;
        char number_of_axes;
        char number_of_buttons;
        int driver_version;
        struct pollfd poll_fd;
        unsigned char *map;
};

struct ext_s joy;

int j2m_init()
{
        if ((joy.fd_joydev = open( joy.path_joydev, O_RDONLY|O_NONBLOCK)) < 0) {
                perror("Error when opening joystick device ");
                return -1;
        }

        if ((ioctl( joy.fd_joydev, JSIOCGAXES, &(joy.number_of_axes))) < 0) {
                perror("Error when asking for number of axes ");
                return -1;
        }

        if ((ioctl( joy.fd_joydev, JSIOCGBUTTONS, &(joy.number_of_buttons))) < 0) {
                perror("Error when asking for number of buttons ");
                return -1;
        }

        if ((ioctl( joy.fd_joydev, JSIOCGVERSION, &(joy.driver_version))) < 0) {
                perror("Error when asking for joystick's driver version ");
                return -1;
        }

        if ( joy.driver_version < 100000 ) {
                fprintf( stderr, "Error : incompatible joystick's driver version\n");
                return -1;
        }

        return 0;
}

void j2m_create_default_map()
{
        int i=0;

        joy.map[0] = joy.number_of_buttons;

        for ( i=1; i<joy.number_of_buttons+joy.number_of_axes+1; i++) {
                joy.map[i] = JS_UNUSED;
        }

        joy.map[1] = JS_CLICK_LEFT;
        joy.map[2] = JS_CLICK_RIGHT;
        joy.map[3] = JS_CLICK_MIDDLE;
        joy.map[4] = JS_WHEEL_UP;
        joy.map[5] = JS_WHEEL_DOWN;
        joy.map[6] = JS_CLICK_EXTRA1;
        joy.map[7] = JS_CLICK_EXTRA2;
        joy.map[joy.map[0]+1] = JS_MOVE_HORIZONTAL;
        joy.map[joy.map[0]+2] = JS_MOVE_VERTICAL;
}

__u8 j2m_map_it( struct js_event *joy_event)
{
        __u8 type=0;

        joy_event->type &= ~JS_EVENT_INIT;

        switch (joy_event->type) {
        case JS_EVENT_BUTTON :
                type = joy.map[joy_event->number+1];
                break;
        case JS_EVENT_AXIS :
                type = joy.map[joy_event->number+joy.map[0]+1]; //joy.map[0] = number_of_buttons
                break;
        default :
                type = JS_UNUSED;
                break;
        }

        return type;
}

int j2m_start()
{
        unsigned char *map=NULL;

        if (joy.poll_timeout == 0) {
                fprintf( stderr, "Invalid polling timeout : %i\n", joy.poll_timeout);
                return -1;
        }

        if (j2m_init())
                return -1;

        joy.map = (unsigned char *) mem_calloc( MAX(joy.number_of_axes+joy.number_of_buttons+1, 10)*sizeof(unsigned char) );
        if ( joy.map == NULL ) {
                perror("Error when mem_callocing for the map ");
                return -1;
        }

        joy.poll_fd.events = POLLIN;
        joy.poll_fd.fd = joy.fd_joydev;

        /*if ( joy.remap ) { //NOT IMPLEMENTED INTO Links, because this is in a beta satge for Js2mouse
         j2m_remap();
         j2m_create_map();
         }
         else  */j2m_create_default_map();

         return 0;
}

static __s16 dx_linksfb=0;
static __s16 dy_linksfb=0;

unsigned char j2m_move_linksfb( __s16 value, struct event *ev, signed char axe)
{
        switch(axe) {
        case X :
                if ( (value == 0) && (dx_linksfb == 0) )
                        return NONE;

                if ((value == 0) || (value>0 && dx_linksfb<0) || (value<0 && dx_linksfb>0)) {
                        dx_linksfb = 0;
                        return DESACTIVED;
                }
                break;
        case Y :
                if ( (value == 0) && (dy_linksfb == 0) )
                        return NONE;

                if ((value == 0) || (value>0 && dy_linksfb<0) || (value<0 && dy_linksfb>0)) {
                        dy_linksfb = 0;
                        return DESACTIVED;
                }
                break;
        default :
                break;
        }
        ev->b = B_MOVE;
        return ACTIVED;
}

unsigned char j2m_click_linksfb( __s16 value, struct event *ev, long click)
{
        if (value) {
                if ( ev->b == (click|B_DOWN) )
                        return NONE;

                ev->b = (click|B_DOWN);
                return  ACTIVED;
        }
        else {
                if ( ev->b == (click|B_UP) )
                        return NONE;

                ev->b = (click|B_UP);
                return  DESACTIVED;
        }
}

void j2m_accel_linksfb(struct event *ev, __s16 value_dx, __s16 value_dy)
{
        if ( value_dx > 0 ) {
                if ( (dx_linksfb - value_dx) < 0 )
                        dx_linksfb++;
                else dx_linksfb = value_dx;
        } else {
                if ( (dx_linksfb - value_dx) > 0 )
                        dx_linksfb--;
                else dx_linksfb = value_dx;
        }

        if ( value_dy > 0 ) {
                if ( (dy_linksfb - value_dy) < 0 )
                        dy_linksfb++;
                else dy_linksfb = value_dy;
        } else {
                if ( (dy_linksfb - value_dy) > 0 )
                        dy_linksfb--;
                else dy_linksfb = value_dy;
        }

        ev->x += dx_linksfb;
        ev->y += dy_linksfb;
}

signed char j2m_convert_linksfb( __u8 type, __s16 value, struct event *ev)
{
        signed char ret=NONE;
        unsigned char new_click=FALSE;
        signed char move=FALSE; //useful for entering or leaving a 0 value
        static long click=-1;
        static short state=0;
        static __s16 value_dx=0;
        static __s16 value_dy=0;

        switch (type) {
        case JS_CLICK_LEFT :
                ret = j2m_click_linksfb( value, ev, B_LEFT);
                if (ret == ACTIVED) {
                        click = B_LEFT;
                        new_click = TRUE;
                }
                else if (click == B_LEFT) click = -1;
                break;

        case JS_CLICK_RIGHT :
                ret = j2m_click_linksfb( value, ev, B_RIGHT);
                if (ret == ACTIVED) {
                        click = B_RIGHT;
                        new_click = TRUE;
                }
                else if (click == B_RIGHT) click = -1;
                break;

        case JS_CLICK_MIDDLE :
                ret = j2m_click_linksfb( value, ev, B_MIDDLE);
                if (ret == ACTIVED) {
                        click = B_MIDDLE;
                        new_click = TRUE;
                }
                else if (click == B_MIDDLE) click = -1;
                break;

                /*I will use it for scrolling... but later...
                 case JS_WHEEL_UP :
                 break;

                 case JS_WHEEL_DOWN :
                 break;

                 case JS_CLICK_EXTRA1 :
                 break;

                 case JS_CLICK_EXTRA2 :
                 break;*/

        case JS_MOVE_HORIZONTAL :
                value = (__s16) (value/2048);
                value = (__s16) (value*value*value/256);

                value_dx = value;
                ret = j2m_move_linksfb( value, ev, X);
                move = TRUE;
                break;

        case JS_MOVE_VERTICAL :
                value = (__s16) (value/2048);
                value = (__s16) (value*value*value/256);

                value_dy = value;
                ret = j2m_move_linksfb( value, ev, Y);
                move = TRUE;
                break;

        default :
                ret = NONE;
                break;
        }

        switch ( ret ) {
        case NONE :
                if ( AXES_ACTIVE || move )
                        ret = GO;
                break;

        case DESACTIVED :
                if ( 0 == state)
                        break;
                state &= ~(1<< type);
                ret = GO;
                break;

        case ACTIVED :
                state |= (1<< type);
                ret = GO;
                break;

        default :
                break;
        }

        if ( AXES_ACTIVE || move ) {
                j2m_accel_linksfb(ev, value_dx, value_dy);

                if (!new_click)
                        switch(click) {
                        case B_LEFT :
                                ev->b = B_LEFT;
                                ev->b |= B_DRAG;
                                break;
                        case B_RIGHT :
                                ev->b = B_RIGHT;
                                ev->b |= B_DRAG;
                                break;
                        case B_MIDDLE :
                                ev->b = B_MIDDLE;
                                ev->b |= B_DRAG;
                                break;
                        default :
                                break;
                        }
        }

        return ret;
}

int dreamcast_txt_xsize, dreamcast_txt_ysize;
struct winsize dreamcast_old_ws;
struct winsize dreamcast_new_ws;
int dreamcast_old_ws_v;
int dreamcast_msetsize;

int fb_hj2m;
struct ext_s ext;

int dreamcast_console;

struct itrm *dreamcast_kbd;

struct graphics_device *dreamcast_old_vd;

int dreamcast_handler;
char *dreamcast_mem;
int dreamcast_mem_size,dreamcast_linesize,dreamcast_bits_pp,dreamcast_pixelsize;
int dreamcast_xsize,dreamcast_ysize;
int dreamcast_colors, dreamcast_palette_colors;
struct fb_var_screeninfo vi;
struct fb_fix_screeninfo fi;

void dreamcast_draw_bitmap(struct graphics_device *dev,struct bitmap* hndl, int x, int y);

static unsigned char *dreamcast_driver_param;
struct graphics_driver dreamcast_driver;
int dreamcast_dreamcast_have_cmap=0;
volatile int dreamcast_active=1;

struct palette
{
        unsigned short *red;
        unsigned short *green;
        unsigned short *blue;
};

struct palette old_palette;
struct palette global_pal;
static struct vt_mode vt_mode,vt_omode;

struct fb_var_screeninfo oldmode;

static volatile int in_gr_operation;

/* mouse */
static int mouse_x, mouse_y;		/* mouse pointer coordinates */
static int mouse_black, mouse_white;
static int background_x, background_y; /* Where was the mouse background taken from */
static unsigned char *mouse_buffer, *background_buffer, *new_background_buffer;
static struct graphics_device *mouse_graphics_device;
static int global_mouse_hidden;
static int utf8_table;


#define TEST_MOUSE(xl,xh,yl,yh) if (RECTANGLES_INTERSECT(\
        (xl),(xh),\
        background_x,background_x+arrow_width,\
        (yl),(yh),\
        background_y,background_y+arrow_height)\
        && !global_mouse_hidden){\
        mouse_hidden=1;\
        hide_mouse();\
        }else mouse_hidden=0;

#define END_MOUSE if (mouse_hidden) show_mouse();

#define START_GR in_gr_operation=1;
#define END_GR	\
        END_MOUSE\
        in_gr_operation=0;\
        if (!dreamcast_active)ioctl(TTY,VT_RELDISP,1);


#define NUMBER_OF_DEVICES	10

#define TEST_INACTIVITY if (!dreamcast_active||dev!=current_virtual_device) return;

#define TEST_INACTIVITY_0 if (!dreamcast_active||dev!=current_virtual_device) return 0;

#define RECTANGLES_INTERSECT(xl0, xh0, xl1, xh1, yl0, yh0, yl1, yh1) (\
        (xl0)<(xh1)\
        && (xl1)<(xh0)\
        && (yl0)<(yh1)\
        && (yl1)<(yh0))

/* This assures that x, y, xs, ys, data will be sane according to clipping
 * rectangle. If nothing lies within this rectangle, the current function
 * returns. The data pointer is automatically advanced by this macro to reflect
 * the right position to start with inside the bitmap. */
#define	CLIP_PREFACE \
        int mouse_hidden;\
        int xs=hndl->x,ys=hndl->y;\
        char *data=hndl->data;\
        \
        TEST_INACTIVITY\
        if (x>=dev->clip.x2||x+xs<=dev->clip.x1) return;\
        if (y>=dev->clip.y2||y+ys<=dev->clip.y1) return;\
        if (x+xs>dev->clip.x2) xs=dev->clip.x2-x;\
        if (y+ys>dev->clip.y2) ys=dev->clip.y2-y;\
        if (dev->clip.x1-x>0){\
        xs-=(dev->clip.x1-x);\
        data+=dreamcast_pixelsize*(dev->clip.x1-x);\
        x=dev->clip.x1;\
        }\
        if (dev->clip.y1-y>0){\
        ys-=(dev->clip.y1-y);\
        data+=hndl->skip*(dev->clip.y1-y);\
        y=dev->clip.y1;\
        }\
        /* xs, ys: how much pixels to paint\
         * data: where to start painting from\
         */\
        START_GR\
        TEST_MOUSE (x,x+xs,y,y+ys)


/* fill_area: 5,5,10,10 fills in 25 pixels! */

/* This assures that left, right, top, bottom will be sane according to the
 * clipping rectangle set up by svga_driver->set_clip_area. If empty region
 * results, return from current function occurs. */
#define FILL_CLIP_PREFACE \
        int mouse_hidden;\
        TEST_INACTIVITY\
        if (left>=right||top>=bottom) return;\
        if (left>=dev->clip.x2||right<=dev->clip.x1||top>=dev->clip.y2||bottom<=dev->clip.y1) return;\
        if (left<dev->clip.x1) left=dev->clip.x1;\
        if (right>dev->clip.x2) right=dev->clip.x2;\
        if (top<dev->clip.y1) top=dev->clip.y1;\
        if (bottom>dev->clip.y2) bottom=dev->clip.y2;\
        START_GR\
        TEST_MOUSE(left,right,top,bottom)


#define HLINE_CLIP_PREFACE \
        int mouse_hidden;\
        TEST_INACTIVITY\
        if (y<dev->clip.y1||y>=dev->clip.y2||right<=dev->clip.x1||left>=dev->clip.x2) return;\
        if (left<dev->clip.x1) left=dev->clip.x1;\
        if (right>dev->clip.x2) right=dev->clip.x2;\
        if (left>=right) return;\
        START_GR\
        TEST_MOUSE (left,right,y,y+1)

#define VLINE_CLIP_PREFACE \
        int mouse_hidden;\
        TEST_INACTIVITY\
        if (x<dev->clip.x1||x>=dev->clip.x2||top>=dev->clip.y2||bottom<=dev->clip.y1) return;\
        if (top<dev->clip.y1) top=dev->clip.y1;\
        if (bottom>dev->clip.y2) bottom=dev->clip.y2;\
        if (top>=bottom) return;\
        START_GR\
        TEST_MOUSE(x,x+1,top,bottom)

#define HSCROLL_CLIP_PREFACE \
        int mouse_hidden;\
        TEST_INACTIVITY_0\
        if (!sc) return 0;\
        if (sc>(dev->clip.x2-dev->clip.x1)||-sc>(dev->clip.x2-dev->clip.x1))\
        return 1;\
        START_GR\
        TEST_MOUSE (dev->clip.x1,dev->clip.x2,dev->clip.y1,dev->clip.y2)

#define VSCROLL_CLIP_PREFACE \
        int mouse_hidden;\
        TEST_INACTIVITY_0\
        if (!sc) return 0;\
        if (sc>dev->clip.y2-dev->clip.y1||-sc>dev->clip.y2-dev->clip.y1) return 1;\
        START_GR\
        TEST_MOUSE (dev->clip.x1, dev->clip.x2, dev->clip.y1, dev->clip.y2)\


/* n is in bytes. dest must begin on pixel boundary. If n is not a whole number of pixels, rounding is
 * performed downwards.
 */
static void inline pixel_set(unsigned char *dest, int n,void * pattern)
{
        int a;

        switch(dreamcast_pixelsize)
        {
        case 1:
                memset(dest,*(char *)pattern,n);
                break;

        case 2:
                {
#ifdef t2c
                        short v=*(t2c *)pattern;
                        int a;

                        for (a=0;a<(n>>1);a++) ((t2c *)dest)[a]=v;
#else
                        unsigned char a,b;
                        int i;

                        a=*(char*)pattern;
                        b=((char*)pattern)[1];
                        for (i=0;i<=n-2;i+=2){
                                dest[i]=a;
                                dest[i+1]=b;
                        }
#endif
                }
                break;

        case 3:
                {
                        unsigned char a,b,c;
                        int i;

                        a=*(char*)pattern;
                        b=((char*)pattern)[1];
                        c=((char*)pattern)[2];
                        for (i=0;i<=n-3;i+=3){
                                dest[i]=a;
                                dest[i+1]=b;
                                dest[i+2]=c;
                        }
                }
                break;

        case 4:
                {
#ifdef t4c
                        long v=*(t4c *)pattern;
                        int a;

                        for (a=0;a<(n>>2);a++) ((t4c *)dest)[a]=v;
#else
                        unsigned char a,b,c,d;
                        int i;

                        a=*(char*)pattern;
                        b=((char*)pattern)[1];
                        c=((char*)pattern)[2];
                        d=((char*)pattern)[3];
                        for (i=0;i<=n-4;i+=4){
                                dest[i]=a;
                                dest[i+1]=b;
                                dest[i+2]=c;
                                dest[i+3]=d;
                        }
#endif
                }
                break;

        default:
                for (a=0;a<n/dreamcast_pixelsize;a++,dest+=dreamcast_pixelsize) memcpy(dest,pattern,dreamcast_pixelsize);
                break;
        }
}

static void redraw_mouse(void);

static void dreamcast_mouse_move(int dx, int dy)
{
        struct event ev;
        mouse_x += dx;
        mouse_y += dy;
        ev.ev = EV_MOUSE;
        if (mouse_x >= dreamcast_xsize) mouse_x = dreamcast_xsize - 1;
        if (mouse_y >= dreamcast_ysize) mouse_y = dreamcast_ysize - 1;
        if (mouse_x < 0) mouse_x = 0;
        if (mouse_y < 0) mouse_y = 0;
        ev.x = mouse_x;
        ev.y = mouse_y;
        ev.b = B_MOVE;
        if (!current_virtual_device) return;
        if (current_virtual_device->mouse_handler) current_virtual_device->mouse_handler(current_virtual_device, ev.x, ev.y, ev.b);
        redraw_mouse();
}

static void dreamcast_key_in(void *p, struct event *ev, int size)
{
        if (size != sizeof(struct event) || ev->ev != EV_KBD) return;
        if ((ev->y & KBD_ALT) && ev->x >= '0' && ev->x <= '9') {
                switch_virtual_device((ev->x - '1' + 10) % 10);
                return;
        }
        if (!current_virtual_device) return;
        if (!ev->y && ev->x == KBD_F5) dreamcast_mouse_move(-3, 0);
        else if (!ev->y && ev->x == KBD_F6) dreamcast_mouse_move(0, 3);
        else if (!ev->y && ev->x == KBD_F7) dreamcast_mouse_move(0, -3);
        else if (!ev->y && ev->x == KBD_F8) dreamcast_mouse_move(3, 0);
        else
        {
                if (dreamcast_driver.codepage!=utf8_table&&(ev->x)>=128&&(ev->x)<=255)
                        if ((ev->x=cp2u(ev->x,dreamcast_driver.codepage)) == -1) return;
                if (current_virtual_device->keyboard_handler) current_virtual_device->keyboard_handler(current_virtual_device, ev->x, ev->y);
        }
}




#define mouse_getscansegment(buf,x,y,w) memcpy(buf,dreamcast_mem+y*dreamcast_linesize+x*dreamcast_pixelsize,w)
#define mouse_drawscansegment(ptr,x,y,w) memcpy(dreamcast_mem+y*dreamcast_linesize+x*dreamcast_pixelsize,ptr,w);

/* Flushes the background_buffer onscreen where it was originally taken from. */
static void place_mouse_background(void)
{
        struct bitmap bmp;

        bmp.x=arrow_width;
        bmp.y=arrow_height;
        bmp.skip=arrow_width*dreamcast_pixelsize;
        bmp.data=background_buffer;

        {
                struct graphics_device * current_virtual_device_backup;

                current_virtual_device_backup=current_virtual_device;
                current_virtual_device=mouse_graphics_device;
                dreamcast_draw_bitmap(mouse_graphics_device, &bmp, background_x,
                                  background_y);
                current_virtual_device=current_virtual_device_backup;
        }

}

/* Only when the old and new mouse don't interfere. Using it on interfering mouses would
 * cause a flicker.
 */
static void hide_mouse(void)
{

        global_mouse_hidden=1;
        place_mouse_background();
}

/* Gets background from the screen (clipping provided only right and bottom) to the
 * passed buffer.
 */
static void get_mouse_background(unsigned char *buffer_ptr)
{
        int width,height,skip,x,y;

        skip=arrow_width*dreamcast_pixelsize;

        x=mouse_x;
        y=mouse_y;

        width=dreamcast_pixelsize*(arrow_width+x>dreamcast_xsize?dreamcast_xsize-x:arrow_width);
        height=arrow_height+y>dreamcast_ysize?dreamcast_ysize-y:arrow_height;

        for (;height;height--){
                mouse_getscansegment(buffer_ptr,x,y,width);
                buffer_ptr+=skip;
                y++;
        }
}

/* Overlays the arrow's image over the mouse_buffer
 * Doesn't draw anything into the screen
 */
static void render_mouse_arrow(void)
{
        int x,y, reg0, reg1;
        unsigned char *mouse_ptr=mouse_buffer;
        int *arrow_ptr=arrow;

        for (y=arrow_height;y;y--){
                reg0=*arrow_ptr;
                reg1=arrow_ptr[1];
                arrow_ptr+=2;
                for (x=arrow_width;x;)
                {
                        int mask=1<<(--x);

                        if (reg0&mask)
                                memcpy (mouse_ptr, &mouse_black, dreamcast_pixelsize);
                        else if (reg1&mask)
                                memcpy (mouse_ptr, &mouse_white, dreamcast_pixelsize);
                        mouse_ptr+=dreamcast_pixelsize;
                }
        }
}

static void place_mouse(void)
{
        struct bitmap bmp;

        bmp.x=arrow_width;
        bmp.y=arrow_height;
        bmp.skip=arrow_width*dreamcast_pixelsize;
        bmp.data=mouse_buffer;
        {
                struct graphics_device * current_graphics_device_backup;

                current_graphics_device_backup=current_virtual_device;
                current_virtual_device=mouse_graphics_device;
                dreamcast_draw_bitmap(mouse_graphics_device, &bmp, mouse_x, mouse_y);
                current_virtual_device=current_graphics_device_backup;
        }
        global_mouse_hidden=0;
}

/* Only when the old and the new mouse positions do not interfere. Using this routine
 * on interfering positions would cause a flicker.
 */
static void show_mouse(void)
{

        get_mouse_background(background_buffer);
        background_x=mouse_x;
        background_y=mouse_y;
        memcpy(mouse_buffer,background_buffer,dreamcast_pixelsize*arrow_area);
        render_mouse_arrow();
        place_mouse();
}

/* Doesn't draw anything into the screen
 */
static void put_and_clip_background_buffer_over_mouse_buffer(void)
{
        unsigned char *bbufptr=background_buffer, *mbufptr=mouse_buffer;
        int left=background_x-mouse_x;
        int top=background_y-mouse_y;
        int right,bottom;
        int bmpixelsizeL=dreamcast_pixelsize;
        int number_of_bytes;
        int byte_skip;

        right=left+arrow_width;
        bottom=top+arrow_height;

        if (left<0){
                bbufptr-=left*bmpixelsizeL;
                left=0;
        }
        if (right>arrow_width) right=arrow_width;
        if (top<0){
                bbufptr-=top*bmpixelsizeL*arrow_width;
                top=0;
        }
        if (bottom>arrow_height) bottom=arrow_height;
        mbufptr+=bmpixelsizeL*(left+arrow_width*top);
        byte_skip=arrow_width*bmpixelsizeL;
        number_of_bytes=bmpixelsizeL*(right-left);
        for (;top<bottom;top++){
                memcpy(mbufptr,bbufptr,number_of_bytes);
                mbufptr+=byte_skip;
                bbufptr+=byte_skip;
        }
}

/* This draws both the contents of background_buffer and mouse_buffer in a scan
 * way (left-right, top-bottom), so the flicker is reduced.
 */
static inline void place_mouse_composite(void)
{
        int mouse_left=mouse_x;
        int mouse_top=mouse_y;
        int background_left=background_x;
        int background_top=background_y;
        int mouse_right=mouse_left+arrow_width;
        int mouse_bottom=mouse_top+arrow_height;
        int background_right=background_left+arrow_width;
        int background_bottom=background_top+arrow_height;
        int skip=arrow_width*dreamcast_pixelsize;
        int background_length,mouse_length;
        unsigned char *mouse_ptr=mouse_buffer,*background_ptr=background_buffer;
        int yend;

        if (mouse_bottom>dreamcast_ysize) mouse_bottom=dreamcast_ysize;
        if (background_bottom>dreamcast_ysize) background_bottom=dreamcast_ysize;

        /* Let's do the top part */
        if (background_top<mouse_top){
                /* Draw the background */
                background_length=background_right>dreamcast_xsize?dreamcast_xsize-background_left
                        :arrow_width;
                for (;background_top<mouse_top;background_top++){
                        mouse_drawscansegment(background_ptr,background_left
                                              ,background_top,background_length*dreamcast_pixelsize);
                        background_ptr+=skip;
                }

        }else if (background_top>mouse_top){
                /* Draw the mouse */
                mouse_length=mouse_right>dreamcast_xsize
                        ?dreamcast_xsize-mouse_left:arrow_width;
                for (;mouse_top<background_top;mouse_top++){
                        mouse_drawscansegment(mouse_ptr,mouse_left,mouse_top,mouse_length*dreamcast_pixelsize);
                        mouse_ptr+=skip;
                }
        }

        /* Let's do the middle part */
        yend=mouse_bottom<background_bottom?mouse_bottom:background_bottom;
        if (background_left<mouse_left){
                /* Draw background, mouse */
                mouse_length=mouse_right>dreamcast_xsize?dreamcast_xsize-mouse_left:arrow_width;
                for (;mouse_top<yend;mouse_top++){
                        mouse_drawscansegment(background_ptr,background_left,mouse_top
                                              ,(mouse_left-background_left)*dreamcast_pixelsize);
                        mouse_drawscansegment(mouse_ptr,mouse_left,mouse_top,mouse_length*dreamcast_pixelsize);
                        mouse_ptr+=skip;
                        background_ptr+=skip;
                }

        }else{
                int l1, l2, l3;

                /* Draw mouse, background */
                mouse_length=mouse_right>dreamcast_xsize?dreamcast_xsize-mouse_left:arrow_width;
                background_length=background_right-mouse_right;
                if (background_length+mouse_right>dreamcast_xsize)
                        background_length=dreamcast_xsize-mouse_right;
                l1=mouse_length*dreamcast_pixelsize;
                l2=(mouse_right-background_left)*dreamcast_pixelsize;
                l3=background_length*dreamcast_pixelsize;
                for (;mouse_top<yend;mouse_top++){
                        mouse_drawscansegment(mouse_ptr,mouse_left,mouse_top,l1);
                        if (background_length>0)
                                mouse_drawscansegment(
                                                      background_ptr +l2,
                                                      mouse_right,mouse_top ,l3);
                        mouse_ptr+=skip;
                        background_ptr+=skip;
                }
        }

        if (background_bottom<mouse_bottom){
                /* Count over bottoms! tops will be invalid! */
                /* Draw mouse */
                mouse_length=mouse_right>dreamcast_xsize?dreamcast_xsize-mouse_left
                        :arrow_width;
                for (;background_bottom<mouse_bottom;background_bottom++){
                        mouse_drawscansegment(mouse_ptr,mouse_left,background_bottom
                                              ,mouse_length*dreamcast_pixelsize);
                        mouse_ptr+=skip;
                }
        }else{
                /* Draw background */
                background_length=background_right>dreamcast_xsize?dreamcast_xsize-background_left
                        :arrow_width;
                for (;mouse_bottom<background_bottom;mouse_bottom++){
                        mouse_drawscansegment(background_ptr,background_left,mouse_bottom
                                              ,background_length*dreamcast_pixelsize);
                        background_ptr+=skip;
                }
        }
}

/* This moves the mouse a sophisticated way when the old and new position of the
 * cursor overlap.
 */
static inline void redraw_mouse_sophisticated()
{
        int new_background_x, new_background_y;

        get_mouse_background(mouse_buffer);
        put_and_clip_background_buffer_over_mouse_buffer();
        memcpy(new_background_buffer,mouse_buffer,dreamcast_pixelsize*arrow_area);
        new_background_x=mouse_x;
        new_background_y=mouse_y;
        render_mouse_arrow();
        place_mouse_composite();
        memcpy(background_buffer,new_background_buffer,dreamcast_pixelsize*arrow_area);
        background_x=new_background_x;
        background_y=new_background_y;
}

static void redraw_mouse(void){

        if (!dreamcast_active) return; /* We are not drawing */
        if (mouse_x!=background_x||mouse_y!=background_y){
                if (RECTANGLES_INTERSECT(
                                         background_x, background_x+arrow_width,
                                         mouse_x, mouse_x+arrow_width,
                                         background_y, background_y+arrow_height,
                                         mouse_y, mouse_y+arrow_height)){
                        redraw_mouse_sophisticated();
                }else{
                        /* Do a normal hide/show */
                        get_mouse_background(mouse_buffer);
                        memcpy(new_background_buffer,
                               mouse_buffer,arrow_area*dreamcast_pixelsize);
                        render_mouse_arrow();
                        hide_mouse();
                        place_mouse();
                        memcpy(background_buffer,new_background_buffer
                               ,arrow_area*dreamcast_pixelsize);
                        background_x=mouse_x;
                        background_y=mouse_y;
                }
        }
}

/* This is an empiric magic that ensures
 * Good white purity
 * Correct rounding and dithering prediction
 * And this is the cabbala:
 * 063 021 063
 * 009 009 021
 * 255 085 255
 * 036 036 084
 */
static void generate_palette(struct palette *palette)
{
        int a;

        switch (dreamcast_colors)
        {
        case 16:
                for (a=0;a<dreamcast_palette_colors;a++)
                {
                        palette->red[a]=(a&8)?65535:0;
                        palette->green[a]=((a>>1)&3)*(65535/3);
                        palette->blue[a]=(a&1)?65535:0;
                }
                break;
        case 256:
                for (a=0;a<dreamcast_palette_colors;a++){
                        palette->red[a]=((a>>5)&7)*(65535/7);
                        palette->green[a]=((a>>2)&7)*(65535/7);
                        palette->blue[a]=(a&3)*(65535/3);
                }
                break;
        case 32768:
                for (a=0;a<dreamcast_palette_colors;a++){
                        palette->red[a]=((a>>10)&31)*(65535/31);
                        palette->green[a]=((a>>5)&31)*(65535/31);
                        palette->blue[a]=(a&31)*(65535/31);
                }
                break;
        case 65536:
                for (a=0;a<dreamcast_palette_colors;a++){
                        palette->red[a]=((a>>11)&31)*(65535/31);
                        palette->green[a]=((a>>5)&63)*(65535/63);
                        palette->blue[a]=(a&31)*(65535/31);
                }
                break;
        }
}

static void alloc_palette(struct palette *pal)
{
        pal->red=mem_calloc(sizeof(unsigned short)*dreamcast_palette_colors);
        pal->green=mem_calloc(sizeof(unsigned short)*dreamcast_palette_colors);
        pal->blue=mem_calloc(sizeof(unsigned short)*dreamcast_palette_colors);

        if (!pal->red||!pal->green||!pal->blue)/*internal("Cannot create palette.\n")*/;
}


static void free_palette(struct palette *pal)
{
        mem_free(pal->red);
        mem_free(pal->green);
        mem_free(pal->blue);
}


static void set_palette(struct palette *pal)
{
        struct fb_cmap cmap;
        unsigned i;
        unsigned short *red=pal->red;
        unsigned short *green=pal->green;
        unsigned short *blue=pal->blue;
        __u16 *r, *g, *b, *t;

        r=mem_alloc(dreamcast_palette_colors*sizeof(__u16));
        g=mem_alloc(dreamcast_palette_colors*sizeof(__u16));
        b=mem_alloc(dreamcast_palette_colors*sizeof(__u16));
        t=mem_calloc(dreamcast_palette_colors*sizeof(__u16));

        if (!r||!g||!b||!t)/*internal("Cannot allocate memory.\n")*/;

        for (i = 0; i < dreamcast_palette_colors; i++)
        {
                r[i] = red[i];
                g[i] = green[i];
                b[i] = blue[i];
                /*fprintf(stderr, "%d %d %d\n", r[i], g[i], b[i]);*/

        }

        cmap.start = 0;
        cmap.len = dreamcast_palette_colors;
        cmap.red = r;
        cmap.green = g;
        cmap.blue = b;
        cmap.transp = t;

        if ((ioctl(dreamcast_handler, FBIOPUTCMAP, &cmap))==-1)/*internal("Cannot set palette\n")*/;

        mem_free(r);mem_free(g);mem_free(b);mem_free(t);
}


static void get_palette(struct palette *pal)
{
        struct fb_cmap cmap;
        unsigned i;
        __u16 *r, *g, *b, *t;

        r=mem_alloc(dreamcast_palette_colors*sizeof(__u16));
        g=mem_alloc(dreamcast_palette_colors*sizeof(__u16));
        b=mem_alloc(dreamcast_palette_colors*sizeof(__u16));
        t=mem_alloc(dreamcast_palette_colors*sizeof(__u16));

        if (!r||!g||!b||!t)/*internal("Cannot allocate memory.\n")*/;

        cmap.start = 0;
        cmap.len = dreamcast_palette_colors;
        cmap.red = r;
        cmap.green = g;
        cmap.blue = b;
        cmap.transp = t;

        if (ioctl(dreamcast_handler, FBIOGETCMAP, &cmap))
                /*internal("Cannot get palette\n")*/;

        for (i = 0; i < dreamcast_palette_colors; i++)
        {
                /*printf("%d %d %d\n",r[i],g[i],b[i]);*/
                pal->red[i] = r[i];
                pal->green[i] = g[i];
                pal->blue[i] = b[i];
        }

        mem_free(r);mem_free(g);mem_free(b);mem_free(t);
}


static void dreamcast_switch_signal(void *data)
{
        struct vt_stat st;
        struct rect r;
        int signal=(int)data;

        switch(signal)
        {
        case SIGUSR1: /* release */
                dreamcast_active=0;
                if (!in_gr_operation)ioctl(TTY,VT_RELDISP,1);
                break;

        case SIGUSR2: /* acq */
                if (ioctl(TTY,VT_GETSTATE,&st)) return;
                if (st.v_active != dreamcast_console) return;
                dreamcast_active=1;
                ioctl(TTY,VT_RELDISP,VT_ACKACQ);
                if (dreamcast_dreamcast_have_cmap && current_virtual_device)
                        set_palette(&global_pal);
                r.x1=0;
                r.y1=0;
                r.x2=dreamcast_xsize;
                r.y2=dreamcast_ysize;
                if (current_virtual_device) current_virtual_device->redraw_handler(current_virtual_device,&r);
                break;
        }
}


static unsigned char *dreamcast_switch_init(void)
{

        install_signal_handler(SIGUSR1, dreamcast_switch_signal, (void*)SIGUSR1, 1);
        install_signal_handler(SIGUSR2, dreamcast_switch_signal, (void*)SIGUSR2, 0);
        if (-1 == ioctl(TTY,VT_GETMODE, &vt_omode)) {
                return stracpy("Could not get VT mode.\n");
        }
        memcpy(&vt_mode, &vt_omode, sizeof(vt_mode));

        vt_mode.mode   = VT_PROCESS;
        vt_mode.waitv  = 0;
        vt_mode.relsig = SIGUSR1;
        vt_mode.acqsig = SIGUSR2;

        if (-1 == ioctl(TTY,VT_SETMODE, &vt_mode)) {
                return stracpy("Could not set VT mode.\n");
        }
        return NULL;
}

static void dreamcast_switch_shutdown(void)
{
        ioctl(TTY,VT_SETMODE, &vt_omode);
}

void dreamcast_shutdown_palette()
{
        if (dreamcast_dreamcast_have_cmap)
        {
                set_palette(&old_palette);
                free_palette(&old_palette);
                free_palette(&global_pal);
        }
}

void dreamcast_ctrl_c(struct itrm *i)
{
        kbd_ctrl_c();
}

void dreamcast_mouse_setsize()
{
        struct vt_stat vs;
        if (!ioctl(0, VT_GETSTATE, &vs)) {
                fd_set zero;
                struct timeval tv;
                FD_ZERO(&zero);
                memset(&tv, 0, sizeof tv);
                ioctl(0, VT_ACTIVATE, vs.v_active > 1 ? 1 : 2);
                tv.tv_sec = 0;
                tv.tv_usec = 100000;
                select(0, &zero, &zero, &zero, &tv);
                tv.tv_sec = 0;
                tv.tv_usec = 100000;
                select(0, &zero, &zero, &zero, &tv);
                tv.tv_sec = 0;
                tv.tv_usec = 100000;
                select(0, &zero, &zero, &zero, &tv);
                ioctl(0, VT_ACTIVATE, vs.v_active);
        }
}


void unhandle_dreamcast_mouse();

void dreamcast_j2m_in(void *nic)
{
        static struct event ev;

        __u8 type=0;
        static signed char state=0;
        signed char ret=0;
        struct js_event joy_event;

        ev.x = mouse_x;
        ev.y = mouse_y;

        while(1) {
                if ((ret = poll( &(joy.poll_fd), 1, joy.poll_timeout)) < 0) {
                        perror("Error when waiting for joystick event ");
                        exit(-1);
                }

                if ((0 == ret) && (state != GO))
                        return;

                if ( ret != 0 ) {
                        if (( read( joy.fd_joydev, &joy_event, sizeof( struct js_event))) < 0) {
                                perror("Error when reading joystick event");
                                exit(-1);
                        }
                }

                type = j2m_map_it( &joy_event);

                state = j2m_convert_linksfb( type, joy_event.value, &ev);

                if  ( state != GO )
                        return;

                //fprintf( stderr, "ev.x=%i, ev.y=%i, ev.b=%i, ev.ev=%i\n", ev.x, ev.y, ev.b, ev.ev);

                joy_event.type = 0;

                ev.ev = EV_MOUSE;
                mouse_x = ev.x;
                mouse_y= ev.y;

                if (mouse_x >= dreamcast_xsize) mouse_x = dreamcast_xsize - 1;
                if (mouse_y >= dreamcast_ysize) mouse_y = dreamcast_ysize - 1;
                if (mouse_x < 0) mouse_x = 0;
                if (mouse_y < 0) mouse_y = 0;

                ev.x = mouse_x;
                ev.y = mouse_y;

                if (!current_virtual_device) return;
                if (current_virtual_device->mouse_handler) current_virtual_device->mouse_handler(current_virtual_device, ev.x, ev.y, ev.b);
                redraw_mouse();
        }

}

int handle_dreamcast_mouse()
{

        memset( &joy, 0, sizeof(struct ext_s));

        strncpy( joy.path_joydev, DEFAULT_DEV, sizeof(joy.path_joydev) );
        joy.poll_timeout = DEFAULT_POLLTIMEOUT;
        joy.remap = 0;

        if(j2m_start())
                return -1;

        fb_hj2m = joy.fd_joydev;

        set_handlers(fb_hj2m, dreamcast_j2m_in, NULL, NULL, NULL);

#ifdef SIGTSTP
        install_signal_handler(SIGTSTP, (void (*)(void *))sig_ign, NULL, 0);
#endif
#ifdef SIGCONT
        install_signal_handler(SIGCONT, (void (*)(void *))sig_ign, NULL, 0);
#endif
        return 0;
}

void unhandle_dreamcast_mouse()
{
#ifdef SIGTSTP
        install_signal_handler(SIGTSTP, NULL, NULL, 0);
#endif
#ifdef SIGCONT
        install_signal_handler(SIGCONT, NULL, NULL, 0);
#endif
        set_handlers(fb_hj2m, NULL, NULL, NULL, NULL);

        if ( close(joy.fd_joydev)<0 ) {
                perror("Error when closing joystick device ");
                exit(-1);
        }
        mem_free(joy.map);
}

void block_dreamcast_mouse()
{
        if (fb_hj2m >= 0) set_handlers(fb_hj2m, NULL, NULL, NULL, NULL);
}

void unblock_dreamcast_mouse()
{
        if (fb_hj2m >= 0) set_handlers(fb_hj2m, dreamcast_j2m_in, NULL, NULL, NULL);
}

unsigned char *dreamcast_init_driver(unsigned char *param, unsigned char *ignore)
{
        unsigned char *e;
        struct stat st;
        kbd_set_raw = 1;
        dreamcast_old_vd = NULL;
        ignore=ignore;
        dreamcast_driver_param=param;

        dreamcast_driver.flags |= GD_NEED_CODEPAGE;
        utf8_table=get_cp_index("UTF-8");

        if (fstat(TTY, &st)) return stracpy("Cannon stat stdin.\n");

        dreamcast_console = st.st_rdev & 0xff;

        ioctl(TTY, VT_WAITACTIVE, dreamcast_console);
        if ((e = dreamcast_switch_init())) return e;

        dreamcast_handler=open("/dev/fb0",O_RDWR);
        if (dreamcast_handler==-1) {
                dreamcast_switch_shutdown();
                return stracpy("Cannot open /dev/fb0.\n");
        }

        if ((ioctl (dreamcast_handler, FBIOGET_VSCREENINFO, &vi))==-1)
        {
                close(dreamcast_handler);
                dreamcast_switch_shutdown();
                return stracpy("Cannot get FB VSCREENINFO.\n");
        }

        oldmode=vi;

        if ((ioctl (dreamcast_handler, FBIOGET_FSCREENINFO, &fi))==-1)
        {
                close(dreamcast_handler);
                dreamcast_switch_shutdown();
                return stracpy("Cannot get FB FSCREENINFO.\n");
        }

        dreamcast_driver.x=dreamcast_xsize=vi.xres;
        dreamcast_driver.y=dreamcast_ysize=vi.yres;
        dreamcast_bits_pp=vi.bits_per_pixel;

        switch(dreamcast_bits_pp)
        {
        case 4:
                dreamcast_pixelsize=1;
                dreamcast_palette_colors=16;
                break;

        case 8:
                dreamcast_pixelsize=1;
                dreamcast_palette_colors=256;
                break;

        case 15:
        case 16:
                dreamcast_pixelsize=2;
                dreamcast_palette_colors=32;
                break;

        case 24:
                dreamcast_palette_colors=256;
                dreamcast_pixelsize=3;
                break;

        case 32:
                dreamcast_palette_colors=256;
                dreamcast_pixelsize=4;
                dreamcast_bits_pp=24;
                break;

        default:
                close(dreamcast_handler);
                dreamcast_switch_shutdown();
                return stracpy("Unknown bit depth");
        }
        dreamcast_colors=1<<dreamcast_bits_pp;

        if (fi.visual==FB_VISUAL_PSEUDOCOLOR) /* set palette */
        {
                dreamcast_dreamcast_have_cmap=1;
                dreamcast_palette_colors=dreamcast_colors;
                alloc_palette(&old_palette);
                get_palette(&old_palette);

                alloc_palette(&global_pal);
                generate_palette(&global_pal);
                set_palette(&global_pal);
        }
        if (fi.visual==FB_VISUAL_DIRECTCOLOR) /* set pseudo palette */
        {
                dreamcast_dreamcast_have_cmap=2;
                alloc_palette(&old_palette);
                get_palette(&old_palette);

                alloc_palette(&global_pal);
                generate_palette(&global_pal);
                set_palette(&global_pal);
        }

        dreamcast_linesize=fi.line_length;
        dreamcast_mem_size=fi.smem_len;

        vi.xoffset=0;
        vi.yoffset=0;
        if ((ioctl(dreamcast_handler, FBIOPAN_DISPLAY, &vi))==-1)
        {
                /* mikulas : nechodilo mi to, tak jsem tohle vyhodil a ono to chodi */
                /*dreamcast_shutdown_palette();
                 close(dreamcast_handler);
                 return stracpy("Cannot pan display.\n");
                 */
        }

        if (init_virtual_devices(&dreamcast_driver, NUMBER_OF_DEVICES)){
                dreamcast_shutdown_palette();
                close(dreamcast_handler);
                dreamcast_switch_shutdown();
                return stracpy("Allocation of virtual devices failed.\n");
        }
        dreamcast_kbd = handle_svgalib_keyboard((void (*)(void *, unsigned char *, int))dreamcast_key_in);

        /* Mikulas: nechodi to na sparcu */
        if (dreamcast_mem_size < dreamcast_linesize * dreamcast_ysize)
        {
                dreamcast_shutdown_palette();
                svgalib_free_trm(dreamcast_kbd);
                shutdown_virtual_devices();
                close(dreamcast_handler);
                dreamcast_switch_shutdown();
                return stracpy("Nonlinear mapping of graphics memory not supported.\n");
        }


        if ((dreamcast_mem=mmap(0,dreamcast_mem_size,PROT_READ|PROT_WRITE,MAP_SHARED,dreamcast_handler,0))==MAP_FAILED)
        {
                dreamcast_shutdown_palette();
                svgalib_free_trm(dreamcast_kbd);
                shutdown_virtual_devices();

                close(dreamcast_handler);
                dreamcast_switch_shutdown();
                return stracpy("Cannot mmap graphics memory.\n");
        }


        dreamcast_driver.depth=dreamcast_pixelsize&7;
        dreamcast_driver.depth|=(dreamcast_bits_pp&31)<<3;
        dreamcast_driver.depth|=(!!(vi.nonstd))<<8;	/* nonstd byte order */

        dreamcast_driver.get_color=get_color_fn(dreamcast_driver.depth);
        dreamcast_switch_init();
        install_signal_handler(SIGINT, (void (*)(void *))dreamcast_ctrl_c, dreamcast_kbd, 0);

        /* mouse */
        mouse_buffer=mem_alloc(dreamcast_pixelsize*arrow_area);
        background_buffer=mem_alloc(dreamcast_pixelsize*arrow_area);
        new_background_buffer=mem_alloc(dreamcast_pixelsize*arrow_area);
        background_x=mouse_x=dreamcast_xsize>>1;
        background_y=mouse_y=dreamcast_ysize>>1;
        mouse_black=dreamcast_driver.get_color(0);
        mouse_white=dreamcast_driver.get_color(0xffffff);
        mouse_graphics_device=dreamcast_driver.init_device();
        virtual_devices[0] = NULL;
        global_mouse_hidden=1;

        if (handle_dreamcast_mouse()) {
                dreamcast_driver.shutdown_device(mouse_graphics_device);
                mem_free(mouse_buffer);
                mem_free(background_buffer);
                mem_free(new_background_buffer);
                dreamcast_shutdown_palette();
                svgalib_free_trm(dreamcast_kbd);
                shutdown_virtual_devices();

                close(dreamcast_handler);
                dreamcast_switch_shutdown();
                return stracpy("Cannot open joystick.\n");
        }
        /* hide cursor */
        printf("\033[?25l");
        fflush(stdout);
        show_mouse();
        return NULL;
}

void dreamcast_shutdown_driver(void)
{
        mem_free(mouse_buffer);
        mem_free(background_buffer);
        mem_free(new_background_buffer);
        dreamcast_driver.shutdown_device(mouse_graphics_device);
        unhandle_dreamcast_mouse();
        ioctl (dreamcast_handler, FBIOPUT_VSCREENINFO, &oldmode);
        dreamcast_shutdown_palette();
        install_signal_handler(SIGINT, NULL, NULL, 0);

        close(dreamcast_handler);

        memset(dreamcast_mem,0,dreamcast_mem_size);
        munmap(dreamcast_mem,dreamcast_mem_size);
        shutdown_virtual_devices();
        dreamcast_switch_shutdown();
        svgalib_free_trm(dreamcast_kbd);
        /* show cursor */
        printf("\033[?25h");
        fflush(stdout);
}


static unsigned char *dreamcast_get_driver_param(void)
{
        return dreamcast_driver_param;
}


/* Return value:        0 alloced on heap
 *                      1 alloced in vidram
 *                      2 alloced in X server shm
 */
static int dreamcast_get_empty_bitmap(struct bitmap *dest)
{
        dest->data=mem_alloc(dest->x*dest->y*dreamcast_pixelsize);
        dest->skip=dest->x*dreamcast_pixelsize;
        dest->flags=0;
        return 0;
}

/* Return value:        0 alloced on heap
 *                      1 alloced in vidram
 *                      2 alloced in X server shm
 */
static int dreamcast_get_filled_bitmap(struct bitmap *dest, long color)
{
        int n=dest->x*dest->y*dreamcast_pixelsize;

        dest->data=mem_alloc(n);
        pixel_set(dest->data,n,&color);
        dest->skip=dest->x*dreamcast_pixelsize;
        dest->flags=0;
        return 0;
}

void dreamcast_register_bitmap(struct bitmap *bmp)
{
}

void dreamcast_unregister_bitmap(struct bitmap *bmp)
{
        mem_free(bmp->data);
}

void *dreamcast_prepare_strip(struct bitmap *bmp, int top, int lines)
{
        return ((char *)bmp->data)+bmp->skip*top;
}


void dreamcast_commit_strip(struct bitmap *bmp, int top, int lines)
{
        return;
}


void dreamcast_draw_bitmap(struct graphics_device *dev,struct bitmap* hndl, int x, int y)
{
        unsigned char *scr_start;

        CLIP_PREFACE

                scr_start=dreamcast_mem+y*dreamcast_linesize+x*dreamcast_pixelsize;
        for(;ys;ys--){
                memcpy(scr_start,data,xs*dreamcast_pixelsize);
                data+=hndl->skip;
                scr_start+=dreamcast_linesize;
        }
        END_GR
}


void dreamcast_draw_bitmaps(struct graphics_device *dev, struct bitmap **hndls, int n, int x, int y)
{
        TEST_INACTIVITY

                if (x>=dreamcast_xsize||y>dreamcast_ysize) return;
        while(x+(*hndls)->x<=0&&n){
                x+=(*hndls)->x;
                n--;
                hndls++;
        }
        while(n&&x<=dreamcast_xsize){
                dreamcast_draw_bitmap(dev, *hndls, x, y);
                x+=(*hndls)->x;
                n--;
                hndls++;
        }
}



void dreamcast_fill_area(struct graphics_device *dev, int left, int top, int right, int bottom, long color)
{
        unsigned char *dest;
        int y;

        FILL_CLIP_PREFACE

                dest=dreamcast_mem+top*dreamcast_linesize+left*dreamcast_pixelsize;
        for (y=bottom-top;y;y--){
                pixel_set(dest,(right-left)*dreamcast_pixelsize,&color);
                dest+=dreamcast_linesize;
        }
        END_GR
}


void dreamcast_draw_hline(struct graphics_device *dev, int left, int y, int right, long color)
{
        unsigned char *dest;
        HLINE_CLIP_PREFACE

                dest=dreamcast_mem+y*dreamcast_linesize+left*dreamcast_pixelsize;
        pixel_set(dest,(right-left)*dreamcast_pixelsize,&color);
        END_GR
}


void dreamcast_draw_vline(struct graphics_device *dev, int x, int top, int bottom, long color)
{
        unsigned char *dest;
        int y;
        VLINE_CLIP_PREFACE

                dest=dreamcast_mem+top*dreamcast_linesize+x*dreamcast_pixelsize;
        for (y=(bottom-top);y;y--){
                memcpy(dest,&color,dreamcast_pixelsize);
                dest+=dreamcast_linesize;
        }
        END_GR
}


int dreamcast_hscroll(struct graphics_device *dev, struct rect_set **ignore, int sc)
{
        unsigned char *dest, *src;
        int y;
        int len;
        HSCROLL_CLIP_PREFACE

                ignore=NULL;
        if (sc>0){
                len=(dev->clip.x2-dev->clip.x1-sc)*dreamcast_pixelsize;
                src=dreamcast_mem+dreamcast_linesize*dev->clip.y1+dev->clip.x1*dreamcast_pixelsize;
                dest=src+sc*dreamcast_pixelsize;
                for (y=dev->clip.y2-dev->clip.y1;y;y--){
                        memmove(dest,src,len);
                        dest+=dreamcast_linesize;
                        src+=dreamcast_linesize;
                }
        }else{
                len=(dev->clip.x2-dev->clip.x1+sc)*dreamcast_pixelsize;
                dest=dreamcast_mem+dreamcast_linesize*dev->clip.y1+dev->clip.x1*dreamcast_pixelsize;
                src=dest-sc*dreamcast_pixelsize;
                for (y=dev->clip.y2-dev->clip.y1;y;y--){
                        memmove(dest,src,len);
                        dest+=dreamcast_linesize;
                        src+=dreamcast_linesize;
                }
        }
        END_GR
                return 1;
}


int dreamcast_vscroll(struct graphics_device *dev, struct rect_set **ignore, int sc)
{
        unsigned char *dest, *src;
        int y;
        int len;

        VSCROLL_CLIP_PREFACE

                ignore=NULL;
        len=(dev->clip.x2-dev->clip.x1)*dreamcast_pixelsize;
        if (sc>0){
                /* Down */
                dest=dreamcast_mem+(dev->clip.y2-1)*dreamcast_linesize+dev->clip.x1*dreamcast_pixelsize;
                src=dest-dreamcast_linesize*sc;
                for (y=dev->clip.y2-dev->clip.y1-sc;y;y--){
                        memcpy(dest,src,len);
                        dest-=dreamcast_linesize;
                        src-=dreamcast_linesize;
                }
        }else{
                /* Up */
                dest=dreamcast_mem+dev->clip.y1*dreamcast_linesize+dev->clip.x1*dreamcast_pixelsize;
                src=dest-dreamcast_linesize*sc;
                for (y=dev->clip.y2-dev->clip.y1+sc;y;y--){
                        memcpy(dest,src,len);
                        dest+=dreamcast_linesize;
                        src+=dreamcast_linesize;
                }
        }
        END_GR
                return 1;
}


void dreamcast_set_clip_area(struct graphics_device *dev, struct rect *r)
{
        memcpy(&dev->clip, r, sizeof(struct rect));
        if (dev->clip.x1>=dev->clip.x2||dev->clip.y2<=dev->clip.y1||dev->clip.y2<=0||dev->clip.x2<=0||dev->clip.x1>=dreamcast_xsize
            ||dev->clip.y1>=dreamcast_ysize){
                /* Empty region */
                dev->clip.x1=dev->clip.x2=dev->clip.y1=dev->clip.y2=0;
        }else{
                if (dev->clip.x1<0) dev->clip.x1=0;
                if (dev->clip.x2>dreamcast_xsize) dev->clip.x2=dreamcast_xsize;
                if (dev->clip.y1<0) dev->clip.y1=0;
                if (dev->clip.y2>dreamcast_ysize) dev->clip.y2=dreamcast_ysize;
        }
}

int dreamcast_block(struct graphics_device *dev)
{
        if (dreamcast_old_vd) return 1;
        unhandle_dreamcast_mouse();
        dreamcast_old_vd = current_virtual_device;
        current_virtual_device=NULL;
        svgalib_block_itrm(dreamcast_kbd);
        if (dreamcast_dreamcast_have_cmap) set_palette(&old_palette);
        printf("\033[?25h");
        fflush(stdout);
        return 0;
}

void dreamcast_unblock(struct graphics_device *dev)
{
#ifdef DEBUG
        if (current_virtual_device) {
                internal("dreamcast_unblock called without dreamcast_block");
                return;
        }
#endif /* #ifdef DEBUG */
        current_virtual_device = dreamcast_old_vd;
        dreamcast_old_vd = NULL;
        if (dreamcast_dreamcast_have_cmap) set_palette(&global_pal);
        printf("\033[?25l");
        fflush(stdout);
        svgalib_unblock_itrm(dreamcast_kbd);
        handle_dreamcast_mouse();
        if (current_virtual_device) current_virtual_device->redraw_handler(current_virtual_device
                                                                           ,&current_virtual_device->size);
}

void dreamcast_put_to_clipboard(struct graphics_device *gd, char *string,int length)
{
}

void dreamcast_request_clipboard(struct graphics_device *gd)
{
}

unsigned char *dreamcast_get_from_clipboard(struct graphics_device *gd)
{
        return NULL;
}

struct graphics_driver dreamcast_driver={
        "dreamcast",
        dreamcast_init_driver,
        init_virtual_device,
        shutdown_virtual_device,
        dreamcast_shutdown_driver,
        dreamcast_get_driver_param,
        dreamcast_get_empty_bitmap,
        dreamcast_get_filled_bitmap,
        dreamcast_register_bitmap,
        dreamcast_prepare_strip,
        dreamcast_commit_strip,
        dreamcast_unregister_bitmap,
        dreamcast_draw_bitmap,
        dreamcast_draw_bitmaps,
        NULL,	/* dreamcast_get_color */
        dreamcast_fill_area,
        dreamcast_draw_hline,
        dreamcast_draw_vline,
        dreamcast_hscroll,
        dreamcast_vscroll,
        dreamcast_set_clip_area,
        dreamcast_block,
        dreamcast_unblock,
        NULL,	/* set_title */
        dreamcast_put_to_clipboard,
        dreamcast_request_clipboard,
        dreamcast_get_from_clipboard,
        0,				/* depth (filled in dreamcast_init_driver function) */
        0, 0,				/* size (in X is empty) */
        GD_DONT_USE_SCROLL,		/* flags */
};

#endif /* GRDRV_DREAMCAST */
