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