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