/*************************************************************************
*
*   no_X_framebuffer.c
*
*   KVME plugin function for the framebuffer.
*
*   $Id$
*
*   Copyright 2004-2014 Citrix Systems, Inc.  All Rights Reserved.
*
*************************************************************************/

#include "citrix.h"

#include <stdio.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <linux/kd.h>

#include "mainloop.h"
#include "subwindow.h"
#include "fb_plugin.h"

/* Save a copy of the interface pointer passed to Plugin_init(). */

struct mainloop_plugin *plug_ptr;

#ifndef FB_PLUGIN
static struct consolefontdesc confont;  /* Copy of kernels' console font. */
#endif /* not plugin */

void terminateRoutine(void)
{
    CloseGraphics();
    exit(1);
}

static void segmentationHandler()
{
    CloseGraphics();
    kill(0, SIGILL);    /* We still want a core file. */
}

static void abortHandler()
{
    CloseGraphics();
    kill(0, SIGILL);    /* We still want a core file. */
}

#ifndef FB_PLUGIN
/* Copy the kernel's TTY font, to use in error messages.
 * Assume ASCII for now, but the ioctl(GIO_FONTX) fails with 128.
 */

#define MAX_CHARS 256

static void font_init(void) {
    confont.chardata = malloc(MAX_CHARS * 32);
    if (confont.chardata == NULL) {
        fprintf(stderr, "No memory for console font!\n");
        exit(1);
    }

    confont.charcount = MAX_CHARS;
    if (ioctl(new_tty_file_descriptor, GIO_FONTX, &confont) == -1) {
        perror("GIO_FONT didnt work");
        exit(1);
    }	
}
#endif /* not plugin */

/* Return size and depth for the display.
 * This is called once during session initialisation, and as it needs
 * to look at the framebuffer, most low-level initialisation happens here.
 */

static void get_max_session(struct session_size *ssp)
{
    if (framebuffer.xres == 0) {
        /* First time. */

        if (mouse_init() < 0 || fb_init() < 0)
            exit(1);

        /* Complete low-level initialisation. */

        ttyinit();
#ifndef FB_PLUGIN
        font_init();
#endif /* not plugin */

        signal(SIGSEGV, segmentationHandler);
        signal(SIGABRT, abortHandler);
    }

    /* Set returned information. */

    ssp->width = framebuffer.xres;
    ssp->height = framebuffer.yres;

    /* Guess maximum session depth. */

    ssp->depth = (framebuffer.bytes_per_pixel << 3);
}

/* Connect the main window to the framebuffer. */

static BOOLEAN setup_main_window(struct session_size *info)
{
    int i;

    printf("Session: width %d|%d height %d|%d depth %d|%d\n",
           info->width, framebuffer.xres,
           info->height, framebuffer.yres,
           (int)info->depth, PXL_SIZE);
    if (info->width > framebuffer.xres ||
	info->height > framebuffer.yres ||
        info->depth > PXL_SIZE) {
        CloseGraphics();
        fprintf(stderr, "Bad session, aborting\n");
        terminateRoutine();
    }

    /* Fill the framebuffer with black. */

    for (i = 0; i < framebuffer.yres; ++i) {
        memset(framebuffer.frame + framebuffer.line_length * i, 0,
               framebuffer.xres * framebuffer.bytes_per_pixel);
    }

    /* Switch to raw keyboard input and framebuffer graphics output. */

    keybd_init();
    ioctl(new_tty_file_descriptor, KDSETMODE, KD_GRAPHICS);
    return FALSE; /* False means the video output is unbuffered. */
}

/* "Allocate" the buffer for drawing the session image.
 * This returns a pointer to the memory location where drawing should start.
 */

