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