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 {
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 value_actions =
94  QStringList() << ACTION_HANDLEMEDIA << ACTION_SETVOLUME <<
100 
101  if (!Value.isEmpty() && value_actions.contains(Action))
102  {
103  MythEvent* me = new MythEvent(Action, QStringList(Value));
104  qApp->postEvent(GetMythMainWindow(), me);
105  return true;
106  }
107 
108  if (ACTION_SCREENSHOT == Action)
109  {
110  if (!Width || !Height)
111  {
112  LOG(VB_GENERAL, LOG_ERR, LOC + "Invalid screenshot parameters.");
113  return false;
114  }
115 
116  QStringList args;
117  args << QString::number(Width) << QString::number(Height);
118  MythEvent* me = new MythEvent(Action, args);
119  qApp->postEvent(GetMythMainWindow(), me);
120  return true;
121  }
122 
123  QKeyEvent* ke = new QKeyEvent(QEvent::KeyPress, 0, Qt::NoModifier, Action);
124  qApp->postEvent(GetMythMainWindow(), (QEvent*)ke);
125  return true;
126 }
127 
128 bool Frontend::PlayRecording(int RecordedId, int ChanId,
129  const QDateTime &StartTime)
130 {
131  QDateTime starttime = StartTime;
132 
133  if ((RecordedId <= 0) &&
134  (ChanId <= 0 || !StartTime.isValid()))
135  throw QString("Recorded ID or Channel ID and StartTime appears invalid.");
136 
137  if (RecordedId > 0)
138  {
139  RecordingInfo recInfo = RecordingInfo(RecordedId);
140  ChanId = recInfo.GetChanID();
141  starttime = recInfo.GetRecordingStartTime();
142  }
143 
144  if (GetMythUI()->GetCurrentLocation().toLower() == "playback")
145  {
146  QString message = QString("NETWORK_CONTROL STOP");
147  MythEvent me(message);
148  gCoreContext->dispatch(me);
149 
150  QTime timer;
151  timer.start();
152  while ((timer.elapsed() < 10000) &&
153  (GetMythUI()->GetCurrentLocation().toLower() == "playback"))
154  std::this_thread::sleep_for(std::chrono::milliseconds(10));
155  }
156 
157  if (GetMythUI()->GetCurrentLocation().toLower() != "playbackbox")
158  {
159  GetMythMainWindow()->JumpTo("TV Recording Playback");
160 
161  QTime timer;
162  timer.start();
163  while ((timer.elapsed() < 10000) &&
164  (GetMythUI()->GetCurrentLocation().toLower() != "playbackbox"))
165  std::this_thread::sleep_for(std::chrono::milliseconds(10));
166 
167  timer.start();
168  while ((timer.elapsed() < 10000) &&
170  std::this_thread::sleep_for(std::chrono::milliseconds(10));
171  }
172 
173  if (GetMythUI()->GetCurrentLocation().toLower() == "playbackbox")
174  {
175  LOG(VB_GENERAL, LOG_INFO, LOC +
176  QString("PlayRecording, ChanID: %1 StartTime: %2")
177  .arg(ChanId).arg(starttime.toString(Qt::ISODate)));
178 
179  QString message = QString("NETWORK_CONTROL PLAY PROGRAM %1 %2 %3")
180  .arg(ChanId)
181  .arg(starttime.toString("yyyyMMddhhmmss"))
182  .arg("12345");
183 
184  MythEvent me(message);
185  gCoreContext->dispatch(me);
186  return true;
187  }
188 
189  return false;
190 }
191 
192 bool Frontend::PlayVideo(const QString &Id, bool UseBookmark)
193 {
194  if (TV::IsTVRunning())
195  {
196  LOG(VB_GENERAL, LOG_WARNING, LOC +
197  QString("Ignoring PlayVideo request - frontend is busy."));
198  return false;
199  }
200 
201  bool ok;
202  quint64 id = Id.toUInt(&ok);
203  if (!ok)
204  {
205  LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Invalid video Id."));
206  return false;
207  }
208 
211 
212  if (!metadata)
213  {
214  LOG(VB_GENERAL, LOG_WARNING, LOC +
215  QString("Didn't find any video metadata."));
216  return false;
217  }
218 
219  if (metadata->GetHost().isEmpty())
220  {
221  LOG(VB_GENERAL, LOG_WARNING, LOC +
222  QString("No host for video."));
223  return false;
224  }
225 
226  QString mrl = generate_file_url("Videos", metadata->GetHost(),
227  metadata->GetFilename());
228  LOG(VB_GENERAL, LOG_INFO, LOC +
229  QString("PlayVideo, id: %1 usebookmark: %2 url: '%3'")
230  .arg(id).arg(UseBookmark).arg(mrl));
231 
232  QStringList args;
233  args << mrl << metadata->GetPlot() << metadata->GetTitle()
234  << metadata->GetSubtitle() << metadata->GetDirector()
235  << QString::number(metadata->GetSeason())
236  << QString::number(metadata->GetEpisode())
237  << metadata->GetInetRef() << QString::number(metadata->GetLength())
238  << QString::number(metadata->GetYear())
239  << QString::number(metadata->GetID())
240  << QString::number(UseBookmark);
241 
243  qApp->postEvent(GetMythMainWindow(), me);
244 
245  return true;
246 }
247 
248 QStringList Frontend::GetContextList(void)
249 {
251  return gActionDescriptions.keys();
252 }
253 
255 {
257 
259 
260  QHashIterator<QString,QStringList> contexts(gActionDescriptions);
261  while (contexts.hasNext())
262  {
263  contexts.next();
264  if (!lContext.isEmpty() && contexts.key() != lContext)
265  continue;
266 
267  // TODO can we keep the context data with QMap<QString, QStringList>?
268  QStringList actions = contexts.value();
269  foreach (QString action, actions)
270  {
271  QStringList split = action.split(",");
272  if (split.size() == 2)
273  list->ActionList().insert(split[0], split[1]);
274  }
275  }
276  return list;
277 }
278 
279 bool Frontend::IsValidAction(const QString &Action)
280 {
282  if (gActionList.contains(Action))
283  return true;
284 
285  // TODO There must be a better way to do this
286  if (Action.startsWith("SELECTSUBTITLE_") ||
287  Action.startsWith("SELECTTTC_") ||
288  Action.startsWith("SELECTCC608_") ||
289  Action.startsWith("SELECTCC708_") ||
290  Action.startsWith("SELECTRAWTEXT_") ||
291  Action.startsWith("SELECTAUDIO_"))
292  {
293  return true;
294  }
295 
296  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Action '%1'' is invalid.")
297  .arg(Action));
298  return false;
299 }
300 
302 {
303  static bool initialised = false;
304  if (initialised)
305  return;
306 
307  initialised = true;
308  KeyBindings *bindings = new KeyBindings(gCoreContext->GetHostName());
309  if (bindings)
310  {
311  QStringList contexts = bindings->GetContexts();
312  contexts.sort();
313  foreach (QString context, contexts)
314  {
315  gActionDescriptions[context] = QStringList();
316  QStringList ctx_actions = bindings->GetActions(context);
317  ctx_actions.sort();
318  gActionList += ctx_actions;
319  foreach (QString actions, ctx_actions)
320  {
321  QString desc = actions + "," +
322  bindings->GetActionDescription(context, actions);
323  gActionDescriptions[context].append(desc);
324  }
325  }
326  delete bindings;
327  }
328  gActionList.removeDuplicates();
329  gActionList.sort();
330 
331  foreach (QString actions, gActionList)
332  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Action: %1").arg(actions));
333 }
334 
335 bool Frontend::SendKey(const QString &sKey)
336 {
337  int keyCode;
338  bool ret = false;
339  QObject *keyDest = nullptr;
340  QKeyEvent *event = nullptr;
341  QMap <QString, int> keyMap;
342  QString keyText;
343  QString msg;
344 
345  keyMap["up"] = Qt::Key_Up;
346  keyMap["down"] = Qt::Key_Down;
347  keyMap["left"] = Qt::Key_Left;
348  keyMap["right"] = Qt::Key_Right;
349  keyMap["home"] = Qt::Key_Home;
350  keyMap["end"] = Qt::Key_End;
351  keyMap["enter"] = Qt::Key_Enter;
352  keyMap["return"] = Qt::Key_Return;
353  keyMap["pageup"] = Qt::Key_PageUp;
354  keyMap["pagedown"] = Qt::Key_PageDown;
355  keyMap["escape"] = Qt::Key_Escape;
356  keyMap["tab"] = Qt::Key_Tab;
357  keyMap["backtab"] = Qt::Key_Backtab;
358  keyMap["space"] = Qt::Key_Space;
359  keyMap["backspace"] = Qt::Key_Backspace;
360  keyMap["insert"] = Qt::Key_Insert;
361  keyMap["delete"] = Qt::Key_Delete;
362  keyMap["plus"] = Qt::Key_Plus;
363  keyMap["comma"] = Qt::Key_Comma;
364  keyMap["minus"] = Qt::Key_Minus;
365  keyMap["underscore"] = Qt::Key_Underscore;
366  keyMap["period"] = Qt::Key_Period;
367  keyMap["numbersign"] = Qt::Key_NumberSign;
368  keyMap["poundsign"] = Qt::Key_NumberSign;
369  keyMap["hash"] = Qt::Key_NumberSign;
370  keyMap["bracketleft"] = Qt::Key_BracketLeft;
371  keyMap["bracketright"] = Qt::Key_BracketRight;
372  keyMap["backslash"] = Qt::Key_Backslash;
373  keyMap["dollar"] = Qt::Key_Dollar;
374  keyMap["percent"] = Qt::Key_Percent;
375  keyMap["ampersand"] = Qt::Key_Ampersand;
376  keyMap["parenleft"] = Qt::Key_ParenLeft;
377  keyMap["parenright"] = Qt::Key_ParenRight;
378  keyMap["asterisk"] = Qt::Key_Asterisk;
379  keyMap["question"] = Qt::Key_Question;
380  keyMap["slash"] = Qt::Key_Slash;
381  keyMap["colon"] = Qt::Key_Colon;
382  keyMap["semicolon"] = Qt::Key_Semicolon;
383  keyMap["less"] = Qt::Key_Less;
384  keyMap["equal"] = Qt::Key_Equal;
385  keyMap["greater"] = Qt::Key_Greater;
386  keyMap["f1"] = Qt::Key_F1;
387  keyMap["f2"] = Qt::Key_F2;
388  keyMap["f3"] = Qt::Key_F3;
389  keyMap["f4"] = Qt::Key_F4;
390  keyMap["f5"] = Qt::Key_F5;
391  keyMap["f6"] = Qt::Key_F6;
392  keyMap["f7"] = Qt::Key_F7;
393  keyMap["f8"] = Qt::Key_F8;
394  keyMap["f9"] = Qt::Key_F9;
395  keyMap["f10"] = Qt::Key_F10;
396  keyMap["f11"] = Qt::Key_F11;
397  keyMap["f12"] = Qt::Key_F12;
398  keyMap["f13"] = Qt::Key_F13;
399  keyMap["f14"] = Qt::Key_F14;
400  keyMap["f15"] = Qt::Key_F15;
401  keyMap["f16"] = Qt::Key_F16;
402  keyMap["f17"] = Qt::Key_F17;
403  keyMap["f18"] = Qt::Key_F18;
404  keyMap["f19"] = Qt::Key_F19;
405  keyMap["f20"] = Qt::Key_F20;
406  keyMap["f21"] = Qt::Key_F21;
407  keyMap["f22"] = Qt::Key_F22;
408  keyMap["f23"] = Qt::Key_F23;
409  keyMap["f24"] = Qt::Key_F24;
410 
411  if (sKey.isEmpty())
412  {
413  LOG(VB_GENERAL, LOG_ERR, LOC + QString("SendKey: No Key received"));
414  return ret;
415  }
416 
417  if (GetMythMainWindow())
418  keyDest = GetMythMainWindow();
419  else
420  {
421  LOG(VB_GENERAL, LOG_ERR,
422  LOC + QString("SendKey: Application has no main window"));
423  return ret;
424  }
425 
426  if (GetMythMainWindow()->currentWidget())
427  keyDest = GetMythMainWindow()->currentWidget()->focusWidget();
428 
429  if (keyMap.contains(sKey.toLower()))
430  {
431  keyCode = keyMap[sKey.toLower()];
432  ret = true;
433  }
434  else if (sKey.size() == 1)
435  {
436  keyCode = (int) sKey.toLatin1()[0] & 0x7f;
437  ret = true;
438  }
439  else
440  msg = QString("SendKey: Unknown Key = '%1'").arg(sKey);
441 
442  if (ret)
443  {
445 
446  event = new QKeyEvent(QEvent::KeyPress, keyCode, Qt::NoModifier,
447  keyText);
448  QCoreApplication::postEvent(keyDest, event);
449 
450  event = new QKeyEvent(QEvent::KeyRelease, keyCode, Qt::NoModifier,
451  keyText);
452  QCoreApplication::postEvent(keyDest, event);
453 
454  msg = QString("SendKey: Sent %1").arg(sKey);
455  }
456 
457  LOG(VB_UPNP, LOG_INFO, LOC + msg);
458 
459  return ret;
460 }
#define ACTION_SETHUE
Definition: tv_actions.h:63
QString GetActionDescription(const QString &context_name, const QString &action_name) const
Get an action's description.
#define ACTION_SCREENSHOT
Definition: mythuiactions.h:22
QWidget * currentWidget(void)
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:61
Encapsulates information about the current keybindings.
Definition: keybindings.h:36
static QHash< QString, QStringList > gActionDescriptions
unsigned int uint
Definition: compat.h:140
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:248
DTC::FrontendActionList * GetActionList(const QString &Context) override
Definition: frontend.cpp:254
An action (for this plugin) consists of a description, and a set of key sequences.
Definition: action.h:38
#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:301
QStringList GetActions(const QString &context) const
Get a list of the actions in a context.
Definition: keybindings.cpp:71
This class is used as a container for messages.
Definition: mythevent.h:16
#define ACTION_SETVOLUME
Definition: tv_actions.h:113
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:62
static Type MythUserMessage
Definition: mythevent.h:67
#define ACTION_SETBRIGHTNESS
Definition: tv_actions.h:60
bool PlayRecording(int RecordedId, int ChanId, const QDateTime &StartTime) override
Definition: frontend.cpp:128
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:192
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.
bool IsTopScreenInitialized(void)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
DTC::FrontendStatus * GetStatus(void) override
Definition: frontend.cpp:32
#define LOC
Definition: frontend.cpp:27
#define ACTION_SETAUDIOSYNC
Definition: tv_actions.h:115
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:364
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
Definition: programinfo.h:396
QString generate_file_url(const QString &storage_group, const QString &host, const QString &path)
Definition: videoutils.h:65
void ResetScreensaver(void)
static bool IsValidAction(const QString &action)
Definition: frontend.cpp:279
bool SendKey(const QString &Key) override
Definition: frontend.cpp:335
#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
Default UTC.
Definition: mythdate.h:14
MythNotificationCenter * GetNotificationCenter(void)