MythTV  master
mythfrontendservice.cpp
Go to the documentation of this file.
1 // C++
2 #include <thread>
3 #include <algorithm>
4 
5 // Qt
6 #include <QKeyEvent>
7 #include <QGlobalStatic>
8 #include <QCoreApplication>
9 
10 // MythTV
14 #include "libmythbase/mythversion.h"
18 #include "libmythtv/tv_actions.h"
19 #include "libmythtv/tv_play.h"
21 #include "libmythui/mythuihelper.h"
23 
24 // MythFrontend
25 #include "keybindings.h"
27 
28 #define LOC QString("FrontendServices: ")
29 using ActionDescs = QHash<QString,QStringList>;
30 
37 {
38  public:
39  const QStringList& Actions() { return m_actions; }
40  const QStringList& SelectActions() { return m_selectActions; }
41  const QStringList& ValueActions() { return m_valueActions; }
43 
45  {
46  // Build action and action descriptions
47  if (auto * bindings = new KeyBindings(gCoreContext->GetHostName()))
48  {
49  QStringList contexts = bindings->GetContexts();
50  contexts.sort();
51  for (const QString & context : std::as_const(contexts))
52  {
53  m_actionDescriptions[context].clear();
54  QStringList ctxactions = bindings->GetActions(context);
55  ctxactions.sort();
56  m_actions << ctxactions;
57  for (const QString & actions : std::as_const(ctxactions))
58  {
59  QString desc = actions + "," + bindings->GetActionDescription(context, actions);
60  m_actionDescriptions[context].append(desc);
61  }
62  }
63  delete bindings;
64  }
65  m_actions.removeDuplicates();
66  m_actions.sort();
67 
68  // Build actions that have an implicit value (e.g. SELECTSUBTITLE_0)
69  for (const auto & action : std::as_const(m_actions))
70  if (action.startsWith("select", Qt::CaseInsensitive) && action.contains("_"))
71  m_selectActions.append(action);
72 
73  // Hardcoded list of value actions
80  }
81 
82  private:
83  QStringList m_actions;
84  QStringList m_selectActions;
85  QStringList m_valueActions;
86  QHash<QString,QStringList> m_actionDescriptions;
87 };
88 
89 //NOLINTNEXTLINE(readability-redundant-member-init)
90 Q_GLOBAL_STATIC(FrontendActions, s_actions)
91 
92 // This will be initialised in a thread safe manner on first use
94  (FRONTEND_HANDLE, MythFrontendService::staticMetaObject, &MythFrontendService::RegisterCustomTypes))
95 
96 void MythFrontendService::RegisterCustomTypes()
97 {
98  qRegisterMetaType<FrontendStatus*>("FrontendStatus");
99  qRegisterMetaType<FrontendActionList*>("FrontendActionList");
100 }
101 
103  : MythHTTPService(s_service)
104 {
105 }
106 
108 {
109  QVariantMap state;
111  auto* result = new FrontendStatus(gCoreContext->GetHostName(), GetMythSourceVersion(), state);
112  return result;
113 }
114 
115 bool MythFrontendService::SendKey(const QString& Key)
116 {
117  if (Key.isEmpty() || !HasMythMainWindow())
118  return false;
119 
120  int keycode = 0;
121  bool valid = false;
122  QString key = Key.toLower();
123 
124  // We could statically initialise this mapping of the Qt::Key enum but clients
125  // should really just use actions...
126  QMetaEnum meta = QMetaEnum::fromType<Qt::Key>();
127  for (int i = 0; i < meta.keyCount(); i++)
128  {
129  if (QByteArray(meta.key(i)).mid(4).toLower() == key)
130  {
131  keycode = meta.value(i);
132  valid = true;
133  break;
134  }
135  }
136 
137  if (!valid && key.size() == 1)
138  {
139  keycode = key.toLatin1()[0] & 0x7f;
140  valid = true;
141  }
142 
143  if (!valid)
144  {
145  LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Unknown key: '%1'").arg(Key));
146  return false;
147  }
148 
150  auto * mainwindow = GetMythMainWindow();
151  auto * event1 = new QKeyEvent(QEvent::KeyPress, keycode, Qt::NoModifier, "");
152  QCoreApplication::postEvent(mainwindow, event1);
153  auto * event2 = new QKeyEvent(QEvent::KeyRelease, keycode, Qt::NoModifier, "");
154  QCoreApplication::postEvent(mainwindow, event2);
155  LOG(VB_HTTP, LOG_INFO, LOC + QString("Sent key: '%1'").arg(Key));
156  return true;
157 }
158 
159 bool MythFrontendService::SendMessage(const QString& Message, uint Timeout)
160 {
161  if (Message.isEmpty())
162  return false;
163  QStringList data(QString::number(std::clamp(Timeout, 0U, 1000U)));
164  qApp->postEvent(GetMythMainWindow(), new MythEvent(MythEvent::kMythUserMessage, Message, data));
165  return true;
166 }
167 
174 bool MythFrontendService::SendNotification(bool Error, const QString& Type,
175  const QString& Message, const QString& Origin,
176  const QString& Description, const QString& Image,
177  const QString& Extra, const QString& ProgressText,
178  float Progress, std::chrono::seconds Timeout,
179  bool Fullscreen, uint Visibility,
180  uint Priority)
181 {
182  if (Message.isEmpty() || !GetNotificationCenter())
183  return false;
184 
186  Message, Origin.isNull() ? tr("FrontendServices") : Origin,
187  Description, Image, Extra, ProgressText, Progress, Timeout,
188  Fullscreen, Visibility, static_cast<MythNotification::Priority>(Priority));
189  return true;
190 }
191 
193 {
194  QVariantMap result;
195  QHashIterator<QString,QStringList> contexts(s_actions->Descriptions());
196  while (contexts.hasNext())
197  {
198  contexts.next();
199  if (!Context.isEmpty() && contexts.key() != Context)
200  continue;
201 
202  // TODO can we keep the context data with QMap<QString, QStringList>?
203  QStringList actions = contexts.value();
204  for (const QString & action : std::as_const(actions))
205  {
206  QStringList split = action.split(",");
207  if (split.size() == 2)
208  result.insert(split[0], split[1]);
209  }
210  }
211  return new FrontendActionList(result);
212 }
213 
215 {
216  return s_actions->Descriptions().keys();
217 }
218 
219 bool MythFrontendService::SendAction(const QString& Action, const QString& Value, uint Width, uint Height)
220 {
222  return false;
223 
224  if (!Value.isEmpty() && s_actions->ValueActions().contains(Action))
225  {
227  auto * me = new MythEvent(Action, QStringList(Value));
228  qApp->postEvent(GetMythMainWindow(), me);
229  return true;
230  }
231 
232  if (ACTION_SCREENSHOT == Action)
233  {
234  if (!Width || !Height)
235  {
236  LOG(VB_GENERAL, LOG_ERR, LOC + "Invalid screenshot parameters.");
237  return false;
238  }
239 
240  QStringList args;
241  args << QString::number(Width) << QString::number(Height);
242  auto* me = new MythEvent(Action, args);
243  qApp->postEvent(GetMythMainWindow(), me);
244  return true;
245  }
246 
248  qApp->postEvent(GetMythMainWindow(), new QKeyEvent(QEvent::KeyPress, 0, Qt::NoModifier, Action));
249  return true;
250 }
251 
253 {
254  if (s_actions->Actions().contains(Action) || s_actions->SelectActions().contains(Action))
255  return true;
256  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Action '%1'' is invalid.").arg(Action));
257  return false;
258 }
259 
260 bool MythFrontendService::PlayRecording(int RecordedId, int ChanId, const QDateTime& StartTime)
261 {
262  QDateTime starttime = StartTime;
263 
264  if ((RecordedId <= 0) && (ChanId <= 0 || !StartTime.isValid()))
265  {
266  LOG(VB_GENERAL, LOG_INFO, LOC + "Recorded ID or Channel ID and StartTime appears invalid.");
267  return false;
268  }
269 
270  if (RecordedId > 0)
271  {
272  RecordingInfo recInfo = RecordingInfo(static_cast<uint>(RecordedId));
273  ChanId = static_cast<int>(recInfo.GetChanID());
274  starttime = recInfo.GetRecordingStartTime();
275  }
276 
277  // Note: This is inconsistent with PlayVideo behaviour - which checks for
278  // a current instance of TV and returns false if something is already playing.
279  if (GetMythUI()->GetCurrentLocation().toLower() == "playback")
280  {
281  QString message = QString("NETWORK_CONTROL STOP");
282  MythEvent me(message);
283  gCoreContext->dispatch(me);
284 
285  QElapsedTimer timer;
286  timer.start();
287  while (!timer.hasExpired(10000) && (GetMythUI()->GetCurrentLocation().toLower() == "playback"))
288  std::this_thread::sleep_for(std::chrono::milliseconds(10));
289  }
290 
291  if (GetMythUI()->GetCurrentLocation().toLower() != "playbackbox")
292  {
293  GetMythMainWindow()->JumpTo("TV Recording Playback");
294  QElapsedTimer timer;
295  timer.start();
296  while (!timer.hasExpired(10000) && (GetMythUI()->GetCurrentLocation().toLower() != "playbackbox"))
297  std::this_thread::sleep_for(std::chrono::milliseconds(10));
298 
299  timer.start();
300  while (!timer.hasExpired(10000) && (!MythMainWindow::IsTopScreenInitialized()))
301  std::this_thread::sleep_for(std::chrono::milliseconds(10));
302  }
303 
304  if (GetMythUI()->GetCurrentLocation().toLower() == "playbackbox")
305  {
306  LOG(VB_GENERAL, LOG_INFO, LOC + QString("PlayRecording, ChanID: %1 StartTime: %2")
307  .arg(ChanId).arg(starttime.toString(Qt::ISODate)));
308 
309  QString message = QString("NETWORK_CONTROL PLAY PROGRAM %1 %2 %3")
310  .arg(ChanId).arg(starttime.toString("yyyyMMddhhmmss"), "12345");
311 
312  MythEvent me(message);
313  gCoreContext->dispatch(me);
314  return true;
315  }
316 
317  return false;
318 }
319 
320 bool MythFrontendService::PlayVideo(const QString& Id, bool UseBookmark)
321 {
322  if (TV::IsTVRunning())
323  {
324  LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Ignoring PlayVideo request - frontend is busy."));
325  return false;
326  }
327 
328  bool ok = false;
329  uint id = Id.toUInt(&ok);
330  if (!ok)
331  {
332  LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Invalid video Id."));
333  return false;
334  }
335 
337  if (!metadata)
338  {
339  LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Didn't find any video metadata."));
340  return false;
341  }
342 
343  if (metadata->GetHost().isEmpty())
344  {
345  LOG(VB_GENERAL, LOG_WARNING, LOC + QString("No host for video."));
346  return false;
347  }
348 
349  QString mrl = generate_file_url("Videos", metadata->GetHost(), metadata->GetFilename());
350  LOG(VB_GENERAL, LOG_INFO, LOC + QString("PlayVideo ID: %1 UseBookmark: %2 URL: '%3'")
351  .arg(id).arg(UseBookmark).arg(mrl));
352 
353  QStringList args;
354  args << mrl << metadata->GetPlot() << metadata->GetTitle()
355  << metadata->GetSubtitle() << metadata->GetDirector()
356  << QString::number(metadata->GetSeason())
357  << QString::number(metadata->GetEpisode())
358  << metadata->GetInetRef() << QString::number(metadata->GetLength().count())
359  << QString::number(metadata->GetYear())
360  << QString::number(metadata->GetID())
361  << QString::number(static_cast<int>(UseBookmark));
362 
363  auto * me = new MythEvent(ACTION_HANDLEMEDIA, args);
364  qApp->postEvent(GetMythMainWindow(), me);
365  return true;
366 }
367 
368 FrontendStatus::FrontendStatus(QString Name, QString Version, QVariantMap State)
369  : m_Name(std::move(Name)),
370  m_Version(std::move(Version)),
371  m_State(std::move(State))
372 {
373  if (m_State.contains("chaptertimes") &&
374 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
375  m_State["chaptertimes"].type() == QVariant::List
376 #else
377  m_State["chaptertimes"].typeId() == QMetaType::QVariantList
378 #endif
379  )
380  {
381  m_ChapterTimes = m_State["chaptertimes"].toList();
382  m_State.remove("chaptertimes");
383  }
384 
385  if (m_State.contains("subtitletracks") &&
386 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
387  m_State["subtitletracks"].type() == QVariant::Map
388 #else
389  m_State["subtitletracks"].typeId() == QMetaType::QVariantMap
390 #endif
391  )
392  {
393  m_SubtitleTracks = m_State["subtitletracks"].toMap();
394  m_State.remove("subtitletracks");
395  }
396 
397  if (m_State.contains("audiotracks") &&
398 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
399  m_State["audiotracks"].type() == QVariant::Map
400 #else
401  m_State["audiotracks"].typeId() == QMetaType::QVariantMap
402 #endif
403  )
404  {
405  m_AudioTracks = m_State["audiotracks"].toMap();
406  m_State.remove("audiotracks");
407  }
408 }
409 
411  : m_ActionList(std::move(List))
412 {
413 }
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
ACTION_SETCONTRAST
#define ACTION_SETCONTRAST
Definition: tv_actions.h:60
mythfrontendservice.h
MythEvent::kMythUserMessage
static const Type kMythUserMessage
Definition: mythevent.h:80
ShowNotification
void ShowNotification(const QString &msg, const QString &from, const QString &detail, const VNMask visibility, const MythNotification::Priority priority)
Definition: mythnotificationcenter.cpp:1437
MythFrontendService::IsValidAction
static bool IsValidAction(const QString &Action)
Definition: mythfrontendservice.cpp:252
ACTION_SCREENSHOT
static constexpr const char * ACTION_SCREENSHOT
Definition: mythuiactions.h:22
MythFrontendService
Definition: mythfrontendservice.h:48
FrontendActions::m_valueActions
QStringList m_valueActions
Definition: mythfrontendservice.cpp:85
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:1457
FrontendActionList::FrontendActionList
Q_INVOKABLE FrontendActionList(QObject *parent=nullptr)
Definition: mythfrontendservice.h:41
FrontendActions::Actions
const QStringList & Actions()
Definition: mythfrontendservice.cpp:39
MythEvent
This class is used as a container for messages.
Definition: mythevent.h:16
GetMythSourceVersion
const char * GetMythSourceVersion()
Definition: mythversion.cpp:5
mythhttpmetaservice.h
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
HasMythMainWindow
bool HasMythMainWindow(void)
Definition: mythmainwindow.cpp:109
ACTION_SETHUE
#define ACTION_SETHUE
Definition: tv_actions.h:62
ProgramInfo::GetRecordingStartTime
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
Definition: programinfo.h:405
FrontendActionList
Definition: mythfrontendservice.h:33
FrontendActions::SelectActions
const QStringList & SelectActions()
Definition: mythfrontendservice.cpp:40
Action
An action (for this plugin) consists of a description, and a set of key sequences.
Definition: action.h:40
State
State
Definition: zmserver.h:68
KeyBindings
Encapsulates information about the current keybindings.
Definition: keybindings.h:36
MythFrontendService::GetContextList
static QStringList GetContextList()
Definition: mythfrontendservice.cpp:214
MythFrontendService::GetActionList
static FrontendActionList * GetActionList(const QString &Context)
Definition: mythfrontendservice.cpp:192
MythFrontendService::GetStatus
static FrontendStatus * GetStatus()
Definition: mythfrontendservice.cpp:107
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
FrontendActions::Descriptions
const ActionDescs & Descriptions()
Definition: mythfrontendservice.cpp:42
ACTION_SEEKABSOLUTE
#define ACTION_SEEKABSOLUTE
Definition: tv_actions.h:40
FrontendActions::FrontendActions
FrontendActions()
Definition: mythfrontendservice.cpp:44
ACTION_SETCOLOUR
#define ACTION_SETCOLOUR
Definition: tv_actions.h:61
FRONTEND_HANDLE
#define FRONTEND_HANDLE
Definition: mythfrontendservice.h:8
FrontendActions::m_actions
QStringList m_actions
Definition: mythfrontendservice.cpp:83
FrontendActions::m_selectActions
QStringList m_selectActions
Definition: mythfrontendservice.cpp:84
FrontendStatus::FrontendStatus
Q_INVOKABLE FrontendStatus(QObject *parent=nullptr)
Definition: mythfrontendservice.h:26
FrontendActions
Definition: mythfrontendservice.cpp:36
MythNotification::kError
static const Type kError
Definition: mythnotification.h:35
ACTION_SETBRIGHTNESS
#define ACTION_SETBRIGHTNESS
Definition: tv_actions.h:59
ActionDescs
QHash< QString, QStringList > ActionDescs
Definition: mythfrontendservice.cpp:29
LOC
#define LOC
Definition: mythfrontendservice.cpp:28
MythMainWindow::IsTopScreenInitialized
static bool IsTopScreenInitialized()
Definition: mythmainwindow.cpp:605
MythFrontendService::PlayRecording
static bool PlayRecording(int RecordedId, int ChanId, const QDateTime &StartTime)
Definition: mythfrontendservice.cpp:260
FrontendActions::m_actionDescriptions
QHash< QString, QStringList > m_actionDescriptions
Definition: mythfrontendservice.cpp:86
uint
unsigned int uint
Definition: compat.h:81
MythUILocation::GetCurrentLocation
QString GetCurrentLocation(bool FullPath=false, bool MainStackOnly=true)
Definition: mythuilocation.cpp:20
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:55
clamp
static eu8 clamp(eu8 value, eu8 low, eu8 high)
Definition: pxsup2dast.c:204
mythuistatetracker.h
MythHTTPService
Definition: mythhttpservice.h:19
MythFrontendService::PlayVideo
static bool PlayVideo(const QString &Id, bool UseBookmark)
Definition: mythfrontendservice.cpp:320
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
FrontendStatus
Definition: mythfrontendservice.h:10
MythFrontendService::SendNotification
static 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, std::chrono::seconds Timeout, bool Fullscreen, uint Visibility, uint Priority)
Send a notification to the frontend.
Definition: mythfrontendservice.cpp:174
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:373
ACTION_SETVOLUME
#define ACTION_SETVOLUME
Definition: tv_actions.h:112
mythcorecontext.h
Name
Definition: channelsettings.cpp:71
MythDate::ISODate
@ ISODate
Default UTC.
Definition: mythdate.h:17
VideoMetadataListManager::loadOneFromDatabase
static VideoMetadataPtr loadOneFromDatabase(uint id)
Definition: videometadatalistmanager.cpp:111
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
MythNotification::Priority
Priority
Definition: mythnotification.h:58
MythCoreContext::GetHostName
QString GetHostName(void)
Definition: mythcorecontext.cpp:842
FrontendActions::ValueActions
const QStringList & ValueActions()
Definition: mythfrontendservice.cpp:41
MythFrontendService::SendMessage
static bool SendMessage(const QString &Message, uint Timeout)
Definition: mythfrontendservice.cpp:159
MythMainWindow::ResetScreensaver
static void ResetScreensaver()
Definition: mythmainwindow.cpp:588
GetMythUI
MythUIHelper * GetMythUI()
Definition: mythuihelper.cpp:66
mythmainwindow.h
ACTION_JUMPCHAPTER
#define ACTION_JUMPCHAPTER
Definition: tv_actions.h:52
MythCoreContext::dispatch
void dispatch(const MythEvent &event)
Definition: mythcorecontext.cpp:1727
Priority
Definition: channelsettings.cpp:216
ACTION_SWITCHANGLE
#define ACTION_SWITCHANGLE
Definition: tv_actions.h:54
videoutils.h
Q_GLOBAL_STATIC_WITH_ARGS
Q_GLOBAL_STATIC_WITH_ARGS(MythHTTPMetaService, s_service,(FRONTEND_HANDLE, MythFrontendService::staticMetaObject, &MythFrontendService::RegisterCustomTypes)) void MythFrontendService
Definition: mythfrontendservice.cpp:93
MythHTTPMetaService
Definition: mythhttpmetaservice.h:10
MythFrontendService::MythFrontendService
MythFrontendService()
Definition: mythfrontendservice.cpp:102
MythFrontendService::SendKey
static Q_CLASSINFO("GetContextList", "name=StringList") public slots bool SendKey(const QString &Key)
Definition: mythfrontendservice.cpp:115
tv_play.h