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