Skip to content

Commit

Permalink
Peripherals: Allow changing joystick appearance
Browse files Browse the repository at this point in the history
  • Loading branch information
garbear committed Mar 3, 2023
1 parent d61b2cf commit 0b4ec3a
Show file tree
Hide file tree
Showing 16 changed files with 318 additions and 17 deletions.
1 change: 1 addition & 0 deletions addons/resource.language.en_gb/resources/strings.po
Original file line number Diff line number Diff line change
Expand Up @@ -2264,6 +2264,7 @@ msgctxt "#479"
msgid "Skin & language"
msgstr ""

#: system/peripherals.xml
#: system/settings/settings.xml
#: addons/skin.estuary/xml/Includes.xml
msgctxt "#480"
Expand Down
5 changes: 3 additions & 2 deletions system/peripherals.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@
</peripheral>

<peripheral class="joystick" name="joystick" mapTo="joystick">
<setting key="left_stick_deadzone" type="float" label="35078" order="1" value="0.2" min="0.0" step="0.05" max="1.0" />
<setting key="right_stick_deadzone" type="float" label="35079" order="2" value="0.2" min="0.0" step="0.05" max="1.0" />
<setting key="appearance" type="addon" addontype="kodi.game.controller" label="480" order="1"/>
<setting key="left_stick_deadzone" type="float" label="35078" order="2" value="0.2" min="0.0" step="0.05" max="1.0" />
<setting key="right_stick_deadzone" type="float" label="35079" order="3" value="0.2" min="0.0" step="0.05" max="1.0" />
</peripheral>

</peripherals>
18 changes: 18 additions & 0 deletions xbmc/input/joysticks/interfaces/IButtonMap.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,24 @@ class IButtonMap
*/
virtual bool IsEmpty(void) const = 0;

/*!
* \brief Get the ID of the controller profile that best represents the
* appearance of the peripheral
*
* \return The controller ID, or empty if the appearance is unknown
*/
virtual std::string GetAppearance() const = 0;

/*!
* \brief Set the ID of the controller that best represents the appearance
* of the peripheral
*
* \param controllerId The controller ID, or empty to unset the appearance
*
* \return True if the appearance was set, false on error
*/
virtual bool SetAppearance(const std::string& controllerId) const = 0;

/*!
* \brief Get the feature associated with a driver primitive
*
Expand Down
10 changes: 10 additions & 0 deletions xbmc/peripherals/Peripherals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "EventScanner.h"
#include "addons/AddonButtonMap.h"
#include "addons/AddonManager.h"
#include "addons/addoninfo/AddonInfo.h"
#include "addons/addoninfo/AddonType.h"
#include "addons/gui/GUIDialogAddonSettings.h"
#include "addons/gui/GUIWindowAddonBrowser.h"
Expand Down Expand Up @@ -51,6 +52,7 @@
#include "messaging/ApplicationMessenger.h"
#include "messaging/ThreadMessage.h"
#include "peripherals/dialogs/GUIDialogPeripherals.h"
#include "settings/SettingAddon.h"
#include "settings/Settings.h"
#include "settings/SettingsComponent.h"
#include "settings/lib/Setting.h"
Expand Down Expand Up @@ -615,6 +617,14 @@ void CPeripherals::GetSettingsFromMappingsFile(
setting = std::make_shared<CSettingInt>(strKey, iLabelId, iValue, enums);
}
}
else if (StringUtils::EqualsNoCase(strSettingsType, "addon"))
{
std::string addonFilter = XMLUtils::GetAttribute(currentNode, "addontype");
ADDON::AddonType addonType = ADDON::CAddonInfo::TranslateType(addonFilter);
std::string strValue = XMLUtils::GetAttribute(currentNode, "value");
setting = std::make_shared<CSettingAddon>(strKey, iLabelId, strValue);
static_cast<CSettingAddon&>(*setting).SetAddonType(addonType);
}
else
{
std::string strValue = XMLUtils::GetAttribute(currentNode, "value");
Expand Down
6 changes: 6 additions & 0 deletions xbmc/peripherals/Peripherals.h
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,11 @@ class CPeripherals : public ISettingCallback,
*/
KODI::GAME::CControllerManager& GetControllerProfiles() { return m_controllerProfiles; }

