MythTV master
mythfrontendservice.cpp
Go to the documentation of this file.
1// C++
2#include <thread>
3#include <algorithm>
4
5// Qt
6#include <QKeyEvent>
7#include <QGlobalStatic>
8#include <QCoreApplication>
9
10// MythTV
14#include "libmythbase/mythversion.h"
19#include "libmythtv/tv_play.h"
23
24// MythFrontend
25#include "keybindings.h"
27
28#define LOC QString("FrontendServices: ")
29using ActionDescs = QHash<QString,QStringList>;
30
37{
38 public:
39 const QStringList& Actions() { return m_actions; }
40 const QStringList& SelectActions() { return m_selectActions; }
41 const QStringList& ValueActions() { return m_valueActions; }
43
45 {
46 // Build action and action descriptions
47 if (auto * bindings = new KeyBindings(gCoreContext->GetHostName()))
48 {
49 QStringList contexts = bindings->GetContexts();
50 contexts.sort();
51 for (const QString & context : std::as_const(contexts))
52 {
53 m_actionDescriptions[context].clear();
54 QStringList ctxactions = bindings->GetActions(context);
55 ctxactions.sort();
56 m_actions << ctxactions;
57 for (const QString & actions : std::as_const(ctxactions))
58 {
59 QString desc = actions + "," + bindings->GetActionDescription(context, actions);
60 m_actionDescriptions[context].append(desc);
61 }
62 }
63 delete bindings;
64 }
65 m_actions.removeDuplicates();
66 m_actions.sort();
67
68 // Build actions that have an implicit value (e.g. SELECTSUBTITLE_0)
69 for (const auto & action : std::as_const(m_actions))
70 if (action.startsWith("select", Qt::CaseInsensitive) && action.contains("_"))
71 m_selectActions.append(action);
72
73 // Hardcoded list of value actions
80 }
81
82 private:
83 QStringList m_actions;
84 QStringList m_selectActions;
85 QStringList m_valueActions;
86 QHash<QString,QStringList> m_actionDescriptions;
87};
88
89//NOLINTNEXTLINE(readability-redundant-member-init)
90Q_GLOBAL_STATIC(FrontendActions, s_actions)
91
92// This will be initialised in a thread safe manner on first use
94 (FRONTEND_HANDLE, MythFrontendService::staticMetaObject, &MythFrontendService::RegisterCustomTypes))
95
96void MythFrontendService::RegisterCustomTypes()
97{
98 qRegisterMetaType<FrontendStatus*>("FrontendStatus");
99 qRegisterMetaType<FrontendActionList*>("FrontendActionList");
100}
101
103 : MythHTTPService(s_service)
104{
105}
106
108{
109 QVariantMap state;
111 auto* result = new FrontendStatus(gCoreContext->GetHostName(), GetMythSourceVersion(), state);
112 return result;
113}
114
115bool MythFrontendService::SendKey(const QString& Key)
116{
117 if (Key.isEmpty() || !HasMythMainWindow())
118 return false;
119
120 int keycode = 0;
121 bool valid = false;
122 QString key = Key.toLower();
123
124 // We could statically initialise this mapping of the Qt::Key enum but clients
125 // should really just use actions...
126 QMetaEnum meta = QMetaEnum::fromType<Qt::Key>();
127 for (int i = 0; i < meta.keyCount(); i++)
128 {
129 if (QByteArray(meta.key(i)).mid(4).toLower() == key)
130 {
131 keycode = meta.value(i);
132 valid = true;
133 break;
134 }
135 }
136
137 if (!valid && key.size() == 1)
138 {
139 keycode = key.toLatin1()[0] & 0x7f;
140 valid = true;
141 }
142
143 if (!valid)
144 {
145 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Unknown key: '%1'").arg(Key));
146 return false;
147 }
148
150 auto * mainwindow = GetMythMainWindow();
151 auto * event1 = new QKeyEvent(QEvent::KeyPress, keycode, Qt::NoModifier, "");
152 QCoreApplication::postEvent(mainwindow, event1);
153 auto * event2 = new QKeyEvent(QEvent::KeyRelease, keycode, Qt::NoModifier, "");
154 QCoreApplication::postEvent(mainwindow, event2);
155 LOG(VB_HTTP, LOG_INFO, LOC + QString("Sent key: '%1'").arg(Key));
156 return true;
157}
158
159bool MythFrontendService::SendMessage(const QString& Message, uint Timeout)
160{
161 if (Message.isEmpty())
162 return false;
163 QStringList data(QString::number(std::clamp(Timeout, 0U, 1000U)));
164 qApp->postEvent(GetMythMainWindow(), new MythEvent(MythEvent::kMythUserMessage, Message, data));
165 return true;
166}
167
174bool MythFrontendService::SendNotification(bool Error, const QString& Type,
175 const QString& Message, const QString& Origin,
176 const QString& Description, const QString& Image,
177 const QString& Extra, const QString& ProgressText,
178 float Progress, std::chrono::seconds Timeout,
179 bool Fullscreen, uint Visibility,
181{
182 if (Message.isEmpty() || !GetNotificationCenter())
183 return false;
184
186 Message, Origin.isNull() ? tr("FrontendServices") : Origin,
187 Description, Image, Extra, ProgressText, Progress, Timeout,
188 Fullscreen, Visibility, static_cast<MythNotification::Priority>(Priority));
189 return true;
190}
191
193{
194 QVariantMap result;
195 QHashIterator<QString,QStringList> contexts(s_actions->Descriptions());
196 while (contexts.hasNext())
197 {
198 contexts.next();
199 if (!Context.isEmpty() && contexts.key() != Context)
200 continue;
201
202 // TODO can we keep the context data with QMap<QString, QStringList>?
203 QStringList actions = contexts.value();
204 for (const QString & action : std::as_const(actions))
205 {
206 QStringList split = action.split(",");
207 if (split.size() == 2)
208 result.insert(split[0], split[1]);
209 }
210 }
211 return new FrontendActionList(result);
212}
213
215{
216 return s_actions->Descriptions().keys();
217}
218
219bool MythFrontendService::SendAction(const QString& Action, const QString& Value, uint Width, uint Height)
220{
222 return false;
223
224 if (!Value.isEmpty() && s_actions->ValueActions().contains(Action))
225 {
227 auto * me = new MythEvent(Action, QStringList(Value));
228 qApp->postEvent(GetMythMainWindow(), me);
229 return true;
230 }
231
233 {
234 if (!Width || !Height)
235 {
236 LOG(VB_GENERAL, LOG_ERR, LOC + "Invalid screenshot parameters.");
237 return false;
238 }
239
240 QStringList args;
241 args << QString::number(Width) << QString::number(Height);
242 auto* me = new MythEvent(Action, args);
243 qApp->postEvent(GetMythMainWindow(), me);
244 return true;
245 }
246
248 qApp->postEvent(GetMythMainWindow(), new QKeyEvent(QEvent::KeyPress, 0, Qt::NoModifier, Action));
249 return true;
250}
251
253{
254 if (s_actions->Actions().contains(Action) || s_actions->SelectActions().contains(Action))
255 return true;
256 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Action '%1'' is invalid.").arg(Action));
257 return false;
258}
259
260bool MythFrontendService::PlayRecording(int RecordedId, int ChanId, const QDateTime& StartTime)
261{
262 QDateTime starttime = StartTime;
263
264 if ((RecordedId <= 0) && (ChanId <= 0 || !StartTime.isValid()))
265 {
266 LOG(VB_GENERAL, LOG_INFO, LOC + "Recorded ID or Channel ID and StartTime appears invalid.");
267 return false;
268 }
269
270 if (RecordedId > 0)
271 {
272 RecordingInfo recInfo = RecordingInfo(static_cast<uint>(RecordedId));
273 ChanId = static_cast<int>(recInfo.GetChanID());
274 starttime = recInfo.GetRecordingStartTime();
275 }
276
277 // Note: This is inconsistent with PlayVideo behaviour - which checks for
278 // a current instance of TV and returns false if something is already playing.
279 if (GetMythUI()->GetCurrentLocation().toLower() == "playback")
280 {
281 QString message = QString("NETWORK_CONTROL STOP");
282 MythEvent me(message);
284
285 QElapsedTimer timer;
286 timer.start();
287 while (!timer.hasExpired(10000) && (GetMythUI()->GetCurrentLocation().toLower() == "playback"))
288 std::this_thread::sleep_for(std::chrono::milliseconds(10));
289 }
290
291 if (GetMythUI()->GetCurrentLocation().toLower() != "playbackbox")
292 {
293 GetMythMainWindow()->JumpTo("TV Recording Playback");
294 QElapsedTimer timer;
295 timer.start();
296 while (!timer.hasExpired(10000) && (GetMythUI()->GetCurrentLocation().toLower() != "playbackbox"))
297 std::this_thread::sleep_for(std::chrono::milliseconds(10));
298
299 timer.start();
300 while (!timer.hasExpired(10000) && (!MythMainWindow::IsTopScreenInitialized()))
301 std::this_thread::sleep_for(std::chrono::milliseconds(10));
302 }
303
304 if (GetMythUI()->GetCurrentLocation().toLower() == "playbackbox")
305 {
306 LOG(VB_GENERAL, LOG_INFO, LOC + QString("PlayRecording, ChanID: %1 StartTime: %2")
307 .arg(ChanId).arg(starttime.toString(Qt::ISODate)));
308
309 QString message = QString("NETWORK_CONTROL PLAY PROGRAM %1 %2 %3")
310 .arg(ChanId).arg(starttime.toString("yyyyMMddhhmmss"), "12345");
311
312 MythEvent me(message);
314 return true;
315 }
316
317 return false;
318}
319
320bool MythFrontendService::PlayVideo(const QString& Id, bool UseBookmark)
321{
322 if (TV::IsTVRunning())
323 {
324 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Ignoring PlayVideo request - frontend is busy."));
325 return false;
326 }
327
328 bool ok = false;
329 uint id = Id.toUInt(&ok);
330 if (!ok)
331 {
332 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Invalid video Id."));
333 return false;
334 }
335
337 if (!metadata)
338 {
339 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Didn't find any video metadata."));
340 return false;
341 }
342
343 if (metadata->GetHost().isEmpty())
344 {
345 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("No host for video."));
346 return false;
347 }
348
349 QString mrl = StorageGroup::generate_file_url("Videos", metadata->GetHost(), metadata->GetFilename());
350 LOG(VB_GENERAL, LOG_INFO, LOC + QString("PlayVideo ID: %1 UseBookmark: %2 URL: '%3'")
351 .arg(id).arg(UseBookmark).arg(mrl));
352
353 QStringList args;
354 args << mrl << metadata->GetPlot() << metadata->GetTitle()
355 << metadata->GetSubtitle() << metadata->GetDirector()
356 << QString::number(metadata->GetSeason())
357 << QString::number(metadata->GetEpisode())
358 << metadata->GetInetRef() << QString::number(metadata->GetLength().count())
359 << QString::number(metadata->GetYear())
360 << QString::number(metadata->GetID())
361 << QString::number(static_cast<int>(UseBookmark));
362
363 auto * me = new MythEvent(ACTION_HANDLEMEDIA, args);
364 qApp->postEvent(GetMythMainWindow(), me);
365 return true;
366}
367
368FrontendStatus::FrontendStatus(QString Name, QString Version, QVariantMap State)
369 : m_Name(std::move(Name)),
370 m_Version(std::move(Version)),
371 m_State(std::move(State))
372{
373 if (m_State.contains("chaptertimes") &&
374#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
375 m_State["chaptertimes"].type() == QVariant::List
376#else
377 m_State["chaptertimes"].typeId() == QMetaType::QVariantList
378#endif
379 )
380 {
381 m_ChapterTimes = m_State["chaptertimes"].toList();
382 m_State.remove("chaptertimes");
383 }
384
385 if (m_State.contains("subtitletracks") &&
386#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
387 m_State["subtitletracks"].type() == QVariant::Map
388#else
389 m_State["subtitletracks"].typeId() == QMetaType::QVariantMap
390#endif
391 )
392 {
393 m_SubtitleTracks = m_State["subtitletracks"].toMap();
394 m_State.remove("subtitletracks");
395 }
396
397 if (m_State.contains("audiotracks") &&
398#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
399 m_State["audiotracks"].type() == QVariant::Map
400#else
401 m_State["audiotracks"].typeId() == QMetaType::QVariantMap
402#endif
403 )
404 {
405 m_AudioTracks = m_State["audiotracks"].toMap();
406 m_State.remove("audiotracks");
407 }
408}
409
411 : m_ActionList(std::move(List))
412{
413}
An action (for this plugin) consists of a description, and a set of key sequences.
Definition: action.h:41
Q_INVOKABLE FrontendActionList(QObject *parent=nullptr)
QStringList m_valueActions
QHash< QString, QStringList > m_actionDescriptions
QStringList m_selectActions
const QStringList & Actions()
const ActionDescs & Descriptions()
const QStringList & ValueActions()
const QStringList & SelectActions()
Q_INVOKABLE FrontendStatus(QObject *parent=nullptr)
Encapsulates information about the current keybindings.
Definition: keybindings.h:37
QString GetHostName(void)
void dispatch(const MythEvent &event)
This class is used as a container for messages.
Definition: mythevent.h:17
static const Type kMythUserMessage
Definition: mythevent.h:80
static bool SendNotification(bool Error, const QString &Type, const QString &Message, const QString &Origin, const QString &Description, const QString &Image, const QString &Extra, const QString &ProgressText, float Progress, std::chrono::seconds Timeout, bool Fullscreen, uint Visibility, uint Priority)
Send a notification to the frontend.
static bool PlayVideo(const QString &Id, bool UseBookmark)
static FrontendActionList * GetActionList(const QString &Context)
static bool PlayRecording(int RecordedId, int ChanId, const QDateTime &StartTime)
static bool IsValidAction(const QString &Action)
static FrontendStatus * GetStatus()
static Q_CLASSINFO("GetContextList", "name=StringList") public slots bool SendKey(const QString &Key)
static QStringList GetContextList()
static bool SendMessage(const QString &Message, uint Timeout)
static void ResetScreensaver()
void JumpTo(const QString &Destination, bool Pop=true)
static bool IsTopScreenInitialized()
static Type TypeFromString(const QString &Type)
static const Type kError
QString GetCurrentLocation(bool FullPath=false, bool MainStackOnly=true)
static void GetFreshState(QVariantMap &State)
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:373
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
Definition: programinfo.h:405
Holds information on a TV Program one might wish to record.
Definition: recordinginfo.h:36
static QString generate_file_url(const QString &storage_group, const QString &host, const QString &path)
static bool IsTVRunning()
Check whether media is currently playing.
Definition: tv_play.cpp:173
static VideoMetadataPtr loadOneFromDatabase(uint id)
unsigned int uint
Definition: freesurround.h:24
Main header for keybinding classes.
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOC
QHash< QString, QStringList > ActionDescs
Q_GLOBAL_STATIC_WITH_ARGS(MythHTTPMetaService, s_service,(FRONTEND_HANDLE, MythFrontendService::staticMetaObject, &MythFrontendService::RegisterCustomTypes)) void MythFrontendService
#define FRONTEND_HANDLE
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythNotificationCenter * GetNotificationCenter(void)
bool HasMythMainWindow(void)
MythMainWindow * GetMythMainWindow(void)
void ShowNotification(const QString &msg, const QString &from, const QString &detail, const VNMask visibility, const MythNotification::Priority priority)
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
@ ISODate
Default UTC.
Definition: mythdate.h:17
STL namespace.
static eu8 clamp(eu8 value, eu8 low, eu8 high)
Definition: pxsup2dast.c:206
#define ACTION_SWITCHANGLE
Definition: tv_actions.h:54
#define ACTION_SETCONTRAST
Definition: tv_actions.h:60
#define ACTION_SETCOLOUR
Definition: tv_actions.h:61
#define ACTION_SETBRIGHTNESS
Definition: tv_actions.h:59
#define ACTION_SETHUE
Definition: tv_actions.h:62
#define ACTION_SWITCHTITLE
Definition: tv_actions.h:53
#define ACTION_SEEKABSOLUTE
Definition: tv_actions.h:40
#define ACTION_JUMPCHAPTER
Definition: tv_actions.h:52
#define ACTION_SETAUDIOSYNC
Definition: tv_actions.h:114
#define ACTION_SETVOLUME
Definition: tv_actions.h:112
State
Definition: zmserver.h:69