]> git.lyx.org Git - features.git/blob - development/lyxserver/server_monitor.cpp
Add a Qt version of the LyX server monitor program.
[features.git] / development / lyxserver / server_monitor.cpp
1 /**
2  * \file server_monitor.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Enrico Forestieri
7  *
8  * Full author contact details are available in file CREDITS.
9  *
10  * This program sends commands to a running instance of LyX and
11  * receives information back from LyX.
12  *
13  * Build instructions:
14  * 1) Run moc or moc-qt4 on server_monitor.h to produce moc_server_monitor.cpp:
15  *    moc-qt4 server_monitor.h -o moc_server_monitor.cpp
16  * 2) If the QtGui.pc file is not in the pkg-config search path, find the
17  *    directory where it is located (e.g., use the command `locate QtGui.pc')
18  *    and set the environment variable PKG_CONFIG_PATH to this directory.
19  *    For example:
20  *      export PKG_CONFIG_PATH=/path/to/directory    (if using bash)
21  *      setenv PKG_CONFIG_PATH /path/to/directory    (if using tcsh)
22  *    If the command `pkg-config --modversion QtGui' does not complain and
23  *    prints the Qt version, you don't need to set PKG_CONFIG_PATH.
24  * 3) Compile using the following command:
25  *    g++ server_monitor.cpp -o monitor -I. `pkg-config --cflags --libs QtGui`
26  *
27  * Usage:
28  * 1) Set the LyXserver pipe path in the LyX preferences (on *nix you can use
29  *    any path, for example ~/.lyx/lyxpipe, whereas on Windows the path has
30  *    to start with `\\.\pipe\', for example you can use \\.\pipe\lyxpipe).
31  * 2) Quit and restart LyX.
32  * 3) Launch this program, adjust the pipe name to match that one used in LyX,
33  *    push the button labeled "Open pipes" and then try issuing some commands.
34  */
35
36 #include "server_monitor.h"
37
38 #include <QApplication>
39 #include <QtGui>
40 #include <QtDebug>
41
42 class ReadPipe : public QThread {
43 public:
44         ///
45         ReadPipe(LyXServerMonitor * monitor) : lyxmonitor(monitor) {}
46         ///
47         void run() { lyxmonitor->readPipe(); }
48
49 private:
50         ///
51         LyXServerMonitor * lyxmonitor;
52 };
53
54
55 class DeleteThread : public QEvent {
56 public:
57         ///
58         DeleteThread(ReadPipe * thread)
59         : QEvent(QEvent::User), pipethread(thread)
60         {}
61         ///
62         ReadPipe * pipeThread() const { return pipethread; }
63
64 private:
65         ///
66         ReadPipe * pipethread;
67 };
68
69
70 LyXServerMonitor::LyXServerMonitor()
71         : pipein(-1), pipeout(-1), thread_exit(false), lyx_listen(false)
72 {
73         createGridGroupBox();
74         createCmdsGroupBox();
75
76         char const * const home = getenv("HOME");
77         QString const pipeName = (home && home[0]) ?
78             QString::fromUtf8(home) + "/.lyx/lyxpipe" : "\\\\.\\pipe\\lyxpipe";
79
80         pipeNameLE->setText(pipeName);
81         clientNameLE->setText("monitor");
82         submitCommandPB->setDisabled(true);
83         closePipesPB->setDisabled(true);
84
85         connect(openPipesPB, SIGNAL(clicked()), this, SLOT(openPipes()));
86         connect(closePipesPB, SIGNAL(clicked()), this, SLOT(closePipes()));
87         connect(submitCommandPB, SIGNAL(clicked()), this, SLOT(submitCommand()));
88         connect(donePB, SIGNAL(clicked()), this, SLOT(reject()));
89
90         QVBoxLayout *mainLayout = new QVBoxLayout;
91         mainLayout->addWidget(gridGB);
92         mainLayout->addWidget(horizontalGB);
93         setLayout(mainLayout);
94
95         setWindowTitle("LyX Server Monitor");
96 }
97
98
99 LyXServerMonitor::~LyXServerMonitor()
100 {
101         if (pipein != -1)
102                 closePipes();
103 }
104
105
106 void LyXServerMonitor::createGridGroupBox()
107 {
108         gridGB = new QGroupBox;
109         QGridLayout *layout = new QGridLayout;
110
111         labels[0] = new QLabel("Pipe name");
112         pipeNameLE = new QLineEdit;
113         layout->addWidget(labels[0], 0, 0, Qt::AlignRight);
114         layout->addWidget(pipeNameLE, 0, 1);
115
116         labels[1] = new QLabel("Command");
117         commandLE = new QLineEdit;
118         layout->addWidget(labels[1], 1, 0, Qt::AlignRight);
119         layout->addWidget(commandLE, 1, 1);
120
121         labels[2] = new QLabel("Client name");
122         clientNameLE = new QLineEdit;
123         layout->addWidget(labels[2], 0, 2, Qt::AlignRight);
124         layout->addWidget(clientNameLE, 0, 3);
125
126         labels[3] = new QLabel("Argument");
127         argumentLE = new QLineEdit;
128         layout->addWidget(labels[3], 1, 2, Qt::AlignRight);
129         layout->addWidget(argumentLE, 1, 3);
130
131         labels[4] = new QLabel("Info");
132         infoLB = new QLabel;
133         infoLB->setFrameStyle(QFrame::Panel | QFrame::Sunken);
134         infoLB->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
135         layout->addWidget(labels[4], 2, 0, Qt::AlignRight);
136         layout->addWidget(infoLB, 2, 1, 1, 3);
137
138         labels[5] = new QLabel("Notify");
139         notifyLB = new QLabel;
140         notifyLB->setFrameStyle(QFrame::Panel | QFrame::Sunken);
141         notifyLB->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
142         layout->addWidget(labels[5], 3, 0, Qt::AlignRight);
143         layout->addWidget(notifyLB, 3, 1, 1, 3);
144
145         layout->setColumnMinimumWidth(1, 200);
146         layout->setColumnMinimumWidth(3, 200);
147         gridGB->setLayout(layout);
148 }
149
150
151 void LyXServerMonitor::createCmdsGroupBox()
152 {
153         horizontalGB = new QGroupBox;
154         QHBoxLayout *layout = new QHBoxLayout;
155
156         openPipesPB = new QPushButton("&Open pipes");
157         layout->addWidget(openPipesPB);
158
159         closePipesPB = new QPushButton("C&lose pipes");
160         layout->addWidget(closePipesPB);
161
162         submitCommandPB = new QPushButton("&Submit Command");
163         layout->addWidget(submitCommandPB);
164
165         donePB = new QPushButton("&Done");
166         layout->addWidget(donePB);
167
168         horizontalGB->setLayout(layout);
169 }
170
171
172 void LyXServerMonitor::readPipe()
173 {
174         int n;
175         errno = 0;
176         bool notified = false;
177
178         while ((n = ::read(pipeout, pipedata, BUFSIZE - 1)) && !thread_exit) {
179                 if (n > 0) {
180                         pipedata[n] = 0;
181                         QString const fromLyX =
182                                 QString::fromUtf8(pipedata).trimmed();
183                         qWarning() << "monitor: Coming: " << fromLyX;
184                         if (fromLyX.startsWith("LYXSRV:")) {
185                                 if (fromLyX.contains("bye")) {
186                                         qWarning() << "monitor: LyX has closed "
187                                                       "connection!";
188                                         infoLB->clear();
189                                         notifyLB->setText(fromLyX);
190                                         notified = true;
191                                         break;
192                                 }
193                                 if (fromLyX.contains("hello")) {
194                                         lyx_listen = true;
195                                         qWarning() << "monitor: "
196                                                       "LyX is listening!";
197                                         submitCommandPB->setDisabled(false);
198                                 }
199                         }
200                         if (fromLyX[0] == QLatin1Char('I')) {
201                                 infoLB->setText(fromLyX);
202                                 notifyLB->clear();
203                         } else {
204                                 infoLB->clear();
205                                 notifyLB->setText(fromLyX);
206                         }
207 #ifdef _WIN32
208                         // On Windows, we have to close and reopen
209                         // the pipe after each use.
210                         ::close(pipeout);
211                         pipeout = ::open(
212                                 outPipeName().toLocal8Bit().constData(),
213                                 O_RDONLY);
214                         if (pipeout < 0) {
215                                 perror("monitor");
216                                 infoLB->clear();
217                                 notifyLB->setText("An error occurred, "
218                                                   "closing pipes");
219                                 notified = true;
220                                 break;
221                         }
222 #endif
223                 } else if (n < 0) {
224 #ifdef __CYGWIN__
225                         if (errno == ECOMM) {
226                                 // When talking to a native Windows version of
227                                 // LyX, the second time we try to use the pipe,
228                                 // read() fails with ECOMM. In this case, we
229                                 // have to simply close and reopen it.
230                                 ::close(pipeout);
231                                 pipeout = ::open(
232                                         outPipeName().toLocal8Bit().constData(),
233                                         O_RDONLY);
234                                 if (pipeout >= 0)
235                                         continue;
236                         }
237 #endif
238                         perror("monitor");
239                         infoLB->clear();
240                         notifyLB->setText("An error occurred, closing pipes");
241                         notified = true;
242                         break;
243                 } else
244                         break;
245         }
246
247         if (!notified) {
248                 if (thread_exit) {
249                         qWarning() << "monitor: Closing pipes";
250                         infoLB->clear();
251                         notifyLB->setText("Closing pipes");
252                 } else {
253                         qWarning() << "monitor: LyX has closed connection!";
254                         infoLB->clear();
255                         notifyLB->setText("LyX has closed connection!");
256                 }
257         }
258         DeleteThread * event = new DeleteThread(pipethread);
259         QCoreApplication::postEvent(this, static_cast<QEvent *>(event));
260         lyx_listen = false;
261         closePipes();
262 }
263
264
265 bool LyXServerMonitor::event(QEvent * e)
266 {
267         if (e->type() == QEvent::User) {
268                 ReadPipe * pipeThread =
269                         static_cast<DeleteThread *>(e)->pipeThread();
270                 pipeThread->wait();
271                 thread_exit = false;
272                 delete pipeThread;
273                 return true;
274         }
275         return QDialog::event(e);
276 }
277
278
279 void LyXServerMonitor::openPipes()
280 {
281         if (pipein == -1) {
282                 qWarning() << "monitor: Opening pipes " << inPipeName()
283                            << " and " << outPipeName();
284                 pipein = ::open(inPipeName().toLocal8Bit().constData(),
285                                 O_WRONLY);
286                 pipeout = ::open(outPipeName().toLocal8Bit().constData(),
287                                  O_RDONLY);
288                 if (pipein < 0 || pipeout < 0) {
289                         qWarning() << "monitor: Could not open the pipes";
290                         infoLB->clear();
291                         notifyLB->setText("Could not open the pipes");
292                         if (pipein >= 0 || pipeout >= 0)
293                                 closePipes();
294                         return;
295                 }
296                 pipethread = new ReadPipe(this);
297                 pipethread->start();
298                 if (!pipethread->isRunning()) {
299                         qWarning() << "monitor: Could not create pipe thread";
300                         infoLB->clear();
301                         notifyLB->setText("Could not create pipe thread");
302                         closePipes();
303                         return;
304                 }
305                 openPipesPB->setDisabled(true);
306                 closePipesPB->setDisabled(false);
307                 // greet LyX
308                 QString const clientname = clientNameLE->text();
309                 snprintf(buffer, BUFSIZE - 1,
310                         "LYXSRV:%s:hello\n", clientname.toUtf8().constData());
311                 buffer[BUFSIZE - 1] = '\0';
312                 ::write(pipein, buffer, strlen(buffer));
313         } else
314                 qWarning() << "monitor: Pipes already opened, close them first\n";
315 }
316
317
318 void LyXServerMonitor::closePipes()
319 {
320         if (pipein == -1 && pipeout == -1) {
321                 qWarning() << "monitor: Pipes are not opened";
322                 return;
323         }
324
325         if (pipein >= 0) {
326                 if (lyx_listen) {
327                         lyx_listen = false;
328                         QString const clientname = clientNameLE->text();
329                         if (pipethread->isRunning()) {
330                                 thread_exit = true;
331                                 // The thread, currently blocked on the read()
332                                 // call, will be waked up by the reply from
333                                 // LyX and will exit.
334                                 snprintf(buffer, BUFSIZE - 1,
335                                         "LYXCMD:%s:message:Client '%s' is leaving\n",
336                                         clientname.toUtf8().constData(),
337                                         clientname.toUtf8().constData());
338                                 buffer[BUFSIZE - 1] = '\0';
339                                 ::write(pipein, buffer, strlen(buffer));
340                         }
341                         /* Say goodbye */
342                         snprintf(buffer, BUFSIZE - 1, "LYXSRV:%s:bye\n",
343                                  clientname.toUtf8().constData());
344                         buffer[BUFSIZE - 1] = '\0';
345                         ::write(pipein, buffer, strlen(buffer));
346                         pipethread->wait();
347                         thread_exit = false;
348                 }
349                 ::close(pipein);
350         }
351
352         if (pipeout >= 0)
353                 ::close(pipeout);
354         pipein = pipeout = -1;
355         submitCommandPB->setDisabled(true);
356         openPipesPB->setDisabled(false);
357         closePipesPB->setDisabled(true);
358 }
359
360
361 void LyXServerMonitor::submitCommand()
362 {
363         if (pipein >= 0) {
364                 QString const command = commandLE->text();
365                 QString const argument = argumentLE->text();
366                 QString const clientname = clientNameLE->text();
367                 snprintf(buffer, BUFSIZE - 2, "LYXCMD:%s:%s:%s",
368                          clientname.toUtf8().constData(),
369                          command.toUtf8().constData(),
370                          argument.toUtf8().constData());
371                 buffer[BUFSIZE - 1] = '\0';
372                 qWarning() << "monitor: Sending: " << buffer;
373                 strcat(buffer, "\n");
374                 ::write(pipein, buffer, strlen(buffer));
375         } else
376                 qWarning() << "monitor: Pipe is not opened";
377 }
378
379
380 int main(int argc, char *argv[])
381 {
382         QApplication app(argc, argv);
383         LyXServerMonitor dialog;
384         return dialog.exec();
385 }
386
387 #include "moc_server_monitor.cpp"