// Copyright 2013 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include #include #include #include #include #include #include #include #include "Common/CommonTypes.h" // idk in case I wanted to change it to double or something, idk what's best typedef double ControlState; namespace ciface { // 100Hz which homebrew docs very roughly imply is within WiiMote normal // range, used for periodic haptic effects though often ignored by devices // TODO: Make this configurable. constexpr int RUMBLE_PERIOD_MS = 10; // This needs to be at least as long as the longest rumble that might ever be played. // Too short and it's going to stop in the middle of a long effect. // Infinite values are invalid for ramp effects and probably not sensible. constexpr int RUMBLE_LENGTH_MS = 1000 * 10; // All inputs (other than accel/gyro) return 1.0 as their maximum value. // Battery inputs will almost always be mapped to the "Battery" setting which is a percentage. // If someone actually wants to map a battery input to a regular control they can divide by 100. // I think this is better than requiring multiplication by 100 for the most common usage. constexpr ControlState BATTERY_INPUT_MAX_VALUE = 100.0; namespace Core { enum class DeviceRemoval { Remove, Keep, }; class Device { public: class Input; class Output; // // Control // // Control includes inputs and outputs // class Control // input or output { public: virtual ~Control() = default; virtual std::string GetName() const = 0; virtual Input* ToInput() { return nullptr; } virtual Output* ToOutput() { return nullptr; } // May be overridden to allow multiple valid names. // Useful for backwards-compatible configurations when names change. virtual bool IsMatchingName(std::string_view name) const; // May be overridden to hide in UI. // Useful for backwards-compatible configurations when names change. virtual bool IsHidden() const; }; // // Input // // An input on a device // class Input : public Control { public: // Things like absolute axes/ absolute mouse position should override this to prevent // undesirable behavior in our mapping logic. virtual bool IsDetectable() const { return true; } // Implementations should return a value from 0.0 to 1.0 across their normal range. // One input should be provided for each "direction". (e.g. 2 for each axis) // If possible, negative values may be returned in situations where an opposing input is // activated. (e.g. When an underlying axis, X, is currently negative, "Axis X-", will return a // positive value and "Axis X+" may return a negative value.) // Doing so is solely to allow our input detection logic to better detect false positives. // This is necessary when making use of "FullAnalogSurface" as multiple inputs will be seen // increasing from 0.0 to 1.0 as a user tries to map just one. The negative values provide a // view of the underlying axis. (Negative values are clamped off before they reach // expression-parser or controller-emu) virtual ControlState GetState() const = 0; Input* ToInput() override { return this; } // Overridden by CombinedInput, // so hotkey logic knows Ctrl, L_Ctrl, and R_Ctrl are the same, // and so input detection can return the parent name. virtual bool IsChild(const Input*) const { return false; } }; class RelativeInput : public Input { public: bool IsDetectable() const override { return false; } }; // // Output // // An output on a device // class Output : public Control { public: virtual ~Output() = default; virtual void SetState(ControlState state) = 0; Output* ToOutput() override { return this; } }; virtual ~Device(); int GetId() const { return m_id; } void SetId(int id) { m_id = id; } virtual std::string GetName() const = 0; virtual std::string GetSource() const = 0; std::string GetQualifiedName() const; virtual DeviceRemoval UpdateInput() { return DeviceRemoval::Keep; } // May be overridden to implement hotplug removal. // Currently handled on a per-backend basis but this could change. virtual bool IsValid() const { return true; } // Returns true whether this device is "virtual/emulated", not linked // to any actual physical device. Mostly used by keyboard and mouse devices, // and to avoid uselessly recreating the device unless really necessary. // Doesn't necessarily need to be set to true if the device is virtual. virtual bool IsVirtualDevice() const { return false; } // (e.g. Xbox 360 controllers have controller number LEDs which should match the ID we use.) virtual std::optional GetPreferredId() const; // Use this to change the order in which devices are sorted in their list. // A higher priority means it will be one of the first ones (smaller index), making it more // likely to be index 0, which is automatically set as the default device when there isn't one. // Every platform should have at least one device with priority >= 0. static constexpr int DEFAULT_DEVICE_SORT_PRIORITY = std::numeric_limits::max(); virtual int GetSortPriority() const { return 0; } const std::vector& Inputs() const { return m_inputs; } const std::vector& Outputs() const { return m_outputs; } Input* GetParentMostInput(Input* input) const; Input* FindInput(std::string_view name) const; Output* FindOutput(std::string_view name) const; protected: void AddInput(Input* const i); void AddOutput(Output* const o); class FullAnalogSurface final : public Input { public: FullAnalogSurface(Input* low, Input* high) : m_low(*low), m_high(*high) {} ControlState GetState() const override; std::string GetName() const override; bool IsDetectable() const override; bool IsHidden() const override; bool IsMatchingName(std::string_view name) const override; private: Input& m_low; Input& m_high; }; void AddAnalogInputs(Input* low, Input* high) { AddInput(low); AddInput(high); AddInput(new FullAnalogSurface(low, high)); AddInput(new FullAnalogSurface(high, low)); } void AddCombinedInput(std::string name, const std::pair& inputs); private: int m_id = 0; std::vector m_inputs; std::vector m_outputs; }; // // DeviceQualifier // // Device qualifier used to match devices. // Currently has ( source, id, name ) properties which match a device // class DeviceQualifier { public: DeviceQualifier() : cid(-1) {} DeviceQualifier(std::string source_, const int id_, std::string name_) : source(std::move(source_)), cid(id_), name(std::move(name_)) { } void FromDevice(const Device* const dev); void FromString(const std::string& str); std::string ToString() const; bool operator==(const DeviceQualifier& devq) const; bool operator==(const Device* dev) const; std::string source; int cid; std::string name; }; class DeviceContainer { public: using Clock = std::chrono::steady_clock; struct InputDetection { std::shared_ptr device; Device::Input* input = nullptr; Clock::time_point press_time; std::optional release_time; ControlState smoothness = 0; }; Device::Input* FindInput(std::string_view name, const Device* def_dev) const; Device::Output* FindOutput(std::string_view name, const Device* def_dev) const; std::vector> GetAllDevices() const; std::vector GetAllDeviceStrings() const; bool HasDefaultDevice() const; std::string GetDefaultDeviceString() const; std::shared_ptr FindDevice(const DeviceQualifier& devq) const; bool HasConnectedDevice(const DeviceQualifier& qualifier) const; std::vector DetectInput(const std::vector& device_strings, std::chrono::milliseconds initial_wait, std::chrono::milliseconds confirmation_wait, std::chrono::milliseconds maximum_wait) const; std::recursive_mutex& GetDevicesMutex() const { return m_devices_mutex; } protected: // Exclusively needed when reading/writing the "m_devices" array. // Not needed when individually readring/writing a single device ptr. mutable std::recursive_mutex m_devices_mutex; std::vector> m_devices; }; } // namespace Core } // namespace ciface