Merge pull request #26126 from stephan49/fix-pipewire-unlock-error
[xbmc.git] / xbmc / pvr / windows / GUIWindowPVRBase.cpp
blob2f79373b60c3661b57498d63d3ec6c6e1c263a56
1 /*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "GUIWindowPVRBase.h"
11 #include "FileItem.h"
12 #include "FileItemList.h"
13 #include "GUIUserMessages.h"
14 #include "ServiceBroker.h"
15 #include "URL.h"
16 #include "addons/AddonManager.h"
17 #include "addons/addoninfo/AddonType.h"
18 #include "dialogs/GUIDialogExtendedProgressBar.h"
19 #include "dialogs/GUIDialogSelect.h"
20 #include "guilib/GUIComponent.h"
21 #include "guilib/GUIMessage.h"
22 #include "guilib/GUIWindowManager.h"
23 #include "guilib/LocalizeStrings.h"
24 #include "input/actions/Action.h"
25 #include "input/actions/ActionIDs.h"
26 #include "messaging/ApplicationMessenger.h"
27 #include "messaging/helpers/DialogOKHelper.h"
28 #include "pvr/PVRManager.h"
29 #include "pvr/PVRPlaybackState.h"
30 #include "pvr/PVRThumbLoader.h"
31 #include "pvr/addons/PVRClient.h"
32 #include "pvr/addons/PVRClients.h"
33 #include "pvr/channels/PVRChannelGroup.h"
34 #include "pvr/channels/PVRChannelGroups.h"
35 #include "pvr/channels/PVRChannelGroupsContainer.h"
36 #include "pvr/filesystem/PVRGUIDirectory.h"
37 #include "pvr/guilib/PVRGUIActionsChannels.h"
38 #include "utils/Variant.h"
39 #include "utils/log.h"
41 #include <iterator>
42 #include <memory>
43 #include <mutex>
44 #include <string>
45 #include <utility>
46 #include <vector>
48 using namespace std::chrono_literals;
50 #define MAX_INVALIDATION_FREQUENCY 2000ms // limit to one invalidation per X milliseconds
52 using namespace PVR;
53 using namespace KODI::MESSAGING;
55 namespace PVR
58 class CGUIPVRChannelGroupsSelector
60 public:
61 virtual ~CGUIPVRChannelGroupsSelector() = default;
63 bool Initialize(CGUIWindow* parent, bool bRadio);
65 bool HasFocus() const;
66 std::shared_ptr<CPVRChannelGroup> GetSelectedChannelGroup() const;
67 bool SelectChannelGroup(const std::shared_ptr<CPVRChannelGroup>& newGroup);
69 private:
70 CGUIControl* m_control = nullptr;
71 std::vector<std::shared_ptr<CPVRChannelGroup>> m_channelGroups;
74 } // namespace PVR
76 bool CGUIPVRChannelGroupsSelector::Initialize(CGUIWindow* parent, bool bRadio)
78 CGUIControl* control = parent->GetControl(CONTROL_LSTCHANNELGROUPS);
79 if (control && control->IsContainer())
81 m_control = control;
82 m_channelGroups = CServiceBroker::GetPVRManager().ChannelGroups()->Get(bRadio)->GetMembers(true);
84 CFileItemList channelGroupItems;
85 CPVRGUIDirectory::GetChannelGroupsDirectory(bRadio, true, channelGroupItems);
87 CGUIMessage msg(GUI_MSG_LABEL_BIND, m_control->GetID(), CONTROL_LSTCHANNELGROUPS, 0, 0, &channelGroupItems);
88 m_control->OnMessage(msg);
89 return true;
91 return false;
94 bool CGUIPVRChannelGroupsSelector::HasFocus() const
96 return m_control && m_control->HasFocus();
99 std::shared_ptr<CPVRChannelGroup> CGUIPVRChannelGroupsSelector::GetSelectedChannelGroup() const
101 if (m_control)
103 CGUIMessage msg(GUI_MSG_ITEM_SELECTED, m_control->GetID(), CONTROL_LSTCHANNELGROUPS);
104 m_control->OnMessage(msg);
106 const auto it = std::next(m_channelGroups.begin(), msg.GetParam1());
107 if (it != m_channelGroups.end())
109 return *it;
112 return std::shared_ptr<CPVRChannelGroup>();
115 bool CGUIPVRChannelGroupsSelector::SelectChannelGroup(const std::shared_ptr<CPVRChannelGroup>& newGroup)
117 if (m_control && newGroup)
119 int iIndex = 0;
120 for (const auto& group : m_channelGroups)
122 if (*newGroup == *group)
124 CGUIMessage msg(GUI_MSG_ITEM_SELECT, m_control->GetID(), CONTROL_LSTCHANNELGROUPS, iIndex);
125 m_control->OnMessage(msg);
126 return true;
128 ++iIndex;
131 return false;
134 CGUIWindowPVRBase::CGUIWindowPVRBase(bool bRadio, int id, const std::string& xmlFile)
135 : CGUIMediaWindow(id, xmlFile.c_str()),
136 m_bRadio(bRadio),
137 m_channelGroupsSelector(new CGUIPVRChannelGroupsSelector)
139 // prevent removable drives to appear in directory listing (base class default behavior).
140 m_rootDir.AllowNonLocalSources(false);
142 CServiceBroker::GetPVRManager().Events().Subscribe(this, &CGUIWindowPVRBase::Notify);
145 CGUIWindowPVRBase::~CGUIWindowPVRBase()
147 if (m_channelGroup)
148 m_channelGroup->Events().Unsubscribe(this);
150 CServiceBroker::GetPVRManager().Events().Unsubscribe(this);
153 void CGUIWindowPVRBase::UpdateSelectedItemPath()
155 CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().SetSelectedChannelPath(
156 m_bRadio, m_viewControl.GetSelectedItemPath());
159 void CGUIWindowPVRBase::Notify(const PVREvent& event)
161 // call virtual event handler function
162 NotifyEvent(event);
165 void CGUIWindowPVRBase::NotifyEvent(const PVREvent& event)
167 if (event == PVREvent::ManagerStopped)
169 ClearData();
171 else if (m_active)
173 if (event == PVREvent::SystemSleep)
175 CGUIMessage m(GUI_MSG_SYSTEM_SLEEP, GetID(), 0, static_cast<int>(event));
176 CServiceBroker::GetAppMessenger()->SendGUIMessage(m);
178 else if (event == PVREvent::SystemWake)
180 CGUIMessage m(GUI_MSG_SYSTEM_WAKE, GetID(), 0, static_cast<int>(event));
181 CServiceBroker::GetAppMessenger()->SendGUIMessage(m);
183 else
185 CGUIMessage m(GUI_MSG_REFRESH_LIST, GetID(), 0, static_cast<int>(event));
186 CServiceBroker::GetAppMessenger()->SendGUIMessage(m);
191 bool CGUIWindowPVRBase::OnAction(const CAction& action)
193 switch (action.GetID())
195 case ACTION_PREVIOUS_CHANNELGROUP:
196 ActivatePreviousChannelGroup();
197 return true;
199 case ACTION_NEXT_CHANNELGROUP:
200 ActivateNextChannelGroup();
201 return true;
203 case ACTION_MOVE_RIGHT:
204 case ACTION_MOVE_LEFT:
206 if (m_channelGroupsSelector->HasFocus() && CGUIMediaWindow::OnAction(action))
208 SetChannelGroup(m_channelGroupsSelector->GetSelectedChannelGroup());
209 return true;
214 return CGUIMediaWindow::OnAction(action);
217 bool CGUIWindowPVRBase::ActivatePreviousChannelGroup()
219 const std::shared_ptr<const CPVRChannelGroup> channelGroup = GetChannelGroup();
220 if (channelGroup)
222 const std::shared_ptr<const CPVRChannelGroups> groups{
223 CServiceBroker::GetPVRManager().ChannelGroups()->Get(channelGroup->IsRadio())};
224 if (groups)
226 SetChannelGroup(groups->GetPreviousGroup(*channelGroup));
227 return true;
230 return false;
233 bool CGUIWindowPVRBase::ActivateNextChannelGroup()
235 const std::shared_ptr<const CPVRChannelGroup> channelGroup = GetChannelGroup();
236 if (channelGroup)
238 const std::shared_ptr<const CPVRChannelGroups> groups{
239 CServiceBroker::GetPVRManager().ChannelGroups()->Get(channelGroup->IsRadio())};
240 if (groups)
242 SetChannelGroup(groups->GetNextGroup(*channelGroup));
243 return true;
246 return false;
249 void CGUIWindowPVRBase::ClearData()
251 std::unique_lock<CCriticalSection> lock(m_critSection);
252 m_channelGroup.reset();
253 m_channelGroupsSelector = std::make_unique<CGUIPVRChannelGroupsSelector>();
256 void CGUIWindowPVRBase::OnInitWindow()
258 SetProperty("IsRadio", m_bRadio ? "true" : "");
260 if (InitChannelGroup())
262 m_channelGroupsSelector->Initialize(this, m_bRadio);
264 CGUIMediaWindow::OnInitWindow();
266 // mark item as selected by channel path
267 m_viewControl.SetSelectedItem(
268 CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().GetSelectedChannelPath(m_bRadio));
270 // This has to be done after base class OnInitWindow to restore correct selection
271 m_channelGroupsSelector->SelectChannelGroup(GetChannelGroup());
273 else
275 CGUIWindow::OnInitWindow(); // do not call CGUIMediaWindow as it will do a Refresh which in no case works in this state (no channelgroup!)
276 ShowProgressDialog(g_localizeStrings.Get(19235), 0); // PVR manager is starting up
280 void CGUIWindowPVRBase::OnDeinitWindow(int nextWindowID)
282 HideProgressDialog();
283 UpdateSelectedItemPath();
284 CGUIMediaWindow::OnDeinitWindow(nextWindowID);
287 bool CGUIWindowPVRBase::OnMessage(CGUIMessage& message)
289 bool bReturn = false;
290 switch (message.GetMessage())
292 case GUI_MSG_CLICKED:
294 switch (message.GetSenderId())
296 case CONTROL_BTNCHANNELGROUPS:
297 return OpenChannelGroupSelectionDialog();
299 case CONTROL_LSTCHANNELGROUPS:
301 switch (message.GetParam1())
303 case ACTION_SELECT_ITEM:
304 case ACTION_MOUSE_LEFT_CLICK:
306 SetChannelGroup(m_channelGroupsSelector->GetSelectedChannelGroup());
307 bReturn = true;
308 break;
313 break;
316 case GUI_MSG_REFRESH_LIST:
318 switch (static_cast<PVREvent>(message.GetParam1()))
320 case PVREvent::ManagerStarted:
321 case PVREvent::ClientsInvalidated:
322 case PVREvent::ChannelGroupsInvalidated:
324 if (InitChannelGroup())
326 m_channelGroupsSelector->Initialize(this, m_bRadio);
327 m_channelGroupsSelector->SelectChannelGroup(GetChannelGroup());
328 HideProgressDialog();
329 Refresh(true);
330 m_viewControl.SetFocused();
332 break;
334 default:
335 break;
338 if (IsActive())
340 // Only the active window must set the selected item path which is shared
341 // between all PVR windows, not the last notified window (observer).
342 UpdateSelectedItemPath();
344 bReturn = true;
345 break;
348 case GUI_MSG_NOTIFY_ALL:
350 switch (message.GetParam1())
352 case GUI_MSG_UPDATE_SOURCES:
354 // removable drive connected/disconnected. base class triggers a window
355 // content refresh, which makes no sense for pvr windows.
356 bReturn = true;
357 break;
359 case GUI_MSG_UPDATE:
361 if (IsActive() && m_bUpdating)
363 // no concurrent updates
364 CLog::LogF(LOGWARNING, "GUI_MSG_UPDATE: Updating in progress");
365 bReturn = true;
367 break;
370 break;
374 return bReturn || CGUIMediaWindow::OnMessage(message);
377 void CGUIWindowPVRBase::SetInvalid()
379 if (m_refreshTimeout.IsTimePast())
381 for (const auto& item : *m_vecItems)
382 item->SetInvalid();
384 CGUIMediaWindow::SetInvalid();
385 m_refreshTimeout.Set(MAX_INVALIDATION_FREQUENCY);
389 bool CGUIWindowPVRBase::CanBeActivated() const
391 // check if there is at least one enabled PVR add-on
392 if (!CServiceBroker::GetAddonMgr().HasAddons(ADDON::AddonType::PVRDLL))
394 HELPERS::ShowOKDialogText(CVariant{19296}, CVariant{19272}); // No PVR add-on enabled, You need a tuner, backend software...
395 return false;
398 return true;
401 bool CGUIWindowPVRBase::OpenChannelGroupSelectionDialog()
403 CGUIDialogSelect* dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
404 if (!dialog)
405 return false;
407 CFileItemList options;
408 CPVRGUIDirectory::GetChannelGroupsDirectory(m_bRadio, true, options);
410 dialog->Reset();
411 dialog->SetHeading(CVariant{g_localizeStrings.Get(19146)});
412 dialog->SetMultiSelection(false);
413 dialog->SetItems(options);
415 auto& pvrMgr = CServiceBroker::GetPVRManager();
416 const bool useDetails = pvrMgr.Clients()->CreatedClientAmount() > 1;
417 dialog->SetUseDetails(useDetails);
418 if (useDetails)
420 std::string selectedGroup;
421 const std::shared_ptr<const CPVRChannelGroup> channelGroup = GetChannelGroup();
422 if (channelGroup)
423 selectedGroup = channelGroup->GetPath();
425 CPVRThumbLoader loader;
426 int idx = 0;
427 for (auto& group : options)
429 // set client name as label2
430 const std::shared_ptr<const CPVRClient> client = pvrMgr.GetClient(*group);
431 if (client)
432 group->SetLabel2(client->GetFullClientName());
434 // set thumbnail
435 loader.LoadItem(group.get());
437 // if not yet done, find and select currently active channel group
438 if (idx >= 0)
440 if (group->GetPath() == selectedGroup)
442 dialog->SetSelected(idx);
443 idx = -1; // done
445 else
447 idx++;
452 else
454 const std::shared_ptr<const CPVRChannelGroup> channelGroup = GetChannelGroup();
455 if (channelGroup)
457 int idx = -1;
458 const std::string selectedGroup{channelGroup->GetPath()};
459 for (auto& group : options)
461 // select currently active channel group
462 if (group->GetPath() == selectedGroup)
464 dialog->SetSelected(idx);
465 break;
467 idx++;
472 dialog->Open();
474 if (!dialog->IsConfirmed())
475 return false;
477 const CFileItemPtr item = dialog->GetSelectedFileItem();
478 if (!item)
479 return false;
481 SetChannelGroup(pvrMgr.ChannelGroups()->Get(m_bRadio)->GetGroupByPath(item->GetPath()));
483 return true;
486 bool CGUIWindowPVRBase::InitChannelGroup()
488 if (!CServiceBroker::GetPVRManager().IsStarted())
489 return false;
491 std::shared_ptr<CPVRChannelGroup> group;
492 if (m_channelGroupPath.empty())
494 group = CServiceBroker::GetPVRManager().PlaybackState()->GetActiveChannelGroup(m_bRadio);
496 else
498 group = CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_bRadio)->GetGroupByPath(m_channelGroupPath);
499 if (group)
500 CServiceBroker::GetPVRManager().PlaybackState()->SetActiveChannelGroup(group);
501 else
502 CLog::LogF(LOGERROR, "Found no {} channel group with path '{}'!", m_bRadio ? "radio" : "TV",
503 m_channelGroupPath);
505 m_channelGroupPath.clear();
508 if (group)
510 std::unique_lock<CCriticalSection> lock(m_critSection);
511 if (m_channelGroup != group)
513 m_viewControl.SetSelectedItem(0);
514 SetChannelGroup(std::move(group), false);
516 // Path might have changed since last init. Set it always, not just on group change.
517 m_vecItems->SetPath(GetDirectoryPath());
518 return true;
520 return false;
523 std::shared_ptr<CPVRChannelGroup> CGUIWindowPVRBase::GetChannelGroup()
525 std::unique_lock<CCriticalSection> lock(m_critSection);
526 return m_channelGroup;
529 void CGUIWindowPVRBase::SetChannelGroup(std::shared_ptr<CPVRChannelGroup> &&group, bool bUpdate /* = true */)
531 if (!group)
532 return;
534 std::shared_ptr<CPVRChannelGroup> updateChannelGroup;
536 std::unique_lock<CCriticalSection> lock(m_critSection);
537 if (m_channelGroup != group)
539 if (m_channelGroup)
540 m_channelGroup->Events().Unsubscribe(this);
541 m_channelGroup = std::move(group);
542 // we need to register the window to receive changes from the new group
543 m_channelGroup->Events().Subscribe(this, &CGUIWindowPVRBase::Notify);
544 if (bUpdate)
545 updateChannelGroup = m_channelGroup;
549 if (updateChannelGroup)
551 CServiceBroker::GetPVRManager().PlaybackState()->SetActiveChannelGroup(updateChannelGroup);
552 Update(GetDirectoryPath());
556 void CGUIWindowPVRBase::SetChannelGroupPath(const std::string& path)
558 const CURL url{path};
559 const std::string pathWithoutOptions{url.GetWithoutOptions()};
561 std::unique_lock<CCriticalSection> lock(m_critSection);
562 if (m_channelGroupPath != pathWithoutOptions)
564 m_channelGroupPath = pathWithoutOptions;
568 bool CGUIWindowPVRBase::Update(const std::string& strDirectory, bool updateFilterPath /*= true*/)
570 if (m_bUpdating)
572 // no concurrent updates
573 CLog::LogF(LOGWARNING, "Updating in progress");
574 return false;
577 CUpdateGuard guard(m_bUpdating);
579 if (!GetChannelGroup())
581 // no updates before fully initialized
582 return false;
585 int iOldCount = m_vecItems->Size();
586 int iSelectedItem = m_viewControl.GetSelectedItem();
587 const std::string oldPath = m_vecItems->GetPath();
589 bool bReturn = CGUIMediaWindow::Update(strDirectory, updateFilterPath);
591 if (bReturn &&
592 iSelectedItem != -1) // something must have been selected
594 int iNewCount = m_vecItems->Size();
595 if (iOldCount > iNewCount && // at least one item removed by Update()
596 oldPath == m_vecItems->GetPath()) // update not due changing into another folder
598 // restore selected item if we just deleted one or more items.
599 if (iSelectedItem >= iNewCount)
600 iSelectedItem = iNewCount - 1;
602 m_viewControl.SetSelectedItem(iSelectedItem);
606 return bReturn;
609 void CGUIWindowPVRBase::UpdateButtons()
611 CGUIMediaWindow::UpdateButtons();
613 const std::shared_ptr<CPVRChannelGroup> channelGroup = GetChannelGroup();
614 if (channelGroup)
616 SET_CONTROL_LABEL(CONTROL_BTNCHANNELGROUPS, g_localizeStrings.Get(19141) + ": " + channelGroup->GroupName());
619 m_channelGroupsSelector->SelectChannelGroup(channelGroup);
622 void CGUIWindowPVRBase::ShowProgressDialog(const std::string& strText, int iProgress)
624 if (!m_progressHandle)
626 CGUIDialogExtendedProgressBar* loadingProgressDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogExtendedProgressBar>(WINDOW_DIALOG_EXT_PROGRESS);
627 if (!loadingProgressDialog)
629 CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_EXT_PROGRESS!");
630 return;
632 m_progressHandle = loadingProgressDialog->GetHandle(g_localizeStrings.Get(19235)); // PVR manager is starting up
635 m_progressHandle->SetPercentage(static_cast<float>(iProgress));
636 m_progressHandle->SetText(strText);
639 void CGUIWindowPVRBase::HideProgressDialog()
641 if (m_progressHandle)
643 m_progressHandle->MarkFinished();
644 m_progressHandle = nullptr;