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(GetMythSourceVersion());
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) && (!MythMainWindow::IsTopScreenInitialized()))
171  std::this_thread::sleep_for(std::chrono::milliseconds(10));
172  }
173 
174  if (GetMythUI()->GetCurrentLocation().toLower() == "playbackbox")
175  {
176  LOG(VB_GENERAL, LOG_INFO, LOC +
177  QString("PlayRecording, ChanID: %1 StartTime: %2")
178  .arg(ChanId).arg(starttime.toString(Qt::ISODate)));
179 
180  QString message = QString("NETWORK_CONTROL PLAY PROGRAM %1 %2 %3")
181  .arg(ChanId)
182  .arg(starttime.toString("yyyyMMddhhmmss"))
183  .arg("12345");
184 
185  MythEvent me(message);
186  gCoreContext->dispatch(me);
187  return true;
188  }
189 
190  return false;
191 }
192 
193 bool Frontend::PlayVideo(const QString &Id, bool UseBookmark)
194 {
195  if (TV::IsTVRunning())
196  {
197  LOG(VB_GENERAL, LOG_WARNING, LOC +
198  QString("Ignoring PlayVideo request - frontend is busy."));
199  return false;
200  }
201 
202  bool ok = false;
203  quint64 id = Id.toUInt(&ok);
204  if (!ok)
205  {
206  LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Invalid video Id."));
207  return false;
208  }
209 
212 
213  if (!metadata)
214  {
215  LOG(VB_GENERAL, LOG_WARNING, LOC +
216  QString("Didn't find any video metadata."));
217  return false;
218  }
219 
220  if (metadata->GetHost().isEmpty())
221  {
222  LOG(VB_GENERAL, LOG_WARNING, LOC +
223  QString("No host for video."));
224  return false;
225  }
226 
227  QString mrl = generate_file_url("Videos", metadata->GetHost(),
228  metadata->GetFilename());
229  LOG(VB_GENERAL, LOG_INFO, LOC +
230  QString("PlayVideo, id: %1 usebookmark: %2 url: '%3'")
231  .arg(id).arg(UseBookmark).arg(mrl));
232 
233  QStringList args;
234  args << mrl << metadata->GetPlot() << metadata->GetTitle()
235  << metadata->GetSubtitle() << metadata->GetDirector()
236  << QString::number(metadata->GetSeason())
237  << QString::number(metadata->GetEpisode())
238  << metadata->GetInetRef() << QString::number(metadata->GetLength())
239  << QString::number(metadata->GetYear())
240  << QString::number(metadata->GetID())
241  << QString::number(static_cast<int>(UseBookmark));
242 
243  auto *me = new MythEvent(ACTION_HANDLEMEDIA, args);
244  qApp->postEvent(GetMythMainWindow(), me);
245 
246  return true;
247 }
248 
249 QStringList Frontend::GetContextList(void)
250 {
252  return gActionDescriptions.keys();
253 }
254 
256 {
257  auto *list = new DTC::FrontendActionList();
258 
260 
261  QHashIterator<QString,QStringList> contexts(gActionDescriptions);
262  while (contexts.hasNext())
263  {
264  contexts.next();
265  if (!lContext.isEmpty() && contexts.key() != lContext)
266  continue;
267 
268  // TODO can we keep the context data with QMap<QString, QStringList>?
269  QStringList actions = contexts.value();
270  for (const QString & action : qAsConst(actions))
271  {
272  QStringList split = action.split(",");
273  if (split.size() == 2)
274  list->ActionList().insert(split[0], split[1]);
275  }
276  }
277  return list;
278 }
279 
280 bool Frontend::IsValidAction(const QString &Action)
281 {
283  if (gActionList.contains(Action))
284  return true;
285 
286  // TODO There must be a better way to do this
287  if (Action.startsWith("SELECTSUBTITLE_") ||
288  Action.startsWith("SELECTTTC_") ||
289  Action.startsWith("SELECTCC608_") ||
290  Action.startsWith("SELECTCC708_") ||
291  Action.startsWith("SELECTRAWTEXT_") ||
292  Action.startsWith("SELECTAUDIO_"))
293  {
294  return true;
295  }
296 
297  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Action '%1'' is invalid.")
298  .arg(Action));
299  return false;
300 }
301 
303 {
304  static bool s_initialised = false;
305  if (s_initialised)
306  return;
307 
308  s_initialised = true;
309  auto *bindings = new KeyBindings(gCoreContext->GetHostName());
310  if (bindings)
311  {
312  QStringList contexts = bindings->GetContexts();
313  contexts.sort();
314  for (const QString & context : qAsConst(contexts))
315  {
316  gActionDescriptions[context] = QStringList();
317  QStringList ctx_actions = bindings->GetActions(context);
318  ctx_actions.sort();
319  gActionList += ctx_actions;
320  for (const QString & actions : qAsConst(ctx_actions))
321  {
322  QString desc = actions + "," +
323  bindings->GetActionDescription(context, actions);
324  gActionDescriptions[context].append(desc);
325  }
326  }
327  delete bindings;
328  }
329  gActionList.removeDuplicates();
330  gActionList.sort();
331 
332  for (const QString & actions : qAsConst(gActionList))
333  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Action: %1").arg(actions));
334 }
335 
336 bool Frontend::SendKey(const QString &sKey)
337 {
338  int keyCode = 0;
339  bool ret = false;
340  QObject *keyDest = nullptr;
341  QKeyEvent *event = nullptr;
342  QMap <QString, int> keyMap;
343  QString keyText;
344  QString msg;
345 
346  keyMap["up"] = Qt::Key_Up;
347  keyMap["down"] = Qt::Key_Down;
348  keyMap["left"] = Qt::Key_Left;
349  keyMap["right"] = Qt::Key_Right;
350  keyMap["home"] = Qt::Key_Home;
351  keyMap["end"] = Qt::Key_End;
352  keyMap["enter"] = Qt::Key_Enter;
353  keyMap["return"] = Qt::Key_Return;
354  keyMap["pageup"] = Qt::Key_PageUp;
355  keyMap["pagedown"] = Qt::Key_PageDown;
356  keyMap["escape"] = Qt::Key_Escape;
357  keyMap["tab"] = Qt::Key_Tab;
358  keyMap["backtab"] = Qt::Key_Backtab;
359  keyMap["space"] = Qt::Key_Space;
360  keyMap["backspace"] = Qt::Key_Backspace;
361  keyMap["insert"] = Qt::Key_Insert;
362  keyMap["delete"] = Qt::Key_Delete;
363  keyMap["plus"] = Qt::Key_Plus;
364  keyMap["comma"] = Qt::Key_Comma;
365  keyMap["minus"] = Qt::Key_Minus;
366  keyMap["underscore"] = Qt::Key_Underscore;
367  keyMap["period"] = Qt::Key_Period;
368  keyMap["numbersign"] = Qt::Key_NumberSign;
369  keyMap["poundsign"] = Qt::Key_NumberSign;
370  keyMap["hash"] = Qt::Key_NumberSign;
371  keyMap["bracketleft"] = Qt::Key_BracketLeft;
372  keyMap["bracketright"] = Qt::Key_BracketRight;
373  keyMap["backslash"] = Qt::Key_Backslash;
374  keyMap["dollar"] = Qt::Key_Dollar;
375  keyMap["percent"] = Qt::Key_Percent;
376  keyMap["ampersand"] = Qt::Key_Ampersand;
377  keyMap["parenleft"] = Qt::Key_ParenLeft;
378  keyMap["parenright"] = Qt::Key_ParenRight;
379  keyMap["asterisk"] = Qt::Key_Asterisk;
380  keyMap["question"] = Qt::Key_Question;
381  keyMap["slash"] = Qt::Key_Slash;
382  keyMap["colon"] = Qt::Key_Colon;
383  keyMap["semicolon"] = Qt::Key_Semicolon;
384  keyMap["less"] = Qt::Key_Less;
385  keyMap["equal"] = Qt::Key_Equal;
386  keyMap["greater"] = Qt::Key_Greater;
387  keyMap["f1"] = Qt::Key_F1;
388  keyMap["f2"] = Qt::Key_F2;
389  keyMap["f3"] = Qt::Key_F3;
390  keyMap["f4"] = Qt::Key_F4;
391  keyMap["f5"] = Qt::Key_F5;
392  keyMap["f6"] = Qt::Key_F6;
393  keyMap["f7"] = Qt::Key_F7;
394  keyMap["f8"] = Qt::Key_F8;
395  keyMap["f9"] = Qt::Key_F9;
396  keyMap["f10"] = Qt::Key_F10;
397  keyMap["f11"] = Qt::Key_F11;
398  keyMap["f12"] = Qt::Key_F12;
399  keyMap["f13"] = Qt::Key_F13;
400  keyMap["f14"] = Qt::Key_F14;
401  keyMap["f15"] = Qt::Key_F15;
402  keyMap["f16"] = Qt::Key_F16;
403  keyMap["f17"] = Qt::Key_F17;
404  keyMap["f18"] = Qt::Key_F18;
405  keyMap["f19"] = Qt::Key_F19;
406  keyMap["f20"] = Qt::Key_F20;
407  keyMap["f21"] = Qt::Key_F21;
408  keyMap["f22"] = Qt::Key_F22;
409  keyMap["f23"] = Qt::Key_F23;
410  keyMap["f24"] = Qt::Key_F24;
411 
412  if (sKey.isEmpty())
413  {
414  LOG(VB_GENERAL, LOG_ERR, LOC + QString("SendKey: No Key received"));
415  return ret;
416  }
417 
418  if (GetMythMainWindow())
419  keyDest = GetMythMainWindow();
420  else
421  {
422  LOG(VB_GENERAL, LOG_ERR,
423  LOC + QString("SendKey: Application has no main window"));
424  return ret;
425  }
426 
427  if (keyMap.contains(sKey.toLower()))
428  {
429  keyCode = keyMap[sKey.toLower()];
430  ret = true;
431  }
432  else if (sKey.size() == 1)
433  {
434  keyCode = (int) sKey.toLatin1()[0] & 0x7f;
435  ret = true;
436  }
437  else
438  msg = QString("SendKey: Unknown Key = '%1'").arg(sKey);
439 
440  if (ret)
441  {
443 
444  event = new QKeyEvent(QEvent::KeyPress, keyCode, Qt::NoModifier,
445  keyText);
446  QCoreApplication::postEvent(keyDest, event);
447 
448  event = new QKeyEvent(QEvent::KeyRelease, keyCode, Qt::NoModifier,
449  keyText);
450  QCoreApplication::postEvent(keyDest, event);
451 
452  msg = QString("SendKey: Sent %1").arg(sKey);
453  }
454 
455  LOG(VB_UPNP, LOG_INFO, LOC + msg);
456 
457  return ret;
458 }
Frontend::GetStatus
DTC::FrontendStatus * GetStatus(void) override
Definition: frontend.cpp:32
build_compdb.args
args
Definition: build_compdb.py:11
MythUIStateTracker::GetFreshState
static void GetFreshState(QVariantMap &state)
Definition: mythuistatetracker.cpp:39
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
ShowNotification
void ShowNotification(const QString &msg, const QString &from, const QString &detail, const VNMask visibility, const MythNotification::Priority priority)
Definition: mythnotificationcenter.cpp:1439
videometadata.h
MythMainWindow::JumpTo
void JumpTo(const QString &destination, bool pop=true)
Definition: mythmainwindow.cpp:1486
Frontend::PlayVideo
bool PlayVideo(const QString &Id, bool UseBookmark) override
Definition: frontend.cpp:193
MythNotification::TypeFromString
static Type TypeFromString(const QString &type)
return Type object from type name
Definition: mythnotification.cpp:125
simple_ref_ptr
Definition: quicksp.h:25
RecordingInfo
Holds information on a TV Program one might wish to record.
Definition: recordinginfo.h:35
MythEvent
This class is used as a container for messages.
Definition: mythevent.h:17
arg
arg(title).arg(filename).arg(doDelete))
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:58
keybindings.h
Main header for keybinding classes.
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
ACTION_SCREENSHOT
#define ACTION_SCREENSHOT
Definition: mythuiactions.h:22
Image
Definition: image.h:31
ACTION_SETHUE
#define ACTION_SETHUE
Definition: tv_actions.h:62
ProgramInfo::GetRecordingStartTime
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
Definition: programinfo.h:402
MythEvent::MythUserMessage
static Type MythUserMessage
Definition: mythevent.h:74
Action
An action (for this plugin) consists of a description, and a set of key sequences.
Definition: action.h:41
mythversion.h
KeyBindings
Encapsulates information about the current keybindings.
Definition: keybindings.h:37
ACTION_SETAUDIOSYNC
#define ACTION_SETAUDIOSYNC
Definition: tv_actions.h:114
MythNotification::Error
static Type Error
Definition: mythnotification.h:33
ACTION_SWITCHTITLE
#define ACTION_SWITCHTITLE
Definition: tv_actions.h:53
mythlogging.h
tv_actions.h
ACTION_HANDLEMEDIA
#define ACTION_HANDLEMEDIA
Definition: mythuiactions.h:21
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:255
MythNotification::Priority
Priority
Priority enum A notification can be given a priority.
Definition: mythnotification.h:91
ACTION_SETBRIGHTNESS
#define ACTION_SETBRIGHTNESS
Definition: tv_actions.h:59
Frontend::SendKey
bool SendKey(const QString &Key) override
Definition: frontend.cpp:336
frontend.h
uint
unsigned int uint
Definition: compat.h:141
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:60
mythuistatetracker.h
videometadatalistmanager.h
TV::IsTVRunning
static bool IsTVRunning()
Check whether media is currently playing.
Definition: tv_play.cpp:209
DTC::FrontendStatus
Definition: frontendStatus.h:10
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:370
ACTION_SETVOLUME
#define ACTION_SETVOLUME
Definition: tv_actions.h:112
Frontend::gActionList
static QStringList gActionList
Definition: programs/mythfrontend/services/frontend.h:49
mythcorecontext.h
Frontend::GetContextList
QStringList GetContextList(void) override
Definition: frontend.cpp:249
DTC::FrontendActionList
Definition: frontendActionList.h:10
MythDate::ISODate
@ ISODate
Default UTC.
Definition: mythdate.h:14
Frontend::PlayRecording
bool PlayRecording(int RecordedId, int ChanId, const QDateTime &StartTime) override
Definition: frontend.cpp:130
VideoMetadataListManager::loadOneFromDatabase
static VideoMetadataPtr loadOneFromDatabase(uint id)
Definition: videometadatalistmanager.cpp:111
Frontend::gActionDescriptions
static QHash< QString, QStringList > gActionDescriptions
Definition: programs/mythfrontend/services/frontend.h:50
GetNotificationCenter
MythNotificationCenter * GetNotificationCenter(void)
Definition: mythmainwindow.cpp:128
GetMythMainWindow
MythMainWindow * GetMythMainWindow(void)
Definition: mythmainwindow.cpp:108
build_compdb.action
action
Definition: build_compdb.py:9
GetMythSourceVersion
const char * GetMythSourceVersion()
Definition: mythcoreutil.cpp:313
MythCoreContext::GetHostName
QString GetHostName(void)
Definition: mythcorecontext.cpp:859
mythuiactions.h
Frontend::SendMessage
bool SendMessage(const QString &Message, uint Timeout) override
Definition: frontend.cpp:44
MythMainWindow::IsTopScreenInitialized
static bool IsTopScreenInitialized(void)
Definition: mythmainwindow.cpp:621
Frontend::SendAction
bool SendAction(const QString &Action, const QString &Value, uint Width, uint Height) override
Definition: frontend.cpp:87
MythMainWindow::ResetScreensaver
static void ResetScreensaver()
Definition: mythmainwindow.cpp:604
Frontend::IsValidAction
static bool IsValidAction(const QString &action)
Definition: frontend.cpp:280
GetMythUI
MythUIHelper * GetMythUI()
Definition: mythuihelper.cpp:66
mythmainwindow.h
Frontend::InitialiseActions
static void InitialiseActions(void)
Definition: frontend.cpp:302
LOC
#define LOC
Definition: frontend.cpp:27
ACTION_JUMPCHAPTER
#define ACTION_JUMPCHAPTER
Definition: tv_actions.h:52
MythCoreContext::dispatch
void dispatch(const MythEvent &event)
Definition: mythcorecontext.cpp:1734
Priority
Definition: channelsettings.cpp:192
ACTION_SWITCHANGLE
#define ACTION_SWITCHANGLE
Definition: tv_actions.h:54
videoutils.h
tv_play.h