/*************************************************************************
 *
 * Keybd.c - Keyboard module: Linux framebuffer support
 *
 * $Id$
 *
 * Copyright 2004-2014 Citrix Systems, Inc.  All Rights Reserved.
 *
 *************************************************************************/

#include "citrix.h"

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <linux/keyboard.h>
#include <linux/vt.h>
#include <linux/kd.h>
#include <termios.h>
#include <sys/ioctl.h>

#include "mainloop.h"
#include "ctxnevt.h"
#include "simplified.h"
#include "fb_plugin.h"

//#define VT_SWITCHING /* Too buggy for now ... */
//#define RESIZE_TEST  /* Keys F1, F2, F3 generate resize requests. */

static struct termios LinuxTermios;
static BOOLEAN has_changed;

#ifdef VT_SWITCHING
static void set_mode(int new_mode)
{
    static struct vt_mode mode = {VT_AUTO, 0, 0, SIGUSR2};

    mode.mode = new_mode;
    ioctl(new_tty_file_descriptor, VT_SETMODE, &mode);
}

/* This signal handler should be called whan control returns to our VT. */

static void restart_handler(int sig)
{
printf("Got resume signal\n");
    set_mode(VT_AUTO);
    ioctl(new_tty_file_descriptor, KDSKBMODE, K_RAW);
    ioctl(new_tty_file_descriptor, KDSETMODE, KD_GRAPHICS);
    WD_resume_TW();
}

static void switch_vt(int new_tty_number)
{
    struct sigaction sa;

printf("Switching to tty%d\n", new_tty_number);

    /* Restore some settings. */

    ioctl(new_tty_file_descriptor, KDSKBMODE, K_XLATE);
    ioctl(new_tty_file_descriptor, KDSETMODE, KD_TEXT);

    /* Ask to switch to the new VT. */

    if (ioctl(new_tty_file_descriptor, VT_ACTIVATE, new_tty_number) < 0) {
        perror("VT_ACTIVATE");
        return;
    }

    /* Tell the ICA host to stop sending graphics. */

    WD_pause_TW();

    /* We want to know when our VT becomes active again. */

    sa.sa_handler = restart_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESETHAND | SA_RESTART;
    sigaction(SIGUSR2, &sa, NULL);

    /* Ask for a signal when control returns. */

    set_mode(VT_PROCESS);
}
#endif /*  VT_SWITCHING */

static BOOLEAN toggle_lock(BOOLEAN *state_p, int bit) {
    char keybFlag;

    if (*state_p)
        return TRUE;
    *state_p = TRUE;

    /* Toggle the appropriate keyboard LED. */

    ioctl(new_tty_file_descriptor, KDGETLED, &keybFlag); 
    keybFlag ^= bit;
    ioctl(new_tty_file_descriptor, KDSETLED, keybFlag); 

    return FALSE;
}

/* This is the handler for keyboard input. */

static void process_key_press(void *dummy1, void *dummy2) {
    unsigned char string[1], down, it;
    
    static BOOLEAN capsLockDown = FALSE;
    static BOOLEAN numLockDown = FALSE;
    static BOOLEAN scrollLockDown = FALSE;
                                                
    if (read(new_tty_file_descriptor, string, 1) < 0) {
        perror("Read keyboard");
        return;
    }

    down = !(string[0] & 0x80); /* Down or up? */
    it = string[0] & 0x7F;      /* Extract base value, identifying key. */

    switch (it) {
#ifdef VT_SWITCHING
    static BOOLEAN leftCtlDown = FALSE;
    static BOOLEAN leftAltDown = FALSE;

    case 0x1D: /* Left Ctrl. */
        leftCtlDown = down;
        break;

    case 0x38: /* Left Alt. */
        leftAltDown = down;
        break;

    case 0x3B:  /* F1 */
    case 0x3C:
    case 0x3D:
    case 0x3E:
    case 0x3F:
    case 0x40:
    case 0x41:
    case 0x42:
    case 0x43:
    case 0x44:  /* F10 */
printf("Got SC %#x down is %d downs %d %d\n", it, down, leftCtlDown, leftAltDown);
        if (leftCtlDown && leftAltDown && down) {
            int new_tty_number;

            /* Switch to a different VT. */

            new_tty_number = it - 0x3A;  /* Key number. */
            switch_vt(new_tty_number);
            return;
        }
        break;
#endif /*  VT_SWITCHING */

#ifdef RESIZE_TEST
    case 0x3B:  /* F1 */
        Send_session_resize(400, 400, TRUE);  /* Make small. */
        return;

    case 0x3C:
        Send_session_resize(800, 600, TRUE);  /* Medium. */
        return;

    case 0x3D:
        Send_session_resize(1000, 800, TRUE); /* Larger. */
        return;
#endif /* RESIZE_TEST */

    case 0x3A: /* caps lock scan code */
        if (down) {
            if (toggle_lock(&capsLockDown, LED_CAP))
                return;  
        } else { 
            capsLockDown = 0;
        }
	break;

    case 0x45: /*num lock down scan code*/
        if (down) {
            if (toggle_lock(&numLockDown, LED_NUM))
                return;
        } else {
            numLockDown = 0;
        }
        break;
    
    case 0x46: /*scroll lock down scan code*/
        if (down) {
            if (toggle_lock(&scrollLockDown, LED_SCR))
                return;
        } else {
            scrollLockDown = 0;
#ifndef NO_EXIT_ON_SCROLL_LOCK
            CloseGraphics();
            exit(2);
#endif
        }
        break;
    
    default:
        break;
    }
    Send_scancodes(string, 1);
}

/* Get the kernel's idea of the keyboard's locking shift state. */

unsigned int Get_console_shifts(void)
{
    unsigned char keybFlag = 0;

    ioctl(new_tty_file_descriptor, KDGKBLED, &keybFlag);
    return (unsigned int)keybFlag & 0x7;
}

/* Initialisation and shutdown code. */

void keybd_init(void)
{
    static struct pal_event kbd_input = {{process_key_press, NULL}, 0};
    struct termios          nTty;

    if (has_changed)
        return;

    if (ioctl(new_tty_file_descriptor, KDSKBMODE, K_RAW) < 0)
        perror("KDSKBMODE ioctl");
    tcgetattr(new_tty_file_descriptor, &LinuxTermios);
    nTty = LinuxTermios;
    cfmakeraw(&nTty);
    tcsetattr(new_tty_file_descriptor, TCSANOW, &nTty);

    has_changed = TRUE;

    /* Request input-ready callback for keyboard input. */

    (*plug_ptr->select_fd)(&kbd_input, new_tty_file_descriptor, FALSE);
}

void keybd_fini(void)
{
    if (!has_changed)
        return;
    tcsetattr(new_tty_file_descriptor, TCSANOW, &LinuxTermios);
    ioctl(new_tty_file_descriptor, KDSKBMODE, K_XLATE);
}

