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