diff --git a/src/common/input.h b/src/common/input.h index bb42aaacc..995c35d9d 100644 --- a/src/common/input.h +++ b/src/common/input.h @@ -28,7 +28,7 @@ enum class InputType { Color, Vibration, Nfc, - Ir, + IrSensor, }; // Internal battery charge level @@ -53,6 +53,15 @@ enum class PollingMode { IR, }; +enum class CameraFormat { + Size320x240, + Size160x120, + Size80x60, + Size40x30, + Size20x15, + None, +}; + // Vibration reply from the controller enum class VibrationError { None, @@ -68,6 +77,13 @@ enum class PollingError { Unknown, }; +// Ir camera reply from the controller +enum class CameraError { + None, + NotSupported, + Unknown, +}; + // Hint for amplification curve to be used enum class VibrationAmplificationType { Linear, @@ -176,6 +192,12 @@ struct LedStatus { bool led_4{}; }; +// Raw data fom camera +struct CameraStatus { + CameraFormat format{CameraFormat::None}; + std::vector data{}; +}; + // List of buttons to be passed to Qt that can be translated enum class ButtonNames { Undefined, @@ -233,6 +255,7 @@ struct CallbackStatus { BodyColorStatus color_status{}; BatteryStatus battery_status{}; VibrationStatus vibration_status{}; + CameraStatus camera_status{}; }; // Triggered once every input change @@ -281,6 +304,10 @@ public: virtual PollingError SetPollingMode([[maybe_unused]] PollingMode polling_mode) { return PollingError::NotSupported; } + + virtual CameraError SetCameraFormat([[maybe_unused]] CameraFormat camera_format) { + return CameraError::NotSupported; + } }; /// An abstract class template for a factory that can create input devices. diff --git a/src/common/settings.h b/src/common/settings.h index 06d72c8bf..20959ec89 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -503,6 +503,9 @@ struct Values { Setting enable_ring_controller{true, "enable_ring_controller"}; RingconRaw ringcon_analogs; + BasicSetting enable_ir_sensor{false, "enable_ir_sensor"}; + BasicSetting ir_sensor_device{"auto", "ir_sensor_device"}; + // Data Storage Setting use_virtual_sd{true, "use_virtual_sd"}; Setting gamecard_inserted{false, "gamecard_inserted"}; diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 48e799cf5..90dd629c6 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -1,4 +1,6 @@ add_library(input_common STATIC + drivers/camera.cpp + drivers/camera.h drivers/gc_adapter.cpp drivers/gc_adapter.h drivers/keyboard.cpp diff --git a/src/input_common/drivers/camera.cpp b/src/input_common/drivers/camera.cpp new file mode 100644 index 000000000..dceea67e0 --- /dev/null +++ b/src/input_common/drivers/camera.cpp @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "common/param_package.h" +#include "input_common/drivers/camera.h" + +namespace InputCommon { +constexpr PadIdentifier identifier = { + .guid = Common::UUID{}, + .port = 0, + .pad = 0, +}; + +Camera::Camera(std::string input_engine_) : InputEngine(std::move(input_engine_)) { + PreSetController(identifier); +} + +void Camera::SetCameraData(std::size_t width, std::size_t height, std::vector data) { + const std::size_t desired_width = getImageWidth(); + const std::size_t desired_height = getImageHeight(); + status.data.resize(desired_width * desired_height); + + // Resize image to desired format + for (std::size_t y = 0; y < desired_height; y++) { + for (std::size_t x = 0; x < desired_width; x++) { + const std::size_t pixel_index = y * desired_width + x; + const std::size_t old_x = width * x / desired_width; + const std::size_t old_y = height * y / desired_height; + const std::size_t data_pixel_index = old_y * width + old_x; + status.data[pixel_index] = static_cast(data[data_pixel_index] & 0xFF); + } + } + + SetCamera(identifier, status); +} + +std::size_t Camera::getImageWidth() const { + switch (status.format) { + case Common::Input::CameraFormat::Size320x240: + return 320; + case Common::Input::CameraFormat::Size160x120: + return 160; + case Common::Input::CameraFormat::Size80x60: + return 80; + case Common::Input::CameraFormat::Size40x30: + return 40; + case Common::Input::CameraFormat::Size20x15: + return 20; + case Common::Input::CameraFormat::None: + default: + return 0; + } +} + +std::size_t Camera::getImageHeight() const { + switch (status.format) { + case Common::Input::CameraFormat::Size320x240: + return 240; + case Common::Input::CameraFormat::Size160x120: + return 120; + case Common::Input::CameraFormat::Size80x60: + return 60; + case Common::Input::CameraFormat::Size40x30: + return 30; + case Common::Input::CameraFormat::Size20x15: + return 15; + case Common::Input::CameraFormat::None: + default: + return 0; + } +} + +Common::Input::CameraError Camera::SetCameraFormat( + [[maybe_unused]] const PadIdentifier& identifier_, + const Common::Input::CameraFormat camera_format) { + status.format = camera_format; + return Common::Input::CameraError::None; +} + +} // namespace InputCommon diff --git a/src/input_common/drivers/camera.h b/src/input_common/drivers/camera.h new file mode 100644 index 000000000..b8a7c75e5 --- /dev/null +++ b/src/input_common/drivers/camera.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "input_common/input_engine.h" + +namespace InputCommon { + +/** + * A button device factory representing a keyboard. It receives keyboard events and forward them + * to all button devices it created. + */ +class Camera final : public InputEngine { +public: + explicit Camera(std::string input_engine_); + + void SetCameraData(std::size_t width, std::size_t height, std::vector data); + + std::size_t getImageWidth() const; + std::size_t getImageHeight() const; + + Common::Input::CameraError SetCameraFormat(const PadIdentifier& identifier_, + Common::Input::CameraFormat camera_format) override; + + Common::Input::CameraStatus status{}; +}; + +} // namespace InputCommon diff --git a/src/input_common/input_engine.cpp b/src/input_common/input_engine.cpp index 12214d146..6ede0e4b0 100644 --- a/src/input_common/input_engine.cpp +++ b/src/input_common/input_engine.cpp @@ -90,6 +90,18 @@ void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, const B TriggerOnMotionChange(identifier, motion, value); } +void InputEngine::SetCamera(const PadIdentifier& identifier, + const Common::Input::CameraStatus& value) { + { + std::scoped_lock lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + if (!configuring) { + controller.camera = value; + } + } + TriggerOnCameraChange(identifier, value); +} + bool InputEngine::GetButton(const PadIdentifier& identifier, int button) const { std::scoped_lock lock{mutex}; const auto controller_iter = controller_list.find(identifier); @@ -165,6 +177,18 @@ BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion) return controller.motions.at(motion); } +Common::Input::CameraStatus InputEngine::GetCamera(const PadIdentifier& identifier) const { + std::scoped_lock lock{mutex}; + const auto controller_iter = controller_list.find(identifier); + if (controller_iter == controller_list.cend()) { + LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(), + identifier.pad, identifier.port); + return {}; + } + const ControllerData& controller = controller_iter->second; + return controller.camera; +} + void InputEngine::ResetButtonState() { for (const auto& controller : controller_list) { for (const auto& button : controller.second.buttons) { @@ -317,6 +341,20 @@ void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int mot }); } +void InputEngine::TriggerOnCameraChange(const PadIdentifier& identifier, + [[maybe_unused]] const Common::Input::CameraStatus& value) { + std::scoped_lock lock{mutex_callback}; + for (const auto& poller_pair : callback_list) { + const InputIdentifier& poller = poller_pair.second; + if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Camera, 0)) { + continue; + } + if (poller.callback.on_change) { + poller.callback.on_change(); + } + } +} + bool InputEngine::IsInputIdentifierEqual(const InputIdentifier& input_identifier, const PadIdentifier& identifier, EngineInputType type, int index) const { diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h index 13295bd49..f6b3c4610 100644 --- a/src/input_common/input_engine.h +++ b/src/input_common/input_engine.h @@ -36,11 +36,12 @@ struct BasicMotion { // Types of input that are stored in the engine enum class EngineInputType { None, - Button, - HatButton, Analog, - Motion, Battery, + Button, + Camera, + HatButton, + Motion, }; namespace std { @@ -115,10 +116,17 @@ public: // Sets polling mode to a controller virtual Common::Input::PollingError SetPollingMode( [[maybe_unused]] const PadIdentifier& identifier, - [[maybe_unused]] const Common::Input::PollingMode vibration) { + [[maybe_unused]] const Common::Input::PollingMode polling_mode) { return Common::Input::PollingError::NotSupported; } + // Sets camera format to a controller + virtual Common::Input::CameraError SetCameraFormat( + [[maybe_unused]] const PadIdentifier& identifier, + [[maybe_unused]] Common::Input::CameraFormat camera_format) { + return Common::Input::CameraError::NotSupported; + } + // Returns the engine name [[nodiscard]] const std::string& GetEngineName() const; @@ -174,6 +182,7 @@ public: f32 GetAxis(const PadIdentifier& identifier, int axis) const; Common::Input::BatteryLevel GetBattery(const PadIdentifier& identifier) const; BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const; + Common::Input::CameraStatus GetCamera(const PadIdentifier& identifier) const; int SetCallback(InputIdentifier input_identifier); void SetMappingCallback(MappingCallback callback); @@ -185,6 +194,7 @@ protected: void SetAxis(const PadIdentifier& identifier, int axis, f32 value); void SetBattery(const PadIdentifier& identifier, Common::Input::BatteryLevel value); void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value); + void SetCamera(const PadIdentifier& identifier, const Common::Input::CameraStatus& value); virtual std::string GetHatButtonName([[maybe_unused]] u8 direction_value) const { return "Unknown"; @@ -197,6 +207,7 @@ private: std::unordered_map axes; std::unordered_map motions; Common::Input::BatteryLevel battery{}; + Common::Input::CameraStatus camera{}; }; void TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value); @@ -205,6 +216,8 @@ private: void TriggerOnBatteryChange(const PadIdentifier& identifier, Common::Input::BatteryLevel value); void TriggerOnMotionChange(const PadIdentifier& identifier, int motion, const BasicMotion& value); + void TriggerOnCameraChange(const PadIdentifier& identifier, + const Common::Input::CameraStatus& value); bool IsInputIdentifierEqual(const InputIdentifier& input_identifier, const PadIdentifier& identifier, EngineInputType type, diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp index 49ccb4422..133422d5c 100644 --- a/src/input_common/input_poller.cpp +++ b/src/input_common/input_poller.cpp @@ -664,6 +664,47 @@ private: InputEngine* input_engine; }; +class InputFromCamera final : public Common::Input::InputDevice { +public: + explicit InputFromCamera(PadIdentifier identifier_, InputEngine* input_engine_) + : identifier(identifier_), input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier input_identifier{ + .identifier = identifier, + .type = EngineInputType::Camera, + .index = 0, + .callback = engine_callback, + }; + callback_key = input_engine->SetCallback(input_identifier); + } + + ~InputFromCamera() override { + input_engine->DeleteCallback(callback_key); + } + + Common::Input::CameraStatus GetStatus() const { + return input_engine->GetCamera(identifier); + } + + void ForceUpdate() override { + OnChange(); + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::IrSensor, + .camera_status = GetStatus(), + }; + + TriggerOnChange(status); + } + +private: + const PadIdentifier identifier; + int callback_key; + InputEngine* input_engine; +}; + class OutputFromIdentifier final : public Common::Input::OutputDevice { public: explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_) @@ -682,6 +723,10 @@ public: return input_engine->SetPollingMode(identifier, polling_mode); } + Common::Input::CameraError SetCameraFormat(Common::Input::CameraFormat camera_format) override { + return input_engine->SetCameraFormat(identifier, camera_format); + } + private: const PadIdentifier identifier; InputEngine* input_engine; @@ -920,6 +965,18 @@ std::unique_ptr InputFactory::CreateMotionDevice( properties_y, properties_z, input_engine.get()); } +std::unique_ptr InputFactory::CreateCameraDevice( + const Common::ParamPackage& params) { + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast(params.Get("port", 0)), + .pad = static_cast(params.Get("pad", 0)), + }; + + input_engine->PreSetController(identifier); + return std::make_unique(identifier, input_engine.get()); +} + InputFactory::InputFactory(std::shared_ptr input_engine_) : input_engine(std::move(input_engine_)) {} @@ -928,6 +985,9 @@ std::unique_ptr InputFactory::Create( if (params.Has("battery")) { return CreateBatteryDevice(params); } + if (params.Has("camera")) { + return CreateCameraDevice(params); + } if (params.Has("button") && params.Has("axis")) { return CreateTriggerDevice(params); } diff --git a/src/input_common/input_poller.h b/src/input_common/input_poller.h index 6ebe0dbf5..4410a8415 100644 --- a/src/input_common/input_poller.h +++ b/src/input_common/input_poller.h @@ -211,6 +211,17 @@ private: */ std::unique_ptr CreateMotionDevice(Common::ParamPackage params); + /** + * Creates a camera device from the parameters given. + * @param params contains parameters for creating the device: + * - "guid": text string for identifying controllers + * - "port": port of the connected device + * - "pad": slot of the connected controller + * @returns a unique input device with the parameters specified + */ + std::unique_ptr CreateCameraDevice( + const Common::ParamPackage& params); + std::shared_ptr input_engine; }; } // namespace InputCommon diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 21834fb6b..ca1cb9542 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -5,6 +5,7 @@ #include #include "common/input.h" #include "common/param_package.h" +#include "input_common/drivers/camera.h" #include "input_common/drivers/gc_adapter.h" #include "input_common/drivers/keyboard.h" #include "input_common/drivers/mouse.h" @@ -78,6 +79,15 @@ struct InputSubsystem::Impl { Common::Input::RegisterFactory(tas_input->GetEngineName(), tas_output_factory); + camera = std::make_shared("camera"); + camera->SetMappingCallback(mapping_callback); + camera_input_factory = std::make_shared(camera); + camera_output_factory = std::make_shared(camera); + Common::Input::RegisterFactory(camera->GetEngineName(), + camera_input_factory); + Common::Input::RegisterFactory(camera->GetEngineName(), + camera_output_factory); + #ifdef HAVE_SDL2 sdl = std::make_shared("sdl"); sdl->SetMappingCallback(mapping_callback); @@ -317,6 +327,7 @@ struct InputSubsystem::Impl { std::shared_ptr touch_screen; std::shared_ptr tas_input; std::shared_ptr udp_client; + std::shared_ptr camera; std::shared_ptr keyboard_factory; std::shared_ptr mouse_factory; @@ -324,12 +335,14 @@ struct InputSubsystem::Impl { std::shared_ptr touch_screen_factory; std::shared_ptr udp_client_input_factory; std::shared_ptr tas_input_factory; + std::shared_ptr camera_input_factory; std::shared_ptr keyboard_output_factory; std::shared_ptr mouse_output_factory; std::shared_ptr gcadapter_output_factory; std::shared_ptr udp_client_output_factory; std::shared_ptr tas_output_factory; + std::shared_ptr camera_output_factory; #ifdef HAVE_SDL2 std::shared_ptr sdl; @@ -382,6 +395,14 @@ const TasInput::Tas* InputSubsystem::GetTas() const { return impl->tas_input.get(); } +Camera* InputSubsystem::GetCamera() { + return impl->camera.get(); +} + +const Camera* InputSubsystem::GetCamera() const { + return impl->camera.get(); +} + std::vector InputSubsystem::GetInputDevices() const { return impl->GetInputDevices(); } diff --git a/src/input_common/main.h b/src/input_common/main.h index 147c310c4..b756bb5c6 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -30,6 +30,7 @@ enum Values : int; } namespace InputCommon { +class Camera; class Keyboard; class Mouse; class TouchScreen; @@ -92,9 +93,15 @@ public: /// Retrieves the underlying tas input device. [[nodiscard]] TasInput::Tas* GetTas(); - /// Retrieves the underlying tas input device. + /// Retrieves the underlying tas input device. [[nodiscard]] const TasInput::Tas* GetTas() const; + /// Retrieves the underlying camera input device. + [[nodiscard]] Camera* GetCamera(); + + /// Retrieves the underlying camera input device. + [[nodiscard]] const Camera* GetCamera() const; + /** * Returns all available input devices that this Factory can create a new device with. * Each returned ParamPackage should have a `display` field used for display, a `engine` field