lyx_mirror/development/lyxserver/server_monitor.cpp

377 lines
10 KiB
C++
Raw Permalink Normal View History

/**
* \file server_monitor.cpp
* This file is part of LyX, the document processor.
* Licence details can be found in the file COPYING.
*
* \author Enrico Forestieri
*
* Full author contact details are available in file CREDITS.
*
* This program sends commands to a running instance of LyX and
* receives information back from LyX.
*
* Build instructions:
* 1) Run moc or moc-qt5 on server_monitor.h to produce moc_server_monitor.cpp:
* moc server_monitor.h -o moc_server_monitor.cpp
* 2) If the QtGui.pc file is not in the pkg-config search path, find the
* directory where it is located (e.g., use the command `locate QtGui.pc')
* and set the environment variable PKG_CONFIG_PATH to this directory.
* For example:
* export PKG_CONFIG_PATH=/path/to/directory (if using bash)
* setenv PKG_CONFIG_PATH /path/to/directory (if using tcsh)
* If the command `pkg-config --modversion QtGui' does not complain and
* prints the Qt version, you don't need to set PKG_CONFIG_PATH.
* 3) Compile using the following command:
* g++ -fPIC server_monitor.cpp -o monitor -I. `pkg-config --cflags --libs Qt5Gui Qt5Widgets`
*
* Alternatively, you can create a Makefile with qmake and then build
* the executable by running make (or nmake, if you use msvc):
* qmake
* make
*
* Usage:
* 1) Set the LyXserver pipe path in the LyX preferences (on *nix you can use
* any path, for example ~/.lyx/lyxpipe, whereas on Windows the path has
* to start with `\\.\pipe\', for example you can use \\.\pipe\lyxpipe).
* 2) Quit and restart LyX.
* 3) Launch this program, adjust the pipe name to match that one used in LyX,
* push the button labeled "Open pipes" and then try issuing some commands.
*/
#include <QApplication>
#include <QtGui>
#include <QtDebug>
#include <QtWidgets>
#include "server_monitor.h"
LyXServerMonitor::LyXServerMonitor()
: pipein(-1), pipeout(-1), thread_exit(false), lyx_listen(false)
{
createGridGroupBox();
createCmdsGroupBox();
char const * const home = getenv("HOME");
QString const pipeName = (home && home[0]) ?
QString::fromUtf8(home) + "/.lyx/lyxpipe" : "\\\\.\\pipe\\lyxpipe";
pipeNameLE->setText(pipeName);
clientNameLE->setText("monitor");
submitCommandPB->setDisabled(true);
closePipesPB->setDisabled(true);
connect(openPipesPB, SIGNAL(clicked()), this, SLOT(openPipes()));
connect(closePipesPB, SIGNAL(clicked()), this, SLOT(closePipes()));
connect(submitCommandPB, SIGNAL(clicked()), this, SLOT(submitCommand()));
connect(donePB, SIGNAL(clicked()), this, SLOT(reject()));
QVBoxLayout * mainLayout = new QVBoxLayout;
mainLayout->addWidget(gridGB);
mainLayout->addWidget(horizontalGB);
setLayout(mainLayout);
setWindowTitle("LyX Server Monitor");
}
LyXServerMonitor::~LyXServerMonitor()
{
if (pipein != -1)
closePipes();
}
void LyXServerMonitor::createGridGroupBox()
{
gridGB = new QGroupBox;
QGridLayout * layout = new QGridLayout;
labels[0] = new QLabel("Pipe name");
pipeNameLE = new QLineEdit;
layout->addWidget(labels[0], 0, 0, Qt::AlignRight);
layout->addWidget(pipeNameLE, 0, 1);
labels[1] = new QLabel("Command");
commandLE = new QLineEdit;
layout->addWidget(labels[1], 1, 0, Qt::AlignRight);
layout->addWidget(commandLE, 1, 1);
labels[2] = new QLabel("Client name");
clientNameLE = new QLineEdit;
layout->addWidget(labels[2], 0, 2, Qt::AlignRight);
layout->addWidget(clientNameLE, 0, 3);
labels[3] = new QLabel("Argument");
argumentLE = new QLineEdit;
layout->addWidget(labels[3], 1, 2, Qt::AlignRight);
layout->addWidget(argumentLE, 1, 3);
labels[4] = new QLabel("Info");
infoLB = new QLabel;
infoLB->setFrameStyle(QFrame::Panel | QFrame::Sunken);
infoLB->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
layout->addWidget(labels[4], 2, 0, Qt::AlignRight);
layout->addWidget(infoLB, 2, 1, 1, 3);
labels[5] = new QLabel("Notify");
notifyLB = new QLabel;
notifyLB->setFrameStyle(QFrame::Panel | QFrame::Sunken);
notifyLB->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
layout->addWidget(labels[5], 3, 0, Qt::AlignRight);
layout->addWidget(notifyLB, 3, 1, 1, 3);
layout->setColumnMinimumWidth(1, 200);
layout->setColumnMinimumWidth(3, 200);
gridGB->setLayout(layout);
}
void LyXServerMonitor::createCmdsGroupBox()
{
horizontalGB = new QGroupBox;
QHBoxLayout * layout = new QHBoxLayout;
openPipesPB = new QPushButton("&Open pipes");
layout->addWidget(openPipesPB);
closePipesPB = new QPushButton("C&lose pipes");
layout->addWidget(closePipesPB);
submitCommandPB = new QPushButton("&Submit Command");
layout->addWidget(submitCommandPB);
donePB = new QPushButton("&Done");
layout->addWidget(donePB);
horizontalGB->setLayout(layout);
}
void LyXServerMonitor::readPipe()
{
int n;
errno = 0;
bool notified = false;
while ((n = ::read(pipeout, pipedata, BUFSIZE - 1)) && !thread_exit) {
if (n > 0) {
pipedata[n] = 0;
QString const fromLyX =
QString::fromUtf8(pipedata).trimmed();
qWarning() << "monitor: Coming: " << fromLyX;
if (fromLyX.startsWith("LYXSRV:")) {
if (fromLyX.contains("bye")) {
qWarning() << "monitor: LyX has closed "
"connection!";
pipethread->emitNotice(fromLyX);
notified = true;
break;
}
if (fromLyX.contains("hello")) {
lyx_listen = true;
qWarning() << "monitor: "
"LyX is listening!";
submitCommandPB->setDisabled(false);
}
}
if (fromLyX[0] == QLatin1Char('I'))
pipethread->emitInfo(fromLyX);
else
pipethread->emitNotice(fromLyX);
#ifdef _WIN32
// On Windows, we have to close and reopen
// the pipe after each use.
::close(pipeout);
pipeout = ::open(
outPipeName().toLocal8Bit().constData(),
O_RDONLY);
if (pipeout < 0) {
perror("monitor");
pipethread->emitNotice("An error occurred, "
"closing pipes");
notified = true;
break;
}
#endif
} else if (n < 0) {
#ifdef __CYGWIN__
if (errno == ECOMM) {
// When talking to a native Windows version of
// LyX, the second time we try to use the pipe,
// read() fails with ECOMM. In this case, we
// have to simply close and reopen it.
::close(pipeout);
pipeout = ::open(
outPipeName().toLocal8Bit().constData(),
O_RDONLY);
if (pipeout >= 0)
continue;
}
#endif
perror("monitor");
pipethread->emitNotice("An error occurred, closing pipes");
notified = true;
break;
} else
break;
}
if (!notified) {
if (thread_exit) {
qWarning() << "monitor: Closing pipes";
pipethread->emitNotice("Closing pipes");
} else {
qWarning() << "monitor: LyX has closed connection!";
pipethread->emitNotice("LyX has closed connection!");
}
}
QEvent * event = new QEvent(QEvent::User);
QCoreApplication::postEvent(this, event);
lyx_listen = false;
if (!thread_exit)
pipethread->emitClosing();
}
bool LyXServerMonitor::event(QEvent * e)
{
if (e->type() == QEvent::User) {
pipethread->wait();
thread_exit = false;
delete pipethread;
return true;
}
return QDialog::event(e);
}
void LyXServerMonitor::showInfo(QString const & msg)
{
infoLB->setText(msg);
notifyLB->clear();
}
void LyXServerMonitor::showNotice(QString const & msg)
{
infoLB->clear();
notifyLB->setText(msg);
}
void LyXServerMonitor::openPipes()
{
if (pipein == -1) {
qWarning() << "monitor: Opening pipes " << inPipeName()
<< " and " << outPipeName();
pipein = ::open(inPipeName().toLocal8Bit().constData(),
O_WRONLY);
pipeout = ::open(outPipeName().toLocal8Bit().constData(),
O_RDONLY);
if (pipein < 0 || pipeout < 0) {
qWarning() << "monitor: Could not open the pipes";
infoLB->clear();
notifyLB->setText("Could not open the pipes");
if (pipein >= 0 || pipeout >= 0)
closePipes();
return;
}
pipethread = new ReadPipe(this);
pipethread->start();
if (!pipethread->isRunning()) {
qWarning() << "monitor: Could not create pipe thread";
infoLB->clear();
notifyLB->setText("Could not create pipe thread");
closePipes();
return;
}
connect(pipethread, SIGNAL(info(QString const &)),
this, SLOT(showInfo(QString const &)));
connect(pipethread, SIGNAL(notice(QString const &)),
this, SLOT(showNotice(QString const &)));
connect(pipethread, SIGNAL(closing()),
this, SLOT(closePipes()));
openPipesPB->setDisabled(true);
closePipesPB->setDisabled(false);
// greet LyX
QString const clientname = clientNameLE->text();
snprintf(buffer, BUFSIZE - 1,
"LYXSRV:%s:hello\n", clientname.toUtf8().constData());
buffer[BUFSIZE - 1] = '\0';
::write(pipein, buffer, strlen(buffer));
} else
qWarning() << "monitor: Pipes already opened, close them first\n";
}
void LyXServerMonitor::closePipes()
{
if (pipein == -1 && pipeout == -1) {
qWarning() << "monitor: Pipes are not opened";
return;
}
if (pipein >= 0) {
if (lyx_listen) {
lyx_listen = false;
QString const clientname = clientNameLE->text();
if (pipethread->isRunning()) {
thread_exit = true;
// The thread, currently blocked on the read()
// call, will be waked up by the reply from
// LyX and will exit.
snprintf(buffer, BUFSIZE - 1,
"LYXCMD:%s:message:Client '%s' is leaving\n",
clientname.toUtf8().constData(),
clientname.toUtf8().constData());
buffer[BUFSIZE - 1] = '\0';
::write(pipein, buffer, strlen(buffer));
}
// Say goodbye
snprintf(buffer, BUFSIZE - 1, "LYXSRV:%s:bye\n",
clientname.toUtf8().constData());
buffer[BUFSIZE - 1] = '\0';
::write(pipein, buffer, strlen(buffer));
pipethread->wait();
thread_exit = false;
}
::close(pipein);
}
if (pipeout >= 0)
::close(pipeout);
pipein = pipeout = -1;
submitCommandPB->setDisabled(true);
openPipesPB->setDisabled(false);
closePipesPB->setDisabled(true);
}
void LyXServerMonitor::submitCommand()
{
if (pipein >= 0) {
QString const command = commandLE->text();
QString const argument = argumentLE->text();
QString const clientname = clientNameLE->text();
snprintf(buffer, BUFSIZE - 2, "LYXCMD:%s:%s:%s",
clientname.toUtf8().constData(),
command.toUtf8().constData(),
argument.toUtf8().constData());
buffer[BUFSIZE - 1] = '\0';
qWarning() << "monitor: Sending: " << buffer;
strcat(buffer, "\n");
::write(pipein, buffer, strlen(buffer));
} else
qWarning() << "monitor: Pipe is not opened";
}
int main(int argc, char * argv[])
{
QApplication app(argc, argv);
LyXServerMonitor dialog;
return dialog.exec();
}
#include "moc_server_monitor.cpp"