/*!
* \brief Get a mutex that allows for add-on install tasks to block on each other
*/
CCriticalSection& GetAddonInstallMutex() { return m_addonInstallMutex; }

private:
bool LoadMappings();
bool GetMappingForDevice(const CPeripheralBus& bus, PeripheralScanResult& result) const;
Expand All @@ -361,5 +366,6 @@ class CPeripherals : public ISettingCallback,
std::unique_ptr<CEventScanner> m_eventScanner;
mutable CCriticalSection m_critSectionBusses;
mutable CCriticalSection m_critSectionMappings;
CCriticalSection m_addonInstallMutex;
};
} // namespace PERIPHERALS
14 changes: 14 additions & 0 deletions xbmc/peripherals/addons/AddonButtonMap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ std::string CAddonButtonMap::Location(void) const

bool CAddonButtonMap::Load(void)
{
std::string controllerAppearance;
FeatureMap features;
DriverMap driverMap;
PrimitiveVector ignoredPrimitives;
Expand All @@ -70,6 +71,7 @@ bool CAddonButtonMap::Load(void)

{
std::unique_lock<CCriticalSection> lock(m_mutex);
m_controllerAppearance = std::move(controllerAppearance);
m_features = std::move(features);
m_driverMap = std::move(driverMap);
m_ignoredPrimitives = CPeripheralAddonTranslator::TranslatePrimitives(ignoredPrimitives);
Expand All @@ -91,6 +93,18 @@ bool CAddonButtonMap::IsEmpty(void) const
return m_driverMap.empty();
}

std::string CAddonButtonMap::GetAppearance() const
{
std::unique_lock<CCriticalSection> lock(m_mutex);

return m_controllerAppearance;
}

bool CAddonButtonMap::SetAppearance(const std::string& controllerId) const
{
return false;
}

bool CAddonButtonMap::GetFeature(const CDriverPrimitive& primitive, FeatureName& feature)
{
std::unique_lock<CCriticalSection> lock(m_mutex);
Expand Down
12 changes: 11 additions & 1 deletion xbmc/peripherals/addons/AddonButtonMap.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ class CAddonButtonMap : public KODI::JOYSTICK::IButtonMap

bool IsEmpty(void) const override;

std::string GetAppearance() const override;

bool SetAppearance(const std::string& controllerId) const override;

bool GetFeature(const KODI::JOYSTICK::CDriverPrimitive& primitive,
KODI::JOYSTICK::FeatureName& feature) override;

Expand Down Expand Up @@ -122,12 +126,18 @@ class CAddonButtonMap : public KODI::JOYSTICK::IButtonMap
static JOYSTICK_FEATURE_PRIMITIVE GetPrimitiveIndex(KODI::JOYSTICK::WHEEL_DIRECTION dir);
static JOYSTICK_FEATURE_PRIMITIVE GetPrimitiveIndex(KODI::JOYSTICK::THROTTLE_DIRECTION dir);

// Construction parameters
CPeripheral* const m_device;
std::weak_ptr<CPeripheralAddon> m_addon;
const std::weak_ptr<CPeripheralAddon> m_addon;
const std::string m_strControllerId;

// Button map state
std::string m_controllerAppearance;
FeatureMap m_features;
DriverMap m_driverMap;
JoystickPrimitiveVector m_ignoredPrimitives;

// Synchronization parameters
mutable CCriticalSection m_mutex;
};
} // namespace PERIPHERALS
20 changes: 15 additions & 5 deletions xbmc/peripherals/devices/Peripheral.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "peripherals/addons/PeripheralAddon.h"
#include "peripherals/bus/PeripheralBus.h"
#include "peripherals/bus/virtual/PeripheralBusAddon.h"
#include "settings/SettingAddon.h"
#include "settings/lib/Setting.h"
#include "utils/FileUtils.h"
#include "utils/StringUtils.h"
Expand Down Expand Up @@ -269,12 +270,21 @@ void CPeripheral::AddSetting(const std::string& strKey, const SettingConstPtr& s
break;
case SettingType::String:
{
std::shared_ptr<const CSettingString> mappedSetting =
std::static_pointer_cast<const CSettingString>(setting);
std::shared_ptr<CSettingString> stringSetting =
std::make_shared<CSettingString>(strKey, *mappedSetting);
if (stringSetting)
if (std::dynamic_pointer_cast<const CSettingAddon>(setting))
{
std::shared_ptr<const CSettingAddon> mappedSetting =
std::static_pointer_cast<const CSettingAddon>(setting);
std::shared_ptr<CSettingAddon> addonSetting =
std::make_shared<CSettingAddon>(strKey, *mappedSetting);
addonSetting->SetVisible(mappedSetting->IsVisible());
deviceSetting.m_setting = addonSetting;
}
else
{
std::shared_ptr<const CSettingString> mappedSetting =
std::static_pointer_cast<const CSettingString>(setting);
std::shared_ptr<CSettingString> stringSetting =
std::make_shared<CSettingString>(strKey, *mappedSetting);
stringSetting->SetVisible(mappedSetting->IsVisible());
deviceSetting.m_setting = stringSetting;
}
Expand Down
12 changes: 10 additions & 2 deletions xbmc/peripherals/devices/Peripheral.h
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,16 @@ class CPeripheral : public KODI::JOYSTICK::IInputProvider,
*
* \return The controller profile, or empty if unknown
*/
virtual KODI::GAME::ControllerPtr ControllerProfile() const
virtual KODI::GAME::ControllerPtr ControllerProfile() const { return m_controllerProfile; }

/*!
* \brief Set the controller profile for this peripheral
*
* \param controller The new controller profile
*/
virtual void SetControllerProfile(const KODI::GAME::ControllerPtr& controller)
{
return KODI::GAME::ControllerPtr{};
m_controllerProfile = controller;
}

protected:
Expand Down Expand Up @@ -294,5 +301,6 @@ class CPeripheral : public KODI::JOYSTICK::IInputProvider,
std::map<KODI::MOUSE::IMouseInputHandler*, std::unique_ptr<KODI::MOUSE::IMouseDriverHandler>>
m_mouseHandlers;
std::map<KODI::JOYSTICK::IButtonMapper*, std::unique_ptr<CAddonButtonMapping>> m_buttonMappers;
KODI::GAME::ControllerPtr m_controllerProfile;
};
} // namespace PERIPHERALS
126 changes: 124 additions & 2 deletions xbmc/peripherals/devices/PeripheralJoystick.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

