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