MythTV  master
networkcontrol.cpp
Go to the documentation of this file.
1 
2 #include "networkcontrol.h"
3 
4 #include <chrono> // for milliseconds
5 #include <thread> // for sleep_for
6 
7 #include <QCoreApplication>
8 #include <QRegExp>
9 #include <QStringList>
10 #include <QTextStream>
11 #include <QDir>
12 #include <QKeyEvent>
13 #include <QEvent>
14 #include <QMap>
15 
16 #include "mythcorecontext.h"
17 #include "mythmiscutil.h"
18 #include "mythversion.h"
19 #include "programinfo.h"
20 #include "remoteutil.h"
21 #include "previewgenerator.h"
22 #include "compat.h"
23 #include "mythsystemevent.h"
24 #include "mythdirs.h"
25 #include "mythlogging.h"
26 
27 // libmythui
28 #include "mythmainwindow.h"
29 #include "mythuihelper.h"
30 #include "mythuigroup.h"
31 #include "mythuiclock.h"
32 #include "mythuishape.h"
33 #include "mythuibutton.h"
34 #include "mythuitextedit.h"
35 #include "mythuibuttontree.h"
36 #include "mythuivideo.h"
37 #include "mythuiguidegrid.h"
38 #include "mythuicheckbox.h"
39 #include "mythuispinbox.h"
40 
41 #if CONFIG_QTWEBKIT
42 #include "mythuiwebbrowser.h"
43 #endif
44 
45 #include "mythuiprogressbar.h"
46 #include "mythuiscrollbar.h"
47 #include "mythuieditbar.h"
48 #include "mythuiimage.h"
49 
50 #define LOC QString("NetworkControl: ")
51 #define LOC_ERR QString("NetworkControl Error: ")
52 
53 #define FE_SHORT_TO 2000
54 #define FE_LONG_TO 10000
55 
56 static QEvent::Type kNetworkControlDataReadyEvent =
57  (QEvent::Type) QEvent::registerEventType();
59  (QEvent::Type) QEvent::registerEventType();
60 
68 static bool is_abbrev(QString const& command,
69  QString const& test, int minchars = 1)
70 {
71  if (test.length() < minchars)
72  return command.toLower() == test.toLower();
73  return test.toLower() == command.left(test.length()).toLower();
74 }
75 
77  commandThread(new MThread("NetworkControl", this))
78 {
79  // Eventually this map should be in the jumppoints table
80  jumpMap["channelpriorities"] = "Channel Recording Priorities";
81  jumpMap["livetv"] = "Live TV";
82  jumpMap["mainmenu"] = "Main Menu";
83  jumpMap["managerecordings"] = "Manage Recordings / Fix Conflicts";
84  jumpMap["mythgallery"] = "MythGallery";
85  jumpMap["mythvideo"] = "Video Default";
86  jumpMap["mythweather"] = "MythWeather";
87  jumpMap["mythgame"] = "MythGame";
88  jumpMap["mythnews"] = "MythNews";
89  jumpMap["playdvd"] = "Play Disc";
90  jumpMap["playmusic"] = "Play music";
91  jumpMap["playlistview"] = "Play music";
92  jumpMap["programfinder"] = "Program Finder";
93  jumpMap["programguide"] = "Program Guide";
94  jumpMap["ripcd"] = "Rip CD";
95  jumpMap["musicplaylists"] = "Select music playlists";
96  jumpMap["playbackrecordings"] = "TV Recording Playback";
97  jumpMap["videobrowser"] = "Video Browser";
98  jumpMap["videogallery"] = "Video Gallery";
99  jumpMap["videolistings"] = "Video Listings";
100  jumpMap["videomanager"] = "Video Manager";
101  jumpMap["zoneminderconsole"] = "ZoneMinder Console";
102  jumpMap["zoneminderliveview"] = "ZoneMinder Live View";
103  jumpMap["zoneminderevents"] = "ZoneMinder Events";
104 
105  jumpMap["channelrecpriority"] = "Channel Recording Priorities";
106  jumpMap["viewscheduled"] = "Manage Recordings / Fix Conflicts";
107  jumpMap["previousbox"] = "Previously Recorded";
108  jumpMap["progfinder"] = "Program Finder";
109  jumpMap["guidegrid"] = "Program Guide";
110  jumpMap["managerecrules"] = "Manage Recording Rules";
111  jumpMap["statusbox"] = "Status Screen";
112  jumpMap["playbackbox"] = "TV Recording Playback";
113  jumpMap["pbb"] = "TV Recording Playback";
114 
115  jumpMap["reloadtheme"] = "Reload Theme";
116  jumpMap["showborders"] = "Toggle Show Widget Borders";
117  jumpMap["shownames"] = "Toggle Show Widget Names";
118 
119  keyMap["up"] = Qt::Key_Up;
120  keyMap["down"] = Qt::Key_Down;
121  keyMap["left"] = Qt::Key_Left;
122  keyMap["right"] = Qt::Key_Right;
123  keyMap["home"] = Qt::Key_Home;
124  keyMap["end"] = Qt::Key_End;
125  keyMap["enter"] = Qt::Key_Enter;
126  keyMap["return"] = Qt::Key_Return;
127  keyMap["pageup"] = Qt::Key_PageUp;
128  keyMap["pagedown"] = Qt::Key_PageDown;
129  keyMap["escape"] = Qt::Key_Escape;
130  keyMap["tab"] = Qt::Key_Tab;
131  keyMap["backtab"] = Qt::Key_Backtab;
132  keyMap["space"] = Qt::Key_Space;
133  keyMap["backspace"] = Qt::Key_Backspace;
134  keyMap["insert"] = Qt::Key_Insert;
135  keyMap["delete"] = Qt::Key_Delete;
136  keyMap["plus"] = Qt::Key_Plus;
137  keyMap["+"] = Qt::Key_Plus;
138  keyMap["comma"] = Qt::Key_Comma;
139  keyMap[","] = Qt::Key_Comma;
140  keyMap["minus"] = Qt::Key_Minus;
141  keyMap["-"] = Qt::Key_Minus;
142  keyMap["underscore"] = Qt::Key_Underscore;
143  keyMap["_"] = Qt::Key_Underscore;
144  keyMap["period"] = Qt::Key_Period;
145  keyMap["."] = Qt::Key_Period;
146  keyMap["numbersign"] = Qt::Key_NumberSign;
147  keyMap["poundsign"] = Qt::Key_NumberSign;
148  keyMap["hash"] = Qt::Key_NumberSign;
149  keyMap["#"] = Qt::Key_NumberSign;
150  keyMap["bracketleft"] = Qt::Key_BracketLeft;
151  keyMap["["] = Qt::Key_BracketLeft;
152  keyMap["bracketright"] = Qt::Key_BracketRight;
153  keyMap["]"] = Qt::Key_BracketRight;
154  keyMap["backslash"] = Qt::Key_Backslash;
155  keyMap["\\"] = Qt::Key_Backslash;
156  keyMap["dollar"] = Qt::Key_Dollar;
157  keyMap["$"] = Qt::Key_Dollar;
158  keyMap["percent"] = Qt::Key_Percent;
159  keyMap["%"] = Qt::Key_Percent;
160  keyMap["ampersand"] = Qt::Key_Ampersand;
161  keyMap["&"] = Qt::Key_Ampersand;
162  keyMap["parenleft"] = Qt::Key_ParenLeft;
163  keyMap["("] = Qt::Key_ParenLeft;
164  keyMap["parenright"] = Qt::Key_ParenRight;
165  keyMap[")"] = Qt::Key_ParenRight;
166  keyMap["asterisk"] = Qt::Key_Asterisk;
167  keyMap["*"] = Qt::Key_Asterisk;
168  keyMap["question"] = Qt::Key_Question;
169  keyMap["?"] = Qt::Key_Question;
170  keyMap["slash"] = Qt::Key_Slash;
171  keyMap["/"] = Qt::Key_Slash;
172  keyMap["colon"] = Qt::Key_Colon;
173  keyMap[":"] = Qt::Key_Colon;
174  keyMap["semicolon"] = Qt::Key_Semicolon;
175  keyMap[";"] = Qt::Key_Semicolon;
176  keyMap["less"] = Qt::Key_Less;
177  keyMap["<"] = Qt::Key_Less;
178  keyMap["equal"] = Qt::Key_Equal;
179  keyMap["="] = Qt::Key_Equal;
180  keyMap["greater"] = Qt::Key_Greater;
181  keyMap[">"] = Qt::Key_Greater;
182  keyMap["bar"] = Qt::Key_Bar;
183  keyMap["pipe"] = Qt::Key_Bar;
184  keyMap["|"] = Qt::Key_Bar;
185  keyMap["f1"] = Qt::Key_F1;
186  keyMap["f2"] = Qt::Key_F2;
187  keyMap["f3"] = Qt::Key_F3;
188  keyMap["f4"] = Qt::Key_F4;
189  keyMap["f5"] = Qt::Key_F5;
190  keyMap["f6"] = Qt::Key_F6;
191  keyMap["f7"] = Qt::Key_F7;
192  keyMap["f8"] = Qt::Key_F8;
193  keyMap["f9"] = Qt::Key_F9;
194  keyMap["f10"] = Qt::Key_F10;
195  keyMap["f11"] = Qt::Key_F11;
196  keyMap["f12"] = Qt::Key_F12;
197  keyMap["f13"] = Qt::Key_F13;
198  keyMap["f14"] = Qt::Key_F14;
199  keyMap["f15"] = Qt::Key_F15;
200  keyMap["f16"] = Qt::Key_F16;
201  keyMap["f17"] = Qt::Key_F17;
202  keyMap["f18"] = Qt::Key_F18;
203  keyMap["f19"] = Qt::Key_F19;
204  keyMap["f20"] = Qt::Key_F20;
205  keyMap["f21"] = Qt::Key_F21;
206  keyMap["f22"] = Qt::Key_F22;
207  keyMap["f23"] = Qt::Key_F23;
208  keyMap["f24"] = Qt::Key_F24;
209 
210  keyTextMap[Qt::Key_Space] = " ";
211  keyTextMap[Qt::Key_Plus] = "+";
212  keyTextMap[Qt::Key_Comma] = ",";
213  keyTextMap[Qt::Key_Minus] = "-";
214  keyTextMap[Qt::Key_Underscore] = "_";
215  keyTextMap[Qt::Key_Period] = ".";
216  keyTextMap[Qt::Key_NumberSign] = "#";
217  keyTextMap[Qt::Key_BracketLeft] = "[";
218  keyTextMap[Qt::Key_BracketRight] = "]";
219  keyTextMap[Qt::Key_Backslash] = "\\";
220  keyTextMap[Qt::Key_Dollar] = "$";
221  keyTextMap[Qt::Key_Percent] = "%";
222  keyTextMap[Qt::Key_Ampersand] = "&";
223  keyTextMap[Qt::Key_ParenLeft] = "(";
224  keyTextMap[Qt::Key_ParenRight] = ")";
225  keyTextMap[Qt::Key_Asterisk] = "*";
226  keyTextMap[Qt::Key_Question] = "?";
227  keyTextMap[Qt::Key_Slash] = "/";
228  keyTextMap[Qt::Key_Colon] = ":";
229  keyTextMap[Qt::Key_Semicolon] = ";";
230  keyTextMap[Qt::Key_Less] = "<";
231  keyTextMap[Qt::Key_Equal] = "=";
232  keyTextMap[Qt::Key_Greater] = ">";
233  keyTextMap[Qt::Key_Bar] = "|";
234 
235  commandThread->start();
236 
237  gCoreContext->addListener(this);
238 
239  connect(this, SIGNAL(newConnection(QTcpSocket*)),
240  this, SLOT(newConnection(QTcpSocket*)));
241 }
242 
244 {
246 
247  clientLock.lock();
248  while (!clients.isEmpty())
249  {
250  NetworkControlClient *ncc = clients.takeFirst();
251  delete ncc;
252  }
253  clientLock.unlock();
254 
255  nrLock.lock();
256  networkControlReplies.push_back(new NetworkCommand(nullptr,
257  "mythfrontend shutting down, connection closing..."));
258  nrLock.unlock();
259 
261 
262  ncLock.lock();
263  stopCommandThread = true;
264  ncCond.wakeOne();
265  ncLock.unlock();
266  commandThread->wait();
267  delete commandThread;
268  commandThread = nullptr;
269 }
270 
272 {
273  QMutexLocker locker(&ncLock);
274  while (!stopCommandThread)
275  {
276  while (networkControlCommands.empty() && !stopCommandThread)
277  ncCond.wait(&ncLock);
278  if (!stopCommandThread)
279  {
281  networkControlCommands.pop_front();
282  locker.unlock();
284  locker.relock();
285  }
286  }
287 }
288 
290 {
291  QMutexLocker locker(&clientLock);
292  QString result;
293 
294  int clientID = clients.indexOf(nc->getClient());
295 
296  if (is_abbrev("jump", nc->getArg(0)))
297  result = processJump(nc);
298  else if (is_abbrev("key", nc->getArg(0)))
299  result = processKey(nc);
300  else if (is_abbrev("play", nc->getArg(0)))
301  result = processPlay(nc, clientID);
302  else if (is_abbrev("query", nc->getArg(0)))
303  result = processQuery(nc);
304  else if (is_abbrev("set", nc->getArg(0)))
305  result = processSet(nc);
306  else if (is_abbrev("screenshot", nc->getArg(0)))
307  result = saveScreenshot(nc);
308  else if (is_abbrev("help", nc->getArg(0)))
309  result = processHelp(nc);
310  else if (is_abbrev("message", nc->getArg(0)))
311  result = processMessage(nc);
312  else if (is_abbrev("notification", nc->getArg(0)))
313  result = processNotification(nc);
314  else if (is_abbrev("theme", nc->getArg(0)))
315  result = processTheme(nc);
316  else if ((nc->getArg(0).toLower() == "exit") || (nc->getArg(0).toLower() == "quit"))
317  QCoreApplication::postEvent(this,
319  else if (! nc->getArg(0).isEmpty())
320  result = QString("INVALID command '%1', try 'help' for more info")
321  .arg(nc->getArg(0));
322 
323  nrLock.lock();
324  networkControlReplies.push_back(new NetworkCommand(nc->getClient(),result));
325  nrLock.unlock();
326 
328 }
329 
331 {
332  LOG(VB_GENERAL, LOG_INFO, LOC + "Client Socket disconnected");
333  QMutexLocker locker(&clientLock);
334 
335  gCoreContext->SendSystemEvent("NET_CTRL_DISCONNECTED");
336 
337  QList<NetworkControlClient *>::const_iterator it;
338  for (it = clients.begin(); it != clients.end(); ++it)
339  {
340  NetworkControlClient *ncc = *it;
341  if (ncc->getSocket()->state() == QTcpSocket::UnconnectedState)
342  {
343  deleteClient(ncc);
344  return;
345  }
346  }
347 }
348 
350 {
351  int index = clients.indexOf(ncc);
352  if (index >= 0)
353  {
354  clients.removeAt(index);
355 
356  delete ncc;
357  }
358  else
359  LOG(VB_GENERAL, LOG_ERR, LOC + QString("deleteClient(%1), unable to "
360  "locate specified NetworkControlClient").arg((long long)ncc));
361 }
362 
363 void NetworkControl::newConnection(QTcpSocket *client)
364 {
365  QString welcomeStr;
366 
367  LOG(VB_GENERAL, LOG_INFO, LOC + QString("New connection established."));
368 
369  gCoreContext->SendSystemEvent("NET_CTRL_CONNECTED");
370 
371  NetworkControlClient *ncc = new NetworkControlClient(client);
372 
373  QMutexLocker locker(&clientLock);
374  clients.push_back(ncc);
375 
376  connect(ncc, SIGNAL(commandReceived(QString&)), this,
377  SLOT(receiveCommand(QString&)));
378  connect(client, SIGNAL(disconnected()), this, SLOT(deleteClient()));
379 
380  welcomeStr = "MythFrontend Network Control\r\n";
381  welcomeStr += "Type 'help' for usage information\r\n"
382  "---------------------------------";
383  nrLock.lock();
384  networkControlReplies.push_back(new NetworkCommand(ncc,welcomeStr));
385  nrLock.unlock();
386 
388 }
389 
391 {
392  m_socket = s;
393  m_textStream = new QTextStream(s);
394  m_textStream->setCodec("UTF-8");
395  connect(m_socket, SIGNAL(readyRead()), this, SLOT(readClient()));
396 }
397 
399 {
400  m_socket->close();
401  m_socket->deleteLater();
402 
403  delete m_textStream;
404 }
405 
407 {
408  QTcpSocket *socket = (QTcpSocket *)sender();
409  if (!socket)
410  return;
411 
412  QString lineIn;
413  while (socket->canReadLine())
414  {
415  lineIn = socket->readLine();
416 #if 0
417  lineIn.replace(QRegExp("[^-a-zA-Z0-9\\s\\.:_#/$%&()*+,;<=>?\\[\\]\\|]"),
418  "");
419 #endif
420 
421  // TODO: can this be replaced with lineIn.simplify()?
422  lineIn.replace(QRegExp("[\r\n]"), "");
423  lineIn.replace(QRegExp("^\\s"), "");
424 
425  if (lineIn.isEmpty())
426  continue;
427 
428  LOG(VB_NETWORK, LOG_INFO, LOC +
429  QString("emit commandReceived(%1)").arg(lineIn));
430  emit commandReceived(lineIn);
431  }
432 }
433 
434 void NetworkControl::receiveCommand(QString &command)
435 {
436  LOG(VB_NETWORK, LOG_INFO, LOC +
437  QString("NetworkControl::receiveCommand(%1)").arg(command));
438  NetworkControlClient *ncc = static_cast<NetworkControlClient *>(sender());
439  if (!ncc)
440  return;
441 
442  ncLock.lock();
443  networkControlCommands.push_back(new NetworkCommand(ncc,command));
444  ncCond.wakeOne();
445  ncLock.unlock();
446 }
447 
449 {
450  QString result = "OK";
451 
452  if ((nc->getArgCount() < 2) || (!jumpMap.contains(nc->getArg(1))))
453  return QString("ERROR: See 'help %1' for usage information")
454  .arg(nc->getArg(0));
455 
457 
458  // Fixme, should do some better checking here, but that would
459  // depend on all Locations matching their jumppoints
460  QTime timer;
461  timer.start();
462  while ((timer.elapsed() < FE_SHORT_TO) &&
463  (GetMythUI()->GetCurrentLocation().toLower() != nc->getArg(1)))
464  std::this_thread::sleep_for(std::chrono::milliseconds(10));
465 
466  return result;
467 }
468 
470 {
471  QString result = "OK";
472  QKeyEvent *event = nullptr;
473 
474  if (nc->getArgCount() < 2)
475  return QString("ERROR: See 'help %1' for usage information")
476  .arg(nc->getArg(0));
477 
478  QObject *keyDest = nullptr;
479 
480  if (GetMythMainWindow())
481  keyDest = GetMythMainWindow();
482  else
483  return QString("ERROR: Application has no main window!\n");
484 
485  if (GetMythMainWindow()->currentWidget())
486  keyDest = GetMythMainWindow()->currentWidget()->focusWidget();
487 
488  int curToken = 1;
489  while (curToken < nc->getArgCount())
490  {
491  int tokenLen = nc->getArg(curToken).length();
492 
493  if (nc->getArg(curToken) == "sleep")
494  {
495  std::this_thread::sleep_for(std::chrono::seconds(1));
496  }
497  else if (keyMap.contains(nc->getArg(curToken)))
498  {
499  int keyCode = keyMap[nc->getArg(curToken)];
500  QString keyText;
501 
502  if (keyTextMap.contains(keyCode))
503  keyText = keyTextMap[keyCode];
504 
506 
507  event = new QKeyEvent(QEvent::KeyPress, keyCode, Qt::NoModifier,
508  keyText);
509  QCoreApplication::postEvent(keyDest, event);
510 
511  event = new QKeyEvent(QEvent::KeyRelease, keyCode, Qt::NoModifier,
512  keyText);
513  QCoreApplication::postEvent(keyDest, event);
514  }
515  else if (((tokenLen == 1) &&
516  (nc->getArg(curToken)[0].isLetterOrNumber())) ||
517  ((tokenLen >= 1) &&
518  (nc->getArg(curToken).contains("+"))))
519  {
520  QKeySequence a(nc->getArg(curToken));
521  int keyCode = a[0];
522  Qt::KeyboardModifiers modifiers = Qt::NoModifier;
523 
524  if (tokenLen > 1)
525  {
526  QStringList tokenParts = nc->getArg(curToken).split('+');
527 
528  int partNum = 0;
529  while (partNum < (tokenParts.size() - 1))
530  {
531  if (tokenParts[partNum].toUpper() == "CTRL")
532  modifiers |= Qt::ControlModifier;
533  if (tokenParts[partNum].toUpper() == "SHIFT")
534  modifiers |= Qt::ShiftModifier;
535  if (tokenParts[partNum].toUpper() == "ALT")
536  modifiers |= Qt::AltModifier;
537  if (tokenParts[partNum].toUpper() == "META")
538  modifiers |= Qt::MetaModifier;
539 
540  partNum++;
541  }
542  }
543  else
544  {
545  if (nc->getArg(curToken) == nc->getArg(curToken).toUpper())
546  modifiers = Qt::ShiftModifier;
547  }
548 
550 
551  event = new QKeyEvent(QEvent::KeyPress, keyCode, modifiers,
552  nc->getArg(curToken));
553  QCoreApplication::postEvent(keyDest, event);
554 
555  event = new QKeyEvent(QEvent::KeyRelease, keyCode, modifiers,
556  nc->getArg(curToken));
557  QCoreApplication::postEvent(keyDest, event);
558  }
559  else
560  return QString("ERROR: Invalid syntax at '%1', see 'help %2' for "
561  "usage information")
562  .arg(nc->getArg(curToken)).arg(nc->getArg(0));
563 
564  curToken++;
565  }
566 
567  return result;
568 }
569 
570 QString NetworkControl::processPlay(NetworkCommand *nc, int clientID)
571 {
572  QString result = "OK";
573  QString message;
574 
575  if (nc->getArgCount() < 2)
576  return QString("ERROR: See 'help %1' for usage information")
577  .arg(nc->getArg(0));
578 
579  if ((nc->getArgCount() >= 3) &&
580  (is_abbrev("file", nc->getArg(1))))
581  {
582  if (GetMythUI()->GetCurrentLocation().toLower() != "mainmenu")
583  {
584  GetMythMainWindow()->JumpTo(jumpMap["mainmenu"]);
585 
586  QTime timer;
587  timer.start();
588  while ((timer.elapsed() < FE_LONG_TO) &&
589  (GetMythUI()->GetCurrentLocation().toLower() != "mainmenu"))
590  std::this_thread::sleep_for(std::chrono::milliseconds(10));
591  }
592 
593  if (GetMythUI()->GetCurrentLocation().toLower() == "mainmenu")
594  {
595  QStringList args;
596  args << nc->getFrom(2);
598  qApp->postEvent(GetMythMainWindow(), me);
599  }
600  else
601  return QString("Unable to change to main menu to start playback!");
602  }
603  else if ((nc->getArgCount() >= 4) &&
604  (is_abbrev("program", nc->getArg(1))) &&
605  (nc->getArg(2).contains(QRegExp("^\\d+$"))) &&
606  (nc->getArg(3).contains(QRegExp(
607  "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d$"))))
608  {
609  if (GetMythUI()->GetCurrentLocation().toLower() == "playback")
610  {
611  QString msg = QString("NETWORK_CONTROL STOP");
612  MythEvent me(msg);
613  gCoreContext->dispatch(me);
614 
615  QTime timer;
616  timer.start();
617  while ((timer.elapsed() < FE_LONG_TO) &&
618  (GetMythUI()->GetCurrentLocation().toLower() == "playback"))
619  std::this_thread::sleep_for(std::chrono::milliseconds(10));
620  }
621 
622  if (GetMythUI()->GetCurrentLocation().toLower() != "playbackbox")
623  {
624  GetMythMainWindow()->JumpTo(jumpMap["playbackbox"]);
625 
626  QTime timer;
627  timer.start();
628  while ((timer.elapsed() < 10000) &&
629  (GetMythUI()->GetCurrentLocation().toLower() != "playbackbox"))
630  std::this_thread::sleep_for(std::chrono::milliseconds(10));
631 
632  timer.start();
633  while ((timer.elapsed() < 10000) &&
635  std::this_thread::sleep_for(std::chrono::milliseconds(10));
636  }
637 
638  if (GetMythUI()->GetCurrentLocation().toLower() == "playbackbox")
639  {
640  QString action = "PLAY";
641  if (nc->getArgCount() == 5 && nc->getArg(4) == "resume")
642  action = "RESUME";
643 
644  QString msg = QString("NETWORK_CONTROL %1 PROGRAM %2 %3 %4")
645  .arg(action).arg(nc->getArg(2))
646  .arg(nc->getArg(3).toUpper()).arg(clientID);
647 
648  result.clear();
649  gotAnswer = false;
650  QTime timer;
651  timer.start();
652 
653  MythEvent me(msg);
654  gCoreContext->dispatch(me);
655 
656  while (timer.elapsed() < FE_LONG_TO && !gotAnswer)
657  std::this_thread::sleep_for(std::chrono::milliseconds(10));
658 
659  if (gotAnswer)
660  result += answer;
661  else
662  result = "ERROR: Timed out waiting for reply from player";
663 
664  }
665  else
666  {
667  result = QString("ERROR: Unable to change to PlaybackBox from "
668  "%1, cannot play requested file.")
669  .arg(GetMythUI()->GetCurrentLocation());
670  }
671  }
672  else if (is_abbrev("music", nc->getArg(1)))
673  {
674 #if 0
675  if (GetMythUI()->GetCurrentLocation().toLower() != "playmusic")
676  {
677  return QString("ERROR: You are in %1 mode and this command is "
678  "only for MythMusic")
679  .arg(GetMythUI()->GetCurrentLocation());
680  }
681 #endif
682 
683  QString hostname = gCoreContext->GetHostName();
684 
685  if (nc->getArgCount() == 3)
686  {
687  if (is_abbrev("play", nc->getArg(2)))
688  message = QString("MUSIC_COMMAND %1 PLAY").arg(hostname);
689  else if (is_abbrev("pause", nc->getArg(2)))
690  message = QString("MUSIC_COMMAND %1 PAUSE").arg(hostname);
691  else if (is_abbrev("stop", nc->getArg(2)))
692  message = QString("MUSIC_COMMAND %1 STOP").arg(hostname);
693  else if (is_abbrev("getvolume", nc->getArg(2)))
694  {
695  gotAnswer = false;
696 
697  MythEvent me(QString("MUSIC_COMMAND %1 GET_VOLUME").arg(hostname));
698  gCoreContext->dispatch(me);
699 
700  QTime timer;
701  timer.start();
702  while (timer.elapsed() < FE_SHORT_TO && !gotAnswer)
703  {
704  qApp->processEvents();
705  std::this_thread::sleep_for(std::chrono::milliseconds(10));
706  }
707 
708  if (gotAnswer)
709  return answer;
710 
711  return "unknown";
712  }
713  else if (is_abbrev("getmeta", nc->getArg(2)))
714  {
715  gotAnswer = false;
716 
717  MythEvent me(QString("MUSIC_COMMAND %1 GET_METADATA").arg(hostname));
718  gCoreContext->dispatch(me);
719 
720  QTime timer;
721  timer.start();
722  while (timer.elapsed() < FE_SHORT_TO && !gotAnswer)
723  {
724  qApp->processEvents();
725  std::this_thread::sleep_for(std::chrono::milliseconds(10));
726  }
727 
728  if (gotAnswer)
729  return answer;
730 
731  return "unknown";
732  }
733  else if (is_abbrev("getstatus", nc->getArg(2)))
734  {
735  gotAnswer = false;
736 
737  MythEvent me(QString("MUSIC_COMMAND %1 GET_STATUS").arg(hostname));
738  gCoreContext->dispatch(me);
739 
740  QTime timer;
741  timer.start();
742  while (timer.elapsed() < FE_SHORT_TO && !gotAnswer)
743  {
744  qApp->processEvents();
745  std::this_thread::sleep_for(std::chrono::milliseconds(10));
746  }
747 
748  if (gotAnswer)
749  return answer;
750 
751  return "unknown";
752  }
753  else
754  return QString("ERROR: Invalid 'play music' command");
755  }
756  else if (nc->getArgCount() > 3)
757  {
758  if (is_abbrev("setvolume", nc->getArg(2)))
759  message = QString("MUSIC_COMMAND %1 SET_VOLUME %2")
760  .arg(hostname)
761  .arg(nc->getArg(3));
762  else if (is_abbrev("track", nc->getArg(2)))
763  message = QString("MUSIC_COMMAND %1 PLAY_TRACK %2")
764  .arg(hostname)
765  .arg(nc->getArg(3));
766  else if (is_abbrev("url", nc->getArg(2)))
767  message = QString("MUSIC_COMMAND %1 PLAY_URL %2")
768  .arg(hostname)
769  .arg(nc->getArg(3));
770  else if (is_abbrev("file", nc->getArg(2)))
771  message = QString("MUSIC_COMMAND %1 PLAY_FILE '%2'")
772  .arg(hostname)
773  .arg(nc->getFrom(3));
774  else
775  return QString("ERROR: Invalid 'play music' command");
776  }
777  else
778  return QString("ERROR: Invalid 'play music' command");
779  }
780  // Everything below here requires us to be in playback mode so check to
781  // see if we are
782  else if (GetMythUI()->GetCurrentLocation().toLower() != "playback")
783  {
784  return QString("ERROR: You are in %1 mode and this command is only "
785  "for playback mode")
786  .arg(GetMythUI()->GetCurrentLocation());
787  }
788  else if (is_abbrev("chanid", nc->getArg(1), 5))
789  {
790  if (nc->getArg(2).contains(QRegExp("^\\d+$")))
791  message = QString("NETWORK_CONTROL CHANID %1").arg(nc->getArg(2));
792  else
793  return QString("ERROR: See 'help %1' for usage information")
794  .arg(nc->getArg(0));
795  }
796  else if (is_abbrev("channel", nc->getArg(1), 5))
797  {
798  if (nc->getArgCount() < 3)
799  return "ERROR: See 'help play' for usage information";
800 
801  if (is_abbrev("up", nc->getArg(2)))
802  message = "NETWORK_CONTROL CHANNEL UP";
803  else if (is_abbrev("down", nc->getArg(2)))
804  message = "NETWORK_CONTROL CHANNEL DOWN";
805  else if (nc->getArg(2).contains(QRegExp("^[-\\.\\d_#]+$")))
806  message = QString("NETWORK_CONTROL CHANNEL %1").arg(nc->getArg(2));
807  else
808  return QString("ERROR: See 'help %1' for usage information")
809  .arg(nc->getArg(0));
810  }
811  else if (is_abbrev("seek", nc->getArg(1), 2))
812  {
813  if (nc->getArgCount() < 3)
814  return QString("ERROR: See 'help %1' for usage information")
815  .arg(nc->getArg(0));
816 
817  if (is_abbrev("beginning", nc->getArg(2)))
818  message = "NETWORK_CONTROL SEEK BEGINNING";
819  else if (is_abbrev("forward", nc->getArg(2)))
820  message = "NETWORK_CONTROL SEEK FORWARD";
821  else if (is_abbrev("rewind", nc->getArg(2)) ||
822  is_abbrev("backward", nc->getArg(2)))
823  message = "NETWORK_CONTROL SEEK BACKWARD";
824  else if (nc->getArg(2).contains(QRegExp("^\\d\\d:\\d\\d:\\d\\d$")))
825  {
826  int hours = nc->getArg(2).mid(0, 2).toInt();
827  int minutes = nc->getArg(2).mid(3, 2).toInt();
828  int seconds = nc->getArg(2).mid(6, 2).toInt();
829  message = QString("NETWORK_CONTROL SEEK POSITION %1")
830  .arg((hours * 3600) + (minutes * 60) + seconds);
831  }
832  else
833  return QString("ERROR: See 'help %1' for usage information")
834  .arg(nc->getArg(0));
835  }
836  else if (is_abbrev("speed", nc->getArg(1), 2))
837  {
838  if (nc->getArgCount() < 3)
839  return QString("ERROR: See 'help %1' for usage information")
840  .arg(nc->getArg(0));
841 
842  QString token2 = nc->getArg(2).toLower();
843  if ((token2.contains(QRegExp("^\\-*\\d+x$"))) ||
844  (token2.contains(QRegExp("^\\-*\\d+\\/\\d+x$"))) ||
845  (token2.contains(QRegExp("^\\-*\\d*\\.\\d+x$"))))
846  message = QString("NETWORK_CONTROL SPEED %1").arg(token2);
847  else if (is_abbrev("normal", token2))
848  message = QString("NETWORK_CONTROL SPEED normal");
849  else if (is_abbrev("pause", token2))
850  message = QString("NETWORK_CONTROL SPEED 0x");
851  else
852  return QString("ERROR: See 'help %1' for usage information")
853  .arg(nc->getArg(0));
854  }
855  else if (is_abbrev("save", nc->getArg(1), 2))
856  {
857  if (is_abbrev("screenshot", nc->getArg(2), 2))
858  return saveScreenshot(nc);
859  }
860  else if (is_abbrev("stop", nc->getArg(1), 2))
861  message = QString("NETWORK_CONTROL STOP");
862  else if (is_abbrev("volume", nc->getArg(1), 2))
863  {
864  if ((nc->getArgCount() < 3) ||
865  (!nc->getArg(2).toLower().contains(QRegExp("^\\d+%?$"))))
866  {
867  return QString("ERROR: See 'help %1' for usage information")
868  .arg(nc->getArg(0));
869  }
870 
871  message = QString("NETWORK_CONTROL VOLUME %1")
872  .arg(nc->getArg(2).toLower());
873  }
874  else if (is_abbrev("subtitles", nc->getArg(1), 2))
875  {
876  if (nc->getArgCount() < 3)
877  message = QString("NETWORK_CONTROL SUBTITLES 0");
878  else if (!nc->getArg(2).toLower().contains(QRegExp("^\\d+$")))
879  return QString("ERROR: See 'help %1' for usage information")
880  .arg(nc->getArg(0));
881  else
882  message = QString("NETWORK_CONTROL SUBTITLES %1")
883  .arg(nc->getArg(2));
884  }
885  else
886  return QString("ERROR: See 'help %1' for usage information")
887  .arg(nc->getArg(0));
888 
889  if (!message.isEmpty())
890  {
891  MythEvent me(message);
892  gCoreContext->dispatch(me);
893  }
894 
895  return result;
896 }
897 
899 {
900  QString result = "OK";
901 
902  if (nc->getArgCount() < 2)
903  return QString("ERROR: See 'help %1' for usage information")
904  .arg(nc->getArg(0));
905 
906  if (is_abbrev("location", nc->getArg(1)))
907  {
908  bool fullPath = false;
909  bool mainStackOnly = true;
910 
911  if (nc->getArgCount() > 2)
912  fullPath = (nc->getArg(2).toLower() == "true" || nc->getArg(2) == "1");
913  if (nc->getArgCount() > 3)
914  mainStackOnly = (nc->getArg(3).toLower() == "true" || nc->getArg(3) == "1");
915 
916  QString location = GetMythUI()->GetCurrentLocation(fullPath, mainStackOnly);
917  result = location;
918 
919  // if we're playing something, then find out what
920  if (location == "Playback")
921  {
922  result += " ";
923  gotAnswer = false;
924  QString message = QString("NETWORK_CONTROL QUERY POSITION");
925  MythEvent me(message);
926  gCoreContext->dispatch(me);
927 
928  QTime timer;
929  timer.start();
930  while (timer.elapsed() < FE_SHORT_TO && !gotAnswer)
931  std::this_thread::sleep_for(std::chrono::milliseconds(10));
932 
933  if (gotAnswer)
934  result += answer;
935  else
936  result = "ERROR: Timed out waiting for reply from player";
937  }
938  }
939  else if (is_abbrev("verbose", nc->getArg(1)))
940  {
941  return verboseString;
942  }
943  else if (is_abbrev("liveTV", nc->getArg(1)))
944  {
945  if(nc->getArgCount() == 3) // has a channel ID
946  return listSchedule(nc->getArg(2));
947  return listSchedule();
948  }
949  else if (is_abbrev("version", nc->getArg(1)))
950  {
951  int dbSchema = gCoreContext->GetNumSetting("DBSchemaVer");
952 
953  return QString("VERSION: %1/%2 %3 %4 QT/%5 DBSchema/%6")
954  .arg(MYTH_SOURCE_VERSION)
955  .arg(MYTH_SOURCE_PATH)
956  .arg(MYTH_BINARY_VERSION)
957  .arg(MYTH_PROTO_VERSION)
958  .arg(QT_VERSION_STR)
959  .arg(dbSchema);
960 
961  }
962  else if(is_abbrev("time", nc->getArg(1)))
964  else if (is_abbrev("uptime", nc->getArg(1)))
965  {
966  QString str;
967  time_t uptime;
968 
969  if (getUptime(uptime))
970  str = QString::number(uptime);
971  else
972  str = QString("Could not determine uptime.");
973  return str;
974  }
975  else if (is_abbrev("load", nc->getArg(1)))
976  {
977  QString str;
978 #if defined(_WIN32) || defined(Q_OS_ANDROID)
979  str = QString("getloadavg() failed");
980 #else
981  double loads[3];
982 
983  if (getloadavg(loads,3) == -1)
984  str = QString("getloadavg() failed");
985  else
986  str = QString("%1 %2 %3").arg(loads[0]).arg(loads[1]).arg(loads[2]);
987 #endif
988  return str;
989  }
990  else if (is_abbrev("memstats", nc->getArg(1)))
991  {
992  QString str;
993  int totalMB, freeMB, totalVM, freeVM;
994 
995  if (getMemStats(totalMB, freeMB, totalVM, freeVM))
996  str = QString("%1 %2 %3 %4")
997  .arg(totalMB).arg(freeMB).arg(totalVM).arg(freeVM);
998  else
999  str = QString("Could not determine memory stats.");
1000  return str;
1001  }
1002  else if (is_abbrev("volume", nc->getArg(1)))
1003  {
1004  QString str = "0%";
1005 
1006  QString location = GetMythUI()->GetCurrentLocation(false, false);
1007 
1008  if (location != "Playback")
1009  return str;
1010 
1011  gotAnswer = false;
1012  QString message = QString("NETWORK_CONTROL QUERY VOLUME");
1013  MythEvent me(message);
1014  gCoreContext->dispatch(me);
1015 
1016  QTime timer;
1017  timer.start();
1018  while (timer.elapsed() < FE_SHORT_TO && !gotAnswer)
1019  std::this_thread::sleep_for(std::chrono::milliseconds(10));
1020 
1021  if (gotAnswer)
1022  str = answer;
1023  else
1024  str = "ERROR: Timed out waiting for reply from player";
1025 
1026  return str;
1027  }
1028  else if ((nc->getArgCount() == 4) &&
1029  is_abbrev("recording", nc->getArg(1)) &&
1030  (nc->getArg(2).contains(QRegExp("^\\d+$"))) &&
1031  (nc->getArg(3).contains(QRegExp(
1032  "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d$"))))
1033  return listRecordings(nc->getArg(2), nc->getArg(3).toUpper());
1034  else if (is_abbrev("recordings", nc->getArg(1)))
1035  return listRecordings();
1036  else if (is_abbrev("channels", nc->getArg(1)))
1037  {
1038  if (nc->getArgCount() == 2)
1039  return listChannels(0, 0); // give us all you can
1040  if (nc->getArgCount() == 4)
1041  return listChannels(nc->getArg(2).toLower().toUInt(),
1042  nc->getArg(3).toLower().toUInt());
1043  return QString("ERROR: See 'help %1' for usage information "
1044  "(parameters mismatch)").arg(nc->getArg(0));
1045  }
1046  else
1047  return QString("ERROR: See 'help %1' for usage information")
1048  .arg(nc->getArg(0));
1049 
1050  return result;
1051 }
1052 
1054 {
1055  if (nc->getArgCount() == 1)
1056  return QString("ERROR: See 'help %1' for usage information")
1057  .arg(nc->getArg(0));
1058 
1059  if (nc->getArg(1) == "verbose")
1060  {
1061  if (nc->getArgCount() < 3)
1062  return QString("ERROR: Missing filter name.");
1063 
1064  if (nc->getArgCount() > 3)
1065  return QString("ERROR: Separate filters with commas with no "
1066  "space: playback,audio\r\n See 'help %1' for usage "
1067  "information").arg(nc->getArg(0));
1068 
1069  QString oldVerboseString = verboseString;
1070  QString result = "OK";
1071 
1072  int pva_result = verboseArgParse(nc->getArg(2));
1073 
1074  if (pva_result != 0 /*GENERIC_EXIT_OK */)
1075  result = "Failed";
1076 
1077  result += "\r\n";
1078  result += " Previous filter: " + oldVerboseString + "\r\n";
1079  result += " New Filter: " + verboseString + "\r\n";
1080 
1081  LOG(VB_GENERAL, LOG_NOTICE,
1082  QString("Verbose mask changed, new level is: %1")
1083  .arg(verboseString));
1084 
1085  return result;
1086  }
1087 
1088  return QString("ERROR: See 'help %1' for usage information")
1089  .arg(nc->getArg(0));
1090 }
1091 
1093 {
1094  if (dynamic_cast<MythUIText *>(type))
1095  return "MythUIText";
1096  if (dynamic_cast<MythUITextEdit *>(type))
1097  return "MythUITextEdit";
1098  if (dynamic_cast<MythUIGroup *>(type))
1099  return "MythUIGroup";
1100  if (dynamic_cast<MythUIButton *>(type))
1101  return "MythUIButton";
1102  if (dynamic_cast<MythUICheckBox *>(type))
1103  return "MythUICheckBox";
1104  if (dynamic_cast<MythUIShape *>(type))
1105  return "MythUIShape";
1106  if (dynamic_cast<MythUIButtonList *>(type))
1107  return "MythUIButtonList";
1108  if (dynamic_cast<MythUIImage *>(type))
1109  return "MythUIImage";
1110  if (dynamic_cast<MythUISpinBox *>(type))
1111  return "MythUISpinBox";
1112 #if CONFIG_QTWEBKIT
1113  if (dynamic_cast<MythUIWebBrowser *>(type))
1114  return "MythUIWebBrowser";
1115 #endif
1116  if (dynamic_cast<MythUIClock *>(type))
1117  return "MythUIClock";
1118  if (dynamic_cast<MythUIStateType *>(type))
1119  return "MythUIStateType";
1120  if (dynamic_cast<MythUIProgressBar *>(type))
1121  return "MythUIProgressBar";
1122  if (dynamic_cast<MythUIButtonTree *>(type))
1123  return "MythUIButtonTree";
1124  if (dynamic_cast<MythUIScrollBar *>(type))
1125  return "MythUIScrollBar";
1126  if (dynamic_cast<MythUIVideo *>(type))
1127  return "MythUIVideo";
1128  if (dynamic_cast<MythUIGuideGrid *>(type))
1129  return "MythUIGuideGrid";
1130  if (dynamic_cast<MythUIEditBar *>(type))
1131  return "MythUIEditBar";
1132 
1133  return "Unknown";
1134 }
1135 
1137 {
1138  if (nc->getArgCount() == 1)
1139  return QString("ERROR: See 'help %1' for usage information")
1140  .arg(nc->getArg(0));
1141 
1142  if (nc->getArg(1) == "getthemeinfo")
1143  {
1144  QString themeName = GetMythUI()->GetThemeName();
1145  QString themeDir = GetMythUI()->GetThemeDir();
1146  return QString("%1 - %2").arg(themeName).arg(themeDir);
1147  }
1148  if (nc->getArg(1) == "reload")
1149  {
1150  GetMythMainWindow()->JumpTo(jumpMap["reloadtheme"]);
1151 
1152  return "OK";
1153  }
1154  if (nc->getArg(1) == "showborders")
1155  {
1156  GetMythMainWindow()->JumpTo(jumpMap["showborders"]);
1157 
1158  return "OK";
1159  }
1160  if (nc->getArg(1) == "shownames")
1161  {
1162  GetMythMainWindow()->JumpTo(jumpMap["shownames"]);
1163 
1164  return "OK";
1165  }
1166  if (nc->getArg(1) == "getwidgetnames")
1167  {
1168  QStringList path;
1169 
1170  if (nc->getArgCount() >= 3)
1171  path = nc->getArg(2).split('/');
1172 
1173  MythScreenStack *stack = GetMythMainWindow()->GetStack("popup stack");
1174  MythScreenType *topScreen = stack->GetTopScreen();
1175 
1176  if (!topScreen)
1177  {
1178  stack = GetMythMainWindow()->GetMainStack();
1179  topScreen = stack->GetTopScreen();
1180  }
1181 
1182  if (!topScreen)
1183  return QString("ERROR: no top screen found!");
1184 
1185  MythUIType *currType = static_cast<MythUIType*>(topScreen);
1186 
1187  while (!path.isEmpty())
1188  {
1189  QString childName = path.takeFirst();
1190  currType = currType->GetChild(childName);
1191  if (!currType)
1192  return QString("ERROR: Failed to find child '%1'").arg(childName);
1193  }
1194 
1195  QList<MythUIType*> *children = currType->GetAllChildren();
1196  QString result;
1197 
1198  for (int i = 0; i < children->count(); i++)
1199  {
1200  MythUIType *type = children->at(i);
1201  QString widgetName = type->objectName();
1202  QString widgetType = getWidgetType(type);
1203  result += QString("%1 - %2\n\r").arg(widgetName, -20).arg(widgetType);
1204  }
1205 
1206  return result;
1207  }
1208  if (nc->getArg(1) == "getarea")
1209  {
1210  if (nc->getArgCount() < 3)
1211  return QString("ERROR: Missing widget name.");
1212 
1213  QString widgetName = nc->getArg(2);
1214  QStringList path = widgetName.split('/');
1215 
1216  MythScreenStack *stack = GetMythMainWindow()->GetStack("popup stack");
1217  MythScreenType *topScreen = stack->GetTopScreen();
1218 
1219  if (!topScreen)
1220  {
1221  stack = GetMythMainWindow()->GetMainStack();
1222  topScreen = stack->GetTopScreen();
1223  }
1224 
1225  if (!topScreen)
1226  return QString("ERROR: no top screen found!");
1227 
1228  MythUIType *currType = static_cast<MythUIType*>(topScreen);
1229 
1230  while (path.count() > 1)
1231  {
1232  QString childName = path.takeFirst();
1233  currType = currType->GetChild(childName);
1234  if (!currType)
1235  return QString("ERROR: Failed to find child '%1'").arg(childName);
1236  }
1237 
1238  MythUIType* type = currType->GetChild(path.first());
1239  if (!type)
1240  return QString("ERROR: widget '%1' not found!").arg(widgetName);
1241 
1242  int x = type->GetFullArea().x();
1243  int y = type->GetFullArea().y();
1244  int w = type->GetFullArea().width();
1245  int h = type->GetFullArea().height();
1246  return QString("The area of '%1' is x:%2, y:%3, w:%4, h:%5")
1247  .arg(widgetName).arg(x).arg(y).arg(w).arg(h);
1248  }
1249  if (nc->getArg(1) == "setarea")
1250  {
1251  if (nc->getArgCount() < 3)
1252  return QString("ERROR: Missing widget name.");
1253 
1254  if (nc->getArgCount() < 7)
1255  return QString("ERROR: Missing X, Y, Width or Height.");
1256 
1257  QString widgetName = nc->getArg(2);
1258  QStringList path = widgetName.split('/');
1259  QString x = nc->getArg(3);
1260  QString y = nc->getArg(4);
1261  QString w = nc->getArg(5);
1262  QString h = nc->getArg(6);
1263 
1264  MythScreenStack *stack = GetMythMainWindow()->GetStack("popup stack");
1265  MythScreenType *topScreen = stack->GetTopScreen();
1266 
1267  if (!topScreen)
1268  {
1269  stack = GetMythMainWindow()->GetMainStack();
1270  topScreen = stack->GetTopScreen();
1271  }
1272 
1273  if (!topScreen)
1274  return QString("ERROR: no top screen found!");
1275 
1276  MythUIType *currType = static_cast<MythUIType*>(topScreen);
1277 
1278  while (path.count() > 1)
1279  {
1280  QString childName = path.takeFirst();
1281  currType = currType->GetChild(childName);
1282  if (!currType)
1283  return QString("ERROR: Failed to find child '%1'").arg(childName);
1284  }
1285 
1286  MythUIType* type = currType->GetChild(path.first());
1287  if (!type)
1288  return QString("ERROR: widget '%1' not found!").arg(widgetName);
1289 
1290  type->SetArea(MythRect(x, y, w, h));
1291 
1292  return QString("Changed area of '%1' to x:%2, y:%3, w:%4, h:%5")
1293  .arg(widgetName).arg(x).arg(y).arg(w).arg(h);
1294  }
1295 
1296  return QString("ERROR: See 'help %1' for usage information")
1297  .arg(nc->getArg(0));
1298 }
1299 
1301 {
1302  QString command, helpText;
1303 
1304  if (nc->getArgCount() >= 1)
1305  {
1306  if (is_abbrev("help", nc->getArg(0)))
1307  {
1308  if (nc->getArgCount() >= 2)
1309  command = nc->getArg(1);
1310  else
1311  command.clear();
1312  }
1313  else
1314  {
1315  command = nc->getArg(0);
1316  }
1317  }
1318 
1319  if (is_abbrev("jump", command))
1320  {
1321  QMap<QString, QString>::Iterator it;
1322  helpText +=
1323  "Usage: jump JUMPPOINT\r\n"
1324  "\r\n"
1325  "Where JUMPPOINT is one of the following:\r\n";
1326 
1327  for (it = jumpMap.begin(); it != jumpMap.end(); ++it)
1328  {
1329  helpText += it.key().leftJustified(20, ' ', true) + " - " +
1330  *it + "\r\n";
1331  }
1332  }
1333  else if (is_abbrev("key", command))
1334  {
1335  helpText +=
1336  "key LETTER - Send the letter key specified\r\n"
1337  "key NUMBER - Send the number key specified\r\n"
1338  "key CODE - Send one of the following key codes\r\n"
1339  "\r\n";
1340 
1341  QMap<QString, int>::Iterator it;
1342  bool first = true;
1343  for (it = keyMap.begin(); it != keyMap.end(); ++it)
1344  {
1345  if (first)
1346  first = false;
1347  else
1348  helpText += ", ";
1349 
1350  helpText += it.key();
1351  }
1352  helpText += "\r\n";
1353  }
1354  else if (is_abbrev("play", command))
1355  {
1356  helpText +=
1357  "play volume NUMBER% - Change volume to given percentage value\r\n"
1358  "play channel up - Change channel Up\r\n"
1359  "play channel down - Change channel Down\r\n"
1360  "play channel NUMBER - Change to a specific channel number\r\n"
1361  "play chanid NUMBER - Change to a specific channel id (chanid)\r\n"
1362  "play file FILENAME - Play FILENAME (FILENAME may be a file or a myth:// URL)\r\n"
1363  "play program CHANID yyyy-MM-ddThh:mm:ss\r\n"
1364  " - Play program with chanid & starttime\r\n"
1365  "play program CHANID yyyy-MM-ddThh:mm:ss resume\r\n"
1366  " - Resume program with chanid & starttime\r\n"
1367  "play save preview\r\n"
1368  " - Save preview image from current position\r\n"
1369  "play save preview FILENAME\r\n"
1370  " - Save preview image to FILENAME\r\n"
1371  "play save preview FILENAME WxH\r\n"
1372  " - Save preview image of size WxH\r\n"
1373  "play seek beginning - Seek to the beginning of the recording\r\n"
1374  "play seek forward - Skip forward in the video\r\n"
1375  "play seek backward - Skip backwards in the video\r\n"
1376  "play seek HH:MM:SS - Seek to a specific position\r\n"
1377  "play speed pause - Pause playback\r\n"
1378  "play speed normal - Playback at normal speed\r\n"
1379  "play speed 1x - Playback at normal speed\r\n"
1380  "play speed SPEEDx - Playback where SPEED must be a decimal\r\n"
1381  "play speed 1/8x - Playback at 1/8x speed\r\n"
1382  "play speed 1/4x - Playback at 1/4x speed\r\n"
1383  "play speed 1/3x - Playback at 1/3x speed\r\n"
1384  "play speed 1/2x - Playback at 1/2x speed\r\n"
1385  "play stop - Stop playback\r\n"
1386  "play subtitles [#] - Switch on indicated subtitle tracks\r\n"
1387  "play music play - Resume playback (MythMusic)\r\n"
1388  "play music pause - Pause playback (MythMusic)\r\n"
1389  "play music stop - Stop Playback (MythMusic)\r\n"
1390  "play music setvolume N - Set volume to number (MythMusic)\r\n"
1391  "play music getvolume - Get current volume (MythMusic)\r\n"
1392  "play music getmeta - Get metadata for current track (MythMusic)\r\n"
1393  "play music getstatus - Get music player status playing/paused/stopped (MythMusic)\r\n"
1394  "play music file NAME - Play specified file (MythMusic)\r\n"
1395  "play music track N - Switch to specified track (MythMusic)\r\n"
1396  "play music url URL - Play specified URL (MythMusic)\r\n";
1397  }
1398  else if (is_abbrev("query", command))
1399  {
1400  helpText +=
1401  "query location - Query current screen or location\r\n"
1402  "query volume - Query the current playback volume\r\n"
1403  "query recordings - List currently available recordings\r\n"
1404  "query recording CHANID STARTTIME\r\n"
1405  " - List info about the specified program\r\n"
1406  "query liveTV - List current TV schedule\r\n"
1407  "query liveTV CHANID - Query current program for specified channel\r\n"
1408  "query load - List 1/5/15 load averages\r\n"
1409  "query memstats - List free and total, physical and swap memory\r\n"
1410  "query time - Query current time on frontend\r\n"
1411  "query uptime - Query machine uptime\r\n"
1412  "query verbose - Get current VERBOSE mask\r\n"
1413  "query version - Query Frontend version details\r\n"
1414  "query channels - Query available channels\r\n"
1415  "query channels START LIMIT - Query available channels from START and limit results to LIMIT lines\r\n";
1416  }
1417  else if (is_abbrev("set", command))
1418  {
1419  helpText +=
1420  "set verbose debug-mask - "
1421  "Change the VERBOSE mask to 'debug-mask'\r\n"
1422  " (i.e. 'set verbose playback,audio')\r\n"
1423  " use 'set verbose default' to revert\r\n"
1424  " back to the default level of\r\n";
1425  }
1426  else if (is_abbrev("screenshot", command))
1427  {
1428  helpText +=
1429  "screenshot - Takes a screenshot and saves it as screenshot.png\r\n"
1430  "screenshot WxH - Saves the screenshot as a WxH size image\r\n";
1431  }
1432  else if (command == "exit")
1433  {
1434  helpText +=
1435  "exit - Terminates session\r\n\r\n";
1436  }
1437  else if ((is_abbrev("message", command)))
1438  {
1439  helpText +=
1440  "message - Displays a simple text message popup\r\n";
1441  }
1442  else if ((is_abbrev("notification", command)))
1443  {
1444  helpText +=
1445  "notification - Displays a simple text message notification\r\n";
1446  }
1447  else if (is_abbrev("theme", command))
1448  {
1449  helpText +=
1450  "theme getthemeinfo - Display the name and location of the current theme\r\n"
1451  "theme reload - Reload the theme\r\n"
1452  "theme showborders - Toggle showing widget borders\r\n"
1453  "theme shownames ON/OFF - Toggle showing widget names\r\n"
1454  "theme getwidgetnames PATH - Display the name and type of all the child widgets from PATH\r\n"
1455  "theme getarea WIDGETNAME - Get the area of widget WIDGET on the active screen\r\n"
1456  "theme setarea WIDGETNAME X Y W H - Change the area of widget WIDGET to X Y W H on the active screen\r\n";
1457  }
1458 
1459  if (!helpText.isEmpty())
1460  return helpText;
1461 
1462  if (!command.isEmpty())
1463  helpText += QString("Unknown command '%1'\r\n\r\n").arg(command);
1464 
1465  helpText +=
1466  "Valid Commands:\r\n"
1467  "---------------\r\n"
1468  "jump - Jump to a specified location in Myth\r\n"
1469  "key - Send a keypress to the program\r\n"
1470  "play - Playback related commands\r\n"
1471  "query - Queries\r\n"
1472  "set - Changes\r\n"
1473  "screenshot - Capture screenshot\r\n"
1474  "message - Display a simple text message\r\n"
1475  "notification - Display a simple text notification\r\n"
1476  "theme - Theme related commands\r\n"
1477  "exit - Exit Network Control\r\n"
1478  "\r\n"
1479  "Type 'help COMMANDNAME' for help on any specific command.\r\n";
1480 
1481  return helpText;
1482 }
1483 
1485 {
1486  if (nc->getArgCount() < 2)
1487  return QString("ERROR: See 'help %1' for usage information")
1488  .arg(nc->getArg(0));
1489 
1490  QString message = nc->getCommand().remove(0, 7).trimmed();
1491  MythMainWindow *window = GetMythMainWindow();
1492  MythEvent* me = new MythEvent(MythEvent::MythUserMessage, message);
1493  qApp->postEvent(window, me);
1494  return QString("OK");
1495 }
1496 
1498 {
1499  if (nc->getArgCount() < 2)
1500  return QString("ERROR: See 'help %1' for usage information")
1501  .arg(nc->getArg(0));
1502 
1503  QString message = nc->getCommand().remove(0, 12).trimmed();
1504  MythNotification n(message, tr("Network Control"));
1506  return QString("OK");
1507 }
1508 
1510 {
1511  QCoreApplication::postEvent(
1512  this, new QEvent(kNetworkControlDataReadyEvent));
1513 }
1514 
1516  QString &reply)
1517 {
1518  if (!clients.contains(ncc))
1519  // NetworkControl instance is unaware of control client
1520  // assume connection to client has been terminated and bail
1521  return;
1522 
1523  QRegExp crlfRegEx("\r\n$");
1524  QRegExp crlfcrlfRegEx("\r\n.*\r\n");
1525 
1526  QTcpSocket *client = ncc->getSocket();
1527  QTextStream *clientStream = ncc->getTextStream();
1528 
1529  if (client && clientStream && client->state() == QTcpSocket::ConnectedState)
1530  {
1531  *clientStream << reply;
1532 
1533  if ((!reply.contains(crlfRegEx)) ||
1534  ( reply.contains(crlfcrlfRegEx)))
1535  *clientStream << "\r\n" << prompt;
1536 
1537  clientStream->flush();
1538  client->flush();
1539  }
1540 }
1541 
1543 {
1544  if (e->type() == MythEvent::MythEventMessage)
1545  {
1546  MythEvent *me = static_cast<MythEvent *>(e);
1547  const QString& message = me->Message();
1548 
1549  if (message.startsWith("MUSIC_CONTROL"))
1550  {
1551  QStringList tokens = message.simplified().split(" ");
1552  if ((tokens.size() >= 4) &&
1553  (tokens[1] == "ANSWER") &&
1554  (tokens[2] == gCoreContext->GetHostName()))
1555  {
1556  answer = tokens[3];
1557  for (int i = 4; i < tokens.size(); i++)
1558  answer += QString(" ") + tokens[i];
1559  gotAnswer = true;
1560  }
1561 
1562  }
1563  else if (message.startsWith("NETWORK_CONTROL"))
1564  {
1565  QStringList tokens = message.simplified().split(" ");
1566  if ((tokens.size() >= 3) &&
1567  (tokens[1] == "ANSWER"))
1568  {
1569  answer = tokens[2];
1570  for (int i = 3; i < tokens.size(); i++)
1571  answer += QString(" ") + tokens[i];
1572  gotAnswer = true;
1573  }
1574  else if ((tokens.size() >= 4) &&
1575  (tokens[1] == "RESPONSE"))
1576  {
1577 // int clientID = tokens[2].toInt();
1578  answer = tokens[3];
1579  for (int i = 4; i < tokens.size(); i++)
1580  answer += QString(" ") + tokens[i];
1581  gotAnswer = true;
1582  }
1583  }
1584  }
1585  else if (e->type() == kNetworkControlDataReadyEvent)
1586  {
1587  NetworkCommand *nc;
1588  QString reply;
1589 
1590  QMutexLocker locker(&clientLock);
1591  QMutexLocker nrLocker(&nrLock);
1592 
1593  while (!networkControlReplies.isEmpty())
1594  {
1595  nc = networkControlReplies.front();
1596  networkControlReplies.pop_front();
1597 
1598  reply = nc->getCommand();
1599 
1600  NetworkControlClient * ncc = nc->getClient();
1601  if (ncc)
1602  {
1603  sendReplyToClient(ncc, reply);
1604  }
1605  else //send to all clients
1606  {
1607  QList<NetworkControlClient *>::const_iterator it;
1608  for (it = clients.begin(); it != clients.end(); ++it)
1609  {
1610  NetworkControlClient *ncc2 = *it;
1611  if (ncc2)
1612  sendReplyToClient(ncc2, reply);
1613  }
1614  }
1615  delete nc;
1616  }
1617  }
1618  else if (e->type() == NetworkControlCloseEvent::kEventType)
1619  {
1620  NetworkControlCloseEvent *ncce = static_cast<NetworkControlCloseEvent*>(e);
1621  NetworkControlClient *ncc = ncce->getClient();
1622 
1623  deleteClient(ncc);
1624  }
1625 }
1626 
1627 QString NetworkControl::listSchedule(const QString& chanID) const
1628 {
1629  QString result("");
1630  MSqlQuery query(MSqlQuery::InitCon());
1631  bool appendCRLF = true;
1632  QString queryStr("SELECT chanid, starttime, endtime, title, subtitle "
1633  "FROM program "
1634  "WHERE starttime < :START AND endtime > :END ");
1635 
1636  if (!chanID.isEmpty())
1637  {
1638  queryStr += " AND chanid = :CHANID";
1639  appendCRLF = false;
1640  }
1641 
1642  queryStr += " ORDER BY starttime, endtime, chanid";
1643 
1644  query.prepare(queryStr);
1645  query.bindValue(":START", MythDate::current());
1646  query.bindValue(":END", MythDate::current());
1647  if (!chanID.isEmpty())
1648  {
1649  query.bindValue(":CHANID", chanID);
1650  }
1651 
1652  if (query.exec())
1653  {
1654  while (query.next())
1655  {
1656  QString title = query.value(3).toString();
1657  QString subtitle = query.value(4).toString();
1658 
1659  if (!subtitle.isEmpty())
1660  title += QString(" -\"%1\"").arg(subtitle);
1661  QByteArray atitle = title.toLocal8Bit();
1662 
1663  result +=
1664  QString("%1 %2 %3 %4")
1665  .arg(QString::number(query.value(0).toInt())
1666  .rightJustified(5, ' '))
1667  .arg(MythDate::as_utc(query.value(1).toDateTime()).toString(Qt::ISODate))
1668  .arg(MythDate::as_utc(query.value(2).toDateTime()).toString(Qt::ISODate))
1669  .arg(atitle.constData());
1670 
1671  if (appendCRLF)
1672  result += "\r\n";
1673  }
1674  }
1675  else
1676  {
1677  result = "ERROR: Unable to retrieve current schedule list.";
1678  }
1679  return result;
1680 }
1681 
1682 QString NetworkControl::listRecordings(const QString& chanid, const QString& starttime)
1683 {
1684  QString result;
1685  MSqlQuery query(MSqlQuery::InitCon());
1686  QString queryStr;
1687  bool appendCRLF = true;
1688 
1689  queryStr = "SELECT chanid, starttime, title, subtitle "
1690  "FROM recorded WHERE deletepending = 0 ";
1691 
1692  if ((!chanid.isEmpty()) && (!starttime.isEmpty()))
1693  {
1694  queryStr += "AND chanid = " + chanid + " "
1695  "AND starttime = '" + starttime + "' ";
1696  appendCRLF = false;
1697  }
1698 
1699  queryStr += "ORDER BY starttime, title;";
1700 
1701  query.prepare(queryStr);
1702  if (query.exec())
1703  {
1704  QString episode, title, subtitle;
1705  while (query.next())
1706  {
1707  title = query.value(2).toString();
1708  subtitle = query.value(3).toString();
1709 
1710  if (!subtitle.isEmpty())
1711  episode = QString("%1 -\"%2\"")
1712  .arg(title)
1713  .arg(subtitle);
1714  else
1715  episode = title;
1716 
1717  result +=
1718  QString("%1 %2 %3").arg(query.value(0).toInt())
1719  .arg(MythDate::as_utc(query.value(1).toDateTime()).toString(Qt::ISODate))
1720  .arg(episode);
1721 
1722  if (appendCRLF)
1723  result += "\r\n";
1724  }
1725  }
1726  else
1727  result = "ERROR: Unable to retrieve recordings list.";
1728 
1729  return result;
1730 }
1731 
1732 QString NetworkControl::listChannels(const uint start, const uint limit) const
1733 {
1734  QString result;
1735  MSqlQuery query(MSqlQuery::InitCon());
1736  QString queryStr;
1737  uint cnt;
1738  uint maxcnt;
1739  uint sqlStart = start;
1740 
1741  // sql starts at zero, we want to start at 1
1742  if (sqlStart > 0)
1743  sqlStart--;
1744 
1745  queryStr = "select chanid, callsign, name from channel where visible=1";
1746  queryStr += " ORDER BY callsign";
1747 
1748  if (limit > 0) // only if a limit is specified, we limit the results
1749  {
1750  QString limitStr = QString(" LIMIT %1,%2").arg(sqlStart).arg(limit);
1751  queryStr += limitStr;
1752  }
1753 
1754  query.prepare(queryStr);
1755  if (!query.exec())
1756  {
1757  result = "ERROR: Unable to retrieve channel list.";
1758  return result;
1759  }
1760 
1761  maxcnt = query.size();
1762  cnt = 0;
1763  if (maxcnt == 0) // Feedback we have no usefull information
1764  {
1765  result += QString("0:0 0 \"Invalid\" \"Invalid\"");
1766  return result;
1767  }
1768 
1769  while (query.next())
1770  {
1771  // Feedback is as follow:
1772  // <current line count>:<max line count to expect> <channelid> <callsign name> <channel name>\r\n
1773  cnt++;
1774  result += QString("%1:%2 %3 \"%4\" \"%5\"\r\n")
1775  .arg(cnt).arg(maxcnt).arg(query.value(0).toInt())
1776  .arg(query.value(1).toString())
1777  .arg(query.value(2).toString());
1778  }
1779 
1780  return result;
1781 }
1782 
1784 {
1785  int width = 0;
1786  int height = 0;
1787 
1788  if (nc->getArgCount() == 2)
1789  {
1790  QStringList size = nc->getArg(1).split('x');
1791  if (size.size() == 2)
1792  {
1793  width = size[0].toInt();
1794  height = size[1].toInt();
1795  }
1796  }
1797 
1798  MythMainWindow *window = GetMythMainWindow();
1799  QStringList args;
1800  if (width && height)
1801  {
1802  args << QString::number(width);
1803  args << QString::number(height);
1804  }
1807  qApp->postEvent(window, me);
1808  return "OK";
1809 }
1810 
1811 QString NetworkCommand::getFrom(int arg)
1812 {
1813  QString c = m_command;
1814  for(int i=0 ; i<arg ; i++) {
1815  QString argstr = c.simplified().split(" ")[0];
1816  c = c.mid(argstr.length()).trimmed();
1817  }
1818  return c;
1819 }
1820 
1821 /* vim: set expandtab tabstop=4 shiftwidth=4: */
QTcpSocket * m_socket
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:782
QList< MythUIType * > * GetAllChildren(void)
Return a list of all child widgets.
Definition: mythuitype.cpp:196
void start(QThread::Priority=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:294
QString listChannels(const uint start, const uint limit) const
QList< NetworkCommand * > networkControlCommands
NetworkControlClient * getClient()
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:46
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:863
static bool is_abbrev(QString const &command, QString const &test, int minchars=1)
Is test an abbreviation of command ? The test substring must be at least minchars long.
QMap< QString, QString > jumpMap
void notifyDataAvailable(void)
QMap< int, QString > keyTextMap
NetworkControlClient(QTcpSocket *)
#define ACTION_SCREENSHOT
Definition: mythuiactions.h:22
QWidget * currentWidget(void)
QString toString(MarkTypes type)
QString processMessage(NetworkCommand *nc)
static Type MythEventMessage
Definition: mythevent.h:66
QString processNotification(NetworkCommand *nc)
void removeListener(QObject *listener)
Remove a listener to the observable.
QString getWidgetType(MythUIType *type)
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:125
bool wait(unsigned long time=ULONG_MAX)
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:311
QTcpSocket * getSocket()
void processNetworkControlCommand(NetworkCommand *nc)
int size(void) const
Definition: mythdbcon.h:203
QString current_iso_string(bool stripped)
Returns current Date and Time in UTC as a string.
Definition: mythdate.cpp:18
NetworkControlClient * getClient()
MythScreenStack * GetStack(const QString &stackname)
#define FE_SHORT_TO
unsigned int uint
Definition: compat.h:140
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
QString processTheme(NetworkCommand *nc)
QString GetCurrentLocation(bool fullPath=false, bool mainStackOnly=true)
bool getMemStats(int &totalMB, int &freeMB, int &totalVM, int &freeVM)
Returns memory statistics in megabytes.
MythScreenStack * GetMainStack()
void addListener(QObject *listener)
Add a listener to the observable.
QString GetThemeDir(void)
The base class on which all widgets and screens are based.
Definition: mythuitype.h:63
QDateTime as_utc(const QDateTime &old_dt)
Returns copy of QDateTime with TimeSpec set to UTC.
Definition: mythdate.cpp:23
QString processHelp(NetworkCommand *nc)
#define ACTION_HANDLEMEDIA
Definition: mythuiactions.h:21
QString getFrom(int arg)
QVariant value(int i) const
Definition: mythdbcon.h:198
This class is used as a container for messages.
Definition: mythevent.h:16
QString processQuery(NetworkCommand *nc)
virtual MythScreenType * GetTopScreen(void) const
QList< NetworkCommand * > networkControlReplies
void newConnection(QTcpSocket *client)
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
Wrapper around QRect allowing us to handle percentage and other relative values for areas in mythui.
Definition: mythrect.h:17
#define MYTH_PROTO_VERSION
Increment this whenever the MythTV network protocol changes.
Definition: mythversion.h:48
static Type MythUserMessage
Definition: mythevent.h:67
#define LOC
bool Queue(const MythNotification &notification)
Queue a notification Queue() is thread-safe and can be called from anywhere.
string hostname
Definition: caa.py:17
static MSqlQueryInfo InitCon(ConnectionReuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
void JumpTo(const QString &destination, bool pop=true)
static QEvent::Type kNetworkControlDataReadyEvent
QString processPlay(NetworkCommand *nc, int clientID)
QString listSchedule(const QString &chanID="") const
#define getloadavg(x, y)
Definition: compat.h:322
QWaitCondition ncCond
MythUIHelper * GetMythUI()
void commandReceived(QString &)
void dispatch(const MythEvent &event)
MythMainWindow * GetMythMainWindow(void)
QString listRecordings(const QString &chanid="", const QString &starttime="")
QString GetThemeName(void)
int GetNumSetting(const QString &key, int defaultval=0)
void sendReplyToClient(NetworkControlClient *ncc, QString &reply)
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:807
bool IsTopScreenInitialized(void)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
MThread * commandThread
QString getCommand()
QMap< QString, int > keyMap
void receiveCommand(QString &command)
QString getArg(int arg)
QString processKey(NetworkCommand *nc)
void customEvent(QEvent *e) override
void run(void) override
#define MYTH_BINARY_VERSION
Update this whenever the plug-in ABI changes.
Definition: mythversion.h:16
QString saveScreenshot(NetworkCommand *nc)
void deleteClient(void)
string themeName
Definition: mythburn.py:189
QTextStream * m_textStream
int verboseArgParse(const QString &arg)
Parse the –verbose commandline argument and set the verbose level.
Definition: logging.cpp:984
void ResetScreensaver(void)
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:603
Screen in which all other widgets are contained and rendered.
const QString & Message() const
Definition: mythevent.h:58
QTextStream * getTextStream()
QString GetHostName(void)
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Definition: mythuitype.cpp:132
#define FE_LONG_TO
void SendSystemEvent(const QString &msg)
bool getUptime(time_t &uptime)
Returns uptime statistics.
QString processJump(NetworkCommand *nc)
QList< NetworkControlClient * > clients
Default UTC.
Definition: mythdate.h:14
MythNotificationCenter * GetNotificationCenter(void)
QString processSet(NetworkCommand *nc)
QString verboseString
Definition: logging.cpp:108