#include "PeripheralJoystick.h"

#include "ServiceBroker.h"
#include "addons/AddonInstaller.h"
#include "addons/AddonManager.h"
#include "application/Application.h"
#include "games/GameServices.h"
#include "games/controllers/Controller.h"
Expand Down Expand Up @@ -65,6 +68,10 @@ CPeripheralJoystick::~CPeripheralJoystick(void)
m_appInput.reset();
m_deadzoneFilter.reset();
m_buttonMap.reset();

// Wait for remaining install tasks
for (std::future<void>& task : m_installTasks)
task.wait();
}

bool CPeripheralJoystick::InitialiseFeature(const PeripheralFeature feature)
Expand Down Expand Up @@ -95,6 +102,7 @@ bool CPeripheralJoystick::InitialiseFeature(const PeripheralFeature feature)
if (m_buttonMap->Load())
{
InitializeDeadzoneFiltering(*m_buttonMap);
InitializeControllerProfile(*m_buttonMap);
}
else
{
Expand Down Expand Up @@ -128,6 +136,54 @@ void CPeripheralJoystick::InitializeDeadzoneFiltering(IButtonMap& buttonMap)
m_deadzoneFilter.reset(new CDeadzoneFilter(&buttonMap, this));
}

void CPeripheralJoystick::InitializeControllerProfile(IButtonMap& buttonMap)
{
const std::string controllerId = buttonMap.GetAppearance();
if (controllerId.empty())
return;

auto controller = m_manager.GetControllerProfiles().GetController(controllerId);
if (controller)
CPeripheral::SetControllerProfile(controller);
else
{
std::unique_lock<CCriticalSection> lock(m_controllerInstallMutex);

// Deposit controller into queue
m_controllersToInstall.emplace(controllerId);

// Clean up finished install tasks
m_installTasks.erase(std::remove_if(m_installTasks.begin(), m_installTasks.end(),
[](std::future<void>& task) {
return task.wait_for(std::chrono::seconds(0)) ==
std::future_status::ready;
}),
m_installTasks.end());

// Install controller off-thread
std::future<void> installTask = std::async(std::launch::async, [this]() {
// Withdraw controller from queue
std::string controllerToInstall;
{
std::unique_lock<CCriticalSection> lock(m_controllerInstallMutex);
if (!m_controllersToInstall.empty())
{
controllerToInstall = m_controllersToInstall.front();
m_controllersToInstall.pop();
}
}

// Do the install
GAME::ControllerPtr controller = InstallAsync(controllerToInstall);
if (controller)
CPeripheral::SetControllerProfile(controller);
});

// Hold the task to prevent the destructor from completing during an install
m_installTasks.emplace_back(std::move(installTask));
}
}

