From f20d6d1eabae70039dc78772b85973b62d5e1bdc Mon Sep 17 00:00:00 2001 From: Enrico Forestieri Date: Sat, 12 Sep 2009 00:15:00 +0000 Subject: [PATCH] Add a Qt version of the LyX server monitor program. I tried to account for msvc based on the MS docs, but I cannot test it. Apologies if it does not compile out of the box. It works with mingw. git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@31372 a592a061-630c-0410-9148-cb99ea01b6c8 --- development/lyxserver/server_monitor.cpp | 387 +++++++++++++++++++++++ development/lyxserver/server_monitor.h | 90 ++++++ 2 files changed, 477 insertions(+) create mode 100644 development/lyxserver/server_monitor.cpp create mode 100644 development/lyxserver/server_monitor.h diff --git a/development/lyxserver/server_monitor.cpp b/development/lyxserver/server_monitor.cpp new file mode 100644 index 0000000000..36dbb24de9 --- /dev/null +++ b/development/lyxserver/server_monitor.cpp @@ -0,0 +1,387 @@ +/** + * \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-qt4 on server_monitor.h to produce moc_server_monitor.cpp: + * moc-qt4 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++ server_monitor.cpp -o monitor -I. `pkg-config --cflags --libs QtGui` + * + * 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 "server_monitor.h" + +#include +#include +#include + +class ReadPipe : public QThread { +public: + /// + ReadPipe(LyXServerMonitor * monitor) : lyxmonitor(monitor) {} + /// + void run() { lyxmonitor->readPipe(); } + +private: + /// + LyXServerMonitor * lyxmonitor; +}; + + +class DeleteThread : public QEvent { +public: + /// + DeleteThread(ReadPipe * thread) + : QEvent(QEvent::User), pipethread(thread) + {} + /// + ReadPipe * pipeThread() const { return pipethread; } + +private: + /// + ReadPipe * pipethread; +}; + + +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!"; + infoLB->clear(); + notifyLB->setText(fromLyX); + notified = true; + break; + } + if (fromLyX.contains("hello")) { + lyx_listen = true; + qWarning() << "monitor: " + "LyX is listening!"; + submitCommandPB->setDisabled(false); + } + } + if (fromLyX[0] == QLatin1Char('I')) { + infoLB->setText(fromLyX); + notifyLB->clear(); + } else { + infoLB->clear(); + notifyLB->setText(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"); + infoLB->clear(); + notifyLB->setText("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"); + infoLB->clear(); + notifyLB->setText("An error occurred, closing pipes"); + notified = true; + break; + } else + break; + } + + if (!notified) { + if (thread_exit) { + qWarning() << "monitor: Closing pipes"; + infoLB->clear(); + notifyLB->setText("Closing pipes"); + } else { + qWarning() << "monitor: LyX has closed connection!"; + infoLB->clear(); + notifyLB->setText("LyX has closed connection!"); + } + } + DeleteThread * event = new DeleteThread(pipethread); + QCoreApplication::postEvent(this, static_cast(event)); + lyx_listen = false; + closePipes(); +} + + +bool LyXServerMonitor::event(QEvent * e) +{ + if (e->type() == QEvent::User) { + ReadPipe * pipeThread = + static_cast(e)->pipeThread(); + pipeThread->wait(); + thread_exit = false; + delete pipeThread; + return true; + } + return QDialog::event(e); +} + + +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; + } + 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" diff --git a/development/lyxserver/server_monitor.h b/development/lyxserver/server_monitor.h new file mode 100644 index 0000000000..ade3614f89 --- /dev/null +++ b/development/lyxserver/server_monitor.h @@ -0,0 +1,90 @@ +/** + * \file server_monitor.h + * 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. + */ + +#ifndef SERVER_MONITOR_H +#define SERVER_MONITOR_H + +#include +#include + +#include +#include +#include + +#ifdef _WIN32 +#include +#ifdef _MSC_VER +#include +#define open _open +#define close _close +#define read _read +#define write _write +#define snprintf _snprintf +#define O_RDONLY _O_RDONLY +#define O_WRONLY _O_WRONLY +#endif +#endif + +class QGroupBox; +class QLabel; +class QPushButton; +class ReadPipe; + +class LyXServerMonitor : public QDialog +{ + Q_OBJECT + + enum { BUFSIZE = 512 }; + +public: + LyXServerMonitor(); + /// + ~LyXServerMonitor(); + /// + void readPipe(); + /// + QString inPipeName() { return pipeNameLE->text() + ".in"; } + /// + QString outPipeName() { return pipeNameLE->text() + ".out"; } + +public Q_SLOTS: + void openPipes(); + void closePipes(); + void submitCommand(); + +private: + void createCmdsGroupBox(); + void createGridGroupBox(); + bool event(QEvent *); + + QGroupBox * horizontalGB; + QGroupBox * gridGB; + QLabel * labels[6]; + QLineEdit * pipeNameLE; + QLineEdit * clientNameLE; + QLineEdit * commandLE; + QLineEdit * argumentLE; + QLabel * infoLB; + QLabel * notifyLB; + QPushButton * openPipesPB; + QPushButton * closePipesPB; + QPushButton * submitCommandPB; + QPushButton * donePB; + + int pipein; + int pipeout; + bool thread_exit; + bool lyx_listen; + char buffer[BUFSIZE]; + char pipedata[BUFSIZE]; + ReadPipe * pipethread; +}; + +#endif -- 2.39.2