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