MythTV master
frontend.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 <QKeyEvent>
8#include <QEvent>
9
10// MythTV
14#include "libmythbase/mythversion.h"
19#include "libmythtv/tv_actions.h" // for ACTION_JUMPCHAPTER, etc
20#include "libmythtv/tv_play.h"
22#include "libmythui/mythuiactions.h" // for ACTION_HANDLEMEDIA, etc
25
26// MythFrontend
27#include "frontend.h"
28#include "keybindings.h"
29
30#define LOC QString("Frontend API: ")
31
32QStringList Frontend::gActionList = QStringList();
33QHash<QString,QStringList> Frontend::gActionDescriptions = QHash<QString,QStringList>();
34
36{
37 auto *status = new DTC::FrontendStatus();
38 MythUIStateTracker::GetFreshState(status->State());
39
40 status->setName(gCoreContext->GetHostName());
41 status->setVersion(GetMythSourceVersion());
42
43 status->Process();
44 return status;
45}
46
47bool Frontend::SendMessage(const QString &Message, uint TimeoutInt)
48{
49 if (Message.isEmpty())
50 return false;
51
52 QStringList data;
53 auto Timeout = std::chrono::seconds(TimeoutInt);
54 if (Timeout > 0s && Timeout < 1000s)
55 data << QString::number(Timeout.count());
56 qApp->postEvent(GetMythMainWindow(),
58 data));
59 return true;
60}
61
63 const QString &Type,
64 const QString &Message,
65 const QString &Origin,
66 const QString &Description,
67 const QString &Image,
68 const QString &Extra,
69 const QString &ProgressText,
70 float Progress,
71 int Timeout,
72 bool Fullscreen,
73 uint Visibility,
75{
76 if (Message.isEmpty())
77 return false;
79 return false;
80
83 Message,
84 Origin.isNull() ? tr("FrontendServices") : Origin,
85 Description, Image, Extra,
86 ProgressText, Progress, std::chrono::seconds(Timeout),
87 Fullscreen, Visibility, (MythNotification::Priority)Priority);
88 return true;
89}
90
91bool Frontend::SendAction(const QString &Action, const QString &Value,
92 uint Width, uint Height)
93{
95 return false;
96
97 static const QStringList kValueActions =
98 QStringList() << ACTION_HANDLEMEDIA << ACTION_SETVOLUME <<
104
105 if (!Value.isEmpty() && kValueActions.contains(Action))
106 {
108 auto* me = new MythEvent(Action, QStringList(Value));
109 qApp->postEvent(GetMythMainWindow(), me);
110 return true;
111 }
112
114 {
115 if (!Width || !Height)
116 {
117 LOG(VB_GENERAL, LOG_ERR, LOC + "Invalid screenshot parameters.");
118 return false;
119 }
120
121 QStringList args;
122 args << QString::number(Width) << QString::number(Height);
123 auto* me = new MythEvent(Action, args);
124 qApp->postEvent(GetMythMainWindow(), me);
125 return true;
126 }
127
129 auto* ke = new QKeyEvent(QEvent::KeyPress, 0, Qt::NoModifier, Action);
130 qApp->postEvent(GetMythMainWindow(), (QEvent*)ke);
131 return true;
132}
133
134bool Frontend::PlayRecording(int RecordedId, int ChanId,
135 const QDateTime &StartTime)
136{
137 QDateTime starttime = StartTime;
138
139 if ((RecordedId <= 0) &&
140 (ChanId <= 0 || !StartTime.isValid()))
141 throw QString("Recorded ID or Channel ID and StartTime appears invalid.");
142
143 if (RecordedId > 0)
144 {
145 RecordingInfo recInfo = RecordingInfo(RecordedId);
146 ChanId = recInfo.GetChanID();
147 starttime = recInfo.GetRecordingStartTime();
148 }
149
150 if (GetMythUI()->GetCurrentLocation().toLower() == "playback")
151 {
152 QString message = QString("NETWORK_CONTROL STOP");
153 MythEvent me(message);
155
156 QElapsedTimer timer;
157 timer.start();
158 while (!timer.hasExpired(10000) &&
159 (GetMythUI()->GetCurrentLocation().toLower() == "playback"))
160 std::this_thread::sleep_for(10ms);
161 }
162
163 if (GetMythUI()->GetCurrentLocation().toLower() != "playbackbox")
164 {
165 GetMythMainWindow()->JumpTo("TV Recording Playback");
166
167 QElapsedTimer timer;
168 timer.start();
169 while (!timer.hasExpired(10000) &&
170 (GetMythUI()->GetCurrentLocation().toLower() != "playbackbox"))
171 std::this_thread::sleep_for(10ms);
172
173 timer.start();
174 while (!timer.hasExpired(10000) && (!MythMainWindow::IsTopScreenInitialized()))
175 std::this_thread::sleep_for(10ms);
176 }
177
178 if (GetMythUI()->GetCurrentLocation().toLower() == "playbackbox")
179 {
180 LOG(VB_GENERAL, LOG_INFO, LOC +
181 QString("PlayRecording, ChanID: %1 StartTime: %2")
182 .arg(ChanId).arg(starttime.toString(Qt::ISODate)));
183
184 QString message = QString("NETWORK_CONTROL PLAY PROGRAM %1 %2 %3")
185 .arg(QString::number(ChanId),
186 starttime.toString("yyyyMMddhhmmss"),
187 "12345");
188
189 MythEvent me(message);
191 return true;
192 }
193
194 return false;
195}
196
197bool Frontend::PlayVideo(const QString &Id, bool UseBookmark)
198{
199 if (TV::IsTVRunning())
200 {
201 LOG(VB_GENERAL, LOG_WARNING, LOC +
202 QString("Ignoring PlayVideo request - frontend is busy."));
203 return false;
204 }
205
206 bool ok = false;
207 quint64 id = Id.toUInt(&ok);
208 if (!ok)
209 {
210 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Invalid video Id."));
211 return false;
212 }
213
216
217 if (!metadata)
218 {
219 LOG(VB_GENERAL, LOG_WARNING, LOC +
220 QString("Didn't find any video metadata."));
221 return false;
222 }
223
224 if (metadata->GetHost().isEmpty())
225 {
226 LOG(VB_GENERAL, LOG_WARNING, LOC +
227 QString("No host for video."));
228 return false;
229 }
230
231 QString mrl = StorageGroup::generate_file_url("Videos", metadata->GetHost(),
232 metadata->GetFilename());
233 LOG(VB_GENERAL, LOG_INFO, LOC +
234 QString("PlayVideo, id: %1 usebookmark: %2 url: '%3'")
235 .arg(id).arg(UseBookmark).arg(mrl));
236
237 QStringList args;
238 args << mrl << metadata->GetPlot() << metadata->GetTitle()
239 << metadata->GetSubtitle() << metadata->GetDirector()
240 << QString::number(metadata->GetSeason())
241 << QString::number(metadata->GetEpisode())
242 << metadata->GetInetRef() << QString::number(metadata->GetLength().count())
243 << QString::number(metadata->GetYear())
244 << QString::number(metadata->GetID())
245 << QString::number(static_cast<int>(UseBookmark));
246
247 auto *me = new MythEvent(ACTION_HANDLEMEDIA, args);
248 qApp->postEvent(GetMythMainWindow(), me);
249
250 return true;
251}
252
254{
256 return gActionDescriptions.keys();
257}
258
260{
261 auto *list = new DTC::FrontendActionList();
262
264
265 QHashIterator<QString,QStringList> contexts(gActionDescriptions);
266 while (contexts.hasNext())
267 {
268 contexts.next();
269 if (!lContext.isEmpty() && contexts.key() != lContext)
270 continue;
271
272 // TODO can we keep the context data with QMap<QString, QStringList>?
273 QStringList actions = contexts.value();
274 for (const QString & action : std::as_const(actions))
275 {
276 QStringList split = action.split(",");
277 if (split.size() == 2)
278 list->ActionList().insert(split[0], split[1]);
279 }
280 }
281 return list;
282}
283
284bool Frontend::IsValidAction(const QString &Action)
285{
287 if (gActionList.contains(Action))
288 return true;
289
290 // TODO There must be a better way to do this
291 if (Action.startsWith("SELECTSUBTITLE_") ||
292 Action.startsWith("SELECTTTC_") ||
293 Action.startsWith("SELECTCC608_") ||
294 Action.startsWith("SELECTCC708_") ||
295 Action.startsWith("SELECTRAWTEXT_") ||
296 Action.startsWith("SELECTAUDIO_"))
297 {
298 return true;
299 }
300
301 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Action '%1'' is invalid.")
302 .arg(Action));
303 return false;
304}
305
307{
308 static bool s_initialised = false;
309 if (s_initialised)
310 return;
311
312 s_initialised = true;
313 auto *bindings = new KeyBindings(gCoreContext->GetHostName());
314 if (bindings)
315 {
316 QStringList contexts = bindings->GetContexts();
317 contexts.sort();
318 for (const QString & context : std::as_const(contexts))
319 {
320 gActionDescriptions[context] = QStringList();
321 QStringList ctx_actions = bindings->GetActions(context);
322 ctx_actions.sort();
323 gActionList += ctx_actions;
324 for (const QString & actions : std::as_const(ctx_actions))
325 {
326 QString desc = actions + "," +
327 bindings->GetActionDescription(context, actions);
328 gActionDescriptions[context].append(desc);
329 }
330 }
331 delete bindings;
332 }
333 gActionList.removeDuplicates();
334 gActionList.sort();
335
336 for (const QString & actions : std::as_const(gActionList))
337 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Action: %1").arg(actions));
338}
339
340bool Frontend::SendKey(const QString &sKey)
341{
342 int keyCode = 0;
343 bool ret = false;
344 QObject *keyDest = nullptr;
345 QKeyEvent *event = nullptr;
346 QMap <QString, int> keyMap;
347 QString keyText;
348 QString msg;
349
350 keyMap["up"] = Qt::Key_Up;
351 keyMap["down"] = Qt::Key_Down;
352 keyMap["left"] = Qt::Key_Left;
353 keyMap["right"] = Qt::Key_Right;
354 keyMap["home"] = Qt::Key_Home;
355 keyMap["end"] = Qt::Key_End;
356 keyMap["enter"] = Qt::Key_Enter;
357 keyMap["return"] = Qt::Key_Return;
358 keyMap["pageup"] = Qt::Key_PageUp;
359 keyMap["pagedown"] = Qt::Key_PageDown;
360 keyMap["escape"] = Qt::Key_Escape;
361 keyMap["tab"] = Qt::Key_Tab;
362 keyMap["backtab"] = Qt::Key_Backtab;
363 keyMap["space"] = Qt::Key_Space;
364 keyMap["backspace"] = Qt::Key_Backspace;
365 keyMap["insert"] = Qt::Key_Insert;
366 keyMap["delete"] = Qt::Key_Delete;
367 keyMap["plus"] = Qt::Key_Plus;
368 keyMap["comma"] = Qt::Key_Comma;
369 keyMap["minus"] = Qt::Key_Minus;
370 keyMap["underscore"] = Qt::Key_Underscore;
371 keyMap["period"] = Qt::Key_Period;
372 keyMap["numbersign"] = Qt::Key_NumberSign;
373 keyMap["poundsign"] = Qt::Key_NumberSign;
374 keyMap["hash"] = Qt::Key_NumberSign;
375 keyMap["bracketleft"] = Qt::Key_BracketLeft;
376 keyMap["bracketright"] = Qt::Key_BracketRight;
377 keyMap["backslash"] = Qt::Key_Backslash;
378 keyMap["dollar"] = Qt::Key_Dollar;
379 keyMap["percent"] = Qt::Key_Percent;
380 keyMap["ampersand"] = Qt::Key_Ampersand;
381 keyMap["parenleft"] = Qt::Key_ParenLeft;
382 keyMap["parenright"] = Qt::Key_ParenRight;
383 keyMap["asterisk"] = Qt::Key_Asterisk;
384 keyMap["question"] = Qt::Key_Question;
385 keyMap["slash"] = Qt::Key_Slash;
386 keyMap["colon"] = Qt::Key_Colon;
387 keyMap["semicolon"] = Qt::Key_Semicolon;
388 keyMap["less"] = Qt::Key_Less;
389 keyMap["equal"] = Qt::Key_Equal;
390 keyMap["greater"] = Qt::Key_Greater;
391 keyMap["f1"] = Qt::Key_F1;
392 keyMap["f2"] = Qt::Key_F2;
393 keyMap["f3"] = Qt::Key_F3;
394 keyMap["f4"] = Qt::Key_F4;
395 keyMap["f5"] = Qt::Key_F5;
396 keyMap["f6"] = Qt::Key_F6;
397 keyMap["f7"] = Qt::Key_F7;
398 keyMap["f8"] = Qt::Key_F8;
399 keyMap["f9"] = Qt::Key_F9;
400 keyMap["f10"] = Qt::Key_F10;
401 keyMap["f11"] = Qt::Key_F11;
402 keyMap["f12"] = Qt::Key_F12;
403 keyMap["f13"] = Qt::Key_F13;
404 keyMap["f14"] = Qt::Key_F14;
405 keyMap["f15"] = Qt::Key_F15;
406 keyMap["f16"] = Qt::Key_F16;
407 keyMap["f17"] = Qt::Key_F17;
408 keyMap["f18"] = Qt::Key_F18;
409 keyMap["f19"] = Qt::Key_F19;
410 keyMap["f20"] = Qt::Key_F20;
411 keyMap["f21"] = Qt::Key_F21;
412 keyMap["f22"] = Qt::Key_F22;
413 keyMap["f23"] = Qt::Key_F23;
414 keyMap["f24"] = Qt::Key_F24;
415
416 if (sKey.isEmpty())
417 {
418 LOG(VB_GENERAL, LOG_ERR, LOC + QString("SendKey: No Key received"));
419 return ret;
420 }
421
422 if (GetMythMainWindow())
423 keyDest = GetMythMainWindow();
424 else
425 {
426 LOG(VB_GENERAL, LOG_ERR,
427 LOC + QString("SendKey: Application has no main window"));
428 return ret;
429 }
430
431 if (keyMap.contains(sKey.toLower()))
432 {
433 keyCode = keyMap[sKey.toLower()];
434 ret = true;
435 }
436 else if (sKey.size() == 1)
437 {
438 keyCode = (int) sKey.toLatin1()[0] & 0x7f;
439 ret = true;
440 }
441 else
442 {
443 msg = QString("SendKey: Unknown Key = '%1'").arg(sKey);
444 }
445
446 if (ret)
447 {
449
450 event = new QKeyEvent(QEvent::KeyPress, keyCode, Qt::NoModifier,
451 keyText);
452 QCoreApplication::postEvent(keyDest, event);
453
454 event = new QKeyEvent(QEvent::KeyRelease, keyCode, Qt::NoModifier,
455 keyText);
456 QCoreApplication::postEvent(keyDest, event);
457
458 msg = QString("SendKey: Sent %1").arg(sKey);
459 }
460
461 LOG(VB_UPNP, LOG_INFO, LOC + msg);
462
463 return ret;
464}
An action (for this plugin) consists of a description, and a set of key sequences.
Definition: action.h:41
static void InitialiseActions(void)
Definition: frontend.cpp:306
static bool IsValidAction(const QString &action)
Definition: frontend.cpp:284
DTC::FrontendStatus * GetStatus(void) override
Definition: frontend.cpp:35
bool PlayVideo(const QString &Id, bool UseBookmark) override
Definition: frontend.cpp:197
DTC::FrontendActionList * GetActionList(const QString &Context) override
Definition: frontend.cpp:259
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, int Timeout, bool Fullscreen, uint Visibility, uint Priority) override
Definition: frontend.cpp:62
static QHash< QString, QStringList > gActionDescriptions
Definition: frontend.h:53
bool PlayRecording(int RecordedId, int ChanId, const QDateTime &StartTime) override
Definition: frontend.cpp:134
bool SendAction(const QString &Action, const QString &Value, uint Width, uint Height) override
Definition: frontend.cpp:91
bool SendMessage(const QString &Message, uint TimeoutInt) override
Definition: frontend.cpp:47
static QStringList gActionList
Definition: frontend.h:52
QStringList GetContextList(void) override
Definition: frontend.cpp:253
bool SendKey(const QString &Key) override
Definition: frontend.cpp:340
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 void ResetScreensaver()
void JumpTo(const QString &Destination, bool Pop=true)
static bool IsTopScreenInitialized()
static Type TypeFromString(const QString &Type)
static const Type kError
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
#define LOC
Definition: frontend.cpp:30
Main header for keybinding classes.
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)
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
#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