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
12 #include "libmythbase/mythevent.h"
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
23 #include "libmythui/mythuihelper.h"
25 
26 // MythFrontend
27 #include "frontend.h"
28 #include "keybindings.h"
29 
30 #define LOC QString("Frontend API: ")
31 
32 QStringList Frontend::gActionList = QStringList();
33 QHash<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 
47 bool 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,
74  uint Priority)
75 {
76  if (Message.isEmpty())
77  return false;
78  if (!GetNotificationCenter())
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 
91 bool Frontend::SendAction(const QString &Action, const QString &Value,
92  uint Width, uint Height)
93 {
94  if (!IsValidAction(Action))
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 
113  if (ACTION_SCREENSHOT == Action)
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 
134 bool 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);
154  gCoreContext->dispatch(me);
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);
190  gCoreContext->dispatch(me);
191  return true;
192  }
193 
194  return false;
195 }
196 
197 bool 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 = 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 
253 QStringList Frontend::GetContextList(void)
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 
284 bool 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 
340 bool 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  msg = QString("SendKey: Unknown Key = '%1'").arg(sKey);
443 
444  if (ret)
445  {
447 
448  event = new QKeyEvent(QEvent::KeyPress, keyCode, Qt::NoModifier,
449  keyText);
450  QCoreApplication::postEvent(keyDest, event);
451 
452  event = new QKeyEvent(QEvent::KeyRelease, keyCode, Qt::NoModifier,
453  keyText);
454  QCoreApplication::postEvent(keyDest, event);
455 
456  msg = QString("SendKey: Sent %1").arg(sKey);
457  }
458 
459  LOG(VB_UPNP, LOG_INFO, LOC + msg);
460 
461  return ret;
462 }
Frontend::GetStatus
DTC::FrontendStatus * GetStatus(void) override
Definition: frontend.cpp:35
build_compdb.args
args
Definition: build_compdb.py:11
generate_file_url
QString generate_file_url(const QString &storage_group, const QString &host, const QString &path)
Definition: videoutils.h:65
mythevent.h
ACTION_SETCONTRAST
#define ACTION_SETCONTRAST
Definition: tv_actions.h:60
MythEvent::kMythUserMessage
static const Type kMythUserMessage
Definition: mythevent.h:80
Frontend::SendMessage
bool SendMessage(const QString &Message, uint TimeoutInt) override
Definition: frontend.cpp:47
ShowNotification
void ShowNotification(const QString &msg, const QString &from, const QString &detail, const VNMask visibility, const MythNotification::Priority priority)
Definition: mythnotificationcenter.cpp:1437
videometadata.h
Frontend::PlayVideo
bool PlayVideo(const QString &Id, bool UseBookmark) override
Definition: frontend.cpp:197
ACTION_SCREENSHOT
static constexpr const char * ACTION_SCREENSHOT
Definition: mythuiactions.h:22
simple_ref_ptr
Definition: quicksp.h:24
RecordingInfo
Holds information on a TV Program one might wish to record.
Definition: recordinginfo.h:35
MythMainWindow::JumpTo
void JumpTo(const QString &Destination, bool Pop=true)
Definition: mythmainwindow.cpp:1458
MythEvent
This class is used as a container for messages.
Definition: mythevent.h:16
GetMythSourceVersion
const char * GetMythSourceVersion()
Definition: mythversion.cpp:5
Frontend::SendNotification
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
keybindings.h
Main header for keybinding classes.
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythUIStateTracker::GetFreshState
static void GetFreshState(QVariantMap &State)
Definition: mythuistatetracker.cpp:43
MythNotification::TypeFromString
static Type TypeFromString(const QString &Type)
Definition: mythnotification.cpp:236
ACTION_SETHUE
#define ACTION_SETHUE
Definition: tv_actions.h:62
ProgramInfo::GetRecordingStartTime
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
Definition: programinfo.h:404
Action
An action (for this plugin) consists of a description, and a set of key sequences.
Definition: action.h:40
KeyBindings
Encapsulates information about the current keybindings.
Definition: keybindings.h:36
ACTION_SETAUDIOSYNC
#define ACTION_SETAUDIOSYNC
Definition: tv_actions.h:114
ACTION_SWITCHTITLE
#define ACTION_SWITCHTITLE
Definition: tv_actions.h:53
mythlogging.h
tv_actions.h
ACTION_SEEKABSOLUTE
#define ACTION_SEEKABSOLUTE
Definition: tv_actions.h:40
ACTION_SETCOLOUR
#define ACTION_SETCOLOUR
Definition: tv_actions.h:61
Frontend::GetActionList
DTC::FrontendActionList * GetActionList(const QString &Context) override
Definition: frontend.cpp:259
MythNotification::Priority
Priority
Definition: mythnotification.h:58
MythNotification::kError
static const Type kError
Definition: mythnotification.h:35
ACTION_SETBRIGHTNESS
#define ACTION_SETBRIGHTNESS
Definition: tv_actions.h:59
MythMainWindow::IsTopScreenInitialized
static bool IsTopScreenInitialized()
Definition: mythmainwindow.cpp:606
Frontend::SendKey
bool SendKey(const QString &Key) override
Definition: frontend.cpp:340
frontend.h
uint
unsigned int uint
Definition: compat.h:81
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:55
mythuistatetracker.h
ACTION_HANDLEMEDIA
static constexpr const char * ACTION_HANDLEMEDIA
Definition: mythuiactions.h:21
videometadatalistmanager.h
TV::IsTVRunning
static bool IsTVRunning()
Check whether media is currently playing.
Definition: tv_play.cpp:140
DTC::FrontendStatus
Definition: frontendStatus.h:9
mythuihelper.h
recordinginfo.h
ProgramInfo::GetChanID
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:372
ACTION_SETVOLUME
#define ACTION_SETVOLUME
Definition: tv_actions.h:112
Frontend::gActionList
static QStringList gActionList
Definition: programs/mythfrontend/services/frontend.h:52
mythcorecontext.h
Frontend::GetContextList
QStringList GetContextList(void) override
Definition: frontend.cpp:253
DTC::FrontendActionList
Definition: frontendActionList.h:9
MythDate::ISODate
@ ISODate
Default UTC.
Definition: mythdate.h:17
Frontend::PlayRecording
bool PlayRecording(int RecordedId, int ChanId, const QDateTime &StartTime) override
Definition: frontend.cpp:134
VideoMetadataListManager::loadOneFromDatabase
static VideoMetadataPtr loadOneFromDatabase(uint id)
Definition: videometadatalistmanager.cpp:111
Frontend::gActionDescriptions
static QHash< QString, QStringList > gActionDescriptions
Definition: programs/mythfrontend/services/frontend.h:53
GetNotificationCenter
MythNotificationCenter * GetNotificationCenter(void)
Definition: mythmainwindow.cpp:124
GetMythMainWindow
MythMainWindow * GetMythMainWindow(void)
Definition: mythmainwindow.cpp:104
build_compdb.action
action
Definition: build_compdb.py:9
MythCoreContext::GetHostName
QString GetHostName(void)
Definition: mythcorecontext.cpp:838
mythuiactions.h
Frontend::SendAction
bool SendAction(const QString &Action, const QString &Value, uint Width, uint Height) override
Definition: frontend.cpp:91
MythMainWindow::ResetScreensaver
static void ResetScreensaver()
Definition: mythmainwindow.cpp:589
Frontend::IsValidAction
static bool IsValidAction(const QString &action)
Definition: frontend.cpp:284
GetMythUI
MythUIHelper * GetMythUI()
Definition: mythuihelper.cpp:66
mythmainwindow.h
Frontend::InitialiseActions
static void InitialiseActions(void)
Definition: frontend.cpp:306
LOC
#define LOC
Definition: frontend.cpp:30
ACTION_JUMPCHAPTER
#define ACTION_JUMPCHAPTER
Definition: tv_actions.h:52
MythCoreContext::dispatch
void dispatch(const MythEvent &event)
Definition: mythcorecontext.cpp:1719
Priority
Definition: channelsettings.cpp:216
ACTION_SWITCHANGLE
#define ACTION_SWITCHANGLE
Definition: tv_actions.h:54
videoutils.h
tv_play.h