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 for (auto contexts = s_actions->Descriptions().cbegin();
196 contexts != s_actions->Descriptions().cend(); ++contexts)
197 {
198 if (!Context.isEmpty() && contexts.key() != Context)
199 continue;
200
201 // TODO can we keep the context data with QMap<QString, QStringList>?
202 QStringList actions = contexts.value();
203 for (const QString & action : std::as_const(actions))
204 {
205 QStringList split = action.split(",");
206 if (split.size() == 2)
207 result.insert(split[0], split[1]);
208 }
209 }
210 return new FrontendActionList(result);
211}
212
214{
215 return s_actions->Descriptions().keys();
216}
217
218bool MythFrontendService::SendAction(const QString& Action, const QString& Value, uint Width, uint Height)
219{
221 return false;
222
223 if (!Value.isEmpty() && s_actions->ValueActions().contains(Action))
224 {
226 auto * me = new MythEvent(Action, QStringList(Value));
227 qApp->postEvent(GetMythMainWindow(), me);
228 return true;
229 }
230
232 {
233 if (!Width || !Height)
234 {
235 LOG(VB_GENERAL, LOG_ERR, LOC + "Invalid screenshot parameters.");
236 return false;
237 }
238
239 QStringList args;
240 args << QString::number(Width) << QString::number(Height);
241 auto* me = new MythEvent(Action, args);
242 qApp->postEvent(GetMythMainWindow(), me);
243 return true;
244 }
245
247 qApp->postEvent(GetMythMainWindow(), new QKeyEvent(QEvent::KeyPress, 0, Qt::NoModifier, Action));
248 return true;
249}
250
252{
253 if (s_actions->Actions().contains(Action) || s_actions->SelectActions().contains(Action))
254 return true;
255 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Action '%1'' is invalid.").arg(Action));
256 return false;
257}
258
259bool MythFrontendService::PlayRecording(int RecordedId, int ChanId, const QDateTime& StartTime)
260{
261 QDateTime starttime = StartTime;
262
263 if ((RecordedId <= 0) && (ChanId <= 0 || !StartTime.isValid()))
264 {
265 LOG(VB_GENERAL, LOG_INFO, LOC + "Recorded ID or Channel ID and StartTime appears invalid.");
266 return false;
267 }
268
269 if (RecordedId > 0)
270 {
271 RecordingInfo recInfo = RecordingInfo(static_cast<uint>(RecordedId));
272 ChanId = static_cast<int>(recInfo.GetChanID());
273 starttime = recInfo.GetRecordingStartTime();
274 }
275
276 // Note: This is inconsistent with PlayVideo behaviour - which checks for
277 // a current instance of TV and returns false if something is already playing.
278 if (GetMythUI()->GetCurrentLocation().toLower() == "playback")
279 {
280 QString message = QString("NETWORK_CONTROL STOP");
281 MythEvent me(message);
283
284 QElapsedTimer timer;
285 timer.start();
286 while (!timer.hasExpired(10000) && (GetMythUI()->GetCurrentLocation().toLower() == "playback"))
287 std::this_thread::sleep_for(std::chrono::milliseconds(10));
288 }
289
290 if (GetMythUI()->GetCurrentLocation().toLower() != "playbackbox")
291 {
292 GetMythMainWindow()->JumpTo("TV Recording Playback");
293 QElapsedTimer timer;
294 timer.start();
295 while (!timer.hasExpired(10000) && (GetMythUI()->GetCurrentLocation().toLower() != "playbackbox"))
296 std::this_thread::sleep_for(std::chrono::milliseconds(10));
297
298 timer.start();
299 while (!timer.hasExpired(10000) && (!MythMainWindow::IsTopScreenInitialized()))
300 std::this_thread::sleep_for(std::chrono::milliseconds(10));
301 }
302
303 if (GetMythUI()->GetCurrentLocation().toLower() == "playbackbox")
304 {
305 LOG(VB_GENERAL, LOG_INFO, LOC + QString("PlayRecording, ChanID: %1 StartTime: %2")
306 .arg(ChanId).arg(starttime.toString(Qt::ISODate)));
307
308 QString message = QString("NETWORK_CONTROL PLAY PROGRAM %1 %2 %3")
309 .arg(ChanId).arg(starttime.toString("yyyyMMddhhmmss"), "12345");
310
311 MythEvent me(message);
313 return true;
314 }
315
316 return false;
317}
318
319bool MythFrontendService::PlayVideo(const QString& Id, bool UseBookmark)
320{
321 if (TV::IsTVRunning())
322 {
323 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Ignoring PlayVideo request - frontend is busy."));
324 return false;
325 }
326
327 bool ok = false;
328 uint id = Id.toUInt(&ok);
329 if (!ok)
330 {
331 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Invalid video Id."));
332 return false;
333 }
334
336 if (!metadata)
337 {
338 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Didn't find any video metadata."));
339 return false;
340 }
341
342 if (metadata->GetHost().isEmpty())
343 {
344 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("No host for video."));
345 return false;
346 }
347
348 QString mrl = StorageGroup::generate_file_url("Videos", metadata->GetHost(), metadata->GetFilename());
349 LOG(VB_GENERAL, LOG_INFO, LOC + QString("PlayVideo ID: %1 UseBookmark: %2 URL: '%3'")
350 .arg(id).arg(UseBookmark).arg(mrl));
351
352 QStringList args;
353 args << mrl << metadata->GetPlot() << metadata->GetTitle()
354 << metadata->GetSubtitle() << metadata->GetDirector()
355 << QString::number(metadata->GetSeason())
356 << QString::number(metadata->GetEpisode())
357 << metadata->GetInetRef() << QString::number(metadata->GetLength().count())
358 << QString::number(metadata->GetYear())
359 << QString::number(metadata->GetID())
360 << QString::number(static_cast<int>(UseBookmark));
361
362 auto * me = new MythEvent(ACTION_HANDLEMEDIA, args);
363 qApp->postEvent(GetMythMainWindow(), me);
364 return true;
365}
366
368 const QString &sKey,
369 const QString &sDefault )
370{
371 if (sKey.isEmpty())
372 throw( QString("Missing or empty Key (settings.value)") );
373 return gCoreContext->GetSetting(sKey, sDefault);
374}
375
376
377FrontendStatus::FrontendStatus(QString Name, QString Version, QVariantMap State)
378 : m_Name(std::move(Name)),
379 m_Version(std::move(Version)),
380 m_State(std::move(State))
381{
382 if (m_State.contains("chaptertimes") &&
383#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
384 m_State["chaptertimes"].type() == QVariant::List
385#else
386 m_State["chaptertimes"].typeId() == QMetaType::QVariantList
387#endif
388 )
389 {
390 m_ChapterTimes = m_State["chaptertimes"].toList();
391 m_State.remove("chaptertimes");
392 }
393
394 if (m_State.contains("subtitletracks") &&
395#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
396 m_State["subtitletracks"].type() == QVariant::Map
397#else
398 m_State["subtitletracks"].typeId() == QMetaType::QVariantMap
399#endif
400 )
401 {
402 m_SubtitleTracks = m_State["subtitletracks"].toMap();
403 m_State.remove("subtitletracks");
404 }
405
406 if (m_State.contains("audiotracks") &&
407#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
408 m_State["audiotracks"].type() == QVariant::Map
409#else
410 m_State["audiotracks"].typeId() == QMetaType::QVariantMap
411#endif
412 )
413 {
414 m_AudioTracks = m_State["audiotracks"].toMap();
415 m_State.remove("audiotracks");
416 }
417}
418
420 : m_ActionList(std::move(List))
421{
422}
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)
QString GetSetting(const QString &key, const QString &defaultval="")
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 QString GetSetting(const QString &Key, const QString &Default)
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:380
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
Definition: programinfo.h:412
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: compat.h:60
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:201
#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