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