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.
9 #include "ApplicationSkinHandling.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
,
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
))
65 skin
= std::static_pointer_cast
<ADDON::CSkinInfo
>(addon
);
68 // store player and rendering state
69 bool bPreviousPlayingState
= false;
71 enum class RENDERING_STATE
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
)
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
);
108 currentFocusedControlID
= pWindow
->GetFocusedControlID();
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());
125 CLog::Log(LOGINFO
, " load skin from: {} (version: {})", skin
->Path(),
126 skin
->Version().asString());
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
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
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
)
204 switch (previousRenderingState
)
206 case RENDERING_STATE::VIDEO
:
207 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_FULLSCREEN_VIDEO
);
209 case RENDERING_STATE::GAME
:
210 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_FULLSCREEN_GAME
);
220 void CApplicationSkinHandling::UnloadSkin()
222 if (g_SkinInfo
!= nullptr && m_saveSkinOnUnloading
)
223 g_SkinInfo
->SaveSettings();
224 else if (!m_saveSkinOnUnloading
)
225 m_saveSkinOnUnloading
= true;
228 g_SkinInfo
->Unload();
230 CGUIComponent
* gui
= CServiceBroker::GetGUI();
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
);
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
)
275 std::string skinFile
= URIUtils::GetFileName(item
->GetPath());
276 if (StringUtils::StartsWithNoCase(skinFile
, "custom"))
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());
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
);
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
300 if (pRootElement
->Attribute("type"))
301 strType
= pRootElement
->Attribute("type");
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 {}",
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
);
353 pWindow
= new CGUIWindow(windowId
, skinFile
);
358 CLog::Log(LOGERROR
, "Failed to create custom window from {}", skinFile
);
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
);
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
);
403 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_STARTUP_ANIM
);
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
);
412 CLog::Log(LOGFATAL
, "Failed to load setting for: {}", CSettings::SETTING_LOOKANDFEEL_SKIN
);
416 std::string defaultSkin
= std::static_pointer_cast
<CSettingString
>(setting
)->GetDefault();
417 if (newSkin
!= defaultSkin
)
419 m_confirmSkinChange
= false;
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
)
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();
491 URIUtils::RemoveExtension(theme
);
492 skinColorsSetting
->SetValue(theme
);
493 skinFontSetting
->SetValue(theme
);
497 m_ignoreSkinSettingChanges
= false;
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
);
519 void CApplicationSkinHandling::ProcessSkin() const
521 if (g_SkinInfo
!= nullptr)
522 g_SkinInfo
->ProcessTimers();