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"
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 {
331 QCoreApplication::postEvent(this,
333 }
334 else if (! nc->getArg(0).isEmpty())
335 {
336 result = QString("INVALID command '%1', try 'help' for more info")
337 .arg(nc->getArg(0));
338 }
339
340 m_nrLock.lock();
341 m_networkControlReplies.push_back(new NetworkCommand(nc->getClient(),result));
342 m_nrLock.unlock();
343
345}
346
348{
349 LOG(VB_GENERAL, LOG_INFO, LOC + "Client Socket disconnected");
350 QMutexLocker locker(&m_clientLock);
351
352 gCoreContext->SendSystemEvent("NET_CTRL_DISCONNECTED");
353
354 for (auto * ncc : std::as_const(m_clients))
355 {
356 if (ncc->getSocket()->state() == QTcpSocket::UnconnectedState)
357 {
358 deleteClient(ncc);
359 return;
360 }
361 }
362}
363
365{
366 int index = m_clients.indexOf(ncc);
367 if (index >= 0)
368 {
369 m_clients.removeAt(index);
370
371 delete ncc;
372 }
373 else
374 {
375 LOG(VB_GENERAL, LOG_ERR, LOC + QString("deleteClient(%1), unable to "
376 "locate specified NetworkControlClient").arg((long long)ncc));
377 }
378}
379
381{
382 QString welcomeStr;
383
384 LOG(VB_GENERAL, LOG_INFO, LOC + QString("New connection established."));
385
386 gCoreContext->SendSystemEvent("NET_CTRL_CONNECTED");
387
388 auto *ncc = new NetworkControlClient(client);
389
390 QMutexLocker locker(&m_clientLock);
391 m_clients.push_back(ncc);
392
395 connect(client, &QAbstractSocket::disconnected,
396 this, qOverload<>(&NetworkControl::deleteClient));
397
398 welcomeStr = "MythFrontend Network Control\r\n";
399 welcomeStr += "Type 'help' for usage information\r\n"
400 "---------------------------------";
401 m_nrLock.lock();
402 m_networkControlReplies.push_back(new NetworkCommand(ncc,welcomeStr));
403 m_nrLock.unlock();
404
406}
407
409 : m_socket(s),
410 m_textStream(new QTextStream(s))
411{
412#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
413 m_textStream->setCodec("UTF-8");
414#else
415 m_textStream->setEncoding(QStringConverter::Utf8);
416#endif
417 connect(m_socket, &QIODevice::readyRead, this, &NetworkControlClient::readClient);
418}
419
421{
422 m_socket->close();
423 m_socket->deleteLater();
424
425 delete m_textStream;
426}
427
429{
430 auto *socket = (QTcpSocket *)sender();
431 if (!socket)
432 return;
433
434 while (socket->canReadLine())
435 {
436 QString lineIn = socket->readLine();
437#if 0
438 static const QRegularExpression badChars
439 { "[^-a-zA-Z0-9\\s\\.:_#/$%&()*+,;<=>?\\[\\]\\|]" };
440 lineIn.remove(badChars);
441#endif
442
443 lineIn = lineIn.simplified();
444 if (lineIn.isEmpty())
445 continue;
446
447 LOG(VB_NETWORK, LOG_INFO, LOC +
448 QString("emit commandReceived(%1)").arg(lineIn));
449 emit commandReceived(lineIn);
450 }
451}
452
453void NetworkControl::receiveCommand(QString &command)
454{
455 LOG(VB_NETWORK, LOG_INFO, LOC +
456 QString("NetworkControl::receiveCommand(%1)").arg(command));
457 auto *ncc = qobject_cast<NetworkControlClient *>(sender());
458 if (!ncc)
459 return;
460
461 m_ncLock.lock();
462 m_networkControlCommands.push_back(new NetworkCommand(ncc,command));
463 m_ncCond.wakeOne();
464 m_ncLock.unlock();
465}
466
468{
469 QString result = "OK";
470
471 if ((nc->getArgCount() < 2) || (!m_jumpMap.contains(nc->getArg(1))))
472 return QString("ERROR: See 'help %1' for usage information")
473 .arg(nc->getArg(0));
474
476
477 // Fixme, should do some better checking here, but that would
478 // depend on all Locations matching their jumppoints
479 QElapsedTimer timer;
480 timer.start();
481 while (!timer.hasExpired(FE_SHORT_TO) &&
482 (GetMythUI()->GetCurrentLocation().toLower() != nc->getArg(1)))
483 std::this_thread::sleep_for(10ms);
484
485 return result;
486}
487
489{
490 QString result = "OK";
491 QKeyEvent *event = nullptr;
492
493 if (nc->getArgCount() < 2)
494 return QString("ERROR: See 'help %1' for usage information")
495 .arg(nc->getArg(0));
496
497 QObject *keyDest = nullptr;
498
499 if (GetMythMainWindow())
500 keyDest = GetMythMainWindow();
501 else
502 return {"ERROR: Application has no main window!\n"};
503
504 int curToken = 1;
505 while (curToken < nc->getArgCount())
506 {
507 int tokenLen = nc->getArg(curToken).length();
508
509 if (nc->getArg(curToken) == "sleep")
510 {
511 std::this_thread::sleep_for(1s);
512 }
513 else if (m_keyMap.contains(nc->getArg(curToken)))
514 {
515 int keyCode = m_keyMap[nc->getArg(curToken)];
516 QString keyText;
517
518 if (m_keyTextMap.contains(keyCode))
519 keyText = m_keyTextMap[keyCode];
520
522
523 event = new QKeyEvent(QEvent::KeyPress, keyCode, Qt::NoModifier,
524 keyText);
525 QCoreApplication::postEvent(keyDest, event);
526
527 event = new QKeyEvent(QEvent::KeyRelease, keyCode, Qt::NoModifier,
528 keyText);
529 QCoreApplication::postEvent(keyDest, event);
530 }
531 else if (((tokenLen == 1) &&
532 (nc->getArg(curToken).at(0).isLetterOrNumber())) ||
533 ((tokenLen >= 1) &&
534 (nc->getArg(curToken).contains("+"))))
535 {
536 QKeySequence a(nc->getArg(curToken));
537#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
538 int keyCode = a[0];
539 Qt::KeyboardModifiers modifiers = Qt::NoModifier;
540
541 if (tokenLen > 1)
542 {
543 QStringList tokenParts = nc->getArg(curToken).split('+');
544
545 int partNum = 0;
546 while (partNum < (tokenParts.size() - 1))
547 {
548 if (tokenParts[partNum].toUpper() == "CTRL")
549 modifiers |= Qt::ControlModifier;
550 if (tokenParts[partNum].toUpper() == "SHIFT")
551 modifiers |= Qt::ShiftModifier;
552 if (tokenParts[partNum].toUpper() == "ALT")
553 modifiers |= Qt::AltModifier;
554 if (tokenParts[partNum].toUpper() == "META")
555 modifiers |= Qt::MetaModifier;
556
557 partNum++;
558 }
559 }
560#else
561 int keyCode = a[0].key();
562 Qt::KeyboardModifiers modifiers = a[0].keyboardModifiers();
563#endif
564 if (tokenLen == 1)
565 {
566 if (nc->getArg(curToken) == nc->getArg(curToken).toUpper())
567 modifiers |= Qt::ShiftModifier;
568 }
569
571
572 event = new QKeyEvent(QEvent::KeyPress, keyCode, modifiers,
573 nc->getArg(curToken));
574 QCoreApplication::postEvent(keyDest, event);
575
576 event = new QKeyEvent(QEvent::KeyRelease, keyCode, modifiers,
577 nc->getArg(curToken));
578 QCoreApplication::postEvent(keyDest, event);
579 }
580 else
581 {
582 return QString("ERROR: Invalid syntax at '%1', see 'help %2' for "
583 "usage information")
584 .arg(nc->getArg(curToken), nc->getArg(0));
585 }
586
587 curToken++;
588 }
589
590 return result;
591}
592
594{
595 QString result = "OK";
596 QString message;
597
598 if (nc->getArgCount() < 2)
599 return QString("ERROR: See 'help %1' for usage information")
600 .arg(nc->getArg(0));
601
602 if ((nc->getArgCount() >= 3) &&
603 (is_abbrev("file", nc->getArg(1))))
604 {
605 if (GetMythUI()->GetCurrentLocation().toLower() != "mainmenu")
606 {
607 GetMythMainWindow()->JumpTo(m_jumpMap["mainmenu"]);
608
609 QElapsedTimer timer;
610 timer.start();
611 while (!timer.hasExpired(FE_LONG_TO) &&
612 (GetMythUI()->GetCurrentLocation().toLower() != "mainmenu"))
613 std::this_thread::sleep_for(10ms);
614 }
615
616 if (GetMythUI()->GetCurrentLocation().toLower() == "mainmenu")
617 {
618 QStringList args;
619 args << nc->getFrom(2);
620 auto *me = new MythEvent(ACTION_HANDLEMEDIA, args);
621 qApp->postEvent(GetMythMainWindow(), me);
622 }
623 else
624 {
625 return {"Unable to change to main menu to start playback!"};
626 }
627 }
628 else if ((nc->getArgCount() >= 4) &&
629 (is_abbrev("program", nc->getArg(1))) &&
630 (nc->getArg(2).contains(kChanID1RE)) &&
631 (nc->getArg(3).contains(kStartTimeRE)))
632 {
633 if (GetMythUI()->GetCurrentLocation().toLower() == "playback")
634 {
635 QString msg = QString("NETWORK_CONTROL STOP");
636 MythEvent me(msg);
638
639 QElapsedTimer timer;
640 timer.start();
641 while (!timer.hasExpired(FE_LONG_TO) &&
642 (GetMythUI()->GetCurrentLocation().toLower() == "playback"))
643 std::this_thread::sleep_for(10ms);
644 }
645
646 if (GetMythUI()->GetCurrentLocation().toLower() != "playbackbox")
647 {
648 GetMythMainWindow()->JumpTo(m_jumpMap["playbackbox"]);
649
650 QElapsedTimer timer;
651 timer.start();
652 while (!timer.hasExpired(10000) &&
653 (GetMythUI()->GetCurrentLocation().toLower() != "playbackbox"))
654 std::this_thread::sleep_for(10ms);
655
656 timer.start();
657 while (!timer.hasExpired(10000) && (!MythMainWindow::IsTopScreenInitialized()))
658 std::this_thread::sleep_for(10ms);
659 }
660
661 if (GetMythUI()->GetCurrentLocation().toLower() == "playbackbox")
662 {
663 QString action = "PLAY";
664 if (nc->getArgCount() == 5 && nc->getArg(4) == "resume")
665 action = "RESUME";
666
667 QString msg = QString("NETWORK_CONTROL %1 PROGRAM %2 %3 %4")
668 .arg(action, nc->getArg(2),
669 nc->getArg(3).toUpper(),
670 QString::number(clientID));
671
672 result.clear();
673 m_gotAnswer = false;
674 QElapsedTimer timer;
675 timer.start();
676
677 MythEvent me(msg);
679
680 while (!timer.hasExpired(FE_LONG_TO) && !m_gotAnswer)
681 std::this_thread::sleep_for(10ms);
682
683 if (m_gotAnswer)
684 result += m_answer;
685 else
686 result = "ERROR: Timed out waiting for reply from player";
687
688 }
689 else
690 {
691 result = QString("ERROR: Unable to change to PlaybackBox from "
692 "%1, cannot play requested file.")
693 .arg(GetMythUI()->GetCurrentLocation());
694 }
695 }
696 else if (is_abbrev("music", nc->getArg(1)))
697 {
698#if 0
699 if (GetMythUI()->GetCurrentLocation().toLower() != "playmusic")
700 {
701 return QString("ERROR: You are in %1 mode and this command is "
702 "only for MythMusic")
703 .arg(GetMythUI()->GetCurrentLocation());
704 }
705#endif
706
707 QString hostname = gCoreContext->GetHostName();
708
709 if (nc->getArgCount() == 3)
710 {
711 if (is_abbrev("play", nc->getArg(2)))
712 message = QString("MUSIC_COMMAND %1 PLAY").arg(hostname);
713 else if (is_abbrev("pause", nc->getArg(2)))
714 message = QString("MUSIC_COMMAND %1 PAUSE").arg(hostname);
715 else if (is_abbrev("stop", nc->getArg(2)))
716 message = QString("MUSIC_COMMAND %1 STOP").arg(hostname);
717 else if (is_abbrev("getvolume", nc->getArg(2)))
718 {
719 m_gotAnswer = false;
720
721 MythEvent me(QString("MUSIC_COMMAND %1 GET_VOLUME").arg(hostname));
723
724 QElapsedTimer timer;
725 timer.start();
726 while (!timer.hasExpired(FE_SHORT_TO) && !m_gotAnswer)
727 {
728 qApp->processEvents();
729 std::this_thread::sleep_for(10ms);
730 }
731
732 if (m_gotAnswer)
733 return m_answer;
734
735 return "unknown";
736 }
737 else if (is_abbrev("getmeta", nc->getArg(2)))
738 {
739 m_gotAnswer = false;
740
741 MythEvent me(QString("MUSIC_COMMAND %1 GET_METADATA").arg(hostname));
743
744 QElapsedTimer timer;
745 timer.start();
746 while (!timer.hasExpired(FE_SHORT_TO) && !m_gotAnswer)
747 {
748 qApp->processEvents();
749 std::this_thread::sleep_for(10ms);
750 }
751
752 if (m_gotAnswer)
753 return m_answer;
754
755 return "unknown";
756 }
757 else if (is_abbrev("getstatus", nc->getArg(2)))
758 {
759 m_gotAnswer = false;
760
761 MythEvent me(QString("MUSIC_COMMAND %1 GET_STATUS").arg(hostname));
763
764 QElapsedTimer timer;
765 timer.start();
766 while (!timer.hasExpired(FE_SHORT_TO) && !m_gotAnswer)
767 {
768 qApp->processEvents();
769 std::this_thread::sleep_for(10ms);
770 }
771
772 if (m_gotAnswer)
773 return m_answer;
774
775 return "unknown";
776 }
777 else
778 {
779 return {"ERROR: Invalid 'play music' command"};
780 }
781 }
782 else if (nc->getArgCount() > 3)
783 {
784 if (is_abbrev("setvolume", nc->getArg(2)))
785 {
786 message = QString("MUSIC_COMMAND %1 SET_VOLUME %2")
787 .arg(hostname, nc->getArg(3));
788 }
789 else if (is_abbrev("track", nc->getArg(2)))
790 {
791 message = QString("MUSIC_COMMAND %1 PLAY_TRACK %2")
792 .arg(hostname, nc->getArg(3));
793 }
794 else if (is_abbrev("url", nc->getArg(2)))
795 {
796 message = QString("MUSIC_COMMAND %1 PLAY_URL %2")
797 .arg(hostname, nc->getArg(3));
798 }
799 else if (is_abbrev("file", nc->getArg(2)))
800 {
801 message = QString("MUSIC_COMMAND %1 PLAY_FILE '%2'")
802 .arg(hostname, nc->getFrom(3));
803 }
804 else
805 {
806 return {"ERROR: Invalid 'play music' command"};
807 }
808 }
809 else
810 {
811 return {"ERROR: Invalid 'play music' command"};
812 }
813 }
814 // Everything below here requires us to be in playback mode so check to
815 // see if we are
816 else if (GetMythUI()->GetCurrentLocation().toLower() != "playback")
817 {
818 return QString("ERROR: You are in %1 mode and this command is only "
819 "for playback mode")
820 .arg(GetMythUI()->GetCurrentLocation());
821 }
822 else if (is_abbrev("chanid", nc->getArg(1), 5))
823 {
824 if (nc->getArg(2).contains(kChanID1RE))
825 message = QString("NETWORK_CONTROL CHANID %1").arg(nc->getArg(2));
826 else
827 return QString("ERROR: See 'help %1' for usage information")
828 .arg(nc->getArg(0));
829 }
830 else if (is_abbrev("channel", nc->getArg(1), 5))
831 {
832 static const QRegularExpression kChanID2RE { "^[-\\.\\d_#]+$" };
833
834 if (nc->getArgCount() < 3)
835 return "ERROR: See 'help play' for usage information";
836
837 if (is_abbrev("up", nc->getArg(2)))
838 message = "NETWORK_CONTROL CHANNEL UP";
839 else if (is_abbrev("down", nc->getArg(2)))
840 message = "NETWORK_CONTROL CHANNEL DOWN";
841 else if (nc->getArg(2).contains(kChanID2RE))
842 message = QString("NETWORK_CONTROL CHANNEL %1").arg(nc->getArg(2));
843 else
844 return QString("ERROR: See 'help %1' for usage information")
845 .arg(nc->getArg(0));
846 }
847 else if (is_abbrev("seek", nc->getArg(1), 2))
848 {
849 static const QRegularExpression kSeekTimeRE { R"(^\d\d:\d\d:\d\d$)" };
850
851 if (nc->getArgCount() < 3)
852 return QString("ERROR: See 'help %1' for usage information")
853 .arg(nc->getArg(0));
854
855 if (is_abbrev("beginning", nc->getArg(2)))
856 message = "NETWORK_CONTROL SEEK BEGINNING";
857 else if (is_abbrev("forward", nc->getArg(2)))
858 message = "NETWORK_CONTROL SEEK FORWARD";
859 else if (is_abbrev("rewind", nc->getArg(2)) ||
860 is_abbrev("backward", nc->getArg(2)))
861 message = "NETWORK_CONTROL SEEK BACKWARD";
862 else if (nc->getArg(2).contains(kSeekTimeRE))
863 {
864 int hours = nc->getArg(2).mid(0, 2).toInt();
865 int minutes = nc->getArg(2).mid(3, 2).toInt();
866 int seconds = nc->getArg(2).mid(6, 2).toInt();
867 message = QString("NETWORK_CONTROL SEEK POSITION %1")
868 .arg((hours * 3600) + (minutes * 60) + seconds);
869 }
870 else
871 {
872 return QString("ERROR: See 'help %1' for usage information")
873 .arg(nc->getArg(0));
874 }
875 }
876 else if (is_abbrev("speed", nc->getArg(1), 2))
877 {
878 static const QRegularExpression kSpeed1RE { R"(^\-*\d+x$)" };
879 static const QRegularExpression kSpeed2RE { R"(^\-*\d+\/\d+x$)" };
880 static const QRegularExpression kSpeed3RE { R"(^\-*\d*\.\d+x$)" };
881
882 if (nc->getArgCount() < 3)
883 return QString("ERROR: See 'help %1' for usage information")
884 .arg(nc->getArg(0));
885
886 QString token2 = nc->getArg(2).toLower();
887 if ((token2.contains(kSpeed1RE)) ||
888 (token2.contains(kSpeed2RE)) ||
889 (token2.contains(kSpeed3RE)))
890 message = QString("NETWORK_CONTROL SPEED %1").arg(token2);
891 else if (is_abbrev("normal", token2))
892 message = QString("NETWORK_CONTROL SPEED normal");
893 else if (is_abbrev("pause", token2))
894 message = QString("NETWORK_CONTROL SPEED 0x");
895 else
896 return QString("ERROR: See 'help %1' for usage information")
897 .arg(nc->getArg(0));
898 }
899 else if (is_abbrev("save", nc->getArg(1), 2))
900 {
901 if (is_abbrev("screenshot", nc->getArg(2), 2))
902 return saveScreenshot(nc);
903 }
904 else if (is_abbrev("stop", nc->getArg(1), 2))
905 {
906 message = QString("NETWORK_CONTROL STOP");
907 }
908 else if (is_abbrev("volume", nc->getArg(1), 2))
909 {
910 static const QRegularExpression kVolumeRE { "^\\d+%?$" };
911
912 if ((nc->getArgCount() < 3) ||
913 (!nc->getArg(2).toLower().contains(kVolumeRE)))
914 {
915 return QString("ERROR: See 'help %1' for usage information")
916 .arg(nc->getArg(0));
917 }
918
919 message = QString("NETWORK_CONTROL VOLUME %1")
920 .arg(nc->getArg(2).toLower());
921 }
922 else if (is_abbrev("subtitles", nc->getArg(1), 2))
923 {
924 static const QRegularExpression kNumberRE { "^\\d+$" };
925 if (nc->getArgCount() < 3)
926 message = QString("NETWORK_CONTROL SUBTITLES 0");
927 else if (!nc->getArg(2).toLower().contains(kNumberRE))
928 {
929 return QString("ERROR: See 'help %1' for usage information")
930 .arg(nc->getArg(0));
931 }
932 else
933 {
934 message = QString("NETWORK_CONTROL SUBTITLES %1")
935 .arg(nc->getArg(2));
936 }
937 }
938 else
939 {
940 return QString("ERROR: See 'help %1' for usage information")
941 .arg(nc->getArg(0));
942 }
943
944 if (!message.isEmpty())
945 {
946 // Don't broadcast this event as the TV object will see it
947 // twice: once directly from the Qt event system, and once
948 // because its filtering all events before they are sent to
949 // the main window. Its much easier to get a pointer to the
950 // MainWindow object than is is to a pointer to the TV object,
951 // so send the event directly there. The TV object will get
952 // it anyway because because of the filter hook. (The last
953 // third of this function requires you to be in playback mode
954 // so the TV object is guaranteed to exist.)
955 auto *me = new MythEvent(message);
956 qApp->postEvent(GetMythMainWindow(), me);
957 }
958
959 return result;
960}
961
963{
964 QString result = "OK";
965
966 if (nc->getArgCount() < 2)
967 return QString("ERROR: See 'help %1' for usage information")
968 .arg(nc->getArg(0));
969
970 if (is_abbrev("location", nc->getArg(1)))
971 {
972 bool fullPath = false;
973 bool mainStackOnly = true;
974
975 if (nc->getArgCount() > 2)
976 fullPath = (nc->getArg(2).toLower() == "true" || nc->getArg(2) == "1");
977 if (nc->getArgCount() > 3)
978 mainStackOnly = (nc->getArg(3).toLower() == "true" || nc->getArg(3) == "1");
979
980 QString location = GetMythUI()->GetCurrentLocation(fullPath, mainStackOnly);
981 result = location;
982
983 // if we're playing something, then find out what
984 if (location == "Playback")
985 {
986 result += " ";
987 m_gotAnswer = false;
988 QString message = QString("NETWORK_CONTROL QUERY POSITION");
989 MythEvent me(message);
991
992 QElapsedTimer timer;
993 timer.start();
994 while (!timer.hasExpired(FE_SHORT_TO) && !m_gotAnswer)
995 std::this_thread::sleep_for(10ms);
996
997 if (m_gotAnswer)
998 result += m_answer;
999 else
1000 result = "ERROR: Timed out waiting for reply from player";
1001 }
1002 }
1003 else if (is_abbrev("verbose", nc->getArg(1)))
1004 {
1005 return verboseString;
1006 }
1007 else if (is_abbrev("liveTV", nc->getArg(1)))
1008 {
1009 if(nc->getArgCount() == 3) // has a channel ID
1010 return listSchedule(nc->getArg(2));
1011 return listSchedule();
1012 }
1013 else if (is_abbrev("version", nc->getArg(1)))
1014 {
1015 int dbSchema = gCoreContext->GetNumSetting("DBSchemaVer");
1016
1017 return QString("VERSION: %1/%2 %3 %4 QT/%5 DBSchema/%6")
1018 .arg(GetMythSourceVersion(),
1020 MYTH_BINARY_VERSION,
1021 MYTH_PROTO_VERSION,
1022 QT_VERSION_STR,
1023 QString::number(dbSchema));
1024
1025 }
1026 else if(is_abbrev("time", nc->getArg(1)))
1027 {
1029 }
1030 else if (is_abbrev("uptime", nc->getArg(1)))
1031 {
1032 QString str;
1033 std::chrono::seconds uptime = 0s;
1034
1035 if (getUptime(uptime))
1036 str = QString::number(uptime.count());
1037 else
1038 str = QString("Could not determine uptime.");
1039 return str;
1040 }
1041 else if (is_abbrev("load", nc->getArg(1)))
1042 {
1043 QString str;
1044 loadArray loads = getLoadAvgs();
1045 if (loads[0] == -1)
1046 str = QString("getloadavg() failed");
1047 else
1048 str = QString("%1 %2 %3").arg(loads[0]).arg(loads[1]).arg(loads[2]);
1049 return str;
1050 }
1051 else if (is_abbrev("memstats", nc->getArg(1)))
1052 {
1053 QString str;
1054 int totalMB = 0;
1055 int freeMB = 0;
1056 int totalVM = 0;
1057 int freeVM = 0;
1058
1059 if (getMemStats(totalMB, freeMB, totalVM, freeVM))
1060 {
1061 str = QString("%1 %2 %3 %4")
1062 .arg(totalMB).arg(freeMB).arg(totalVM).arg(freeVM);
1063 }
1064 else
1065 {
1066 str = QString("Could not determine memory stats.");
1067 }
1068 return str;
1069 }
1070 else if (is_abbrev("volume", nc->getArg(1)))
1071 {
1072 QString str = "0%";
1073
1074 QString location = GetMythUI()->GetCurrentLocation(false, false);
1075
1076 if (location != "Playback")
1077 return str;
1078
1079 m_gotAnswer = false;
1080 QString message = QString("NETWORK_CONTROL QUERY VOLUME");
1081 MythEvent me(message);
1083
1084 QElapsedTimer timer;
1085 timer.start();
1086 while (!timer.hasExpired(FE_SHORT_TO) && !m_gotAnswer)
1087 std::this_thread::sleep_for(10ms);
1088
1089 if (m_gotAnswer)
1090 str = m_answer;
1091 else
1092 str = "ERROR: Timed out waiting for reply from player";
1093
1094 return str;
1095 }
1096 else if ((nc->getArgCount() == 4) &&
1097 is_abbrev("recording", nc->getArg(1)) &&
1098 (nc->getArg(2).contains(kChanID1RE)) &&
1099 (nc->getArg(3).contains(kStartTimeRE)))
1100 {
1101 return listRecordings(nc->getArg(2), nc->getArg(3).toUpper());
1102 }
1103 else if (is_abbrev("recordings", nc->getArg(1)))
1104 {
1105 return listRecordings();
1106 }
1107 else if (is_abbrev("channels", nc->getArg(1)))
1108 {
1109 if (nc->getArgCount() == 2)
1110 return listChannels(0, 0); // give us all you can
1111 if (nc->getArgCount() == 4)
1112 return listChannels(nc->getArg(2).toLower().toUInt(),
1113 nc->getArg(3).toLower().toUInt());
1114 return QString("ERROR: See 'help %1' for usage information "
1115 "(parameters mismatch)").arg(nc->getArg(0));
1116 }
1117 else
1118 {
1119 return QString("ERROR: See 'help %1' for usage information")
1120 .arg(nc->getArg(0));
1121 }
1122
1123 return result;
1124}
1125
1127{
1128 if (nc->getArgCount() == 1)
1129 return QString("ERROR: See 'help %1' for usage information")
1130 .arg(nc->getArg(0));
1131
1132 if (nc->getArg(1) == "verbose")
1133 {
1134 if (nc->getArgCount() < 3)
1135 return {"ERROR: Missing filter name."};
1136
1137 if (nc->getArgCount() > 3)
1138 {
1139 return QString("ERROR: Separate filters with commas with no "
1140 "space: playback,audio\r\n See 'help %1' for usage "
1141 "information").arg(nc->getArg(0));
1142 }
1143
1144 QString oldVerboseString = verboseString;
1145 QString result = "OK";
1146
1147 int pva_result = verboseArgParse(nc->getArg(2));
1148
1149 if (pva_result != 0 /*GENERIC_EXIT_OK */)
1150 result = "Failed";
1151
1152 result += "\r\n";
1153 result += " Previous filter: " + oldVerboseString + "\r\n";
1154 result += " New Filter: " + verboseString + "\r\n";
1155
1156 LOG(VB_GENERAL, LOG_NOTICE,
1157 QString("Verbose mask changed, new level is: %1")
1158 .arg(verboseString));
1159
1160 return result;
1161 }
1162
1163 return QString("ERROR: See 'help %1' for usage information")
1164 .arg(nc->getArg(0));
1165}
1166
1168{
1169 if (dynamic_cast<MythUIText *>(type))
1170 return "MythUIText";
1171 if (dynamic_cast<MythUITextEdit *>(type))
1172 return "MythUITextEdit";
1173 if (dynamic_cast<MythUIGroup *>(type))
1174 return "MythUIGroup";
1175 if (dynamic_cast<MythUIButton *>(type))
1176 return "MythUIButton";
1177 if (dynamic_cast<MythUICheckBox *>(type))
1178 return "MythUICheckBox";
1179 if (dynamic_cast<MythUIShape *>(type))
1180 return "MythUIShape";
1181 if (dynamic_cast<MythUIButtonList *>(type))
1182 return "MythUIButtonList";
1183 if (dynamic_cast<MythUIImage *>(type))
1184 return "MythUIImage";
1185 if (dynamic_cast<MythUISpinBox *>(type))
1186 return "MythUISpinBox";
1187#if CONFIG_QTWEBENGINE
1188 if (dynamic_cast<MythUIWebBrowser *>(type))
1189 return "MythUIWebBrowser";
1190#endif
1191 if (dynamic_cast<MythUIClock *>(type))
1192 return "MythUIClock";
1193 if (dynamic_cast<MythUIStateType *>(type))
1194 return "MythUIStateType";
1195 if (dynamic_cast<MythUIProgressBar *>(type))
1196 return "MythUIProgressBar";
1197 if (dynamic_cast<MythUIButtonTree *>(type))
1198 return "MythUIButtonTree";
1199 if (dynamic_cast<MythUIScrollBar *>(type))
1200 return "MythUIScrollBar";
1201 if (dynamic_cast<MythUIVideo *>(type))
1202 return "MythUIVideo";
1203 if (dynamic_cast<MythUIGuideGrid *>(type))
1204 return "MythUIGuideGrid";
1205 if (dynamic_cast<MythUIEditBar *>(type))
1206 return "MythUIEditBar";
1207
1208 return "Unknown";
1209}
1210
1212{
1213 if (nc->getArgCount() == 1)
1214 return QString("ERROR: See 'help %1' for usage information")
1215 .arg(nc->getArg(0));
1216
1217 if (nc->getArg(1) == "getthemeinfo")
1218 {
1219 QString themeName = GetMythUI()->GetThemeName();
1220 QString themeDir = GetMythUI()->GetThemeDir();
1221 return QString("%1 - %2").arg(themeName, themeDir);
1222 }
1223 if (nc->getArg(1) == "reload")
1224 {
1225 GetMythMainWindow()->JumpTo(m_jumpMap["reloadtheme"]);
1226
1227 return "OK";
1228 }
1229 if (nc->getArg(1) == "showborders")
1230 {
1231 GetMythMainWindow()->JumpTo(m_jumpMap["showborders"]);
1232
1233 return "OK";
1234 }
1235 if (nc->getArg(1) == "shownames")
1236 {
1237 GetMythMainWindow()->JumpTo(m_jumpMap["shownames"]);
1238
1239 return "OK";
1240 }
1241 if (nc->getArg(1) == "getwidgetnames")
1242 {
1243 QStringList path;
1244
1245 if (nc->getArgCount() >= 3)
1246 path = nc->getArg(2).split('/');
1247
1248 MythScreenStack *stack = GetMythMainWindow()->GetStack("popup stack");
1249 MythScreenType *topScreen = stack->GetTopScreen();
1250
1251 if (!topScreen)
1252 {
1253 stack = GetMythMainWindow()->GetMainStack();
1254 topScreen = stack->GetTopScreen();
1255 }
1256
1257 if (!topScreen)
1258 return {"ERROR: no top screen found!"};
1259
1260 MythUIType *currType = topScreen;
1261
1262 while (!path.isEmpty())
1263 {
1264 QString childName = path.takeFirst();
1265 currType = currType->GetChild(childName);
1266 if (!currType)
1267 return QString("ERROR: Failed to find child '%1'").arg(childName);
1268 }
1269
1270 QList<MythUIType*> *children = currType->GetAllChildren();
1271 QString result;
1272
1273 for (int i = 0; i < children->count(); i++)
1274 {
1275 MythUIType *type = children->at(i);
1276 QString widgetName = type->objectName();
1277 QString widgetType = getWidgetType(type);
1278 result += QString("%1 - %2\n\r").arg(widgetName, -20).arg(widgetType);
1279 }
1280
1281 return result;
1282 }
1283 if (nc->getArg(1) == "getarea")
1284 {
1285 if (nc->getArgCount() < 3)
1286 return {"ERROR: Missing widget name."};
1287
1288 QString widgetName = nc->getArg(2);
1289 QStringList path = widgetName.split('/');
1290
1291 MythScreenStack *stack = GetMythMainWindow()->GetStack("popup stack");
1292 MythScreenType *topScreen = stack->GetTopScreen();
1293
1294 if (!topScreen)
1295 {
1296 stack = GetMythMainWindow()->GetMainStack();
1297 topScreen = stack->GetTopScreen();
1298 }
1299
1300 if (!topScreen)
1301 return {"ERROR: no top screen found!"};
1302
1303 MythUIType *currType = topScreen;
1304
1305 while (path.count() > 1)
1306 {
1307 QString childName = path.takeFirst();
1308 currType = currType->GetChild(childName);
1309 if (!currType)
1310 return QString("ERROR: Failed to find child '%1'").arg(childName);
1311 }
1312
1313 MythUIType* type = currType->GetChild(path.first());
1314 if (!type)
1315 return QString("ERROR: widget '%1' not found!").arg(widgetName);
1316
1317 int x = type->GetFullArea().x();
1318 int y = type->GetFullArea().y();
1319 int w = type->GetFullArea().width();
1320 int h = type->GetFullArea().height();
1321 return QString("The area of '%1' is x:%2, y:%3, w:%4, h:%5")
1322 .arg(widgetName).arg(x).arg(y).arg(w).arg(h);
1323 }
1324 if (nc->getArg(1) == "setarea")
1325 {
1326 if (nc->getArgCount() < 3)
1327 return {"ERROR: Missing widget name."};
1328
1329 if (nc->getArgCount() < 7)
1330 return {"ERROR: Missing X, Y, Width or Height."};
1331
1332 QString widgetName = nc->getArg(2);
1333 QStringList path = widgetName.split('/');
1334 QString x = nc->getArg(3);
1335 QString y = nc->getArg(4);
1336 QString w = nc->getArg(5);
1337 QString h = nc->getArg(6);
1338
1339 MythScreenStack *stack = GetMythMainWindow()->GetStack("popup stack");
1340 MythScreenType *topScreen = stack->GetTopScreen();
1341
1342 if (!topScreen)
1343 {
1344 stack = GetMythMainWindow()->GetMainStack();
1345 topScreen = stack->GetTopScreen();
1346 }
1347
1348 MythUIType *currType = topScreen;
1349 if (!topScreen)
1350 return {"ERROR: no top screen found!"};
1351
1352 while (path.count() > 1)
1353 {
1354 QString childName = path.takeFirst();
1355 currType = currType->GetChild(childName);
1356 if (!currType)
1357 return QString("ERROR: Failed to find child '%1'").arg(childName);
1358 }
1359
1360 MythUIType* type = currType->GetChild(path.first());
1361 if (!type)
1362 return QString("ERROR: widget '%1' not found!").arg(widgetName);
1363
1364 type->SetArea(MythRect(x, y, w, h));
1365
1366 return QString("Changed area of '%1' to x:%2, y:%3, w:%4, h:%5")
1367 .arg(widgetName, x, y, w, h);
1368 }
1369
1370 return QString("ERROR: See 'help %1' for usage information")
1371 .arg(nc->getArg(0));
1372}
1373
1375{
1376 QString command;
1377 QString helpText;
1378
1379 if (nc->getArgCount() >= 1)
1380 {
1381 if (is_abbrev("help", nc->getArg(0)))
1382 {
1383 if (nc->getArgCount() >= 2)
1384 command = nc->getArg(1);
1385 else
1386 command.clear();
1387 }
1388 else
1389 {
1390 command = nc->getArg(0);
1391 }
1392 }
1393
1394 if (is_abbrev("jump", command))
1395 {
1396 QMap<QString, QString>::Iterator it;
1397 helpText +=
1398 "Usage: jump JUMPPOINT\r\n"
1399 "\r\n"
1400 "Where JUMPPOINT is one of the following:\r\n";
1401
1402 for (it = m_jumpMap.begin(); it != m_jumpMap.end(); ++it)
1403 {
1404 helpText += it.key().leftJustified(20, ' ', true) + " - " +
1405 *it + "\r\n";
1406 }
1407 }
1408 else if (is_abbrev("key", command))
1409 {
1410 helpText +=
1411 "key LETTER - Send the letter key specified\r\n"
1412 "key NUMBER - Send the number key specified\r\n"
1413 "key CODE - Send one of the following key codes\r\n"
1414 "\r\n";
1415
1416 QMap<QString, int>::Iterator it;
1417 bool first = true;
1418 for (it = m_keyMap.begin(); it != m_keyMap.end(); ++it)
1419 {
1420 if (first)
1421 first = false;
1422 else
1423 helpText += ", ";
1424
1425 helpText += it.key();
1426 }
1427 helpText += "\r\n";
1428 }
1429 else if (is_abbrev("play", command))
1430 {
1431 helpText +=
1432 "play volume NUMBER% - Change volume to given percentage value\r\n"
1433 "play channel up - Change channel Up\r\n"
1434 "play channel down - Change channel Down\r\n"
1435 "play channel NUMBER - Change to a specific channel number\r\n"
1436 "play chanid NUMBER - Change to a specific channel id (chanid)\r\n"
1437 "play file FILENAME - Play FILENAME (FILENAME may be a file or a myth:// URL)\r\n"
1438 "play program CHANID yyyy-MM-ddThh:mm:ss\r\n"
1439 " - Play program with chanid & starttime\r\n"
1440 "play program CHANID yyyy-MM-ddThh:mm:ss resume\r\n"
1441 " - Resume program with chanid & starttime\r\n"
1442 "play save preview\r\n"
1443 " - Save preview image from current position\r\n"
1444 "play save preview FILENAME\r\n"
1445 " - Save preview image to FILENAME\r\n"
1446 "play save preview FILENAME WxH\r\n"
1447 " - Save preview image of size WxH\r\n"
1448 "play seek beginning - Seek to the beginning of the recording\r\n"
1449 "play seek forward - Skip forward in the video\r\n"
1450 "play seek backward - Skip backwards in the video\r\n"
1451 "play seek HH:MM:SS - Seek to a specific position\r\n"
1452 "play speed pause - Pause playback\r\n"
1453 "play speed normal - Playback at normal speed\r\n"
1454 "play speed 1x - Playback at normal speed\r\n"
1455 "play speed SPEEDx - Playback where SPEED must be a decimal\r\n"
1456 "play speed 1/8x - Playback at 1/8x speed\r\n"
1457 "play speed 1/4x - Playback at 1/4x speed\r\n"
1458 "play speed 1/3x - Playback at 1/3x speed\r\n"
1459 "play speed 1/2x - Playback at 1/2x speed\r\n"
1460 "play stop - Stop playback\r\n"
1461 "play subtitles [#] - Switch on indicated subtitle tracks\r\n"
1462 "play music play - Resume playback (MythMusic)\r\n"
1463 "play music pause - Pause playback (MythMusic)\r\n"
1464 "play music stop - Stop Playback (MythMusic)\r\n"
1465 "play music setvolume N - Set volume to number (MythMusic)\r\n"
1466 "play music getvolume - Get current volume (MythMusic)\r\n"
1467 "play music getmeta - Get metadata for current track (MythMusic)\r\n"
1468 "play music getstatus - Get music player status playing/paused/stopped (MythMusic)\r\n"
1469 "play music file NAME - Play specified file (MythMusic)\r\n"
1470 "play music track N - Switch to specified track (MythMusic)\r\n"
1471 "play music url URL - Play specified URL (MythMusic)\r\n";
1472 }
1473 else if (is_abbrev("query", command))
1474 {
1475 helpText +=
1476 "query location - Query current screen or location\r\n"
1477 "query volume - Query the current playback volume\r\n"
1478 "query recordings - List currently available recordings\r\n"
1479 "query recording CHANID STARTTIME\r\n"
1480 " - List info about the specified program\r\n"
1481 "query liveTV - List current TV schedule\r\n"
1482 "query liveTV CHANID - Query current program for specified channel\r\n"
1483 "query load - List 1/5/15 load averages\r\n"
1484 "query memstats - List free and total, physical and swap memory\r\n"
1485 "query time - Query current time on frontend\r\n"
1486 "query uptime - Query machine uptime\r\n"
1487 "query verbose - Get current VERBOSE mask\r\n"
1488 "query version - Query Frontend version details\r\n"
1489 "query channels - Query available channels\r\n"
1490 "query channels START LIMIT - Query available channels from START and limit results to LIMIT lines\r\n";
1491 }
1492 else if (is_abbrev("set", command))
1493 {
1494 helpText +=
1495 "set verbose debug-mask - "
1496 "Change the VERBOSE mask to 'debug-mask'\r\n"
1497 " (i.e. 'set verbose playback,audio')\r\n"
1498 " use 'set verbose default' to revert\r\n"
1499 " back to the default level of\r\n";
1500 }
1501 else if (is_abbrev("screenshot", command))
1502 {
1503 helpText +=
1504 "screenshot - Takes a screenshot and saves it as screenshot.png\r\n"
1505 "screenshot WxH - Saves the screenshot as a WxH size image\r\n";
1506 }
1507 else if (command == "exit")
1508 {
1509 helpText +=
1510 "exit - Terminates session\r\n\r\n";
1511 }
1512 else if ((is_abbrev("message", command)))
1513 {
1514 helpText +=
1515 "message - Displays a simple text message popup\r\n";
1516 }
1517 else if ((is_abbrev("notification", command)))
1518 {
1519 helpText +=
1520 "notification - Displays a simple text message notification\r\n";
1521 }
1522 else if (is_abbrev("theme", command))
1523 {
1524 helpText +=
1525 "theme getthemeinfo - Display the name and location of the current theme\r\n"
1526 "theme reload - Reload the theme\r\n"
1527 "theme showborders - Toggle showing widget borders\r\n"
1528 "theme shownames ON/OFF - Toggle showing widget names\r\n"
1529 "theme getwidgetnames PATH - Display the name and type of all the child widgets from PATH\r\n"
1530 "theme getarea WIDGETNAME - Get the area of widget WIDGET on the active screen\r\n"
1531 "theme setarea WIDGETNAME X Y W H - Change the area of widget WIDGET to X Y W H on the active screen\r\n";
1532 }
1533
1534 if (!helpText.isEmpty())
1535 return helpText;
1536
1537 if (!command.isEmpty())
1538 helpText += QString("Unknown command '%1'\r\n\r\n").arg(command);
1539
1540 helpText +=
1541 "Valid Commands:\r\n"
1542 "---------------\r\n"
1543 "jump - Jump to a specified location in Myth\r\n"
1544 "key - Send a keypress to the program\r\n"
1545 "play - Playback related commands\r\n"
1546 "query - Queries\r\n"
1547 "set - Changes\r\n"
1548 "screenshot - Capture screenshot\r\n"
1549 "message - Display a simple text message\r\n"
1550 "notification - Display a simple text notification\r\n"
1551 "theme - Theme related commands\r\n"
1552 "exit - Exit Network Control\r\n"
1553 "\r\n"
1554 "Type 'help COMMANDNAME' for help on any specific command.\r\n";
1555
1556 return helpText;
1557}
1558
1560{
1561 if (nc->getArgCount() < 2)
1562 return QString("ERROR: See 'help %1' for usage information")
1563 .arg(nc->getArg(0));
1564
1565 QString message = nc->getCommand().remove(0, 7).trimmed();
1567 auto* me = new MythEvent(MythEvent::kMythUserMessage, message);
1568 qApp->postEvent(window, me);
1569 return {"OK"};
1570}
1571
1573{
1574 if (nc->getArgCount() < 2)
1575 return QString("ERROR: See 'help %1' for usage information")
1576 .arg(nc->getArg(0));
1577
1578 QString message = nc->getCommand().remove(0, 12).trimmed();
1579 MythNotification n(message, tr("Network Control"));
1581 return {"OK"};
1582}
1583
1585{
1586 QCoreApplication::postEvent(
1587 this, new QEvent(kNetworkControlDataReadyEvent));
1588}
1589
1591 const QString &reply)
1592{
1593 if (!m_clients.contains(ncc))
1594 {
1595 // NetworkControl instance is unaware of control client
1596 // assume connection to client has been terminated and bail
1597 return;
1598 }
1599
1600 static const QRegularExpression crlfRegEx("\r\n$");
1601 static const QRegularExpression crlfcrlfRegEx("\r\n.*\r\n");
1602
1603 QTcpSocket *client = ncc->getSocket();
1604 QTextStream *clientStream = ncc->getTextStream();
1605
1606 if (client && clientStream && client->state() == QTcpSocket::ConnectedState)
1607 {
1608 *clientStream << reply;
1609
1610 if ((!reply.contains(crlfRegEx)) ||
1611 ( reply.contains(crlfcrlfRegEx)))
1612 *clientStream << "\r\n" << m_prompt;
1613
1614 clientStream->flush();
1615 client->flush();
1616 }
1617}
1618
1620{
1621 if (e->type() == MythEvent::kMythEventMessage)
1622 {
1623 auto *me = dynamic_cast<MythEvent *>(e);
1624 if (me == nullptr)
1625 return;
1626
1627 const QString& message = me->Message();
1628
1629 if (message.startsWith("MUSIC_CONTROL"))
1630 {
1631 QStringList tokens = message.simplified().split(" ");
1632 if ((tokens.size() >= 4) &&
1633 (tokens[1] == "ANSWER") &&
1634 (tokens[2] == gCoreContext->GetHostName()))
1635 {
1636 m_answer = tokens[3];
1637 for (int i = 4; i < tokens.size(); i++)
1638 m_answer += QString(" ") + tokens[i];
1639 m_gotAnswer = true;
1640 }
1641
1642 }
1643 else if (message.startsWith("NETWORK_CONTROL"))
1644 {
1645 QStringList tokens = message.simplified().split(" ");
1646 if ((tokens.size() >= 3) &&
1647 (tokens[1] == "ANSWER"))
1648 {
1649 m_answer = tokens[2];
1650 for (int i = 3; i < tokens.size(); i++)
1651 m_answer += QString(" ") + tokens[i];
1652 m_gotAnswer = true;
1653 }
1654 else if ((tokens.size() >= 4) &&
1655 (tokens[1] == "RESPONSE"))
1656 {
1657// int clientID = tokens[2].toInt();
1658 m_answer = tokens[3];
1659 for (int i = 4; i < tokens.size(); i++)
1660 m_answer += QString(" ") + tokens[i];
1661 m_gotAnswer = true;
1662 }
1663 }
1664 }
1665 else if (e->type() == kNetworkControlDataReadyEvent)
1666 {
1667 QString reply;
1668
1669 QMutexLocker locker(&m_clientLock);
1670 QMutexLocker nrLocker(&m_nrLock);
1671
1672 while (!m_networkControlReplies.isEmpty())
1673 {
1675 m_networkControlReplies.pop_front();
1676
1677 reply = nc->getCommand();
1678
1679 NetworkControlClient * ncc = nc->getClient();
1680 if (ncc)
1681 {
1682 sendReplyToClient(ncc, reply);
1683 }
1684 else //send to all clients
1685 {
1686 for (auto * ncc2 : std::as_const(m_clients))
1687 {
1688 if (ncc2)
1689 sendReplyToClient(ncc2, reply);
1690 }
1691 }
1692 delete nc;
1693 }
1694 }
1695 else if (e->type() == NetworkControlCloseEvent::kEventType)
1696 {
1697 auto *ncce = dynamic_cast<NetworkControlCloseEvent*>(e);
1698 if (ncce == nullptr)
1699 return;
1700
1701 NetworkControlClient *ncc = ncce->getClient();
1702 deleteClient(ncc);
1703 }
1704}
1705
1706QString NetworkControl::listSchedule(const QString& chanID)
1707{
1708 QString result("");
1710 bool appendCRLF = true;
1711 QString queryStr("SELECT chanid, starttime, endtime, title, subtitle "
1712 "FROM program "
1713 "WHERE starttime < :START AND endtime > :END ");
1714
1715 if (!chanID.isEmpty())
1716 {
1717 queryStr += " AND chanid = :CHANID";
1718 appendCRLF = false;
1719 }
1720
1721 queryStr += " ORDER BY starttime, endtime, chanid";
1722
1723 query.prepare(queryStr);
1724 query.bindValue(":START", MythDate::current());
1725 query.bindValue(":END", MythDate::current());
1726 if (!chanID.isEmpty())
1727 {
1728 query.bindValue(":CHANID", chanID);
1729 }
1730
1731 if (query.exec())
1732 {
1733 while (query.next())
1734 {
1735 QString title = query.value(3).toString();
1736 QString subtitle = query.value(4).toString();
1737
1738 if (!subtitle.isEmpty())
1739 title += QString(" -\"%1\"").arg(subtitle);
1740 QByteArray atitle = title.toLocal8Bit();
1741
1742 result +=
1743 QString("%1 %2 %3 %4")
1744 .arg(QString::number(query.value(0).toInt()).rightJustified(5, ' '),
1745 MythDate::as_utc(query.value(1).toDateTime()).toString(Qt::ISODate),
1746 MythDate::as_utc(query.value(2).toDateTime()).toString(Qt::ISODate),
1747 atitle);
1748
1749 if (appendCRLF)
1750 result += "\r\n";
1751 }
1752 }
1753 else
1754 {
1755 result = "ERROR: Unable to retrieve current schedule list.";
1756 }
1757 return result;
1758}
1759
1760QString NetworkControl::listRecordings(const QString& chanid, const QString& starttime)
1761{
1762 QString result;
1764 QString queryStr;
1765 bool appendCRLF = true;
1766
1767 queryStr = "SELECT chanid, starttime, title, subtitle "
1768 "FROM recorded WHERE deletepending = 0 ";
1769
1770 if ((!chanid.isEmpty()) && (!starttime.isEmpty()))
1771 {
1772 queryStr += "AND chanid = " + chanid + " "
1773 "AND starttime = '" + starttime + "' ";
1774 appendCRLF = false;
1775 }
1776
1777 queryStr += "ORDER BY starttime, title;";
1778
1779 query.prepare(queryStr);
1780 if (query.exec())
1781 {
1782 QString episode;
1783 QString title;
1784 QString subtitle;
1785 while (query.next())
1786 {
1787 title = query.value(2).toString();
1788 subtitle = query.value(3).toString();
1789
1790 if (!subtitle.isEmpty())
1791 {
1792 episode = QString("%1 -\"%2\"").arg(title, subtitle);
1793 }
1794 else
1795 {
1796 episode = title;
1797 }
1798
1799 result +=
1800 QString("%1 %2 %3")
1801 .arg(query.value(0).toString(),
1802 MythDate::as_utc(query.value(1).toDateTime()).toString(Qt::ISODate),
1803 episode);
1804
1805 if (appendCRLF)
1806 result += "\r\n";
1807 }
1808 }
1809 else
1810 {
1811 result = "ERROR: Unable to retrieve recordings list.";
1812 }
1813
1814 return result;
1815}
1816
1817QString NetworkControl::listChannels(const uint start, const uint limit)
1818{
1819 QString result;
1821 QString queryStr;
1822 uint sqlStart = start;
1823
1824 // sql starts at zero, we want to start at 1
1825 if (sqlStart > 0)
1826 sqlStart--;
1827
1828 queryStr = "select chanid, callsign, name from channel "
1829 "where deleted IS NULL and visible > 0 "
1830 "ORDER BY callsign";
1831
1832 if (limit > 0) // only if a limit is specified, we limit the results
1833 {
1834 QString limitStr = QString(" LIMIT %1,%2").arg(sqlStart).arg(limit);
1835 queryStr += limitStr;
1836 }
1837
1838 query.prepare(queryStr);
1839 if (!query.exec())
1840 {
1841 result = "ERROR: Unable to retrieve channel list.";
1842 return result;
1843 }
1844
1845 uint maxcnt = query.size();
1846 uint cnt = 0;
1847 if (maxcnt == 0) // Feedback we have no usefull information
1848 {
1849 result += QString(R"(0:0 0 "Invalid" "Invalid")");
1850 return result;
1851 }
1852
1853 while (query.next())
1854 {
1855 // Feedback is as follow:
1856 // <current line count>:<max line count to expect> <channelid> <callsign name> <channel name>\r\n
1857 cnt++;
1858 result += QString("%1:%2 %3 \"%4\" \"%5\"\r\n")
1859 .arg(cnt).arg(maxcnt)
1860 .arg(query.value(0).toString(),
1861 query.value(1).toString(),
1862 query.value(2).toString());
1863 }
1864
1865 return result;
1866}
1867
1869{
1870 int width = 0;
1871 int height = 0;
1872
1873 if (nc->getArgCount() == 2)
1874 {
1875 QStringList size = nc->getArg(1).split('x');
1876 if (size.size() == 2)
1877 {
1878 width = size[0].toInt();
1879 height = size[1].toInt();
1880 }
1881 }
1882
1884 QStringList args;
1885 if (width && height)
1886 {
1887 args << QString::number(width);
1888 args << QString::number(height);
1889 }
1892 qApp->postEvent(window, me);
1893 return "OK";
1894}
1895
1897{
1898 QString c = m_command;
1899 for(int i=0 ; i<arg ; i++) {
1900 QString argstr = c.simplified().split(" ")[0];
1901 c = c.mid(argstr.length()).trimmed();
1902 }
1903 return c;
1904}
1905
1906/* 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:23
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: compat.h:68
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