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 "FileItemList.h"
13 #include "GUIInfoManager.h"
14 #include "GUILargeTextureManager.h"
15 #include "GUIUserMessages.h"
16 #include "PlayListPlayer.h"
17 #include "ServiceBroker.h"
18 #include "TextureCache.h"
19 #include "addons/AddonManager.h"
20 #include "addons/AddonVersion.h"
21 #include "addons/Skin.h"
22 #include "addons/addoninfo/AddonType.h"
23 #include "application/ApplicationComponents.h"
24 #include "application/ApplicationPlayer.h"
25 #include "dialogs/GUIDialogButtonMenu.h"
26 #include "dialogs/GUIDialogKaiToast.h"
27 #include "dialogs/GUIDialogSubMenu.h"
28 #include "filesystem/Directory.h"
29 #include "filesystem/DirectoryCache.h"
30 #include "guilib/GUIAudioManager.h"
31 #include "guilib/GUIColorManager.h"
32 #include "guilib/GUIComponent.h"
33 #include "guilib/GUIFontManager.h"
34 #include "guilib/GUIWindowManager.h"
35 #include "guilib/LocalizeStrings.h"
36 #include "guilib/StereoscopicsManager.h"
37 #include "messaging/ApplicationMessenger.h"
38 #include "messaging/helpers/DialogHelper.h"
39 #include "settings/Settings.h"
40 #include "settings/SettingsComponent.h"
41 #include "settings/SkinSettings.h"
42 #include "settings/lib/Setting.h"
43 #include "utils/StringUtils.h"
44 #include "utils/URIUtils.h"
45 #include "utils/XBMCTinyXML.h"
46 #include "utils/log.h"
47 #include "video/dialogs/GUIDialogFullScreenInfo.h"
49 using namespace KODI::MESSAGING
;
51 CApplicationSkinHandling::CApplicationSkinHandling(IMsgTargetCallback
* msgCb
,
52 IWindowManagerCallback
* wCb
,
54 : m_msgCb(msgCb
), m_wCb(wCb
), m_bInitializing(bInitializing
)
58 bool CApplicationSkinHandling::LoadSkin(const std::string
& skinID
)
60 std::shared_ptr
<ADDON::CSkinInfo
> skin
;
62 ADDON::AddonPtr addon
;
63 if (!CServiceBroker::GetAddonMgr().GetAddon(skinID
, addon
, ADDON::AddonType::SKIN
,
64 ADDON::OnlyEnabled::CHOICE_YES
))
66 skin
= std::static_pointer_cast
<ADDON::CSkinInfo
>(addon
);
69 // store player and rendering state
70 bool bPreviousPlayingState
= false;
72 enum class RENDERING_STATE
77 } previousRenderingState
= RENDERING_STATE::NONE
;
79 auto& components
= CServiceBroker::GetAppComponents();
80 const auto appPlayer
= components
.GetComponent
<CApplicationPlayer
>();
81 if (appPlayer
&& appPlayer
->IsPlayingVideo())
83 bPreviousPlayingState
= !appPlayer
->IsPausedPlayback();
84 if (bPreviousPlayingState
)
86 appPlayer
->FlushRenderer();
87 if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_VIDEO
)
89 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_HOME
);
90 previousRenderingState
= RENDERING_STATE::VIDEO
;
92 else if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() ==
93 WINDOW_FULLSCREEN_GAME
)
95 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_HOME
);
96 previousRenderingState
= RENDERING_STATE::GAME
;
100 std::unique_lock
<CCriticalSection
> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
102 // store current active window with its focused control
103 int currentWindowID
= CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow();
104 int currentFocusedControlID
= -1;
105 if (currentWindowID
!= WINDOW_INVALID
)
107 CGUIWindow
* pWindow
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow(currentWindowID
);
109 currentFocusedControlID
= pWindow
->GetFocusedControlID();
116 // migrate any skin-specific settings that are still stored in guisettings.xml
117 CSkinSettings::GetInstance().MigrateSettings(skin
);
119 // check if the skin has been properly loaded and if it has a Home.xml
120 if (!skin
->HasSkinFile("Home.xml"))
122 CLog::Log(LOGERROR
, "failed to load requested skin '{}'", skin
->ID());
126 CLog::Log(LOGINFO
, " load skin from: {} (version: {})", skin
->Path(),
127 skin
->Version().asString());
130 CLog::Log(LOGINFO
, " load fonts for skin...");
131 CServiceBroker::GetWinSystem()->GetGfxContext().SetMediaDir(skin
->Path());
132 g_directoryCache
.ClearSubPaths(skin
->Path());
134 const std::shared_ptr
<CSettings
> settings
= CServiceBroker::GetSettingsComponent()->GetSettings();
135 CServiceBroker::GetGUI()->GetColorManager().Load(
136 settings
->GetString(CSettings::SETTING_LOOKANDFEEL_SKINCOLORS
));
138 g_SkinInfo
->LoadIncludes();
140 g_fontManager
.LoadFonts(settings
->GetString(CSettings::SETTING_LOOKANDFEEL_FONT
));
142 // load in the skin strings
143 std::string langPath
= URIUtils::AddFileToFolder(skin
->Path(), "language");
144 URIUtils::AddSlashAtEnd(langPath
);
146 g_localizeStrings
.LoadSkinStrings(langPath
,
147 settings
->GetString(CSettings::SETTING_LOCALE_LANGUAGE
));
148 g_SkinInfo
->LoadTimers();
150 const auto start
= std::chrono::steady_clock::now();
152 CLog::Log(LOGINFO
, " load new skin...");
154 // Load custom windows
157 const auto end
= std::chrono::steady_clock::now();
158 std::chrono::duration
<double, std::milli
> duration
= end
- start
;
160 CLog::Log(LOGDEBUG
, "Load Skin XML: {:.2f} ms", duration
.count());
162 CLog::Log(LOGINFO
, " initialize new skin...");
163 CServiceBroker::GetGUI()->GetWindowManager().AddMsgTarget(m_msgCb
);
164 CServiceBroker::GetGUI()->GetWindowManager().AddMsgTarget(&CServiceBroker::GetPlaylistPlayer());
165 CServiceBroker::GetGUI()->GetWindowManager().AddMsgTarget(&g_fontManager
);
166 CServiceBroker::GetGUI()->GetWindowManager().AddMsgTarget(
167 &CServiceBroker::GetGUI()->GetStereoscopicsManager());
168 CServiceBroker::GetGUI()->GetWindowManager().SetCallback(*m_wCb
);
170 //@todo should be done by GUIComponents
171 CServiceBroker::GetGUI()->GetWindowManager().Initialize();
172 CServiceBroker::GetGUI()->GetAudioManager().Enable(true);
173 CServiceBroker::GetGUI()->GetAudioManager().Load();
174 CServiceBroker::GetTextureCache()->Initialize();
176 if (g_SkinInfo
->HasSkinFile("DialogFullScreenInfo.xml"))
177 CServiceBroker::GetGUI()->GetWindowManager().Add(new CGUIDialogFullScreenInfo
);
179 CLog::Log(LOGINFO
, " skin loaded...");
181 // leave the graphics lock
184 // restore active window
185 if (currentWindowID
!= WINDOW_INVALID
)
187 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(currentWindowID
);
188 if (currentFocusedControlID
!= -1)
190 CGUIWindow
* pWindow
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow(currentWindowID
);
191 if (pWindow
&& pWindow
->HasSaveLastControl())
193 CGUIMessage
msg(GUI_MSG_SETFOCUS
, currentWindowID
, currentFocusedControlID
, 0);
194 pWindow
->OnMessage(msg
);
199 // restore player and rendering state
200 if (appPlayer
&& appPlayer
->IsPlayingVideo())
202 if (bPreviousPlayingState
)
205 switch (previousRenderingState
)
207 case RENDERING_STATE::VIDEO
:
208 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_FULLSCREEN_VIDEO
);
210 case RENDERING_STATE::GAME
:
211 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_FULLSCREEN_GAME
);
221 void CApplicationSkinHandling::UnloadSkin()
223 if (g_SkinInfo
!= nullptr && m_saveSkinOnUnloading
)
224 g_SkinInfo
->SaveSettings();
225 else if (!m_saveSkinOnUnloading
)
226 m_saveSkinOnUnloading
= true;
229 g_SkinInfo
->Unload();
231 CGUIComponent
* gui
= CServiceBroker::GetGUI();
234 gui
->GetAudioManager().Enable(false);
236 gui
->GetWindowManager().DeInitialize();
237 CServiceBroker::GetTextureCache()->Deinitialize();
239 // remove the skin-dependent window
240 gui
->GetWindowManager().Delete(WINDOW_DIALOG_FULLSCREEN_INFO
);
242 gui
->GetTextureManager().Cleanup();
243 gui
->GetLargeTextureManager().CleanupUnusedImages(true);
245 g_fontManager
.Clear();
247 gui
->GetColorManager().Clear();
249 gui
->GetInfoManager().Clear();
252 // The g_SkinInfo shared_ptr ought to be reset here
253 // but there are too many places it's used without checking for nullptr
254 // and as a result a race condition on exit can cause a crash.
255 CLog::Log(LOGINFO
, "Unloaded skin");
258 bool CApplicationSkinHandling::LoadCustomWindows()
260 // Start from wherever home.xml is
261 std::vector
<std::string
> vecSkinPath
;
262 g_SkinInfo
->GetSkinPaths(vecSkinPath
);
264 for (const auto& skinPath
: vecSkinPath
)
266 CLog::Log(LOGINFO
, "Loading custom window XMLs from skin path {}", skinPath
);
269 if (XFILE::CDirectory::GetDirectory(skinPath
, items
, ".xml", XFILE::DIR_FLAG_NO_FILE_DIRS
))
271 for (const auto& item
: items
)
273 if (item
->m_bIsFolder
)
276 std::string skinFile
= URIUtils::GetFileName(item
->GetPath());
277 if (StringUtils::StartsWithNoCase(skinFile
, "custom"))
280 if (!xmlDoc
.LoadFile(item
->GetPath()))
282 CLog::Log(LOGERROR
, "Unable to load custom window XML {}. Line {}\n{}", item
->GetPath(),
283 xmlDoc
.ErrorRow(), xmlDoc
.ErrorDesc());
287 // Root element should be <window>
288 TiXmlElement
* pRootElement
= xmlDoc
.RootElement();
289 std::string strValue
= pRootElement
->Value();
290 if (!StringUtils::EqualsNoCase(strValue
, "window"))
292 CLog::Log(LOGERROR
, "No <window> root element found for custom window in {}", skinFile
);
296 int id
= WINDOW_INVALID
;
298 // Read the type attribute or element to get the window type to create
299 // If no type is specified, create a CGUIWindow as default
301 if (pRootElement
->Attribute("type"))
302 strType
= pRootElement
->Attribute("type");
305 const TiXmlNode
* pType
= pRootElement
->FirstChild("type");
306 if (pType
&& pType
->FirstChild())
307 strType
= pType
->FirstChild()->Value();
310 // Read the id attribute or element to get the window id
311 if (!pRootElement
->Attribute("id", &id
))
313 const TiXmlNode
* pType
= pRootElement
->FirstChild("id");
314 if (pType
&& pType
->FirstChild())
315 id
= atol(pType
->FirstChild()->Value());
318 int windowId
= id
+ WINDOW_HOME
;
319 if (id
== WINDOW_INVALID
||
320 CServiceBroker::GetGUI()->GetWindowManager().GetWindow(windowId
))
322 // No id specified or id already in use
323 CLog::Log(LOGERROR
, "No id specified or id already in use for custom window in {}",
328 CGUIWindow
* pWindow
= nullptr;
329 bool hasVisibleCondition
= false;
331 if (StringUtils::EqualsNoCase(strType
, "dialog"))
333 DialogModalityType modality
= DialogModalityType::MODAL
;
334 hasVisibleCondition
= pRootElement
->FirstChildElement("visible") != nullptr;
335 // By default dialogs that have visible conditions are considered modeless unless explicitly
336 // set to "modal" by the skinner using the "modality" attribute in the root XML element of the window
337 if (hasVisibleCondition
&&
338 (!pRootElement
->Attribute("modality") ||
339 !StringUtils::EqualsNoCase(pRootElement
->Attribute("modality"), "modal")))
340 modality
= DialogModalityType::MODELESS
;
342 pWindow
= new CGUIDialog(windowId
, skinFile
, modality
);
344 else if (StringUtils::EqualsNoCase(strType
, "submenu"))
346 pWindow
= new CGUIDialogSubMenu(windowId
, skinFile
);
348 else if (StringUtils::EqualsNoCase(strType
, "buttonmenu"))
350 pWindow
= new CGUIDialogButtonMenu(windowId
, skinFile
);
354 pWindow
= new CGUIWindow(windowId
, skinFile
);
359 CLog::Log(LOGERROR
, "Failed to create custom window from {}", skinFile
);
363 pWindow
->SetCustom(true);
365 // Determining whether our custom dialog is modeless (visible condition is present)
366 // will be done on load. Therefore we need to initialize the custom dialog on gui init.
367 pWindow
->SetLoadType(hasVisibleCondition
? CGUIWindow::LOAD_ON_GUI_INIT
368 : CGUIWindow::KEEP_IN_MEMORY
);
370 CServiceBroker::GetGUI()->GetWindowManager().AddCustomWindow(pWindow
);
378 void CApplicationSkinHandling::ReloadSkin(bool confirm
)
380 if (!g_SkinInfo
|| m_bInitializing
)
381 return; // Don't allow reload before skin is loaded by system
383 std::string oldSkin
= g_SkinInfo
->ID();
385 CGUIMessage
msg(GUI_MSG_LOAD_SKIN
, -1,
386 CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow());
387 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
389 const std::shared_ptr
<CSettings
> settings
= CServiceBroker::GetSettingsComponent()->GetSettings();
390 std::string newSkin
= settings
->GetString(CSettings::SETTING_LOOKANDFEEL_SKIN
);
391 if (LoadSkin(newSkin
))
393 /* The Reset() or SetString() below will cause recursion, so the m_confirmSkinChange boolean is set so as to not prompt the
394 user as to whether they want to keep the current skin. */
395 if (confirm
&& m_confirmSkinChange
)
397 if (HELPERS::ShowYesNoDialogText(CVariant
{13123}, CVariant
{13111}, CVariant
{""}, CVariant
{""},
398 10000) != HELPERS::DialogResponse::CHOICE_YES
)
400 m_confirmSkinChange
= false;
401 settings
->SetString(CSettings::SETTING_LOOKANDFEEL_SKIN
, oldSkin
);
404 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_STARTUP_ANIM
);
409 // skin failed to load - we revert to the default only if we didn't fail loading the default
410 auto setting
= settings
->GetSetting(CSettings::SETTING_LOOKANDFEEL_SKIN
);
413 CLog::Log(LOGFATAL
, "Failed to load setting for: {}", CSettings::SETTING_LOOKANDFEEL_SKIN
);
417 std::string defaultSkin
= std::static_pointer_cast
<CSettingString
>(setting
)->GetDefault();
418 if (newSkin
!= defaultSkin
)
420 m_confirmSkinChange
= false;
422 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error
, g_localizeStrings
.Get(24102),
423 g_localizeStrings
.Get(24103));
426 m_confirmSkinChange
= true;
429 bool CApplicationSkinHandling::OnSettingChanged(const CSetting
& setting
)
431 const std::string
& settingId
= setting
.GetId();
433 if (settingId
== CSettings::SETTING_LOOKANDFEEL_SKIN
||
434 settingId
== CSettings::SETTING_LOOKANDFEEL_FONT
||
435 settingId
== CSettings::SETTING_LOOKANDFEEL_SKINTHEME
||
436 settingId
== CSettings::SETTING_LOOKANDFEEL_SKINCOLORS
)
438 // check if we should ignore this change event due to changing skins in which case we have to
439 // change several settings and each one of them could lead to a complete skin reload which would
440 // result in multiple skin reloads. Therefore we manually specify to ignore specific settings
441 // which are going to be changed.
442 if (m_ignoreSkinSettingChanges
)
445 // if the skin changes and the current color/theme/font is not the default one, reset
446 // the it to the default value
447 if (settingId
== CSettings::SETTING_LOOKANDFEEL_SKIN
)
449 const std::shared_ptr
<CSettings
> settings
=
450 CServiceBroker::GetSettingsComponent()->GetSettings();
451 SettingPtr skinRelatedSetting
=
452 settings
->GetSetting(CSettings::SETTING_LOOKANDFEEL_SKINCOLORS
);
453 if (!skinRelatedSetting
->IsDefault())
455 m_ignoreSkinSettingChanges
= true;
456 skinRelatedSetting
->Reset();
459 skinRelatedSetting
= settings
->GetSetting(CSettings::SETTING_LOOKANDFEEL_SKINTHEME
);
460 if (!skinRelatedSetting
->IsDefault())
462 m_ignoreSkinSettingChanges
= true;
463 skinRelatedSetting
->Reset();
466 skinRelatedSetting
= settings
->GetSetting(CSettings::SETTING_LOOKANDFEEL_FONT
);
467 if (!skinRelatedSetting
->IsDefault())
469 m_ignoreSkinSettingChanges
= true;
470 skinRelatedSetting
->Reset();
473 else if (settingId
== CSettings::SETTING_LOOKANDFEEL_SKINTHEME
)
475 std::shared_ptr
<CSettingString
> skinColorsSetting
= std::static_pointer_cast
<CSettingString
>(
476 CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting(
477 CSettings::SETTING_LOOKANDFEEL_SKINCOLORS
));
478 std::shared_ptr
<CSettingString
> skinFontSetting
= std::static_pointer_cast
<CSettingString
>(
479 CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting(
480 CSettings::SETTING_LOOKANDFEEL_FONT
));
481 m_ignoreSkinSettingChanges
= true;
483 // we also need to adjust the skin color theme and fontset
484 std::string theme
= static_cast<const CSettingString
&>(setting
).GetValue();
485 if (setting
.IsDefault() || StringUtils::EqualsNoCase(theme
, "Textures.xbt"))
487 skinColorsSetting
->Reset();
488 skinFontSetting
->Reset();
492 URIUtils::RemoveExtension(theme
);
493 skinColorsSetting
->SetValue(theme
);
494 skinFontSetting
->SetValue(theme
);
498 m_ignoreSkinSettingChanges
= false;
502 // now we can finally reload skins
503 std::string
builtin("ReloadSkin");
504 if (settingId
== CSettings::SETTING_LOOKANDFEEL_SKIN
&& m_confirmSkinChange
)
505 builtin
+= "(confirm)";
506 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_EXECUTE_BUILT_IN
, -1, -1, nullptr, builtin
);
509 else if (settingId
== CSettings::SETTING_LOOKANDFEEL_SKINZOOM
)
511 CGUIMessage
msg(GUI_MSG_NOTIFY_ALL
, 0, 0, GUI_MSG_WINDOW_RESIZE
);
512 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg
);
520 void CApplicationSkinHandling::ProcessSkin() const
522 if (g_SkinInfo
!= nullptr)
523 g_SkinInfo
->ProcessTimers();