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  auto Timeout = std::chrono::seconds(_Timeout);
51  if (Timeout > 0s && Timeout < 1000s)
52  data << QString::number(Timeout.count());
53  qApp->postEvent(GetMythMainWindow(),
55  data));
56  return true;
57 }
58 
60  const QString &Type,
61  const QString &Message,
62  const QString &Origin,
63  const QString &Description,
64  const QString &Image,
65  const QString &Extra,
66  const QString &ProgressText,
67  float Progress,
68  int Timeout,
69  bool Fullscreen,
70  uint Visibility,
71  uint Priority)
72 {
73  if (Message.isEmpty())
74  return false;
75  if (!GetNotificationCenter())
76  return false;
77 
80  Message,
81  Origin.isNull() ? tr("FrontendServices") : Origin,
82  Description, Image, Extra,
83  ProgressText, Progress, std::chrono::seconds(Timeout),
84  Fullscreen, Visibility, (MythNotification::Priority)Priority);
85  return true;
86 }
87 
88 bool Frontend::SendAction(const QString &Action, const QString &Value,
89  uint Width, uint Height)
90 {
91  if (!IsValidAction(Action))
92  return false;
93 
94  static const QStringList kValueActions =
95  QStringList() << ACTION_HANDLEMEDIA << ACTION_SETVOLUME <<
101 
102  if (!Value.isEmpty() && kValueActions.contains(Action))
103  {
105  auto* me = new MythEvent(Action, QStringList(Value));
106  qApp->postEvent(GetMythMainWindow(), me);
107  return true;
108  }
109 
110  if (ACTION_SCREENSHOT == Action)
111  {
112  if (!Width || !Height)
113  {
114  LOG(VB_GENERAL, LOG_ERR, LOC + "Invalid screenshot parameters.");
115  return false;
116  }
117 
118  QStringList args;
119  args << QString::number(Width) << QString::number(Height);
120  auto* me = new MythEvent(Action, args);
121  qApp->postEvent(GetMythMainWindow(), me);
122  return true;
123  }
124 
126  auto* ke = new QKeyEvent(QEvent::KeyPress, 0, Qt::NoModifier, Action);
127  qApp->postEvent(GetMythMainWindow(), (QEvent*)ke);
128  return true;
129 }
130 
131 bool Frontend::PlayRecording(int RecordedId, int ChanId,
132  const QDateTime &StartTime)
133 {
134  QDateTime starttime = StartTime;
135 
136  if ((RecordedId <= 0) &&
137  (ChanId <= 0 || !StartTime.isValid()))
138  throw QString("Recorded ID or Channel ID and StartTime appears invalid.");
139 
140  if (RecordedId > 0)
141  {
142  RecordingInfo recInfo = RecordingInfo(RecordedId);
143  ChanId = recInfo.GetChanID();
144  starttime = recInfo.GetRecordingStartTime();
145  }
146 
147  if (GetMythUI()->GetCurrentLocation().toLower() == "playback")
148  {
149  QString message = QString("NETWORK_CONTROL STOP");
150  MythEvent me(message);
151  gCoreContext->dispatch(me);
152 
153  QElapsedTimer timer;
154  timer.start();
155  while (!timer.hasExpired(10000) &&
156  (GetMythUI()->GetCurrentLocation().toLower() == "playback"))
157  std::this_thread::sleep_for(10ms);
158  }
159 
160  if (GetMythUI()->GetCurrentLocation().toLower() != "playbackbox")
161  {
162  GetMythMainWindow()->JumpTo("TV Recording Playback");
163 
164  QElapsedTimer timer;
165  timer.start();
166  while (!timer.hasExpired(10000) &&
167  (GetMythUI()->GetCurrentLocation().toLower() != "playbackbox"))
168  std::this_thread::sleep_for(10ms);
169 
170  timer.start();
171  while (!timer.hasExpired(10000) && (!MythMainWindow::IsTopScreenInitialized()))
172  std::this_thread::sleep_for(10ms);
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().count())
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 }
Frontend::GetStatus
DTC::FrontendStatus * GetStatus(void) override
Definition: frontend.cpp:32
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
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
Frontend::PlayVideo
bool PlayVideo(const QString &Id, bool UseBookmark) override
Definition: frontend.cpp:194
simple_ref_ptr
Definition: quicksp.h:24
RecordingInfo
Holds information on a TV Program one might wish to record.
Definition: recordinginfo.h:34
MythMainWindow::JumpTo
void JumpTo(const QString &Destination, bool Pop=true)
Definition: mythmainwindow.cpp:1444
MythEvent
This class is used as a container for messages.
Definition: mythevent.h:16
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:59
keybindings.h
Main header for keybinding classes.
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
MythUIStateTracker::GetFreshState
static void GetFreshState(QVariantMap &State)
Definition: mythuistatetracker.cpp:41
MythNotification::TypeFromString
static Type TypeFromString(const QString &Type)
Definition: mythnotification.cpp:228
ACTION_SCREENSHOT
#define ACTION_SCREENSHOT
Definition: mythuiactions.h:22
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:40
mythversion.h
KeyBindings
Encapsulates information about the current keybindings.
Definition: keybindings.h:36
Frontend::SendMessage
bool SendMessage(const QString &Message, uint _Timeout) override
Definition: frontend.cpp:44
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_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:256
MythNotification::Priority
Priority
Definition: mythnotification.h:55
ACTION_SETBRIGHTNESS
#define ACTION_SETBRIGHTNESS
Definition: tv_actions.h:59
MythMainWindow::IsTopScreenInitialized
static bool IsTopScreenInitialized()
Definition: mythmainwindow.cpp:605
Frontend::SendKey
bool SendKey(const QString &Key) override
Definition: frontend.cpp:337
frontend.h
uint
unsigned int uint
Definition: compat.h:140
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:151
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: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:250
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:131
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:124
GetMythMainWindow
MythMainWindow * GetMythMainWindow(void)
Definition: mythmainwindow.cpp:104
build_compdb.action
action
Definition: build_compdb.py:9
GetMythSourceVersion
const char * GetMythSourceVersion()
Definition: mythcoreutil.cpp:314
MythCoreContext::GetHostName
QString GetHostName(void)
Definition: mythcorecontext.cpp:858
mythuiactions.h
Frontend::SendAction
bool SendAction(const QString &Action, const QString &Value, uint Width, uint Height) override
Definition: frontend.cpp:88
MythMainWindow::ResetScreensaver
static void ResetScreensaver()
Definition: mythmainwindow.cpp:588
Frontend::IsValidAction
static bool IsValidAction(const QString &action)
Definition: frontend.cpp:281
GetMythUI
MythUIHelper * GetMythUI()
Definition: mythuihelper.cpp:66
mythmainwindow.h
Frontend::InitialiseActions
static void InitialiseActions(void)
Definition: frontend.cpp:303
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
MythNotification::Error
static Type Error
Definition: mythnotification.h:35
Priority
Definition: channelsettings.cpp:191
ACTION_SWITCHANGLE
#define ACTION_SWITCHANGLE
Definition: tv_actions.h:54
videoutils.h
tv_play.h