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