[ExecString] combine SplitParameters with identical function of CUtil
[xbmc.git] / xbmc / application / ApplicationSkinHandling.cpp
blobbf73f1436359deeab9d0b8917f953f1249419a99
1 /*
2 * Copyright (C) 2005-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 "ApplicationSkinHandling.h"
11 #include "FileItem.h"
12 #include "GUIInfoManager.h"
13 #include "GUILargeTextureManager.h"
14 #include "GUIUserMessages.h"
15 #include "PlayListPlayer.h"
16 #include "ServiceBroker.h"
17 #include "TextureCache.h"
18 #include "addons/AddonManager.h"
19 #include "addons/AddonVersion.h"
20 #include "addons/Skin.h"
21 #include "addons/addoninfo/AddonType.h"
22 #include "application/ApplicationComponents.h"
23 #include "application/ApplicationPlayer.h"
24 #include "dialogs/GUIDialogButtonMenu.h"
25 #include "dialogs/GUIDialogKaiToast.h"
26 #include "dialogs/GUIDialogSubMenu.h"
27 #include "filesystem/Directory.h"
28 #include "filesystem/DirectoryCache.h"
29 #include "guilib/GUIAudioManager.h"
30 #include "guilib/GUIColorManager.h"
31 #include "guilib/GUIComponent.h"
32 #include "guilib/GUIFontManager.h"
33 #include "guilib/GUIWindowManager.h"
34 #include "guilib/LocalizeStrings.h"
35 #include "guilib/StereoscopicsManager.h"
36 #include "messaging/ApplicationMessenger.h"
37 #include "messaging/helpers/DialogHelper.h"
38 #include "settings/Settings.h"
39 #include "settings/SettingsComponent.h"
40 #include "settings/SkinSettings.h"
41 #include "settings/lib/Setting.h"
42 #include "utils/StringUtils.h"
43 #include "utils/URIUtils.h"
44 #include "utils/XBMCTinyXML.h"
45 #include "utils/log.h"
46 #include "video/dialogs/GUIDialogFullScreenInfo.h"
48 using namespace KODI::MESSAGING;
50 CApplicationSkinHandling::CApplicationSkinHandling(IMsgTargetCallback* msgCb,
51 IWindowManagerCallback* wCb,
52 bool& bInitializing)
53 : m_msgCb(msgCb), m_wCb(wCb), m_bInitializing(bInitializing)
57 bool CApplicationSkinHandling::LoadSkin(const std::string& skinID)
59 std::shared_ptr<ADDON::CSkinInfo> skin;
61 ADDON::AddonPtr addon;
62 if (!CServiceBroker::GetAddonMgr().GetAddon(skinID, addon, ADDON::AddonType::SKIN,
63 ADDON::OnlyEnabled::CHOICE_YES))
64 return false;
65 skin = std::static_pointer_cast<ADDON::CSkinInfo>(addon);
68 // store player and rendering state
69 bool bPreviousPlayingState = false;
71 enum class RENDERING_STATE
73 NONE,
74 VIDEO,
75 GAME,
76 } previousRenderingState = RENDERING_STATE::NONE;
78 auto& components = CServiceBroker::GetAppComponents();
79 const auto appPlayer = components.GetComponent<CApplicationPlayer>();
80 if (appPlayer && appPlayer->IsPlayingVideo())
82 bPreviousPlayingState = !appPlayer->IsPausedPlayback();
83 if (bPreviousPlayingState)
84 appPlayer->Pause();
85 appPlayer->FlushRenderer();
86 if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_VIDEO)
88 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_HOME);
89 previousRenderingState = RENDERING_STATE::VIDEO;
91 else if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() ==
92 WINDOW_FULLSCREEN_GAME)
94 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_HOME);
95 previousRenderingState = RENDERING_STATE::GAME;
99 std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
101 // store current active window with its focused control
102 int currentWindowID = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow();
103 int currentFocusedControlID = -1;
104 if (currentWindowID != WINDOW_INVALID)
106 CGUIWindow* pWindow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(currentWindowID);
107 if (pWindow)
108 currentFocusedControlID = pWindow->GetFocusedControlID();
111 UnloadSkin();
113 skin->Start();
115 // migrate any skin-specific settings that are still stored in guisettings.xml
116 CSkinSettings::GetInstance().MigrateSettings(skin);
118 // check if the skin has been properly loaded and if it has a Home.xml
119 if (!skin->HasSkinFile("Home.xml"))
121 CLog::Log(LOGERROR, "failed to load requested skin '{}'", skin->ID());
122 return false;
125 CLog::Log(LOGINFO, " load skin from: {} (version: {})", skin->Path(),
126 skin->Version().asString());
127 g_SkinInfo = skin;
129 CLog::Log(LOGINFO, " load fonts for skin...");
130 CServiceBroker::GetWinSystem()->GetGfxContext().SetMediaDir(skin->Path());
131 g_directoryCache.ClearSubPaths(skin->Path());
133 const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
134 CServiceBroker::GetGUI()->GetColorManager().Load(
135 settings->GetString(CSettings::SETTING_LOOKANDFEEL_SKINCOLORS));
137 g_SkinInfo->LoadIncludes();
139 g_fontManager.LoadFonts(settings->GetString(CSettings::SETTING_LOOKANDFEEL_FONT));
141 // load in the skin strings
142 std::string langPath = URIUtils::AddFileToFolder(skin->Path(), "language");
143 URIUtils::AddSlashAtEnd(langPath);
145 g_localizeStrings.LoadSkinStrings(langPath,
146 settings->GetString(CSettings::SETTING_LOCALE_LANGUAGE));
147 g_SkinInfo->LoadTimers();
149 const auto start = std::chrono::steady_clock::now();
151 CLog::Log(LOGINFO, " load new skin...");
153 // Load custom windows
154 LoadCustomWindows();
156 const auto end = std::chrono::steady_clock::now();
157 std::chrono::duration<double, std::milli> duration = end - start;
159 CLog::Log(LOGDEBUG, "Load Skin XML: {:.2f} ms", duration.count());
161 CLog::Log(LOGINFO, " initialize new skin...");
162 CServiceBroker::GetGUI()->GetWindowManager().AddMsgTarget(m_msgCb);
163 CServiceBroker::GetGUI()->GetWindowManager().AddMsgTarget(&CServiceBroker::GetPlaylistPlayer());
164 CServiceBroker::GetGUI()->GetWindowManager().AddMsgTarget(&g_fontManager);
165 CServiceBroker::GetGUI()->GetWindowManager().AddMsgTarget(
166 &CServiceBroker::GetGUI()->GetStereoscopicsManager());
167 CServiceBroker::GetGUI()->GetWindowManager().SetCallback(*m_wCb);
169 //@todo should be done by GUIComponents
170 CServiceBroker::GetGUI()->GetWindowManager().Initialize();
171 CServiceBroker::GetGUI()->GetAudioManager().Enable(true);
172 CServiceBroker::GetGUI()->GetAudioManager().Load();
173 CServiceBroker::GetTextureCache()->Initialize();
175 if (g_SkinInfo->HasSkinFile("DialogFullScreenInfo.xml"))
176 CServiceBroker::GetGUI()->GetWindowManager().Add(new CGUIDialogFullScreenInfo);
178 CLog::Log(LOGINFO, " skin loaded...");
180 // leave the graphics lock
181 lock.unlock();
183 // restore active window
184 if (currentWindowID != WINDOW_INVALID)
186 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(currentWindowID);
187 if (currentFocusedControlID != -1)
189 CGUIWindow* pWindow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(currentWindowID);
190 if (pWindow && pWindow->HasSaveLastControl())
192 CGUIMessage msg(GUI_MSG_SETFOCUS, currentWindowID, currentFocusedControlID, 0);
193 pWindow->OnMessage(msg);
198 // restore player and rendering state
199 if (appPlayer && appPlayer->IsPlayingVideo())
201 if (bPreviousPlayingState)
202 appPlayer->Pause();
204 switch (previousRenderingState)
206 case RENDERING_STATE::VIDEO:
207 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_FULLSCREEN_VIDEO);
208 break;
209 case RENDERING_STATE::GAME:
210 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_FULLSCREEN_GAME);
211 break;
212 default:
213 break;
217 return true;
220 void CApplicationSkinHandling::UnloadSkin()
222 if (g_SkinInfo != nullptr && m_saveSkinOnUnloading)
223 g_SkinInfo->SaveSettings();
224 else if (!m_saveSkinOnUnloading)
225 m_saveSkinOnUnloading = true;
227 if (g_SkinInfo)
228 g_SkinInfo->Unload();
230 CGUIComponent* gui = CServiceBroker::GetGUI();
231 if (gui)
233 gui->GetAudioManager().Enable(false);
235 gui->GetWindowManager().DeInitialize();
236 CServiceBroker::GetTextureCache()->Deinitialize();
238 // remove the skin-dependent window
239 gui->GetWindowManager().Delete(WINDOW_DIALOG_FULLSCREEN_INFO);
241 gui->GetTextureManager().Cleanup();
242 gui->GetLargeTextureManager().CleanupUnusedImages(true);
244 g_fontManager.Clear();
246 gui->GetColorManager().Clear();
248 gui->GetInfoManager().Clear();
251 // The g_SkinInfo shared_ptr ought to be reset here
252 // but there are too many places it's used without checking for nullptr
253 // and as a result a race condition on exit can cause a crash.
254 CLog::Log(LOGINFO, "Unloaded skin");
257 bool CApplicationSkinHandling::LoadCustomWindows()
259 // Start from wherever home.xml is
260 std::vector<std::string> vecSkinPath;
261 g_SkinInfo->GetSkinPaths(vecSkinPath);
263 for (const auto& skinPath : vecSkinPath)
265 CLog::Log(LOGINFO, "Loading custom window XMLs from skin path {}", skinPath);
267 CFileItemList items;
268 if (XFILE::CDirectory::GetDirectory(skinPath, items, ".xml", XFILE::DIR_FLAG_NO_FILE_DIRS))
270 for (const auto& item : items)
272 if (item->m_bIsFolder)
273 continue;
275 std::string skinFile = URIUtils::GetFileName(item->GetPath());
276 if (StringUtils::StartsWithNoCase(skinFile, "custom"))
278 CXBMCTinyXML xmlDoc;
279 if (!xmlDoc.LoadFile(item->GetPath()))
281 CLog::Log(LOGERROR, "Unable to load custom window XML {}. Line {}\n{}", item->GetPath(),
282 xmlDoc.ErrorRow(), xmlDoc.ErrorDesc());
283 continue;
286 // Root element should be <window>
287 TiXmlElement* pRootElement = xmlDoc.RootElement();
288 std::string strValue = pRootElement->Value();
289 if (!StringUtils::EqualsNoCase(strValue, "window"))
291 CLog::Log(LOGERROR, "No <window> root element found for custom window in {}", skinFile);
292 continue;
295 int id = WINDOW_INVALID;
297 // Read the type attribute or element to get the window type to create
298 // If no type is specified, create a CGUIWindow as default
299 std::string strType;
300 if (pRootElement->Attribute("type"))
301 strType = pRootElement->Attribute("type");
302 else
304 const TiXmlNode* pType = pRootElement->FirstChild("type");
305 if (pType && pType->FirstChild())
306 strType = pType->FirstChild()->Value();
309 // Read the id attribute or element to get the window id
310 if (!pRootElement->Attribute("id", &id))
312 const TiXmlNode* pType = pRootElement->FirstChild("id");
313 if (pType && pType->FirstChild())
314 id = atol(pType->FirstChild()->Value());
317 int windowId = id + WINDOW_HOME;
318 if (id == WINDOW_INVALID ||
319 CServiceBroker::GetGUI()->GetWindowManager().GetWindow(windowId))
321 // No id specified or id already in use
322 CLog::Log(LOGERROR, "No id specified or id already in use for custom window in {}",
323 skinFile);
324 continue;
327 CGUIWindow* pWindow = nullptr;
328 bool hasVisibleCondition = false;
330 if (StringUtils::EqualsNoCase(strType, "dialog"))
332 DialogModalityType modality = DialogModalityType::MODAL;
333 hasVisibleCondition = pRootElement->FirstChildElement("visible") != nullptr;
334 // By default dialogs that have visible conditions are considered modeless unless explicitly
335 // set to "modal" by the skinner using the "modality" attribute in the root XML element of the window
336 if (hasVisibleCondition &&
337 (!pRootElement->Attribute("modality") ||
338 !StringUtils::EqualsNoCase(pRootElement->Attribute("modality"), "modal")))
339 modality = DialogModalityType::MODELESS;
341 pWindow = new CGUIDialog(windowId, skinFile, modality);
343 else if (StringUtils::EqualsNoCase(strType, "submenu"))
345 pWindow = new CGUIDialogSubMenu(windowId, skinFile);
347 else if (StringUtils::EqualsNoCase(strType, "buttonmenu"))
349 pWindow = new CGUIDialogButtonMenu(windowId, skinFile);
351 else
353 pWindow = new CGUIWindow(windowId, skinFile);
356 if (!pWindow)
358 CLog::Log(LOGERROR, "Failed to create custom window from {}", skinFile);
359 continue;
362 pWindow->SetCustom(true);
364 // Determining whether our custom dialog is modeless (visible condition is present)
365 // will be done on load. Therefore we need to initialize the custom dialog on gui init.
366 pWindow->SetLoadType(hasVisibleCondition ? CGUIWindow::LOAD_ON_GUI_INIT
367 : CGUIWindow::KEEP_IN_MEMORY);
369 CServiceBroker::GetGUI()->GetWindowManager().AddCustomWindow(pWindow);
374 return true;
377 void CApplicationSkinHandling::ReloadSkin(bool confirm)
379 if (!g_SkinInfo || m_bInitializing)
380 return; // Don't allow reload before skin is loaded by system
382 std::string oldSkin = g_SkinInfo->ID();
384 CGUIMessage msg(GUI_MSG_LOAD_SKIN, -1,
385 CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow());
386 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
388 const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
389 std::string newSkin = settings->GetString(CSettings::SETTING_LOOKANDFEEL_SKIN);
390 if (LoadSkin(newSkin))
392 /* The Reset() or SetString() below will cause recursion, so the m_confirmSkinChange boolean is set so as to not prompt the
393 user as to whether they want to keep the current skin. */
394 if (confirm && m_confirmSkinChange)
396 if (HELPERS::ShowYesNoDialogText(CVariant{13123}, CVariant{13111}, CVariant{""}, CVariant{""},
397 10000) != HELPERS::DialogResponse::CHOICE_YES)
399 m_confirmSkinChange = false;
400 settings->SetString(CSettings::SETTING_LOOKANDFEEL_SKIN, oldSkin);
402 else
403 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_STARTUP_ANIM);
406 else
408 // skin failed to load - we revert to the default only if we didn't fail loading the default
409 auto setting = settings->GetSetting(CSettings::SETTING_LOOKANDFEEL_SKIN);
410 if (!setting)
412 CLog::Log(LOGFATAL, "Failed to load setting for: {}", CSettings::SETTING_LOOKANDFEEL_SKIN);
413 return;
416 std::string defaultSkin = std::static_pointer_cast<CSettingString>(setting)->GetDefault();
417 if (newSkin != defaultSkin)
419 m_confirmSkinChange = false;
420 setting->Reset();
421 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(24102),
422 g_localizeStrings.Get(24103));
425 m_confirmSkinChange = true;
428 bool CApplicationSkinHandling::OnSettingChanged(const CSetting& setting)
430 const std::string& settingId = setting.GetId();
432 if (settingId == CSettings::SETTING_LOOKANDFEEL_SKIN ||
433 settingId == CSettings::SETTING_LOOKANDFEEL_FONT ||
434 settingId == CSettings::SETTING_LOOKANDFEEL_SKINTHEME ||
435 settingId == CSettings::SETTING_LOOKANDFEEL_SKINCOLORS)
437 // check if we should ignore this change event due to changing skins in which case we have to
438 // change several settings and each one of them could lead to a complete skin reload which would
439 // result in multiple skin reloads. Therefore we manually specify to ignore specific settings
440 // which are going to be changed.
441 if (m_ignoreSkinSettingChanges)
442 return true;
444 // if the skin changes and the current color/theme/font is not the default one, reset
445 // the it to the default value
446 if (settingId == CSettings::SETTING_LOOKANDFEEL_SKIN)
448 const std::shared_ptr<CSettings> settings =
449 CServiceBroker::GetSettingsComponent()->GetSettings();
450 SettingPtr skinRelatedSetting =
451 settings->GetSetting(CSettings::SETTING_LOOKANDFEEL_SKINCOLORS);
452 if (!skinRelatedSetting->IsDefault())
454 m_ignoreSkinSettingChanges = true;
455 skinRelatedSetting->Reset();
458 skinRelatedSetting = settings->GetSetting(CSettings::SETTING_LOOKANDFEEL_SKINTHEME);
459 if (!skinRelatedSetting->IsDefault())
461 m_ignoreSkinSettingChanges = true;
462 skinRelatedSetting->Reset();
465 skinRelatedSetting = settings->GetSetting(CSettings::SETTING_LOOKANDFEEL_FONT);
466 if (!skinRelatedSetting->IsDefault())
468 m_ignoreSkinSettingChanges = true;
469 skinRelatedSetting->Reset();
472 else if (settingId == CSettings::SETTING_LOOKANDFEEL_SKINTHEME)
474 std::shared_ptr<CSettingString> skinColorsSetting = std::static_pointer_cast<CSettingString>(
475 CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting(
476 CSettings::SETTING_LOOKANDFEEL_SKINCOLORS));
477 std::shared_ptr<CSettingString> skinFontSetting = std::static_pointer_cast<CSettingString>(
478 CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting(
479 CSettings::SETTING_LOOKANDFEEL_FONT));
480 m_ignoreSkinSettingChanges = true;
482 // we also need to adjust the skin color theme and fontset
483 std::string theme = static_cast<const CSettingString&>(setting).GetValue();
484 if (setting.IsDefault() || StringUtils::EqualsNoCase(theme, "Textures.xbt"))
486 skinColorsSetting->Reset();
487 skinFontSetting->Reset();
489 else
491 URIUtils::RemoveExtension(theme);
492 skinColorsSetting->SetValue(theme);
493 skinFontSetting->SetValue(theme);
497 m_ignoreSkinSettingChanges = false;
499 if (g_SkinInfo)
501 // now we can finally reload skins
502 std::string builtin("ReloadSkin");
503 if (settingId == CSettings::SETTING_LOOKANDFEEL_SKIN && m_confirmSkinChange)
504 builtin += "(confirm)";
505 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, builtin);
508 else if (settingId == CSettings::SETTING_LOOKANDFEEL_SKINZOOM)
510 CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_WINDOW_RESIZE);
511 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
513 else
514 return false;
516 return true;
519 void CApplicationSkinHandling::ProcessSkin() const
521 if (g_SkinInfo != nullptr)
522 g_SkinInfo->ProcessTimers();