6#include <QCoreApplication>
11#include <QRegularExpression>
12#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
13#include <QStringConverter>
25#include "libmythbase/mythversion.h"
56#define LOC QString("NetworkControl: ")
57#define LOC_ERR QString("NetworkControl Error: ")
63 (QEvent::Type) QEvent::registerEventType();
65 (QEvent::Type) QEvent::registerEventType();
69 { R
"(^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ?$)" };
79 QString
const& test,
int minchars = 1)
81 if (test.length() < minchars)
82 return command.toLower() == test.toLower();
83 return test.toLower() == command.left(test.length()).toLower();
87 m_commandThread(new
MThread(
"NetworkControl", this))
90 m_jumpMap[
"channelpriorities"] =
"Channel Recording Priorities";
93 m_jumpMap[
"managerecordings"] =
"Manage Recordings / Fix Conflicts";
101 m_jumpMap[
"playlistview"] =
"Play music";
102 m_jumpMap[
"programfinder"] =
"Program Finder";
103 m_jumpMap[
"programguide"] =
"Program Guide";
105 m_jumpMap[
"musicplaylists"] =
"Select music playlists";
106 m_jumpMap[
"playbackrecordings"] =
"TV Recording Playback";
107 m_jumpMap[
"videobrowser"] =
"Video Browser";
108 m_jumpMap[
"videogallery"] =
"Video Gallery";
109 m_jumpMap[
"videolistings"] =
"Video Listings";
110 m_jumpMap[
"videomanager"] =
"Video Manager";
111 m_jumpMap[
"zoneminderconsole"] =
"ZoneMinder Console";
112 m_jumpMap[
"zoneminderliveview"] =
"ZoneMinder Live View";
113 m_jumpMap[
"zoneminderevents"] =
"ZoneMinder Events";
115 m_jumpMap[
"channelrecpriority"] =
"Channel Recording Priorities";
116 m_jumpMap[
"viewscheduled"] =
"Manage Recordings / Fix Conflicts";
117 m_jumpMap[
"previousbox"] =
"Previously Recorded";
118 m_jumpMap[
"progfinder"] =
"Program Finder";
119 m_jumpMap[
"guidegrid"] =
"Program Guide";
120 m_jumpMap[
"managerecrules"] =
"Manage Recording Rules";
121 m_jumpMap[
"statusbox"] =
"Status Screen";
122 m_jumpMap[
"playbackbox"] =
"TV Recording Playback";
123 m_jumpMap[
"pbb"] =
"TV Recording Playback";
125 m_jumpMap[
"reloadtheme"] =
"Reload Theme";
126 m_jumpMap[
"showborders"] =
"Toggle Show Widget Borders";
127 m_jumpMap[
"shownames"] =
"Toggle Show Widget Names";
136 m_keyMap[
"return"] = Qt::Key_Return;
137 m_keyMap[
"pageup"] = Qt::Key_PageUp;
138 m_keyMap[
"pagedown"] = Qt::Key_PageDown;
139 m_keyMap[
"escape"] = Qt::Key_Escape;
141 m_keyMap[
"backtab"] = Qt::Key_Backtab;
143 m_keyMap[
"backspace"] = Qt::Key_Backspace;
144 m_keyMap[
"insert"] = Qt::Key_Insert;
145 m_keyMap[
"delete"] = Qt::Key_Delete;
152 m_keyMap[
"underscore"] = Qt::Key_Underscore;
154 m_keyMap[
"period"] = Qt::Key_Period;
156 m_keyMap[
"numbersign"] = Qt::Key_NumberSign;
157 m_keyMap[
"poundsign"] = Qt::Key_NumberSign;
158 m_keyMap[
"hash"] = Qt::Key_NumberSign;
160 m_keyMap[
"bracketleft"] = Qt::Key_BracketLeft;
161 m_keyMap[
"["] = Qt::Key_BracketLeft;
162 m_keyMap[
"bracketright"] = Qt::Key_BracketRight;
163 m_keyMap[
"]"] = Qt::Key_BracketRight;
164 m_keyMap[
"backslash"] = Qt::Key_Backslash;
166 m_keyMap[
"dollar"] = Qt::Key_Dollar;
168 m_keyMap[
"percent"] = Qt::Key_Percent;
170 m_keyMap[
"ampersand"] = Qt::Key_Ampersand;
172 m_keyMap[
"parenleft"] = Qt::Key_ParenLeft;
174 m_keyMap[
"parenright"] = Qt::Key_ParenRight;
176 m_keyMap[
"asterisk"] = Qt::Key_Asterisk;
178 m_keyMap[
"question"] = Qt::Key_Question;
184 m_keyMap[
"semicolon"] = Qt::Key_Semicolon;
190 m_keyMap[
"greater"] = Qt::Key_Greater;
266 "mythfrontend shutting down, connection closing...");
331 else if ((nc->
getArg(0).toLower() ==
"exit") || (nc->
getArg(0).toLower() ==
"quit"))
333 QCoreApplication::postEvent(
this,
336 else if (! nc->
getArg(0).isEmpty())
338 result = QString(
"INVALID command '%1', try 'help' for more info")
351 LOG(VB_GENERAL, LOG_INFO,
LOC +
"Client Socket disconnected");
356 for (
auto * ncc : std::as_const(
m_clients))
358 if (ncc->getSocket()->state() == QTcpSocket::UnconnectedState)
377 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"deleteClient(%1), unable to "
378 "locate specified NetworkControlClient").arg((
long long)ncc));
386 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"New connection established."));
397 connect(client, &QAbstractSocket::disconnected,
400 welcomeStr =
"MythFrontend Network Control\r\n";
401 welcomeStr +=
"Type 'help' for usage information\r\n"
402 "---------------------------------";
412 m_textStream(new QTextStream(s))
414#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
432 auto *socket = (QTcpSocket *)sender();
436 while (socket->canReadLine())
438 QString lineIn = socket->readLine();
440 static const QRegularExpression
badChars
441 {
"[^-a-zA-Z0-9\\s\\.:_#/$%&()*+,;<=>?\\[\\]\\|]" };
445 lineIn = lineIn.simplified();
446 if (lineIn.isEmpty())
449 LOG(VB_NETWORK, LOG_INFO,
LOC +
450 QString(
"emit commandReceived(%1)").arg(lineIn));
457 LOG(VB_NETWORK, LOG_INFO,
LOC +
458 QString(
"NetworkControl::receiveCommand(%1)").arg(command));
459 auto *ncc = qobject_cast<NetworkControlClient *>(sender());
471 QString result =
"OK";
474 return QString(
"ERROR: See 'help %1' for usage information")
485 std::this_thread::sleep_for(10ms);
492 QString result =
"OK";
493 QKeyEvent *
event =
nullptr;
496 return QString(
"ERROR: See 'help %1' for usage information")
499 QObject *keyDest =
nullptr;
504 return {
"ERROR: Application has no main window!\n"};
507 while (curToken < nc->getArgCount())
509 int tokenLen = nc->
getArg(curToken).length();
511 if (nc->
getArg(curToken) ==
"sleep")
513 std::this_thread::sleep_for(1s);
525 event =
new QKeyEvent(QEvent::KeyPress, keyCode, Qt::NoModifier,
527 QCoreApplication::postEvent(keyDest, event);
529 event =
new QKeyEvent(QEvent::KeyRelease, keyCode, Qt::NoModifier,
531 QCoreApplication::postEvent(keyDest, event);
533 else if (((tokenLen == 1) &&
534 (nc->
getArg(curToken).at(0).isLetterOrNumber())) ||
536 (nc->
getArg(curToken).contains(
"+"))))
538 QKeySequence a(nc->
getArg(curToken));
539#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
541 Qt::KeyboardModifiers modifiers = Qt::NoModifier;
545 QStringList tokenParts = nc->
getArg(curToken).split(
'+');
548 while (partNum < (tokenParts.size() - 1))
550 if (tokenParts[partNum].toUpper() ==
"CTRL")
551 modifiers |= Qt::ControlModifier;
552 if (tokenParts[partNum].toUpper() ==
"SHIFT")
553 modifiers |= Qt::ShiftModifier;
554 if (tokenParts[partNum].toUpper() ==
"ALT")
555 modifiers |= Qt::AltModifier;
556 if (tokenParts[partNum].toUpper() ==
"META")
557 modifiers |= Qt::MetaModifier;
563 int keyCode = a[0].key();
564 Qt::KeyboardModifiers modifiers = a[0].keyboardModifiers();
568 if (nc->
getArg(curToken) == nc->
getArg(curToken).toUpper())
569 modifiers |= Qt::ShiftModifier;
574 event =
new QKeyEvent(QEvent::KeyPress, keyCode, modifiers,
576 QCoreApplication::postEvent(keyDest, event);
578 event =
new QKeyEvent(QEvent::KeyRelease, keyCode, modifiers,
580 QCoreApplication::postEvent(keyDest, event);
584 return QString(
"ERROR: Invalid syntax at '%1', see 'help %2' for "
597 QString result =
"OK";
601 return QString(
"ERROR: See 'help %1' for usage information")
607 if (
GetMythUI()->GetCurrentLocation().toLower() !=
"mainmenu")
614 (
GetMythUI()->GetCurrentLocation().toLower() !=
"mainmenu"))
615 std::this_thread::sleep_for(10ms);
618 if (
GetMythUI()->GetCurrentLocation().toLower() ==
"mainmenu")
627 return {
"Unable to change to main menu to start playback!"};
635 if (
GetMythUI()->GetCurrentLocation().toLower() ==
"playback")
637 QString msg = QString(
"NETWORK_CONTROL STOP");
644 (
GetMythUI()->GetCurrentLocation().toLower() ==
"playback"))
645 std::this_thread::sleep_for(10ms);
648 if (
GetMythUI()->GetCurrentLocation().toLower() !=
"playbackbox")
654 while (!timer.hasExpired(10000) &&
655 (
GetMythUI()->GetCurrentLocation().toLower() !=
"playbackbox"))
656 std::this_thread::sleep_for(10ms);
660 std::this_thread::sleep_for(10ms);
663 if (
GetMythUI()->GetCurrentLocation().toLower() ==
"playbackbox")
669 QString msg = QString(
"NETWORK_CONTROL %1 PROGRAM %2 %3 %4")
672 QString::number(clientID));
683 std::this_thread::sleep_for(10ms);
688 result =
"ERROR: Timed out waiting for reply from player";
693 result = QString(
"ERROR: Unable to change to PlaybackBox from "
694 "%1, cannot play requested file.")
701 if (
GetMythUI()->GetCurrentLocation().toLower() !=
"playmusic")
703 return QString(
"ERROR: You are in %1 mode and this command is "
704 "only for MythMusic")
714 message = QString(
"MUSIC_COMMAND %1 PLAY").arg(
hostname);
716 message = QString(
"MUSIC_COMMAND %1 PAUSE").arg(
hostname);
718 message = QString(
"MUSIC_COMMAND %1 STOP").arg(
hostname);
730 qApp->processEvents();
731 std::this_thread::sleep_for(10ms);
750 qApp->processEvents();
751 std::this_thread::sleep_for(10ms);
770 qApp->processEvents();
771 std::this_thread::sleep_for(10ms);
781 return {
"ERROR: Invalid 'play music' command"};
788 message = QString(
"MUSIC_COMMAND %1 SET_VOLUME %2")
793 message = QString(
"MUSIC_COMMAND %1 PLAY_TRACK %2")
798 message = QString(
"MUSIC_COMMAND %1 PLAY_URL %2")
803 message = QString(
"MUSIC_COMMAND %1 PLAY_FILE '%2'")
808 return {
"ERROR: Invalid 'play music' command"};
813 return {
"ERROR: Invalid 'play music' command"};
818 else if (
GetMythUI()->GetCurrentLocation().toLower() !=
"playback")
820 return QString(
"ERROR: You are in %1 mode and this command is only "
827 message = QString(
"NETWORK_CONTROL CHANID %1").arg(nc->
getArg(2));
829 return QString(
"ERROR: See 'help %1' for usage information")
834 static const QRegularExpression kChanID2RE {
"^[-\\.\\d_#]+$" };
837 return "ERROR: See 'help play' for usage information";
840 message =
"NETWORK_CONTROL CHANNEL UP";
842 message =
"NETWORK_CONTROL CHANNEL DOWN";
843 else if (nc->
getArg(2).contains(kChanID2RE))
844 message = QString(
"NETWORK_CONTROL CHANNEL %1").arg(nc->
getArg(2));
846 return QString(
"ERROR: See 'help %1' for usage information")
851 static const QRegularExpression kSeekTimeRE { R
"(^\d\d:\d\d:\d\d$)" };
854 return QString(
"ERROR: See 'help %1' for usage information")
858 message =
"NETWORK_CONTROL SEEK BEGINNING";
860 message =
"NETWORK_CONTROL SEEK FORWARD";
863 message =
"NETWORK_CONTROL SEEK BACKWARD";
864 else if (nc->
getArg(2).contains(kSeekTimeRE))
866 int hours = nc->
getArg(2).mid(0, 2).toInt();
867 int minutes = nc->
getArg(2).mid(3, 2).toInt();
868 int seconds = nc->
getArg(2).mid(6, 2).toInt();
869 message = QString(
"NETWORK_CONTROL SEEK POSITION %1")
870 .arg((hours * 3600) + (minutes * 60) + seconds);
874 return QString(
"ERROR: See 'help %1' for usage information")
880 static const QRegularExpression kSpeed1RE { R
"(^\-*\d+x$)" };
881 static const QRegularExpression kSpeed2RE { R
"(^\-*\d+\/\d+x$)" };
882 static const QRegularExpression kSpeed3RE { R
"(^\-*\d*\.\d+x$)" };
885 return QString(
"ERROR: See 'help %1' for usage information")
888 QString token2 = nc->
getArg(2).toLower();
889 if ((token2.contains(kSpeed1RE)) ||
890 (token2.contains(kSpeed2RE)) ||
891 (token2.contains(kSpeed3RE)))
892 message = QString(
"NETWORK_CONTROL SPEED %1").arg(token2);
894 message = QString(
"NETWORK_CONTROL SPEED normal");
896 message = QString(
"NETWORK_CONTROL SPEED 0x");
898 return QString(
"ERROR: See 'help %1' for usage information")
908 message = QString(
"NETWORK_CONTROL STOP");
912 static const QRegularExpression kVolumeRE {
"^\\d+%?$" };
915 (!nc->
getArg(2).toLower().contains(kVolumeRE)))
917 return QString(
"ERROR: See 'help %1' for usage information")
921 message = QString(
"NETWORK_CONTROL VOLUME %1")
922 .arg(nc->
getArg(2).toLower());
926 static const QRegularExpression kNumberRE {
"^\\d+$" };
928 message = QString(
"NETWORK_CONTROL SUBTITLES 0");
929 else if (!nc->
getArg(2).toLower().contains(kNumberRE))
931 return QString(
"ERROR: See 'help %1' for usage information")
936 message = QString(
"NETWORK_CONTROL SUBTITLES %1")
942 return QString(
"ERROR: See 'help %1' for usage information")
946 if (!message.isEmpty())
966 QString result =
"OK";
969 return QString(
"ERROR: See 'help %1' for usage information")
974 bool fullPath =
false;
975 bool mainStackOnly =
true;
978 fullPath = (nc->
getArg(2).toLower() ==
"true" || nc->
getArg(2) ==
"1");
980 mainStackOnly = (nc->
getArg(3).toLower() ==
"true" || nc->
getArg(3) ==
"1");
986 if (location ==
"Playback")
990 QString message = QString(
"NETWORK_CONTROL QUERY POSITION");
997 std::this_thread::sleep_for(10ms);
1002 result =
"ERROR: Timed out waiting for reply from player";
1019 return QString(
"VERSION: %1/%2 %3 %4 QT/%5 DBSchema/%6")
1022 MYTH_BINARY_VERSION,
1025 QString::number(dbSchema));
1035 std::chrono::seconds uptime = 0s;
1038 str = QString::number(uptime.count());
1040 str = QString(
"Could not determine uptime.");
1048 str = QString(
"getloadavg() failed");
1050 str = QString(
"%1 %2 %3").arg(loads[0]).arg(loads[1]).arg(loads[2]);
1061 if (
getMemStats(totalMB, freeMB, totalVM, freeVM))
1063 str = QString(
"%1 %2 %3 %4")
1064 .arg(totalMB).arg(freeMB).arg(totalVM).arg(freeVM);
1068 str = QString(
"Could not determine memory stats.");
1078 if (location !=
"Playback")
1082 QString message = QString(
"NETWORK_CONTROL QUERY VOLUME");
1086 QElapsedTimer timer;
1089 std::this_thread::sleep_for(10ms);
1094 str =
"ERROR: Timed out waiting for reply from player";
1115 nc->
getArg(3).toLower().toUInt());
1116 return QString(
"ERROR: See 'help %1' for usage information "
1117 "(parameters mismatch)").arg(nc->
getArg(0));
1121 return QString(
"ERROR: See 'help %1' for usage information")
1131 return QString(
"ERROR: See 'help %1' for usage information")
1134 if (nc->
getArg(1) ==
"verbose")
1137 return {
"ERROR: Missing filter name."};
1141 return QString(
"ERROR: Separate filters with commas with no "
1142 "space: playback,audio\r\n See 'help %1' for usage "
1143 "information").arg(nc->
getArg(0));
1147 QString result =
"OK";
1151 if (pva_result != 0 )
1155 result +=
" Previous filter: " + oldVerboseString +
"\r\n";
1158 LOG(VB_GENERAL, LOG_NOTICE,
1159 QString(
"Verbose mask changed, new level is: %1")
1165 return QString(
"ERROR: See 'help %1' for usage information")
1172 return "MythUIText";
1174 return "MythUITextEdit";
1176 return "MythUIGroup";
1178 return "MythUIButton";
1180 return "MythUICheckBox";
1182 return "MythUIShape";
1184 return "MythUIButtonList";
1186 return "MythUIImage";
1188 return "MythUISpinBox";
1189#if CONFIG_QTWEBENGINE
1191 return "MythUIWebBrowser";
1194 return "MythUIClock";
1196 return "MythUIStateType";
1198 return "MythUIProgressBar";
1200 return "MythUIButtonTree";
1202 return "MythUIScrollBar";
1204 return "MythUIVideo";
1206 return "MythUIGuideGrid";
1208 return "MythUIEditBar";
1216 return QString(
"ERROR: See 'help %1' for usage information")
1219 if (nc->
getArg(1) ==
"getthemeinfo")
1223 return QString(
"%1 - %2").arg(
themeName, themeDir);
1225 if (nc->
getArg(1) ==
"reload")
1231 if (nc->
getArg(1) ==
"showborders")
1237 if (nc->
getArg(1) ==
"shownames")
1243 if (nc->
getArg(1) ==
"getwidgetnames")
1248 path = nc->
getArg(2).split(
'/');
1260 return {
"ERROR: no top screen found!"};
1264 while (!path.isEmpty())
1266 QString childName = path.takeFirst();
1267 currType = currType->
GetChild(childName);
1269 return QString(
"ERROR: Failed to find child '%1'").arg(childName);
1275 for (
int i = 0; i < children->count(); i++)
1278 QString widgetName =
type->objectName();
1280 result += QString(
"%1 - %2\n\r").arg(widgetName, -20).arg(widgetType);
1285 if (nc->
getArg(1) ==
"getarea")
1288 return {
"ERROR: Missing widget name."};
1290 QString widgetName = nc->
getArg(2);
1291 QStringList path = widgetName.split(
'/');
1303 return {
"ERROR: no top screen found!"};
1307 while (path.count() > 1)
1309 QString childName = path.takeFirst();
1310 currType = currType->
GetChild(childName);
1312 return QString(
"ERROR: Failed to find child '%1'").arg(childName);
1317 return QString(
"ERROR: widget '%1' not found!").arg(widgetName);
1319 int x =
type->GetFullArea().x();
1320 int y =
type->GetFullArea().y();
1321 int w =
type->GetFullArea().width();
1322 int h =
type->GetFullArea().height();
1323 return QString(
"The area of '%1' is x:%2, y:%3, w:%4, h:%5")
1324 .arg(widgetName).arg(x).arg(y).arg(w).arg(h);
1326 if (nc->
getArg(1) ==
"setarea")
1329 return {
"ERROR: Missing widget name."};
1332 return {
"ERROR: Missing X, Y, Width or Height."};
1334 QString widgetName = nc->
getArg(2);
1335 QStringList path = widgetName.split(
'/');
1336 QString x = nc->
getArg(3);
1337 QString y = nc->
getArg(4);
1338 QString w = nc->
getArg(5);
1339 QString h = nc->
getArg(6);
1352 return {
"ERROR: no top screen found!"};
1354 while (path.count() > 1)
1356 QString childName = path.takeFirst();
1357 currType = currType->
GetChild(childName);
1359 return QString(
"ERROR: Failed to find child '%1'").arg(childName);
1364 return QString(
"ERROR: widget '%1' not found!").arg(widgetName);
1368 return QString(
"Changed area of '%1' to x:%2, y:%3, w:%4, h:%5")
1369 .arg(widgetName, x, y, w, h);
1372 return QString(
"ERROR: See 'help %1' for usage information")
1398 QMap<QString, QString>::Iterator it;
1400 "Usage: jump JUMPPOINT\r\n"
1402 "Where JUMPPOINT is one of the following:\r\n";
1406 helpText += it.key().leftJustified(20,
' ',
true) +
" - " +
1413 "key LETTER - Send the letter key specified\r\n"
1414 "key NUMBER - Send the number key specified\r\n"
1415 "key CODE - Send one of the following key codes\r\n"
1418 QMap<QString, int>::Iterator it;
1427 helpText += it.key();
1434 "play volume NUMBER% - Change volume to given percentage value\r\n"
1435 "play channel up - Change channel Up\r\n"
1436 "play channel down - Change channel Down\r\n"
1437 "play channel NUMBER - Change to a specific channel number\r\n"
1438 "play chanid NUMBER - Change to a specific channel id (chanid)\r\n"
1439 "play file FILENAME - Play FILENAME (FILENAME may be a file or a myth:// URL)\r\n"
1440 "play program CHANID yyyy-MM-ddThh:mm:ss\r\n"
1441 " - Play program with chanid & starttime\r\n"
1442 "play program CHANID yyyy-MM-ddThh:mm:ss resume\r\n"
1443 " - Resume program with chanid & starttime\r\n"
1444 "play save preview\r\n"
1445 " - Save preview image from current position\r\n"
1446 "play save preview FILENAME\r\n"
1447 " - Save preview image to FILENAME\r\n"
1448 "play save preview FILENAME WxH\r\n"
1449 " - Save preview image of size WxH\r\n"
1450 "play seek beginning - Seek to the beginning of the recording\r\n"
1451 "play seek forward - Skip forward in the video\r\n"
1452 "play seek backward - Skip backwards in the video\r\n"
1453 "play seek HH:MM:SS - Seek to a specific position\r\n"
1454 "play speed pause - Pause playback\r\n"
1455 "play speed normal - Playback at normal speed\r\n"
1456 "play speed 1x - Playback at normal speed\r\n"
1457 "play speed SPEEDx - Playback where SPEED must be a decimal\r\n"
1458 "play speed 1/8x - Playback at 1/8x speed\r\n"
1459 "play speed 1/4x - Playback at 1/4x speed\r\n"
1460 "play speed 1/3x - Playback at 1/3x speed\r\n"
1461 "play speed 1/2x - Playback at 1/2x speed\r\n"
1462 "play stop - Stop playback\r\n"
1463 "play subtitles [#] - Switch on indicated subtitle tracks\r\n"
1464 "play music play - Resume playback (MythMusic)\r\n"
1465 "play music pause - Pause playback (MythMusic)\r\n"
1466 "play music stop - Stop Playback (MythMusic)\r\n"
1467 "play music setvolume N - Set volume to number (MythMusic)\r\n"
1468 "play music getvolume - Get current volume (MythMusic)\r\n"
1469 "play music getmeta - Get metadata for current track (MythMusic)\r\n"
1470 "play music getstatus - Get music player status playing/paused/stopped (MythMusic)\r\n"
1471 "play music file NAME - Play specified file (MythMusic)\r\n"
1472 "play music track N - Switch to specified track (MythMusic)\r\n"
1473 "play music url URL - Play specified URL (MythMusic)\r\n";
1478 "query location - Query current screen or location\r\n"
1479 "query volume - Query the current playback volume\r\n"
1480 "query recordings - List currently available recordings\r\n"
1481 "query recording CHANID STARTTIME\r\n"
1482 " - List info about the specified program\r\n"
1483 "query liveTV - List current TV schedule\r\n"
1484 "query liveTV CHANID - Query current program for specified channel\r\n"
1485 "query load - List 1/5/15 load averages\r\n"
1486 "query memstats - List free and total, physical and swap memory\r\n"
1487 "query time - Query current time on frontend\r\n"
1488 "query uptime - Query machine uptime\r\n"
1489 "query verbose - Get current VERBOSE mask\r\n"
1490 "query version - Query Frontend version details\r\n"
1491 "query channels - Query available channels\r\n"
1492 "query channels START LIMIT - Query available channels from START and limit results to LIMIT lines\r\n";
1497 "set verbose debug-mask - "
1498 "Change the VERBOSE mask to 'debug-mask'\r\n"
1499 " (i.e. 'set verbose playback,audio')\r\n"
1500 " use 'set verbose default' to revert\r\n"
1501 " back to the default level of\r\n";
1503 else if (
is_abbrev(
"screenshot", command))
1506 "screenshot - Takes a screenshot and saves it as screenshot.png\r\n"
1507 "screenshot WxH - Saves the screenshot as a WxH size image\r\n";
1509 else if (command ==
"exit")
1512 "exit - Terminates session\r\n\r\n";
1514 else if ((
is_abbrev(
"message", command)))
1517 "message - Displays a simple text message popup\r\n";
1519 else if ((
is_abbrev(
"notification", command)))
1522 "notification - Displays a simple text message notification\r\n";
1527 "theme getthemeinfo - Display the name and location of the current theme\r\n"
1528 "theme reload - Reload the theme\r\n"
1529 "theme showborders - Toggle showing widget borders\r\n"
1530 "theme shownames ON/OFF - Toggle showing widget names\r\n"
1531 "theme getwidgetnames PATH - Display the name and type of all the child widgets from PATH\r\n"
1532 "theme getarea WIDGETNAME - Get the area of widget WIDGET on the active screen\r\n"
1533 "theme setarea WIDGETNAME X Y W H - Change the area of widget WIDGET to X Y W H on the active screen\r\n";
1536 if (!helpText.isEmpty())
1539 if (!command.isEmpty())
1540 helpText += QString(
"Unknown command '%1'\r\n\r\n").arg(command);
1543 "Valid Commands:\r\n"
1544 "---------------\r\n"
1545 "jump - Jump to a specified location in Myth\r\n"
1546 "key - Send a keypress to the program\r\n"
1547 "play - Playback related commands\r\n"
1548 "query - Queries\r\n"
1550 "screenshot - Capture screenshot\r\n"
1551 "message - Display a simple text message\r\n"
1552 "notification - Display a simple text notification\r\n"
1553 "theme - Theme related commands\r\n"
1554 "exit - Exit Network Control\r\n"
1556 "Type 'help COMMANDNAME' for help on any specific command.\r\n";
1564 return QString(
"ERROR: See 'help %1' for usage information")
1567 QString message = nc->
getCommand().remove(0, 7).trimmed();
1570 qApp->postEvent(window, me);
1577 return QString(
"ERROR: See 'help %1' for usage information")
1580 QString message = nc->
getCommand().remove(0, 12).trimmed();
1588 QCoreApplication::postEvent(
1593 const QString &reply)
1602 static const QRegularExpression crlfRegEx(
"\r\n$");
1603 static const QRegularExpression crlfcrlfRegEx(
"\r\n.*\r\n");
1608 if (client && clientStream && client->state() == QTcpSocket::ConnectedState)
1610 *clientStream << reply;
1612 if ((!reply.contains(crlfRegEx)) ||
1613 ( reply.contains(crlfcrlfRegEx)))
1614 *clientStream <<
"\r\n" <<
m_prompt;
1616 clientStream->flush();
1625 auto *me =
dynamic_cast<MythEvent *
>(e);
1629 const QString& message = me->
Message();
1631 if (message.startsWith(
"MUSIC_CONTROL"))
1633 QStringList tokens = message.simplified().split(
" ");
1634 if ((tokens.size() >= 4) &&
1635 (tokens[1] ==
"ANSWER") &&
1639 for (
int i = 4; i < tokens.size(); i++)
1640 m_answer += QString(
" ") + tokens[i];
1645 else if (message.startsWith(
"NETWORK_CONTROL"))
1647 QStringList tokens = message.simplified().split(
" ");
1648 if ((tokens.size() >= 3) &&
1649 (tokens[1] ==
"ANSWER"))
1652 for (
int i = 3; i < tokens.size(); i++)
1653 m_answer += QString(
" ") + tokens[i];
1656 else if ((tokens.size() >= 4) &&
1657 (tokens[1] ==
"RESPONSE"))
1661 for (
int i = 4; i < tokens.size(); i++)
1662 m_answer += QString(
" ") + tokens[i];
1688 for (
auto * ncc2 : std::as_const(
m_clients))
1700 if (ncce ==
nullptr)
1712 bool appendCRLF =
true;
1713 QString queryStr(
"SELECT chanid, starttime, endtime, title, subtitle "
1715 "WHERE starttime < :START AND endtime > :END ");
1717 if (!chanID.isEmpty())
1719 queryStr +=
" AND chanid = :CHANID";
1723 queryStr +=
" ORDER BY starttime, endtime, chanid";
1728 if (!chanID.isEmpty())
1735 while (query.
next())
1737 QString title = query.
value(3).toString();
1738 QString subtitle = query.
value(4).toString();
1740 if (!subtitle.isEmpty())
1741 title += QString(
" -\"%1\"").arg(subtitle);
1742 QByteArray atitle = title.toLocal8Bit();
1745 QString(
"%1 %2 %3 %4")
1746 .arg(QString::number(query.
value(0).toInt()).rightJustified(5,
' '),
1757 result =
"ERROR: Unable to retrieve current schedule list.";
1767 bool appendCRLF =
true;
1769 queryStr =
"SELECT chanid, starttime, title, subtitle "
1770 "FROM recorded WHERE deletepending = 0 ";
1772 if ((!chanid.isEmpty()) && (!starttime.isEmpty()))
1774 queryStr +=
"AND chanid = " + chanid +
" "
1775 "AND starttime = '" + starttime +
"' ";
1779 queryStr +=
"ORDER BY starttime, title;";
1787 while (query.
next())
1789 title = query.
value(2).toString();
1790 subtitle = query.
value(3).toString();
1792 if (!subtitle.isEmpty())
1794 episode = QString(
"%1 -\"%2\"").arg(title, subtitle);
1803 .arg(query.
value(0).toString(),
1813 result =
"ERROR: Unable to retrieve recordings list.";
1824 uint sqlStart = start;
1830 queryStr =
"select chanid, callsign, name from channel "
1831 "where deleted IS NULL and visible > 0 "
1832 "ORDER BY callsign";
1836 QString limitStr = QString(
" LIMIT %1,%2").arg(sqlStart).arg(limit);
1837 queryStr += limitStr;
1843 result =
"ERROR: Unable to retrieve channel list.";
1851 result += QString(R
"(0:0 0 "Invalid" "Invalid")");
1855 while (query.
next())
1860 result += QString(
"%1:%2 %3 \"%4\" \"%5\"\r\n")
1861 .arg(cnt).arg(maxcnt)
1862 .arg(query.
value(0).toString(),
1863 query.
value(1).toString(),
1864 query.
value(2).toString());
1877 QStringList size = nc->
getArg(1).split(
'x');
1878 if (size.size() == 2)
1880 width = size[0].toInt();
1881 height = size[1].toInt();
1887 if (width && height)
1889 args << QString::number(width);
1890 args << QString::number(height);
1894 qApp->postEvent(window, me);
1901 for(
int i=0 ; i<arg ; i++) {
1902 QString argstr = c.simplified().split(
" ")[0];
1903 c = c.mid(argstr.length()).trimmed();
QSqlQuery wrapper that fetches a DB connection from the connection pool.
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
QVariant value(int i) const
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
This is a wrapper around QThread that does several additional things.
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
QString GetHostName(void)
void SendSystemEvent(const QString &msg)
void dispatch(const MythEvent &event)
int GetNumSetting(const QString &key, int defaultval=0)
This class is used as a container for messages.
const QString & Message() const
static const Type kMythEventMessage
static const Type kMythUserMessage
static void ResetScreensaver()
MythScreenStack * GetMainStack()
void JumpTo(const QString &Destination, bool Pop=true)
static bool IsTopScreenInitialized()
MythScreenStack * GetStack(const QString &Stackname)
bool Queue(const MythNotification ¬ification)
Queue a notification Queue() is thread-safe and can be called from anywhere.
void addListener(QObject *listener)
Add a listener to the observable.
void removeListener(QObject *listener)
Remove a listener to the observable.
Wrapper around QRect allowing us to handle percentage and other relative values for areas in mythui.
virtual MythScreenType * GetTopScreen(void) const
Screen in which all other widgets are contained and rendered.
A checkbox widget supporting three check states - on,off,half and two conditions - selected and unsel...
A simple text clock widget.
A narrow purpose widget used to represent cut positions and regions when editing a video.
Create a group of widgets.
A narrow purpose widget used to show television programs and the timeslots they occupy on channels.
Image widget, displays a single image or multiple images in sequence.
QString GetCurrentLocation(bool FullPath=false, bool MainStackOnly=true)
A widget for rendering primitive shapes and lines.
A widget for offering a range of numerical values where only the the bounding values and interval are...
This widget is used for grouping other widgets for display when a particular named state is called.
A text entry and edit widget.
All purpose text widget, displays a text string.
The base class on which all widgets and screens are based.
QList< MythUIType * > * GetAllChildren(void)
Return a list of all child widgets.
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Video widget, displays raw image data.
NetworkControlClient * getClient()
NetworkControlClient(QTcpSocket *s)
void commandReceived(QString &)
~NetworkControlClient() override
QTextStream * m_textStream
QTextStream * getTextStream()
static const Type kEventType
void processNetworkControlCommand(NetworkCommand *nc)
QList< NetworkCommand * > m_networkControlReplies
static QString processSet(NetworkCommand *nc)
static QString getWidgetType(MythUIType *type)
QRecursiveMutex m_clientLock
static QString processNotification(NetworkCommand *nc)
QString processJump(NetworkCommand *nc)
void receiveCommand(QString &command)
QList< NetworkControlClient * > m_clients
QString processQuery(NetworkCommand *nc)
QMap< int, QString > m_keyTextMap
static QString processMessage(NetworkCommand *nc)
void customEvent(QEvent *e) override
QString processTheme(NetworkCommand *nc)
static QString listChannels(uint start, uint limit)
QMap< QString, int > m_keyMap
QString processPlay(NetworkCommand *nc, int clientID)
static QString listSchedule(const QString &chanID="")
void newControlConnection(QTcpSocket *client)
QList< NetworkCommand * > m_networkControlCommands
MThread * m_commandThread
QString processKey(NetworkCommand *nc)
void notifyDataAvailable(void)
static QString listRecordings(const QString &chanid="", const QString &starttime="")
QString processHelp(NetworkCommand *nc)
void sendReplyToClient(NetworkControlClient *ncc, const QString &reply)
QMap< QString, QString > m_jumpMap
~NetworkControl() override
static QString saveScreenshot(NetworkCommand *nc)
void newConnection(QTcpSocket *)
int verboseArgParse(const QString &arg)
Parse the –verbose commandline argument and set the verbose level.
static const QRegularExpression badChars
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
MythNotificationCenter * GetNotificationCenter(void)
MythMainWindow * GetMythMainWindow(void)
bool getMemStats(int &totalMB, int &freeMB, int &totalVM, int &freeVM)
Returns memory statistics in megabytes.
loadArray getLoadAvgs(void)
Returns the system load averages.
bool getUptime(std::chrono::seconds &uptime)
Returns uptime statistics.
std::array< double, 3 > loadArray
static constexpr const char * ACTION_SCREENSHOT
static constexpr const char * ACTION_HANDLEMEDIA
MythUIHelper * GetMythUI()
const char * GetMythSourceVersion()
const char * GetMythSourcePath()
QString current_iso_string(bool stripped)
Returns current Date and Time in UTC as a string.
QDateTime as_utc(const QDateTime &old_dt)
Returns copy of QDateTime with TimeSpec set to UTC.
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
static constexpr qint64 FE_LONG_TO
static const QRegularExpression kStartTimeRE
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.
static const QRegularExpression kChanID1RE
static const QEvent::Type kNetworkControlDataReadyEvent
static constexpr qint64 FE_SHORT_TO