/*
 * consoles.c: A plugin for the Video Disk Recorder
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id$
 */


#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <stropts.h>

#include <vdr/config.h>
#include <vdr/remote.h>

#include "virtualconsoles.h"
#include "virtualconsole.h"
#include "i18n.h"


// This program will be used to realize a console.
// Be carefull! If your VDR runs as root, then every
// user can kill your machine if you don't protect
// the cnsoles.
// So the default is "/bin/login".

#ifdef CONSOLE_USE_SHELL_INSTEAD_LOGIN
const char *ConsoleCmd="/bin/sh";
#else
const char *ConsoleCmd="/bin/login";
#endif


cConsConsoles::cConsConsoles()
: _inputActive   (0),
  _terminate     (false),
  _openConsoleNr (-1),
  _selectConsoleNr (-1)
{
  // The output handler thread should also react on this signal ...
  _wait.Add(&_consolesChanged);
}



cConsConsoles::~cConsConsoles() {

  // tell the thread to exit
  _terminate = true;
  _consolesChanged.Signal();

  if (_inputActive > 0) {
    _inputActive = 1;
    deactivateInputForTerminal();
  }

  // wait until thread has terminated
  Cancel(500);
  dsyslog("console: engine destructed");
}



bool cConsConsoles::Start() {
  return cThread::Start();
}



void cConsConsoles::activateInputForTerminal() {

  if (_inputActive++ == 0) {

    // prepare STDIN for Console
    termios t;
    tcgetattr(STDIN_FILENO, &t);

    // save settings to reset on exit
    _oldTerminalSettings = t;

    // change the settings for virtual console mode
    t.c_lflag &= ~(ICANON | ISIG | ECHO | ECHOCTL | ECHOE | ECHOK | ECHOKE | ECHONL | ECHOPRT);
    t.c_iflag |= IGNBRK;
    t.c_iflag &= ~(ICRNL | INLCR | ISTRIP | BRKINT);

    t.c_cflag &= ~(CSIZE);
    t.c_cflag |= (CS8);

    t.c_cc[VMIN] = 0;
    t.c_cc[VTIME] = 0;
    tcsetattr(STDIN_FILENO, TCSANOW, &t);

    Flush(200);
  }
}



void cConsConsoles::deactivateInputForTerminal() {

  if (--_inputActive == 0) {

    // resetting console
    tcsetattr(STDIN_FILENO, TCSANOW, &_oldTerminalSettings);

    Flush(200);
  }
}



void cConsConsoles::WantAllRefreshEvents(bool WantIt) {

  LOCK_THREAD;

  for (int i = _consoles.Count() - 1; i >= 0; --i)
    _consoles.Get(i)->getScreen().WantRefreshEvent(WantIt);
}



void cConsConsoles::Flush(int WaitMs) {

  for (;;) {

    cPoller poll(STDIN_FILENO);
    if (poll.Poll(WaitMs)) {

      char buf[20];
      read(STDIN_FILENO, buf, sizeof(buf));
    } else
      break;
  }
}



int cConsConsoles::CreateConsole() {

  char* const args[] = { (char*)ConsoleCmd, NULL };

  cConsVirtualConsole* p = new cConsVirtualConsole( tr("Console"), ConsoleCmd, args );

  if (p) {

    LOCK_THREAD;

    _consoles.Add(p);
    _wait.Add(p);
    _consolesChanged.Signal();
    return _consoles.Count() - 1;
  }

  return -1;
}



int cConsConsoles::CreateCommand(const char* title, const char* command) {

  char* const args[] = {"sh", "-c", (char*)command, NULL};

  cConsVirtualConsole* p = new cConsVirtualConsole(title, "/bin/sh", args);

  if (p) {

    LOCK_THREAD;

    _consoles.Add(p);
    _wait.Add(p);
    _consolesChanged.Signal();
    return _consoles.Count() - 1;
  }

  return -1;
}



void cConsConsoles::Remove(cConsVirtualConsole* pObject) {

  LOCK_THREAD;

  _wait.Remove(pObject);
  _consoles.Del(pObject);
  _consolesChanged.Signal();
}



// Return true if a console is open
bool cConsConsoles::Active() {

  cThreadLock l(gl_pConsConsoles);

  for (int i = gl_pConsConsoles->getCount() - 1; i >= 0; --i) {
    if (gl_pConsConsoles->getItem(i).IsOpen())
      return true;
  }

  // no console is open
  return false;
}



void cConsConsoles::Action() {

  isyslog("console: output handler thread startet (pid=%d)", getpid());

  for(;;) {

    if (_wait.Wait(-1)) {  // wait infinite


      // This is to awaik the loop if the list has
      // changed and new consoles are to be watched
      // or old consoles are no more to be watched.

      if (_consolesChanged.IsSignalled()) {
        // Have to reset the signal manually
        _consolesChanged.Reset();
      }

      if (_terminate)
        break;

      LOCK_THREAD;

      for (int i = _consoles.Count() - 1; i >= 0; --i) {

        cConsVirtualConsole* pConsole = _consoles.Get(i);

        if (_wait.IsSignalled(pConsole))
          if (! pConsole->HandleOutput())
            _wait.Remove(pConsole);

        // make a fast shutdown possible
        if (_terminate)
          break;
      }
    }

    if (_terminate)
      break;
  }

  isyslog("console: output handler thread stopped");
}


