/*
* xine_frontend_kbd.c: Forward (local) console key presses to VDR (server)
*
* See the main source file 'xineliboutput.c' for copyright information and
* how to reach the author.
*
* $Id: xine_frontend_kbd.c,v 1.2 2011/11/13 09:51:44 phintuka Exp $
*
*/
#include "features.h"
#include <inttypes.h>
#include <poll.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#define LOG_MODULENAME "[console] "
#include "logdefs.h"
#include "xine_frontend.h"
#include "xine_frontend_kbd.h"
/*
* stdin (keyboard/slave mode) reading
*/
/* static data */
static pthread_t kbd_thread;
static struct termios tm, saved_tm;
/* xine_frontend_main.c: */
extern int gui_hotkeys;
/*
* read_key()
*
* Try to read single char from stdin.
*
* Returns: >=0 char readed
* -1 nothing to read
* -2 fatal error
*/
#define READ_KEY_ERROR -2
#define READ_KEY_EAGAIN -1
static int read_key(void)
{
unsigned char ch;
int err;
struct pollfd pfd;
pfd.fd = STDIN_FILENO;
pfd.events = POLLIN;
errno = 0;
pthread_testcancel();
if (1 == (err = poll(&pfd, 1, 50))) {
pthread_testcancel();
if (1 == (err = read(STDIN_FILENO, &ch, 1)))
return (int)ch;
if (err < 0)
LOGERR("read_key: read(stdin) failed");
else
LOGERR("read_key: read(stdin) failed: no stdin");
return READ_KEY_ERROR;
} else if (err < 0 && errno != EINTR) {
LOGERR("read_key: poll(stdin) failed");
return READ_KEY_ERROR;
}
pthread_testcancel();
return READ_KEY_EAGAIN;
}
/*
* read_key_seq()
*
* Read a key sequence from stdin.
* Key sequence is either normal key or escape sequence.
*
* Returns the key or escape sequence as uint64_t.
*
* Originally copied from vdr:
* remote.c: General Remote Control handling
* Copyright (C) 2000, 2003, 2006, 2008 Klaus Schmidinger
*/
#define READ_KEY_SEQ_ERROR 0xffffffff
static uint64_t read_key_seq(void)
{
/* from vdr, remote.c */
uint64_t k = 0;
int key1;
if ((key1 = read_key()) >= 0) {
k = key1;
if (key1 == 0x1B) {
/* Start of escape sequence */
if ((key1 = read_key()) >= 0) {
k <<= 8;
k |= key1 & 0xFF;
switch (key1) {
case 0x4F: /* 3-byte sequence */
if ((key1 = read_key()) >= 0) {
k <<= 8;
k |= key1 & 0xFF;
}
break;
case 0x5B: /* 3- or more-byte sequence */
if ((key1 = read_key()) >= 0) {
k <<= 8;
k |= key1 & 0xFF;
switch (key1) {
case 0x31 ... 0x3F: /* more-byte sequence */
case 0x5B: /* strange, may apparently occur */
do {
if ((key1 = read_key()) < 0)
break; /* Sequence ends here */
k <<= 8;
k |= key1 & 0xFF;
} while (key1 != 0x7E);
break;
default:;
}
}
break;
default:;
}
}
}
}
if (key1 == READ_KEY_ERROR)
return READ_KEY_SEQ_ERROR;
return k;
}
/*
* kbd_receiver_thread()
*
* Read key(sequence)s from stdin and pass those to frontend.
*/
static void kbd_receiver_thread_cleanup(void *arg)
{
int status;
tcsetattr(STDIN_FILENO, TCSANOW, &saved_tm);
status = system("setterm -cursor on");
if (status < 0)
LOGMSG("system(\"setterm -cursor on\") failed\n");
LOGMSG("Keyboard thread terminated");
}
static void *kbd_receiver_thread(void *fe_gen)
{
frontend_t *fe = (frontend_t*)fe_gen;
uint64_t code = 0;
char str[64];
int status;
status = system("setterm -cursor off");
if (status < 0)
LOGMSG("system(\"setterm -cursor off\") failed\n");
status = system("setterm -blank off");
if (status < 0)
LOGMSG("system(\"setterm -blank off\") failed\n");
/* Set stdin to deliver keypresses without buffering whole lines */
tcgetattr(STDIN_FILENO, &saved_tm);
if (tcgetattr(STDIN_FILENO, &tm) == 0) {
tm.c_iflag = 0;
tm.c_lflag &= ~(ICANON | ECHO);
tm.c_cc[VMIN] = 0;
tm.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSANOW, &tm);
}
pthread_cleanup_push(kbd_receiver_thread_cleanup, NULL);
do {
alarm(0);
errno = 0;
code = read_key_seq();
alarm(3); /* watchdog */
if (code == 0)
continue;
if (code == READ_KEY_SEQ_ERROR)
break;
if (code == 27) {
fe->send_event(fe, "QUIT");
break;
}
if (gui_hotkeys) {
if (code == 'f' || code == 'F') {
fe->send_event(fe, "TOGGLE_FULLSCREEN");
continue;
}
if (code == 'p' || code == 'P') {
fe->send_event(fe, "POWER_OFF");
continue;
}
if (code == 'd' || code == 'D') {
fe->send_event(fe, "TOGGLE_DEINTERLACE");
continue;
}
}
snprintf(str, sizeof(str), "%016" PRIX64, code);
fe->send_input_event(fe, "KBD", str, 0, 0);
} while (fe->xine_is_finished(fe, 0) != FE_XINE_EXIT);
alarm(0);
LOGDBG("Keyboard thread terminating");
pthread_cleanup_pop(1);
pthread_exit(NULL);
return NULL; /* never reached */
}
/*
* slave_receiver_thread()
*
* Read slave mode commands from stdin
* Interpret and execute valid commands
*/
static void slave_receiver_thread_cleanup(void *arg)
{
/* restore terminal settings */
tcsetattr(STDIN_FILENO, TCSANOW, &saved_tm);
LOGDBG("Slave mode receiver terminated");
}
static void *slave_receiver_thread(void *fe_gen)
{
frontend_t *fe = (frontend_t*)fe_gen;
char str[128], *pt;
tcgetattr(STDIN_FILENO, &saved_tm);
pthread_cleanup_push(slave_receiver_thread_cleanup, NULL);
do {
errno = 0;
str[0] = 0;
pthread_testcancel();
if (!fgets(str, sizeof(str), stdin))
break;
pthread_testcancel();
if (NULL != (pt = strchr(str, '\r')))
*pt = 0;
if (NULL != (pt = strchr(str, '\n')))
*pt = 0;
if (!strncasecmp(str, "QUIT", 4)) {
fe->send_event(fe, "QUIT");
break;
}
if (!strncasecmp(str, "FULLSCREEN", 10)) {
if (strpbrk(str + 10, "01"))
fe->send_event(fe, str);
else
fe->send_event(fe, "TOGGLE_FULLSCREEN");
continue;
}
if (!strncasecmp(str, "DEINTERLACE ", 12)) {
fe->send_event(fe, str);
continue;
}
if (!strncasecmp(str, "HITK ", 5)) {
fe->send_input_event(fe, NULL, str+5, 0, 0);
continue;
}
LOGMSG("Unknown slave mode command: %s", str);
} while (fe->xine_is_finished(fe, 0) != FE_XINE_EXIT);
LOGDBG("Slave mode receiver terminating");
pthread_cleanup_pop(1);
pthread_exit(NULL);
return NULL; /* never reached */
}
/*
* kbd_start()
*
* Start keyboard/slave mode reader thread
*/
void kbd_start(frontend_t *fe, int slave_mode)
{
int err;
if ((err = pthread_create (&kbd_thread,
NULL,
slave_mode ? slave_receiver_thread : kbd_receiver_thread,
(void*)fe)) != 0) {
fprintf(stderr, "Can't create new thread for keyboard (%s)\n",
strerror(err));
}
}
/*
* kbd_stop()
*
* Stop keyboard/slave mode reader thread
*/
void kbd_stop(void)
{
void *p;
pthread_cancel(kbd_thread);
pthread_join(kbd_thread, &p);
}