static void LVB_allocate(struct special_buffer *description)
{
    unsigned int                 size;
    struct image_buffer_params  *pp;

    pp = &description->params.image_buffer;
    switch (description->type) {
    case session_image:
        /* "Allocate" frame buffer as LVB. */
        {
            unsigned char *start;

            /* Centre the session's "window". */

            start = framebuffer.frame +
                framebuffer.line_length *
                    (framebuffer.yres - pp->height) / 2 +
                framebuffer.bytes_per_pixel *
                    (framebuffer.xres - pp->width) / 2;

            /* This assumes that the FB stride is a whole number of pixels! */

            pp->width = framebuffer.line_length / framebuffer.bytes_per_pixel;
            description->buf_addr = start;
        }
        return;
    case image_output:
    case small_bitmap:
        size = pp->width * pp->height * pp->pixel_size;
        break;
    case image_cache:
    case image_input:
        size = description->params.memory_buffer.size;
        break;
    case overlay:
        size = pp->width * pp->height * pp->pixel_size;
        if (description->params.overlay_buffer.flags & MM_DOUBLE_BUFFER)
            size <<= 1;
    default:
        description->buf_addr = NULL;
        return;
    }

    /* Ordinary memory for everything but the LVB. */

    description->buf_addr = malloc(size);
}

/* This function is expected to free the memory "allocated" above. */

static void LVB_free(struct special_buffer *description)
{
    if (description->type == session_image)
        return;         /* LVB in framebuffer. */
    free(description->buf_addr);
    description->buf_addr = NULL;
}

#ifndef FB_PLUGIN
/* Get the console font and return glyphs to the dialogue drawing code. */

static void font_func(int utf32_char, struct Draw_rect *glyph_return)
{
    static unsigned char                image[2 * 32];
    char                               *temploc;
    int                                 i;

    /* The console fonts are 8 bits wide,
     * but the client wants a multiple of 16.
     */

    temploc = confont.chardata + (utf32_char * 32);
    for (i = 0; i < confont.charheight; ++i)
        image[i << 1] = temploc[i]; 

    glyph_return->x = 0;
    glyph_return->y = 0;
    glyph_return->width = 8;
    glyph_return->height = confont.charheight;
    glyph_return->pdata = image;
    glyph_return->stride = 2;   /* bytes */
    glyph_return->bytes_per_pixel = 0;
}
#endif /* not plugin */

/* Set hardware colour palette. */

#define ENTRIES 256

static void set_palette(struct ctx_colour *map)
{
    int                 index;
    struct fb_cmap      cmap;
    __u16               r[ENTRIES], g[ENTRIES], b[ENTRIES];
         
    cmap.start = 0;
    cmap.len = ENTRIES;
    cmap.red = r;
    cmap.green = g;
    cmap.blue = b;

    for (index = 0; index < ENTRIES; index++) {
        r[index] = map[index].red | (map[index].red << 8);
        g[index] = map[index].green | (map[index].green << 8);
        b[index] = map[index].blue | (map[index].blue << 8);
    }
    if (ioctl(framebuffer.fd, FBIOPUTCMAP, &cmap) < 0) {
        /* This happens in 24-bit depth systems, but seems harmless. */

        perror("Could not set the colormap (FBIOPUTCMAP)");
    }
}

/* Initialisation of the framebuffer video/event-loop plugin. */

void Plugin_init(struct mainloop_plugin *plug)
{
    extern unsigned int Get_console_shifts(void); /* keybd.c */
    extern void Syscall_loop_init(struct mainloop_plugin *); /* linux_loop.c */

    plug->major_version = KVME_MAJOR_VERSION;
    plug->minor_version = KVME_MINOR_VERSION;
    plug->plugin_flags = 0;                     /* Not X11 */
    plug->get_screen_size = get_max_session;
    plug->start_video = setup_main_window;
    plug->special_allocate = LVB_allocate;
    plug->special_free = LVB_free;
    plug->query_keyboard = Get_console_shifts;
    plug->close_mainloop = CloseGraphics;
    plug->set_palette = set_palette;

#ifdef FB_PLUGIN
    plug->show_dialogue = stub_dialogue_handler;
#else /* Stand-alone framebuffer build. */
    /* This says that we want the client to draw error dialogues for us
     * in the framebuffer.
     */

    Use_internal_dialogues(font_func);
#endif

    Syscall_loop_init(plug);    /* Complete the initialisation. */

    /* Enable the client's internal cursor-drawing code. */

    Enable_software_cursor();

    /* Save a copy of the function argument. */

    plug_ptr = plug;
}