void CPeripheralJoystick::OnUserNotification()
{
IInputReceiver* inputReceiver = m_appInput->GetInputReceiver(m_rumbleGenerator->ControllerID());
Expand Down Expand Up @@ -192,11 +248,38 @@ IKeymap* CPeripheralJoystick::GetKeymap(const std::string& controllerId)

GAME::ControllerPtr CPeripheralJoystick::ControllerProfile() const
{
//! @todo Allow the user to change which controller profile represents their
// controller. For now, just use the default.
// Button map has the freshest state
if (m_buttonMap)
{
const std::string controllerId = m_buttonMap->GetAppearance();
if (!controllerId.empty())
{
auto controller = m_manager.GetControllerProfiles().GetController(controllerId);
if (controller)
return controller;
}
}

// Fall back to last set controller profile
if (m_controllerProfile)
return m_controllerProfile;

// Fall back to default controller
return m_manager.GetControllerProfiles().GetDefaultController();
}

void CPeripheralJoystick::SetControllerProfile(const KODI::GAME::ControllerPtr& controller)
{
CPeripheral::SetControllerProfile(controller);

// Save preference to buttonmap
if (m_buttonMap)
{
if (m_buttonMap->SetAppearance(controller->ID()))
m_buttonMap->SaveButtonMap();
}
}

bool CPeripheralJoystick::OnButtonMotion(unsigned int buttonIndex, bool bPressed)
{
// Silence debug log if controllers are not enabled
Expand Down Expand Up @@ -413,3 +496,42 @@ void CPeripheralJoystick::SetSupportsPowerOff(bool bSupportsPowerOff)
else if (std::find(m_features.begin(), m_features.end(), FEATURE_POWER_OFF) == m_features.end())
m_features.push_back(FEATURE_POWER_OFF);
}

GAME::ControllerPtr CPeripheralJoystick::InstallAsync(const std::string& controllerId)
{
GAME::ControllerPtr controller;

// Only 1 install at a time. Remaining installs will wake when this one
// is done.
std::unique_lock<CCriticalSection> lockInstall(m_manager.GetAddonInstallMutex());

CLog::LogF(LOGDEBUG, "Installing {}", controllerId);

if (InstallSync(controllerId))
controller = m_manager.GetControllerProfiles().GetController(controllerId);
else
CLog::LogF(LOGERROR, "Failed to install {}", controllerId);

return controller;
}

bool CPeripheralJoystick::InstallSync(const std::string& controllerId)
{
// If the addon isn't installed we need to install it
bool installed = CServiceBroker::GetAddonMgr().IsAddonInstalled(controllerId);
if (!installed)
{
ADDON::AddonPtr installedAddon;
installed = ADDON::CAddonInstaller::GetInstance().InstallModal(
controllerId, installedAddon, ADDON::InstallModalPrompt::CHOICE_NO);
}

if (installed)
{
// Make sure add-on is enabled
if (CServiceBroker::GetAddonMgr().IsAddonDisabled(controllerId))
CServiceBroker::GetAddonMgr().EnableAddon(controllerId);
}

return installed;
}
Loading

0 comments on commit 0b4ec3a

Please sign in to comment.