/*
 * Copyright Johannes Sixt
 * This file is licensed under the GNU General Public License Version 2.
 * See the file COPYING in the toplevel directory of the source directory.
 */

#include <tqsocketnotifier.h>
#include <tqpopupmenu.h>
#include "ttywnd.h"
#include <tdeglobalsettings.h>
#include <tdelocale.h>

#include "config.h"
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>			/* open, close, etc. */
#endif
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_PTY_H
#include <pty.h>			/* openpty on Linux */
#endif
#ifdef HAVE_LIBUTIL_H
#include <libutil.h>			/* openpty on FreeBSD */
#endif
#ifdef HAVE_UTIL_H			/* openpty on NetBSD, OpenBSD */
#include <util.h>
#endif
#include <errno.h>

#include "mydebug.h"


STTY::STTY() :
	TQObject(),
	m_masterfd(-1),
	m_slavefd(-1),
	m_outNotifier(0)
{
    if (findTTY())
    {
	::fcntl(m_masterfd, F_SETFL, O_NDELAY);
	m_outNotifier = new TQSocketNotifier(m_masterfd, TQSocketNotifier::Read);
	connect(m_outNotifier, TQ_SIGNAL(activated(int)), TQ_SLOT(outReceived(int)));
    } else {
	m_slavetty = TQString();
    }
}

STTY::~STTY()
{
    if (m_outNotifier) {
	::close(m_masterfd);
	if (m_slavefd >= 0)
	    ::close(m_slavefd);
	delete m_outNotifier;
    }
}

bool STTY::findTTY()
{
    m_masterfd  = -1;

#ifdef HAVE_FUNC_OPENPTY
    /* use glibc2's openpty */
    if (m_masterfd < 0)
    {
	if (::openpty(&m_masterfd, &m_slavefd, 0, 0, 0) == 0) {
	    const char* tname = ::ttyname(m_slavefd);
	    if (tname != 0) {
		m_slavetty = tname;
	    } else {
		::close(m_slavefd);
		::close(m_masterfd);
		m_masterfd = m_slavefd = -1;
	    }
	}
    }
#endif

    // resort to BSD-style terminals
    if (m_masterfd < 0)
    {
	const char* s3;
	const char* s4;

	char ptynam[] = "/dev/ptyxx";
	char ttynam[] = "/dev/ttyxx";
	static const char ptyc3[] = "pqrstuvwxyzabcde";
	static const char ptyc4[] = "0123456789abcdef";

	// Find a master pty that we can open
	for (s3 = ptyc3; *s3 != 0 && m_masterfd < 0; s3++)
	{
	    for (s4 = ptyc4; *s4 != 0; s4++)
	    {
		ptynam[8] = ttynam[8] = *s3;
		ptynam[9] = ttynam[9] = *s4;
		if ((m_masterfd = ::open(ptynam,O_RDWR)) >= 0)
		{
		    if (::geteuid() == 0 || ::access(ttynam,R_OK|W_OK) == 0)
		    {
			m_slavetty = ttynam;
			break;
		    }
		    ::close(m_masterfd);
		    m_masterfd = -1;
		}
	    }
	}
    }

    return m_masterfd >= 0;
}

void STTY::outReceived(int f)
{
    for (;;) {
	char buf[1024];
	int n = ::read(f, buf, sizeof(buf));
	if (n < 0) {
	    if (errno != EAGAIN) {	/* this is not an error */
		// ugh! error! somebody disconnect this signal please!
	    }
	    break;
	}
	emit output(buf, n);
	if (n == 0)
	    break;
    }
}



TTYWindow::TTYWindow(TQWidget* parent, const char* name) :
	TQTextEdit(parent, name),
	m_tty(0),
	m_hPos(0)
{
    setFont(TDEGlobalSettings::fixedFont());
    setReadOnly(true);
    setAutoFormatting(AutoNone);
    setTextFormat(PlainText);
    setWordWrap(NoWrap);
}

TTYWindow::~TTYWindow()
{
    if (m_tty)
	deactivate();
}


TQString TTYWindow::activate()
{
    // allocate a pseudo terminal
    m_tty = new STTY;

    TQString ttyName = m_tty->slaveTTY();
    if (ttyName.isEmpty()) {
	// failed to allocate terminal
	delete m_tty;
	m_tty = 0;
	return TQString();
    } else {
	connect(m_tty, TQ_SIGNAL(output(char*,int)), TQ_SLOT(slotAppend(char*,int)));
	return ttyName;
    }
}

void TTYWindow::deactivate()
{
    delete m_tty;
    m_tty = 0;
}

/**
 * Note that it is necessary to track the horizontal position explicitly
 * since if the user modifies the selected text in the window, the cursor
 * position changes, too.
 */
void TTYWindow::slotAppend(char* buffer, int count)
{
    // parse off lines
    char* start = buffer;
    while (count > 0) {
	int len = 0;
	while (count > 0 && start[len] != '\n' && start[len] != '\r') {
	    --count;
	    ++len;
	}
	if (len > 0) {
	    TQString str = TQString::fromLatin1(start, len);
	    // replace text in the last line
	    int para = paragraphs()-1;
	    // this selection is non-empty only after a '\r' that was not
	    // followed by a '\n'
	    setSelection(para, m_hPos, para, m_hPos+len, 1);
	    removeSelectedText(1);
	    insertAt(str, para, m_hPos);
	    m_hPos += len;
	    start += len;
	    len = 0;
	}
	if (count > 0 && *start == '\r') {
	    ++start;
	    --count;
	    m_hPos = 0;
	}
	if (count > 0 && *start == '\n') {
	    ++start;
	    --count;
	    append(TQString());
	    m_hPos = 0;
	}
    }
}

TQPopupMenu* TTYWindow::createPopupMenu(const TQPoint& pos)
{
    TQPopupMenu* menu = TQTextEdit::createPopupMenu(pos);
    menu->insertSeparator();
    menu->insertItem(i18n("&Clear"), this, TQ_SLOT(slotClear()));
    return menu;
}

void TTYWindow::slotClear()
{
    clear();
    m_hPos = 0;
}

#include "ttywnd.moc"
