mirror of
https://github.com/RPCS3/rpcs3.git
synced 2024-11-22 02:32:36 +01:00
cellGem: implement real ps move handler
This commit is contained in:
parent
e7faec6b0e
commit
b89cc9b973
@ -6,12 +6,15 @@
|
||||
#include "Emu/Cell/PPUModule.h"
|
||||
#include "Emu/Cell/timers.hpp"
|
||||
#include "Emu/Io/MouseHandler.h"
|
||||
#include "Emu/Io/PadHandler.h"
|
||||
#include "Emu/Io/gem_config.h"
|
||||
#include "Emu/system_config.h"
|
||||
#include "Emu/System.h"
|
||||
#include "Emu/IdManager.h"
|
||||
#include "Emu/RSX/Overlays/overlay_cursor.h"
|
||||
#include "Input/pad_thread.h"
|
||||
#include "Input/ps_move_config.h"
|
||||
#include "Input/ps_move_tracker.h"
|
||||
|
||||
#ifdef HAVE_LIBEVDEV
|
||||
#include "Input/evdev_gun_handler.h"
|
||||
@ -174,7 +177,8 @@ using gun_thread = named_thread<gun_handler>;
|
||||
|
||||
#endif
|
||||
|
||||
cfg_gems g_cfg_gem;
|
||||
cfg_gems g_cfg_gem_real;
|
||||
cfg_fake_gems g_cfg_gem_fake;
|
||||
|
||||
struct gem_config_data
|
||||
{
|
||||
@ -232,6 +236,7 @@ public:
|
||||
u32 hue = 0; // Tracking hue of the motion controller
|
||||
f32 distance{1500.0f}; // Distance from the camera in mm
|
||||
f32 radius{10.0f}; // Radius of the sphere in camera pixels
|
||||
bool radius_valid = true; // If the radius and distance of the sphere was computed.
|
||||
|
||||
bool is_calibrating{false}; // Whether or not we are currently calibrating
|
||||
u64 calibration_start_us{0}; // The start timestamp of the calibration in microseconds
|
||||
@ -252,7 +257,7 @@ public:
|
||||
std::array<gem_controller, CELL_GEM_MAX_NUM> controllers;
|
||||
u32 connected_controllers = 0;
|
||||
atomic_t<bool> video_conversion_in_progress{false};
|
||||
atomic_t<bool> update_started{false};
|
||||
atomic_t<bool> updating{false};
|
||||
u32 camera_frame{};
|
||||
u32 memory_ptr{};
|
||||
|
||||
@ -298,6 +303,26 @@ public:
|
||||
|
||||
switch (g_cfg.io.move)
|
||||
{
|
||||
case move_handler::real:
|
||||
{
|
||||
connected_controllers = 0;
|
||||
std::lock_guard lock(pad::g_pad_mutex);
|
||||
const auto handler = pad::get_current_handler();
|
||||
for (u32 i = 0; i < std::min<u32>(attribute.max_connect, CELL_GEM_MAX_NUM); i++)
|
||||
{
|
||||
const auto& pad = ::at32(handler->GetPads(), pad_num(i));
|
||||
if (pad && pad->m_pad_handler == pad_handler::move && (pad->m_port_status & CELL_PAD_STATUS_CONNECTED))
|
||||
{
|
||||
connected_controllers++;
|
||||
|
||||
if (gem_num == i)
|
||||
{
|
||||
is_connected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case move_handler::fake:
|
||||
{
|
||||
connected_controllers = 0;
|
||||
@ -368,9 +393,14 @@ public:
|
||||
|
||||
gem_config_data()
|
||||
{
|
||||
if (!g_cfg_gem.load())
|
||||
if (!g_cfg_gem_real.load())
|
||||
{
|
||||
cellGem.notice("Could not load gem config. Using defaults.");
|
||||
cellGem.notice("Could not load real gem config. Using defaults.");
|
||||
}
|
||||
|
||||
if (!g_cfg_gem_fake.load())
|
||||
{
|
||||
cellGem.notice("Could not load fake gem config. Using defaults.");
|
||||
}
|
||||
|
||||
cellGem.notice("Gem config=\n", g_cfg_gem.to_string());
|
||||
@ -390,7 +420,7 @@ public:
|
||||
[[maybe_unused]] const s32 version = GET_OR_USE_SERIALIZATION_VERSION(ar.is_writing(), cellGem);
|
||||
|
||||
ar(attribute, vc_attribute, status_flags, enable_pitch_correction, inertial_counter, controllers
|
||||
, connected_controllers, update_started, camera_frame, memory_ptr, start_timestamp_us);
|
||||
, connected_controllers, updating, camera_frame, memory_ptr, start_timestamp_us);
|
||||
}
|
||||
|
||||
gem_config_data(utils::serial& ar)
|
||||
@ -400,15 +430,24 @@ public:
|
||||
if (ar.is_writing())
|
||||
return;
|
||||
|
||||
if (!g_cfg_gem.load())
|
||||
if (!g_cfg_gem_real.load())
|
||||
{
|
||||
cellGem.notice("Could not load gem config. Using defaults.");
|
||||
cellGem.notice("Could not load real gem config. Using defaults.");
|
||||
}
|
||||
|
||||
cellGem.notice("Gem config=\n", g_cfg_gem.to_string());
|
||||
if (!g_cfg_gem_fake.load())
|
||||
{
|
||||
cellGem.notice("Could not load fake gem config. Using defaults.");
|
||||
}
|
||||
|
||||
cellGem.notice("Real gem config=\n", g_cfg_gem_real.to_string());
|
||||
cellGem.notice("Fake gem config=\n", g_cfg_gem_fake.to_string());
|
||||
}
|
||||
};
|
||||
|
||||
extern std::pair<u32, u32> get_video_resolution(const CellCameraInfoEx& info);
|
||||
extern u32 get_buffer_size_by_format(s32 format, s32 width, s32 height);
|
||||
|
||||
static inline int32_t cellGemGetVideoConvertSize(s32 output_format)
|
||||
{
|
||||
switch (output_format)
|
||||
@ -605,6 +644,207 @@ void gem_config_data::operator()()
|
||||
|
||||
using gem_config = named_thread<gem_config_data>;
|
||||
|
||||
class gem_tracker
|
||||
{
|
||||
public:
|
||||
gem_tracker()
|
||||
{
|
||||
}
|
||||
|
||||
bool is_busy()
|
||||
{
|
||||
return m_busy;
|
||||
}
|
||||
|
||||
void wake_up()
|
||||
{
|
||||
m_wake_up.release(1);
|
||||
m_wake_up.notify_one();
|
||||
}
|
||||
|
||||
void wait_for_result()
|
||||
{
|
||||
if (!m_done)
|
||||
{
|
||||
m_done.wait(0);
|
||||
m_done.release(0);
|
||||
}
|
||||
}
|
||||
|
||||
bool set_image(u32 addr)
|
||||
{
|
||||
if (!addr)
|
||||
return false;
|
||||
|
||||
auto& g_camera = g_fxo->get<camera_thread>();
|
||||
std::lock_guard lock(g_camera.mutex);
|
||||
m_camera_info = g_camera.info;
|
||||
|
||||
if (m_camera_info.buffer.addr() != addr && m_camera_info.pbuf[0].addr() != addr && m_camera_info.pbuf[1].addr() != addr)
|
||||
{
|
||||
cellGem.error("gem_tracker: unexcepted image address: addr=0x%x, expected one of: 0x%x, 0x%x, 0x%x", addr, m_camera_info.buffer.addr(), m_camera_info.pbuf[0].addr(), m_camera_info.pbuf[1].addr());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy image data for further processing
|
||||
|
||||
const auto& [width, height] = get_video_resolution(m_camera_info);
|
||||
const u32 expected_size = get_buffer_size_by_format(m_camera_info.format, width, height);
|
||||
|
||||
if (!m_camera_info.bytesize || m_camera_info.bytesize != expected_size)
|
||||
{
|
||||
cellGem.error("gem_tracker: unexcepted image size: size=%d, expected=%d", m_camera_info.bytesize, expected_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_camera_info.bytesize)
|
||||
{
|
||||
cellGem.error("gem_tracker: unexcepted image size: %d", m_camera_info.bytesize);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_tracker.set_image_data(m_camera_info.buffer.get_ptr(), m_camera_info.bytesize, m_camera_info.width, m_camera_info.height, m_camera_info.format);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool hue_is_trackable(u32 hue)
|
||||
{
|
||||
if (g_cfg.io.move != move_handler::real)
|
||||
{
|
||||
return 1; // potentially true if less than 20 pixels have the hue
|
||||
}
|
||||
|
||||
return hue < m_hues.size() && m_hues[hue] < 20; // potentially true if less than 20 pixels have the hue
|
||||
}
|
||||
|
||||
ps_move_info& get_info(u32 gem_num)
|
||||
{
|
||||
return ::at32(m_info, gem_num);
|
||||
}
|
||||
|
||||
gem_tracker& operator=(thread_state)
|
||||
{
|
||||
wake_up();
|
||||
return *this;
|
||||
}
|
||||
|
||||
void operator()()
|
||||
{
|
||||
if (g_cfg.io.move != move_handler::real)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!g_cfg_move.load())
|
||||
{
|
||||
cellGem.notice("Could not load PS Move config. Using defaults.");
|
||||
}
|
||||
|
||||
auto& gem = g_fxo->get<gem_config>();
|
||||
|
||||
while (thread_ctrl::state() != thread_state::aborting)
|
||||
{
|
||||
// Check if we have a new frame
|
||||
if (!m_wake_up)
|
||||
{
|
||||
m_wake_up.wait(0);
|
||||
m_wake_up.release(0);
|
||||
|
||||
if (thread_ctrl::state() == thread_state::aborting)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_busy.release(true);
|
||||
|
||||
// Update PS Move LED colors
|
||||
{
|
||||
std::lock_guard lock(pad::g_pad_mutex);
|
||||
const auto handler = pad::get_current_handler();
|
||||
auto& handlers = handler->get_handlers();
|
||||
if (auto it = handlers.find(pad_handler::move); it != handlers.end())
|
||||
{
|
||||
for (auto& binding : it->second->bindings())
|
||||
{
|
||||
if (!binding.device) continue;
|
||||
|
||||
// last 4 out of 7 ports (6,5,4,3). index starts at 0
|
||||
const s32 gem_num = std::abs(binding.device->player_id - CELL_PAD_MAX_PORT_NUM) - 1;
|
||||
|
||||
if (gem_num < 0 || gem_num >= CELL_GEM_MAX_NUM) continue;
|
||||
|
||||
const cfg_ps_move* config = ::at32(g_cfg_move.move, gem_num);
|
||||
|
||||
binding.device->color_override_active = true;
|
||||
binding.device->color_override.r = config->r.get();
|
||||
binding.device->color_override.g = config->g.get();
|
||||
binding.device->color_override.b = config->b.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update tracker config
|
||||
for (u32 gem_num = 0; gem_num < CELL_GEM_MAX_NUM; gem_num++)
|
||||
{
|
||||
const auto& controller = gem.controllers[gem_num];
|
||||
const cfg_ps_move* config = g_cfg_move.move[gem_num];
|
||||
|
||||
m_tracker.set_active(gem_num, controller.enabled_tracking && controller.status == CELL_GEM_STATUS_READY);
|
||||
m_tracker.set_hue(gem_num, config->hue);
|
||||
m_tracker.set_hue_threshold(gem_num, config->hue_threshold);
|
||||
m_tracker.set_saturation_threshold(gem_num, config->saturation_threshold);
|
||||
}
|
||||
|
||||
m_tracker.set_min_radius(static_cast<f32>(g_cfg_move.min_radius.get() / g_cfg_move.min_radius.max));
|
||||
m_tracker.set_max_radius(static_cast<f32>(g_cfg_move.max_radius.get() / g_cfg_move.max_radius.max));
|
||||
|
||||
// Process camera image
|
||||
m_tracker.process_image();
|
||||
|
||||
// Update cellGem with results
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
m_hues = m_tracker.hues();
|
||||
m_info = m_tracker.info();
|
||||
|
||||
for (u32 gem_num = 0; gem_num < CELL_GEM_MAX_NUM; gem_num++)
|
||||
{
|
||||
const ps_move_info& info = m_info[gem_num];
|
||||
auto& controller = gem.controllers[gem_num];
|
||||
controller.radius_valid = info.valid;
|
||||
|
||||
if (info.valid)
|
||||
{
|
||||
// Only set new radius and distance if the radius is valid
|
||||
controller.radius = info.radius;
|
||||
controller.distance = info.distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify that we are finished with this frame
|
||||
m_done.release(1);
|
||||
m_done.notify_one();
|
||||
|
||||
m_busy.release(false);
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr auto thread_name = "GemUpdateThread"sv;
|
||||
|
||||
shared_mutex mutex;
|
||||
|
||||
private:
|
||||
atomic_t<u32> m_wake_up = 0;
|
||||
atomic_t<u32> m_done = 1;
|
||||
atomic_t<bool> m_busy = false;
|
||||
ps_move_tracker<false> m_tracker{};
|
||||
CellCameraInfoEx m_camera_info{};
|
||||
std::array<u32, 360> m_hues{};
|
||||
std::array<ps_move_info, CELL_GEM_MAX_NUM> m_info{};
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Verifies that a Move controller id is valid
|
||||
* \param gem_num Move controler ID to verify
|
||||
@ -739,8 +979,7 @@ static void ds3_input_to_pad(const u32 gem_num, be_t<u16>& digital_buttons, be_t
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& cfg = ::at32(g_cfg_gem.players, gem_num);
|
||||
cfg->handle_input(pad, true, [&](gem_btn btn, u16 value, bool pressed)
|
||||
const auto handle_input = [&](gem_btn btn, u16 value, bool pressed)
|
||||
{
|
||||
if (!pressed)
|
||||
return;
|
||||
@ -777,7 +1016,16 @@ static void ds3_input_to_pad(const u32 gem_num, be_t<u16>& digital_buttons, be_t
|
||||
case gem_btn::count:
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (g_cfg.io.move == move_handler::real)
|
||||
{
|
||||
::at32(g_cfg_gem_real.players, gem_num)->handle_input(pad, true, handle_input);
|
||||
}
|
||||
else
|
||||
{
|
||||
::at32(g_cfg_gem_fake.players, gem_num)->handle_input(pad, true, handle_input);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr u16 ds3_max_x = 255;
|
||||
@ -788,7 +1036,7 @@ static inline void ds3_get_stick_values(u32 gem_num, const std::shared_ptr<Pad>&
|
||||
x_pos = 0;
|
||||
y_pos = 0;
|
||||
|
||||
const auto& cfg = ::at32(g_cfg_gem.players, gem_num);
|
||||
const auto& cfg = ::at32(g_cfg_gem_fake.players, gem_num);
|
||||
cfg->handle_input(pad, true, [&](gem_btn btn, u16 value, bool pressed)
|
||||
{
|
||||
if (!pressed)
|
||||
@ -839,6 +1087,37 @@ static void ds3_pos_to_gem_state(u32 gem_num, const gem_config::gem_controller&
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void ps_move_pos_to_gem_state(u32 gem_num, const gem_config::gem_controller& controller, T& gem_state)
|
||||
{
|
||||
if (!gem_state || !is_input_allowed())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard lock(pad::g_pad_mutex);
|
||||
|
||||
const auto handler = pad::get_current_handler();
|
||||
const auto& pad = ::at32(handler->GetPads(), pad_num(gem_num));
|
||||
|
||||
if (pad->m_pad_handler != pad_handler::move || !(pad->m_port_status & CELL_PAD_STATUS_CONNECTED))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto& tracker = g_fxo->get<named_thread<gem_tracker>>(); // Let's not lock the mutex. This not really important here
|
||||
const ps_move_info& info = tracker.get_info(gem_num);
|
||||
|
||||
if constexpr (std::is_same_v<T, vm::ptr<CellGemState>>)
|
||||
{
|
||||
pos_to_gem_state(gem_num, controller, gem_state, info.x_pos, info.y_pos, info.x_max, info.y_max);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, vm::ptr<CellGemImageState>>)
|
||||
{
|
||||
pos_to_gem_image_state(gem_num, controller, gem_state, info.x_pos, info.y_pos, info.x_max, info.y_max);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Maps external Move controller data to DS3 input. (This can be input from any physical pad, not just the DS3)
|
||||
* Implementation detail: CellGemExtPortData's digital/analog fields map the same way as
|
||||
@ -847,7 +1126,7 @@ static void ds3_pos_to_gem_state(u32 gem_num, const gem_config::gem_controller&
|
||||
* \param ext External data to modify
|
||||
* \return true on success, false if controller is disconnected
|
||||
*/
|
||||
static void ds3_input_to_ext(const u32 gem_num, const gem_config::gem_controller& controller, CellGemExtPortData& ext)
|
||||
static void ds3_input_to_ext(u32 gem_num, gem_config::gem_controller& controller, CellGemExtPortData& ext)
|
||||
{
|
||||
ext = {};
|
||||
|
||||
@ -866,23 +1145,58 @@ static void ds3_input_to_ext(const u32 gem_num, const gem_config::gem_controller
|
||||
return;
|
||||
}
|
||||
|
||||
ext.status = 0; // CELL_GEM_EXT_CONNECTED | CELL_GEM_EXT_EXT0 | CELL_GEM_EXT_EXT1
|
||||
ext.analog_left_x = pad->m_analog_left_x; // HACK: these pad members are actually only set in cellPad
|
||||
ext.analog_left_y = pad->m_analog_left_y;
|
||||
ext.analog_right_x = pad->m_analog_right_x;
|
||||
ext.analog_right_y = pad->m_analog_right_y;
|
||||
ext.digital1 = pad->m_digital_1;
|
||||
ext.digital2 = pad->m_digital_2;
|
||||
const auto& move_data = pad->move_data;
|
||||
|
||||
if (controller.ext_id == SHARP_SHOOTER_DEVICE_ID)
|
||||
controller.ext_status = move_data.external_device_connected ? CELL_GEM_EXT_CONNECTED : 0; // TODO: | CELL_GEM_EXT_EXT0 | CELL_GEM_EXT_EXT1
|
||||
controller.ext_id = move_data.external_device_connected ? move_data.external_device_id : 0;
|
||||
|
||||
ext.status = controller.ext_status;
|
||||
|
||||
for (const AnalogStick& stick : pad->m_sticks)
|
||||
{
|
||||
// TODO set custom[0] bits as follows:
|
||||
// 1xxxxxxx: RL reload button is pressed.
|
||||
// x1xxxxxx: T button trigger is pressed.
|
||||
// xxxxx001: Firing mode selector is in position 1.
|
||||
// xxxxx010: Firing mode selector is in position 2.
|
||||
// xxxxx100: Firing mode selector is in position 3.
|
||||
switch (stick.m_offset)
|
||||
{
|
||||
case CELL_PAD_BTN_OFFSET_ANALOG_LEFT_X: ext.analog_left_x = stick.m_value; break;
|
||||
case CELL_PAD_BTN_OFFSET_ANALOG_LEFT_Y: ext.analog_left_y = stick.m_value; break;
|
||||
case CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X: ext.analog_right_x = stick.m_value; break;
|
||||
case CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_Y: ext.analog_right_y = stick.m_value; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
for (const Button& button : pad->m_buttons)
|
||||
{
|
||||
if (!button.m_pressed)
|
||||
continue;
|
||||
|
||||
switch (button.m_offset)
|
||||
{
|
||||
case CELL_PAD_BTN_OFFSET_DIGITAL1: ext.digital1 |= button.m_outKeyCode; break;
|
||||
case CELL_PAD_BTN_OFFSET_DIGITAL2: ext.digital2 |= button.m_outKeyCode; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!move_data.external_device_connected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// The sharpshooter only sets the custom bytes as follows:
|
||||
// custom[0] (0x01): Firing mode selector is in position 1.
|
||||
// custom[0] (0x02): Firing mode selector is in position 2.
|
||||
// custom[0] (0x04): Firing mode selector is in position 3.
|
||||
// custom[0] (0x40): T button trigger is pressed.
|
||||
// custom[0] (0x80): RL reload button is pressed.
|
||||
|
||||
// The racing wheel sets the custom bytes as follows:
|
||||
// custom[0] 0-255: Throttle
|
||||
// custom[1] 0-255: L2
|
||||
// custom[2] 0-255: R2
|
||||
// custom[3] (0x01): Left paddle
|
||||
// custom[3] (0x02): Right paddle
|
||||
|
||||
std::memcpy(ext.custom, move_data.external_device_data.data(), 5);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -892,7 +1206,7 @@ static void ds3_input_to_ext(const u32 gem_num, const gem_config::gem_controller
|
||||
* \param analog_t Analog value of Move's Trigger.
|
||||
* \return true on success, false if mouse_no is invalid
|
||||
*/
|
||||
static bool mouse_input_to_pad(const u32 mouse_no, be_t<u16>& digital_buttons, be_t<u16>& analog_t)
|
||||
static bool mouse_input_to_pad(u32 mouse_no, be_t<u16>& digital_buttons, be_t<u16>& analog_t)
|
||||
{
|
||||
digital_buttons = 0;
|
||||
analog_t = 0;
|
||||
@ -954,7 +1268,7 @@ static bool mouse_input_to_pad(const u32 mouse_no, be_t<u16>& digital_buttons, b
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void mouse_pos_to_gem_state(const u32 mouse_no, const gem_config::gem_controller& controller, T& gem_state)
|
||||
static void mouse_pos_to_gem_state(u32 mouse_no, const gem_config::gem_controller& controller, T& gem_state)
|
||||
{
|
||||
if (!gem_state || !is_input_allowed())
|
||||
{
|
||||
@ -986,7 +1300,7 @@ static void mouse_pos_to_gem_state(const u32 mouse_no, const gem_config::gem_con
|
||||
}
|
||||
|
||||
#ifdef HAVE_LIBEVDEV
|
||||
static bool gun_input_to_pad(const u32 gem_no, be_t<u16>& digital_buttons, be_t<u16>& analog_t)
|
||||
static bool gun_input_to_pad(u32 gem_no, be_t<u16>& digital_buttons, be_t<u16>& analog_t)
|
||||
{
|
||||
digital_buttons = 0;
|
||||
analog_t = 0;
|
||||
@ -1027,7 +1341,7 @@ static bool gun_input_to_pad(const u32 gem_no, be_t<u16>& digital_buttons, be_t<
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void gun_pos_to_gem_state(const u32 gem_no, const gem_config::gem_controller& controller, T& gem_state)
|
||||
static void gun_pos_to_gem_state(u32 gem_no, const gem_config::gem_controller& controller, T& gem_state)
|
||||
{
|
||||
if (!gem_state || !is_input_allowed())
|
||||
return;
|
||||
@ -1271,6 +1585,11 @@ error_code cellGemEnd(ppu_thread& ppu)
|
||||
return CELL_OK;
|
||||
}
|
||||
|
||||
auto& tracker = g_fxo->get<named_thread<gem_tracker>>();
|
||||
tracker.wait_for_result();
|
||||
|
||||
gem.updating = false;
|
||||
|
||||
return CELL_GEM_ERROR_UNINITIALIZED;
|
||||
}
|
||||
|
||||
@ -1368,9 +1687,12 @@ error_code cellGemGetAllTrackableHues(vm::ptr<u8> hues)
|
||||
return CELL_GEM_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < 360; i++)
|
||||
auto& tracker = g_fxo->get<named_thread<gem_tracker>>();
|
||||
std::lock_guard lock(tracker.mutex);
|
||||
|
||||
for (u32 hue = 0; hue < 360; hue++)
|
||||
{
|
||||
hues[i] = true;
|
||||
hues[hue] = tracker.hue_is_trackable(hue);
|
||||
}
|
||||
|
||||
return CELL_OK;
|
||||
@ -1445,7 +1767,10 @@ error_code cellGemGetHuePixels(vm::cptr<void> camera_frame, u32 hue, vm::ptr<u8>
|
||||
|
||||
std::memset(pixels.get_ptr(), 0, 640 * 480 * sizeof(u8));
|
||||
|
||||
// TODO
|
||||
if (g_cfg.io.move == move_handler::real)
|
||||
{
|
||||
// TODO: get pixels from tracker
|
||||
}
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
@ -1478,10 +1803,13 @@ error_code cellGemGetImageState(u32 gem_num, vm::ptr<CellGemImageState> gem_imag
|
||||
gem_image_state->r = controller.radius; // Radius in camera pixels
|
||||
gem_image_state->distance = controller.distance; // 1.5 meters away from camera
|
||||
gem_image_state->visible = gem.is_controller_ready(gem_num);
|
||||
gem_image_state->r_valid = true;
|
||||
gem_image_state->r_valid = controller.radius_valid;
|
||||
|
||||
switch (g_cfg.io.move)
|
||||
{
|
||||
case move_handler::real:
|
||||
ps_move_pos_to_gem_state(gem_num, controller, gem_image_state);
|
||||
break;
|
||||
case move_handler::fake:
|
||||
ds3_pos_to_gem_state(gem_num, controller, gem_image_state);
|
||||
break;
|
||||
@ -1537,6 +1865,24 @@ error_code cellGemGetInertialState(u32 gem_num, u32 state_flag, u64 timestamp, v
|
||||
|
||||
switch (g_cfg.io.move)
|
||||
{
|
||||
case move_handler::real:
|
||||
{
|
||||
// Get temperature
|
||||
{
|
||||
std::lock_guard lock(pad::g_pad_mutex);
|
||||
|
||||
const auto handler = pad::get_current_handler();
|
||||
const auto& pad = ::at32(handler->GetPads(), pad_num(gem_num));
|
||||
|
||||
if (pad && pad->m_pad_handler == pad_handler::move && (pad->m_port_status & CELL_PAD_STATUS_CONNECTED))
|
||||
{
|
||||
inertial_state->temperature = pad->move_data.temperature;
|
||||
}
|
||||
}
|
||||
|
||||
ds3_input_to_pad(gem_num, inertial_state->pad.digitalbuttons, inertial_state->pad.analog_T);
|
||||
break;
|
||||
}
|
||||
case move_handler::fake:
|
||||
ds3_input_to_pad(gem_num, inertial_state->pad.digitalbuttons, inertial_state->pad.analog_T);
|
||||
break;
|
||||
@ -1575,10 +1921,9 @@ error_code cellGemGetInfo(vm::ptr<CellGemInfo> info)
|
||||
return CELL_GEM_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
// TODO: Support connecting PlayStation Move controllers
|
||||
|
||||
switch (g_cfg.io.move)
|
||||
{
|
||||
case move_handler::real:
|
||||
case move_handler::fake:
|
||||
{
|
||||
gem.connected_controllers = 0;
|
||||
@ -1590,8 +1935,9 @@ error_code cellGemGetInfo(vm::ptr<CellGemInfo> info)
|
||||
{
|
||||
const auto& pad = ::at32(handler->GetPads(), pad_num(i));
|
||||
const bool connected = (pad && (pad->m_port_status & CELL_PAD_STATUS_CONNECTED) && i < gem.attribute.max_connect);
|
||||
const bool is_real_move = g_cfg.io.move != move_handler::real || pad->m_pad_handler == pad_handler::move;
|
||||
|
||||
if (connected)
|
||||
if (connected && is_real_move)
|
||||
{
|
||||
gem.connected_controllers++;
|
||||
gem.controllers[i].status = CELL_GEM_STATUS_READY;
|
||||
@ -1758,7 +2104,7 @@ error_code cellGemGetState(u32 gem_num, u32 flag, u64 time_parameter, vm::ptr<Ce
|
||||
return CELL_GEM_TIME_OUT_OF_RANGE;
|
||||
}
|
||||
|
||||
const auto& controller = gem.controllers[gem_num];
|
||||
auto& controller = gem.controllers[gem_num];
|
||||
|
||||
*gem_state = {};
|
||||
|
||||
@ -1766,18 +2112,33 @@ error_code cellGemGetState(u32 gem_num, u32 flag, u64 time_parameter, vm::ptr<Ce
|
||||
{
|
||||
ds3_input_to_ext(gem_num, controller, gem_state->ext);
|
||||
|
||||
u32 tracking_flags = CELL_GEM_TRACKING_FLAG_VISIBLE;
|
||||
|
||||
if (controller.enabled_tracking)
|
||||
tracking_flags |= CELL_GEM_TRACKING_FLAG_POSITION_TRACKED;
|
||||
{
|
||||
gem_state->tracking_flags |= CELL_GEM_TRACKING_FLAG_POSITION_TRACKED;
|
||||
gem_state->tracking_flags |= CELL_GEM_TRACKING_FLAG_VISIBLE;
|
||||
}
|
||||
|
||||
gem_state->tracking_flags = tracking_flags;
|
||||
gem_state->timestamp = (get_guest_system_time() - gem.start_timestamp_us);
|
||||
gem_state->camera_pitch_angle = 0.f;
|
||||
gem_state->quat[3] = 1.f;
|
||||
|
||||
switch (g_cfg.io.move)
|
||||
{
|
||||
case move_handler::real:
|
||||
{
|
||||
auto& tracker = g_fxo->get<named_thread<gem_tracker>>(); // Let's not lock the mutex. This not really important here
|
||||
const ps_move_info& info = tracker.get_info(gem_num);
|
||||
|
||||
ds3_input_to_pad(gem_num, gem_state->pad.digitalbuttons, gem_state->pad.analog_T);
|
||||
ps_move_pos_to_gem_state(gem_num, controller, gem_state);
|
||||
|
||||
if (info.valid)
|
||||
gem_state->tracking_flags |= CELL_GEM_TRACKING_FLAG_VISIBLE;
|
||||
else
|
||||
gem_state->tracking_flags &= ~CELL_GEM_TRACKING_FLAG_VISIBLE;
|
||||
|
||||
break;
|
||||
}
|
||||
case move_handler::fake:
|
||||
ds3_input_to_pad(gem_num, gem_state->pad.digitalbuttons, gem_state->pad.analog_T);
|
||||
ds3_pos_to_gem_state(gem_num, controller, gem_state);
|
||||
@ -1966,7 +2327,7 @@ error_code cellGemInit(ppu_thread& ppu, vm::cptr<CellGemAttribute> attribute)
|
||||
gem.memory_ptr = 0;
|
||||
}
|
||||
|
||||
gem.update_started = false;
|
||||
gem.updating = false;
|
||||
gem.camera_frame = 0;
|
||||
gem.status_flags = 0;
|
||||
gem.attribute = *attribute;
|
||||
@ -2027,7 +2388,10 @@ s32 cellGemIsTrackableHue(u32 hue)
|
||||
return CELL_GEM_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
return 1; // potentially true if less than 20 pixels have the hue
|
||||
auto& tracker = g_fxo->get<named_thread<gem_tracker>>();
|
||||
std::lock_guard lock(tracker.mutex);
|
||||
|
||||
return tracker.hue_is_trackable(hue);
|
||||
}
|
||||
|
||||
error_code cellGemPrepareCamera(s32 max_exposure, f32 image_quality)
|
||||
@ -2105,7 +2469,7 @@ error_code cellGemPrepareVideoConvert(vm::cptr<CellGemVideoConvertAttribute> vc_
|
||||
|
||||
error_code cellGemReadExternalPortDeviceInfo(u32 gem_num, vm::ptr<u32> ext_id, vm::ptr<u8[CELL_GEM_EXTERNAL_PORT_DEVICE_INFO_SIZE]> ext_info)
|
||||
{
|
||||
cellGem.todo("cellGemReadExternalPortDeviceInfo(gem_num=%d, ext_id=*0x%x, ext_info=%s)", gem_num, ext_id, ext_info);
|
||||
cellGem.warning("cellGemReadExternalPortDeviceInfo(gem_num=%d, ext_id=*0x%x, ext_info=%s)", gem_num, ext_id, ext_info);
|
||||
|
||||
auto& gem = g_fxo->get<gem_config>();
|
||||
|
||||
@ -2124,12 +2488,65 @@ error_code cellGemReadExternalPortDeviceInfo(u32 gem_num, vm::ptr<u32> ext_id, v
|
||||
return CELL_GEM_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
if (!(gem.controllers[gem_num].ext_status & CELL_GEM_EXT_CONNECTED))
|
||||
const u64 start = get_system_time();
|
||||
|
||||
auto& controller = gem.controllers[gem_num];
|
||||
|
||||
if (g_cfg.io.move != move_handler::null)
|
||||
{
|
||||
// Get external device status
|
||||
CellGemExtPortData ext_port_data{};
|
||||
ds3_input_to_ext(gem_num, controller, ext_port_data);
|
||||
}
|
||||
|
||||
if (!(controller.ext_status & CELL_GEM_EXT_CONNECTED))
|
||||
{
|
||||
return CELL_GEM_NO_EXTERNAL_PORT_DEVICE;
|
||||
}
|
||||
|
||||
*ext_id = gem.controllers[gem_num].ext_id;
|
||||
*ext_id = controller.ext_id;
|
||||
|
||||
if (ext_info && g_cfg.io.move == move_handler::real)
|
||||
{
|
||||
bool read_requested = false;
|
||||
|
||||
while (true)
|
||||
{
|
||||
{
|
||||
std::lock_guard lock(pad::g_pad_mutex);
|
||||
|
||||
const auto handler = pad::get_current_handler();
|
||||
const auto& pad = ::at32(handler->GetPads(), pad_num(gem_num));
|
||||
|
||||
if (pad->m_pad_handler != pad_handler::move || !(pad->m_port_status & CELL_PAD_STATUS_CONNECTED))
|
||||
{
|
||||
return CELL_GEM_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
if (!read_requested)
|
||||
{
|
||||
pad->move_data.external_device_read_requested = true;
|
||||
read_requested = true;
|
||||
}
|
||||
|
||||
if (!pad->move_data.external_device_read_requested)
|
||||
{
|
||||
*ext_id = controller.ext_id = pad->move_data.external_device_id;
|
||||
std::memcpy(pad->move_data.external_device_read.data(), ext_info.get_ptr(), CELL_GEM_EXTERNAL_PORT_OUTPUT_SIZE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We wait for 300ms at most
|
||||
if (const u64 elapsed_us = get_system_time() - start; elapsed_us > 300'000)
|
||||
{
|
||||
cellGem.warning("cellGemReadExternalPortDeviceInfo(gem_num=%d): timeout", gem_num);
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: sleep ?
|
||||
}
|
||||
}
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
@ -2229,6 +2646,8 @@ error_code cellGemTrackHues(vm::cptr<u32> req_hues, vm::ptr<u32> res_hues)
|
||||
gem.controllers[i].enabled_tracking = true;
|
||||
gem.controllers[i].enabled_LED = true;
|
||||
|
||||
// TODO: set hue based on tracker data
|
||||
|
||||
switch (i)
|
||||
{
|
||||
default:
|
||||
@ -2272,6 +2691,8 @@ error_code cellGemTrackHues(vm::cptr<u32> req_hues, vm::ptr<u32> res_hues)
|
||||
gem.controllers[i].enabled_LED = true;
|
||||
gem.controllers[i].hue = req_hues[i];
|
||||
|
||||
// TODO: set hue of tracker
|
||||
|
||||
if (res_hues)
|
||||
{
|
||||
res_hues[i] = gem.controllers[i].hue;
|
||||
@ -2297,11 +2718,16 @@ error_code cellGemUpdateFinish()
|
||||
|
||||
std::scoped_lock lock(gem.mtx);
|
||||
|
||||
if (!gem.update_started.exchange(false))
|
||||
if (!gem.updating)
|
||||
{
|
||||
return CELL_GEM_ERROR_UPDATE_NOT_STARTED;
|
||||
}
|
||||
|
||||
auto& tracker = g_fxo->get<named_thread<gem_tracker>>();
|
||||
tracker.wait_for_result();
|
||||
|
||||
gem.updating = false;
|
||||
|
||||
if (!gem.camera_frame)
|
||||
{
|
||||
return not_an_error(CELL_GEM_NO_VIDEO);
|
||||
@ -2321,10 +2747,17 @@ error_code cellGemUpdateStart(vm::cptr<void> camera_frame, u64 timestamp)
|
||||
return CELL_GEM_ERROR_UNINITIALIZED;
|
||||
}
|
||||
|
||||
auto& tracker = g_fxo->get<named_thread<gem_tracker>>();
|
||||
|
||||
if (tracker.is_busy())
|
||||
{
|
||||
return CELL_GEM_ERROR_UPDATE_NOT_FINISHED;
|
||||
}
|
||||
|
||||
std::scoped_lock lock(gem.mtx);
|
||||
|
||||
// Update is starting even when camera_frame is null
|
||||
if (gem.update_started.exchange(true))
|
||||
if (gem.updating.exchange(true))
|
||||
{
|
||||
return CELL_GEM_ERROR_UPDATE_NOT_FINISHED;
|
||||
}
|
||||
@ -2335,17 +2768,20 @@ error_code cellGemUpdateStart(vm::cptr<void> camera_frame, u64 timestamp)
|
||||
}
|
||||
|
||||
gem.camera_frame = camera_frame.addr();
|
||||
if (!camera_frame)
|
||||
|
||||
if (!tracker.set_image(gem.camera_frame))
|
||||
{
|
||||
return not_an_error(CELL_GEM_NO_VIDEO);
|
||||
}
|
||||
|
||||
tracker.wake_up();
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
||||
error_code cellGemWriteExternalPort(u32 gem_num, vm::ptr<u8[CELL_GEM_EXTERNAL_PORT_OUTPUT_SIZE]> data)
|
||||
{
|
||||
cellGem.todo("cellGemWriteExternalPort(gem_num=%d, data=%s)", gem_num, data);
|
||||
cellGem.warning("cellGemWriteExternalPort(gem_num=%d, data=%s)", gem_num, data);
|
||||
|
||||
auto& gem = g_fxo->get<gem_config>();
|
||||
|
||||
@ -2364,11 +2800,27 @@ error_code cellGemWriteExternalPort(u32 gem_num, vm::ptr<u8[CELL_GEM_EXTERNAL_PO
|
||||
return CELL_GEM_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
if (false) // TODO: check if this is still writing to the external port
|
||||
if (data && g_cfg.io.move == move_handler::real)
|
||||
{
|
||||
std::lock_guard lock(pad::g_pad_mutex);
|
||||
|
||||
const auto handler = pad::get_current_handler();
|
||||
const auto& pad = ::at32(handler->GetPads(), pad_num(gem_num));
|
||||
|
||||
if (pad->m_pad_handler != pad_handler::move || !(pad->m_port_status & CELL_PAD_STATUS_CONNECTED))
|
||||
{
|
||||
return CELL_GEM_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
if (pad->move_data.external_device_write_requested)
|
||||
{
|
||||
return CELL_GEM_ERROR_WRITE_NOT_FINISHED;
|
||||
}
|
||||
|
||||
std::memcpy(pad->move_data.external_device_write.data(), data.get_ptr(), CELL_GEM_EXTERNAL_PORT_OUTPUT_SIZE);
|
||||
pad->move_data.external_device_write_requested = true;
|
||||
}
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
||||
|
@ -161,7 +161,8 @@ enum CellGemVideoConvertFormatEnum : s32
|
||||
// External device IDs (types)
|
||||
enum
|
||||
{
|
||||
SHARP_SHOOTER_DEVICE_ID = 0x8081
|
||||
SHARP_SHOOTER_DEVICE_ID = 0x8081,
|
||||
RACING_WHEEL_DEVICE_ID = 0x8101
|
||||
};
|
||||
|
||||
struct CellGemAttribute
|
||||
|
@ -313,9 +313,10 @@ void clear_pad_buffer(const std::shared_ptr<Pad>& pad)
|
||||
pad->m_press_triangle = pad->m_press_circle = pad->m_press_cross = pad->m_press_square = 0;
|
||||
pad->m_press_L1 = pad->m_press_L2 = pad->m_press_R1 = pad->m_press_R2 = 0;
|
||||
|
||||
// ~399 on sensor y is a level non moving controller
|
||||
pad->m_sensor_y = 399;
|
||||
pad->m_sensor_x = pad->m_sensor_z = pad->m_sensor_g = 512;
|
||||
pad->m_sensor_x = DEFAULT_MOTION_X;
|
||||
pad->m_sensor_y = DEFAULT_MOTION_Y;
|
||||
pad->m_sensor_z = DEFAULT_MOTION_Z;
|
||||
pad->m_sensor_g = DEFAULT_MOTION_G;
|
||||
}
|
||||
|
||||
error_code cellPadClearBuf(u32 port_no)
|
||||
|
@ -247,6 +247,21 @@ PadHandlerBase::connection PadHandlerBase::get_next_button_press(const std::stri
|
||||
return status;
|
||||
}
|
||||
|
||||
if (m_type == pad_handler::move)
|
||||
{
|
||||
// Keep the pad cached to reduce expensive one time requests
|
||||
if (!m_pad_for_pad_settings || m_pad_for_pad_settings->m_pad_handler != m_type)
|
||||
{
|
||||
const pad_preview_values preview_values{};
|
||||
m_pad_for_pad_settings = std::make_shared<Pad>(m_type, 0, 0, 0, 0);
|
||||
m_pad_for_pad_settings->m_sensors.resize(preview_values.size(), AnalogSensor(0, 0, 0, 0, 0));
|
||||
}
|
||||
|
||||
// Get extended device ID
|
||||
pad_ensemble binding{m_pad_for_pad_settings, device, nullptr};
|
||||
get_extended_info(binding);
|
||||
}
|
||||
|
||||
// Get the current button values
|
||||
auto data = get_button_values(device);
|
||||
|
||||
@ -348,14 +363,20 @@ void PadHandlerBase::get_motion_sensors(const std::string& pad_id, const motion_
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep the pad cached to reduce expensive one time requests
|
||||
if (!m_pad_for_pad_settings || m_pad_for_pad_settings->m_pad_handler != m_type)
|
||||
{
|
||||
m_pad_for_pad_settings = std::make_shared<Pad>(m_type, 0, 0, 0, 0);
|
||||
m_pad_for_pad_settings->m_sensors.resize(preview_values.size(), AnalogSensor(0, 0, 0, 0, 0));
|
||||
}
|
||||
|
||||
// Get the current motion values
|
||||
std::shared_ptr<Pad> pad = std::make_shared<Pad>(m_type, 0, 0, 0, 0);
|
||||
pad_ensemble binding{pad, device, nullptr};
|
||||
pad_ensemble binding{m_pad_for_pad_settings, device, nullptr};
|
||||
get_extended_info(binding);
|
||||
|
||||
for (usz i = 0; i < preview_values.size(); i++)
|
||||
{
|
||||
preview_values[i] = pad->m_sensors[i].m_value;
|
||||
preview_values[i] = m_pad_for_pad_settings->m_sensors[i].m_value;
|
||||
}
|
||||
|
||||
callback(pad_id, std::move(preview_values));
|
||||
|
@ -29,6 +29,15 @@ public:
|
||||
std::set<u64> trigger_code_right{};
|
||||
std::array<std::set<u64>, 4> axis_code_left{};
|
||||
std::array<std::set<u64>, 4> axis_code_right{};
|
||||
|
||||
struct color
|
||||
{
|
||||
u8 r{};
|
||||
u8 g{};
|
||||
u8 b{};
|
||||
};
|
||||
color color_override{};
|
||||
bool color_override_active{};
|
||||
};
|
||||
|
||||
struct pad_ensemble
|
||||
@ -151,6 +160,8 @@ protected:
|
||||
std::unordered_map<u32, u16> min_button_values;
|
||||
std::set<u32> blacklist;
|
||||
|
||||
std::shared_ptr<Pad> m_pad_for_pad_settings;
|
||||
|
||||
static std::set<u32> narrow_set(const std::set<u64>& src);
|
||||
|
||||
// Search an unordered map for a string value and return found keycode
|
||||
|
@ -32,6 +32,13 @@ public:
|
||||
|
||||
camera_handler_state get_state() const { return m_state.load(); };
|
||||
|
||||
bool mirrored() const { return m_mirrored; };
|
||||
s32 format() const { return m_format; };
|
||||
u32 bytesize() const { return m_bytesize; };
|
||||
u32 width() const { return m_width; };
|
||||
u32 height() const { return m_height; };
|
||||
u32 frame_rate() const { return m_frame_rate; };
|
||||
|
||||
protected:
|
||||
std::mutex m_mutex;
|
||||
atomic_t<camera_handler_state> m_state = camera_handler_state::closed;
|
||||
|
@ -20,9 +20,9 @@ enum class gem_btn
|
||||
count
|
||||
};
|
||||
|
||||
struct cfg_gem final : public emulated_pad_config<gem_btn>
|
||||
struct cfg_fake_gem final : public emulated_pad_config<gem_btn>
|
||||
{
|
||||
cfg_gem(node* owner, const std::string& name) : emulated_pad_config(owner, name) {}
|
||||
cfg_fake_gem(node* owner, const std::string& name) : emulated_pad_config(owner, name) {}
|
||||
|
||||
cfg_pad_btn<gem_btn> start{ this, "Start", gem_btn::start, pad_button::start };
|
||||
cfg_pad_btn<gem_btn> select{ this, "Select", gem_btn::select, pad_button::select };
|
||||
@ -36,9 +36,29 @@ struct cfg_gem final : public emulated_pad_config<gem_btn>
|
||||
cfg_pad_btn<gem_btn> y_axis{ this, "Y-Axis", gem_btn::y_axis, pad_button::ls_y };
|
||||
};
|
||||
|
||||
struct cfg_gems final : public emulated_pads_config<cfg_gem, 4>
|
||||
struct cfg_fake_gems final : public emulated_pads_config<cfg_fake_gem, 4>
|
||||
{
|
||||
cfg_gems() : emulated_pads_config<cfg_gem, 4>("gem") {};
|
||||
cfg_fake_gems() : emulated_pads_config<cfg_fake_gem, 4>("gem") {};
|
||||
};
|
||||
|
||||
extern cfg_gems g_cfg_gem;
|
||||
struct cfg_gem final : public emulated_pad_config<gem_btn>
|
||||
{
|
||||
cfg_gem(node* owner, const std::string& name) : emulated_pad_config(owner, name) {}
|
||||
|
||||
cfg_pad_btn<gem_btn> start{ this, "Start", gem_btn::start, pad_button::start };
|
||||
cfg_pad_btn<gem_btn> select{ this, "Select", gem_btn::select, pad_button::select };
|
||||
cfg_pad_btn<gem_btn> triangle{ this, "Triangle", gem_btn::triangle, pad_button::triangle };
|
||||
cfg_pad_btn<gem_btn> circle{ this, "Circle", gem_btn::circle, pad_button::circle };
|
||||
cfg_pad_btn<gem_btn> cross{ this, "Cross", gem_btn::cross, pad_button::cross };
|
||||
cfg_pad_btn<gem_btn> square{ this, "Square", gem_btn::square, pad_button::square };
|
||||
cfg_pad_btn<gem_btn> move{ this, "Move", gem_btn::move, pad_button::R1 };
|
||||
cfg_pad_btn<gem_btn> t{ this, "T", gem_btn::t, pad_button::R2 };
|
||||
};
|
||||
|
||||
struct cfg_gems final : public emulated_pads_config<cfg_gem, 4>
|
||||
{
|
||||
cfg_gems() : emulated_pads_config<cfg_gem, 4>("gem_real") {};
|
||||
};
|
||||
|
||||
extern cfg_gems g_cfg_gem_real;
|
||||
extern cfg_fake_gems g_cfg_gem_fake;
|
||||
|
@ -14,6 +14,7 @@ void fmt_class_string<pad_handler>::format(std::string& out, u64 arg)
|
||||
case pad_handler::ds4: return "DualShock 4";
|
||||
case pad_handler::dualsense: return "DualSense";
|
||||
case pad_handler::skateboard: return "Skateboard";
|
||||
case pad_handler::move: return "PS Move";
|
||||
#ifdef _WIN32
|
||||
case pad_handler::xinput: return "XInput";
|
||||
case pad_handler::mm: return "MMJoystick";
|
||||
|
@ -10,6 +10,7 @@ enum class pad_handler
|
||||
ds4,
|
||||
dualsense,
|
||||
skateboard,
|
||||
move,
|
||||
#ifdef _WIN32
|
||||
xinput,
|
||||
mm,
|
||||
|
@ -337,8 +337,9 @@ struct CellPadData
|
||||
be_t<u16> button[CELL_PAD_MAX_CODES];
|
||||
};
|
||||
|
||||
static constexpr u16 MOTION_ONE_G = 113;
|
||||
static constexpr u16 DEFAULT_MOTION_X = 512;
|
||||
static constexpr u16 DEFAULT_MOTION_Y = 399;
|
||||
static constexpr u16 DEFAULT_MOTION_Y = 399; // 512 - 113 (113 is 1G gravity)
|
||||
static constexpr u16 DEFAULT_MOTION_Z = 512;
|
||||
static constexpr u16 DEFAULT_MOTION_G = 512;
|
||||
|
||||
@ -455,6 +456,25 @@ struct VibrateMotor
|
||||
{}
|
||||
};
|
||||
|
||||
struct ps_move_data
|
||||
{
|
||||
bool external_device_connected = false;
|
||||
u32 external_device_id = 0;
|
||||
std::array<u8, 5> external_device_data{};
|
||||
std::array<u8, 38> external_device_read{}; // CELL_GEM_EXTERNAL_PORT_DEVICE_INFO_SIZE
|
||||
std::array<u8, 40> external_device_write{}; // CELL_GEM_EXTERNAL_PORT_OUTPUT_SIZE
|
||||
bool external_device_read_requested = false;
|
||||
bool external_device_write_requested = false;
|
||||
|
||||
s16 accelerometer_x = 0;
|
||||
s16 accelerometer_y = 0;
|
||||
s16 accelerometer_z = 0;
|
||||
s16 gyro_x = 0;
|
||||
s16 gyro_y = 0;
|
||||
s16 gyro_z = 0;
|
||||
s16 temperature = 0;
|
||||
};
|
||||
|
||||
struct Pad
|
||||
{
|
||||
const pad_handler m_pad_handler;
|
||||
@ -519,7 +539,6 @@ struct Pad
|
||||
u16 m_press_R2{0};
|
||||
|
||||
// Except for these...0-1023
|
||||
// ~399 on sensor y is a level non moving controller
|
||||
u16 m_sensor_x{DEFAULT_MOTION_X};
|
||||
u16 m_sensor_y{DEFAULT_MOTION_Y};
|
||||
u16 m_sensor_z{DEFAULT_MOTION_Z};
|
||||
@ -530,6 +549,8 @@ struct Pad
|
||||
|
||||
bool is_fake_pad = false;
|
||||
|
||||
ps_move_data move_data{};
|
||||
|
||||
explicit Pad(pad_handler handler, u32 player_id, u32 port_status, u32 device_capability, u32 device_type)
|
||||
: m_pad_handler(handler)
|
||||
, m_player_id(player_id)
|
||||
|
@ -437,6 +437,7 @@ void fmt_class_string<move_handler>::format(std::string& out, u64 arg)
|
||||
switch (value)
|
||||
{
|
||||
case move_handler::null: return "Null";
|
||||
case move_handler::real: return "Real";
|
||||
case move_handler::fake: return "Fake";
|
||||
case move_handler::mouse: return "Mouse";
|
||||
case move_handler::raw_mouse: return "Raw Mouse";
|
||||
|
@ -138,6 +138,7 @@ enum class fake_camera_type
|
||||
enum class move_handler
|
||||
{
|
||||
null,
|
||||
real,
|
||||
fake,
|
||||
mouse,
|
||||
raw_mouse,
|
||||
|
@ -198,6 +198,7 @@ std::shared_ptr<PadHandlerBase> gui_pad_thread::GetHandler(pad_handler type)
|
||||
{
|
||||
case pad_handler::null:
|
||||
case pad_handler::keyboard:
|
||||
case pad_handler::move:
|
||||
// Makes no sense to use this if we are in the GUI anyway
|
||||
return nullptr;
|
||||
case pad_handler::ds3:
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "ds4_pad_handler.h"
|
||||
#include "dualsense_pad_handler.h"
|
||||
#include "skateboard_pad_handler.h"
|
||||
#include "ps_move_handler.h"
|
||||
#include "util/logs.hpp"
|
||||
#include "Utilities/Timer.h"
|
||||
#include "Emu/System.h"
|
||||
@ -77,6 +78,13 @@ void HidDevice::close()
|
||||
hid_close(hidDevice);
|
||||
hidDevice = nullptr;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
if (bt_device)
|
||||
{
|
||||
hid_close(bt_device);
|
||||
bt_device = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
template <class Device>
|
||||
@ -185,8 +193,18 @@ void hid_pad_handler<Device>::enumerate_devices()
|
||||
hid_log.error("Skipping enumeration of device with empty path.");
|
||||
continue;
|
||||
}
|
||||
device_paths.insert(dev_info->path);
|
||||
serials[dev_info->path] = dev_info->serial_number ? std::wstring(dev_info->serial_number) : std::wstring();
|
||||
|
||||
const std::string path = dev_info->path;
|
||||
device_paths.insert(path);
|
||||
|
||||
#ifdef _WIN32
|
||||
// Only add serials for col01 ps move device
|
||||
if (m_type == pad_handler::move && path.find("&Col01#") != umax)
|
||||
#endif
|
||||
{
|
||||
serials[path] = dev_info->serial_number ? std::wstring(dev_info->serial_number) : std::wstring();
|
||||
}
|
||||
|
||||
dev_info = dev_info->next;
|
||||
}
|
||||
hid_free_enumeration(head);
|
||||
@ -196,6 +214,29 @@ void hid_pad_handler<Device>::enumerate_devices()
|
||||
std::lock_guard lock(m_enumeration_mutex);
|
||||
m_new_enumerated_devices = device_paths;
|
||||
m_enumerated_serials = std::move(serials);
|
||||
|
||||
#ifdef _WIN32
|
||||
if (m_type == pad_handler::move)
|
||||
{
|
||||
// Windows enumerates 3 ps move devices: Col01, Col02, and Col03.
|
||||
// We use Col01 for data and Col02 for bluetooth.
|
||||
|
||||
// Filter paths. We only want the Col01 paths.
|
||||
std::set<std::string> col01_paths;
|
||||
|
||||
for (const std::string& path : m_new_enumerated_devices)
|
||||
{
|
||||
hid_log.trace("Found ps move device: %s", path);
|
||||
|
||||
if (path.find("&Col01#") != umax)
|
||||
{
|
||||
col01_paths.insert(path);
|
||||
}
|
||||
}
|
||||
|
||||
m_new_enumerated_devices = std::move(col01_paths);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
template <class Device>
|
||||
@ -235,8 +276,15 @@ void hid_pad_handler<Device>::update_devices()
|
||||
if (std::any_of(m_controllers.cbegin(), m_controllers.cend(), [&path](const auto& c) { return c.second && c.second->path == path; }))
|
||||
continue;
|
||||
|
||||
hid_device* dev = hid_open_path(path.c_str());
|
||||
if (dev)
|
||||
#ifdef _WIN32
|
||||
if (m_type == pad_handler::move)
|
||||
{
|
||||
check_add_device(nullptr, path, m_enumerated_serials[path]);
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (hid_device* dev = hid_open_path(path.c_str()))
|
||||
{
|
||||
if (const hid_device_info* info = hid_get_device_info(dev))
|
||||
{
|
||||
@ -314,3 +362,4 @@ template class hid_pad_handler<ds3_device>;
|
||||
template class hid_pad_handler<DS4Device>;
|
||||
template class hid_pad_handler<DualSenseDevice>;
|
||||
template class hid_pad_handler<skateboard_device>;
|
||||
template class hid_pad_handler<ps_move_device>;
|
||||
|
@ -33,6 +33,9 @@ public:
|
||||
void close();
|
||||
|
||||
hid_device* hidDevice{nullptr};
|
||||
#ifdef _WIN32
|
||||
hid_device* bt_device{nullptr}; // Used in ps move handler
|
||||
#endif
|
||||
std::string path;
|
||||
bool enable_player_leds{false};
|
||||
u8 led_delay_on{0};
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "ds4_pad_handler.h"
|
||||
#include "dualsense_pad_handler.h"
|
||||
#include "skateboard_pad_handler.h"
|
||||
#include "ps_move_handler.h"
|
||||
#ifdef _WIN32
|
||||
#include "xinput_pad_handler.h"
|
||||
#include "mm_joystick_handler.h"
|
||||
@ -198,7 +199,7 @@ void pad_thread::Init()
|
||||
}
|
||||
}
|
||||
|
||||
pad->is_fake_pad = (g_cfg.io.move == move_handler::fake && i >= (static_cast<u32>(CELL_PAD_MAX_PORT_NUM) - static_cast<u32>(CELL_GEM_MAX_NUM)))
|
||||
pad->is_fake_pad = ((g_cfg.io.move == move_handler::real || g_cfg.io.move == move_handler::fake) && i >= (static_cast<u32>(CELL_PAD_MAX_PORT_NUM) - static_cast<u32>(CELL_GEM_MAX_NUM)))
|
||||
|| (pad->m_class_type >= CELL_PAD_FAKE_TYPE_FIRST && pad->m_class_type < CELL_PAD_FAKE_TYPE_LAST);
|
||||
connect_usb_controller(i, input::get_product_by_vid_pid(pad->m_vendor_id, pad->m_product_id));
|
||||
}
|
||||
@ -622,6 +623,8 @@ std::shared_ptr<PadHandlerBase> pad_thread::GetHandler(pad_handler type)
|
||||
return std::make_shared<dualsense_pad_handler>();
|
||||
case pad_handler::skateboard:
|
||||
return std::make_shared<skateboard_pad_handler>();
|
||||
case pad_handler::move:
|
||||
return std::make_shared<ps_move_handler>();
|
||||
#ifdef _WIN32
|
||||
case pad_handler::xinput:
|
||||
return std::make_shared<xinput_pad_handler>();
|
||||
|
40
rpcs3/Input/ps_move_config.cpp
Normal file
40
rpcs3/Input/ps_move_config.cpp
Normal file
@ -0,0 +1,40 @@
|
||||
#include "stdafx.h"
|
||||
#include "ps_move_config.h"
|
||||
|
||||
LOG_CHANNEL(ps_move);
|
||||
|
||||
cfg_ps_moves g_cfg_move;
|
||||
|
||||
cfg_ps_moves::cfg_ps_moves()
|
||||
: cfg::node()
|
||||
#ifdef _WIN32
|
||||
, path(fs::get_config_dir() + "config/ps_move.yml")
|
||||
#else
|
||||
, path(fs::get_config_dir() + "ps_move.yml")
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
bool cfg_ps_moves::load()
|
||||
{
|
||||
ps_move.notice("Loading PS Move config from '%s'", path);
|
||||
|
||||
if (fs::file cfg_file{ path, fs::read })
|
||||
{
|
||||
return from_string(cfg_file.to_string());
|
||||
}
|
||||
|
||||
ps_move.notice("PS Move config missing. Using default settings. Path: %s", path);
|
||||
from_default();
|
||||
return false;
|
||||
}
|
||||
|
||||
void cfg_ps_moves::save() const
|
||||
{
|
||||
ps_move.notice("Saving PS Move config to '%s'", path);
|
||||
|
||||
if (!cfg::node::save(path))
|
||||
{
|
||||
ps_move.error("Failed to save PS Move config to '%s' (error=%s)", path, fs::g_tls_error);
|
||||
}
|
||||
}
|
40
rpcs3/Input/ps_move_config.h
Normal file
40
rpcs3/Input/ps_move_config.h
Normal file
@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "Utilities/Config.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
struct cfg_ps_move final : cfg::node
|
||||
{
|
||||
cfg_ps_move() : cfg::node() {}
|
||||
cfg_ps_move(cfg::node* owner, std::string name) : cfg::node(owner, name) {}
|
||||
|
||||
cfg::uint<0, 255> r{ this, "Color R", 0, true };
|
||||
cfg::uint<0, 255> g{ this, "Color G", 0, true };
|
||||
cfg::uint<0, 255> b{ this, "Color B", 0, true };
|
||||
cfg::uint<0, 359> hue{ this, "Hue", 0, true };
|
||||
cfg::uint<0, 180> hue_threshold{ this, "Hue Threshold", 10, true };
|
||||
cfg::uint<0, 255> saturation_threshold{ this, "Saturation Threshold", 10, true };
|
||||
};
|
||||
|
||||
struct cfg_ps_moves final : cfg::node
|
||||
{
|
||||
cfg_ps_moves();
|
||||
|
||||
cfg_ps_move move1{ this, "PS Move 1" };
|
||||
cfg_ps_move move2{ this, "PS Move 2" };
|
||||
cfg_ps_move move3{ this, "PS Move 3" };
|
||||
cfg_ps_move move4{ this, "PS Move 4" };
|
||||
|
||||
cfg::_float<0, 100> min_radius{ this, "Minimum Radius", 1.0f, true }; // Percentage of image width
|
||||
cfg::_float<0, 100> max_radius{ this, "Maximum Radius", 10.0f, true }; // Percentage of image width
|
||||
|
||||
std::array<cfg_ps_move*, 4> move{ &move1, &move2, &move3, &move4 };
|
||||
|
||||
const std::string path;
|
||||
|
||||
bool load();
|
||||
void save() const;
|
||||
};
|
||||
|
||||
extern cfg_ps_moves g_cfg_move;
|
797
rpcs3/Input/ps_move_handler.cpp
Normal file
797
rpcs3/Input/ps_move_handler.cpp
Normal file
@ -0,0 +1,797 @@
|
||||
#include "stdafx.h"
|
||||
#include "ps_move_handler.h"
|
||||
#include "Emu/Io/pad_config.h"
|
||||
#include "Emu/System.h"
|
||||
#include "Emu/system_config.h"
|
||||
#include "Emu/Cell/Modules/cellGem.h"
|
||||
#include "Input/ps_move_config.h"
|
||||
|
||||
LOG_CHANNEL(move_log, "Move");
|
||||
|
||||
using namespace reports;
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr id_pair MOVE_ID_ZCM1 = {0x054C, 0x03D5};
|
||||
constexpr id_pair MOVE_ID_ZCM2 = {0x054C, 0x0c5e};
|
||||
|
||||
enum button_flags : u16
|
||||
{
|
||||
select = 0x01,
|
||||
start = 0x08,
|
||||
triangle = 0x10,
|
||||
circle = 0x20,
|
||||
cross = 0x40,
|
||||
square = 0x80,
|
||||
ps = 0x0001,
|
||||
move = 0x4008,
|
||||
t = 0x8010,
|
||||
ext_dev = 0x1000,
|
||||
|
||||
// Sharpshooter
|
||||
ss_firing_mode_1 = 0x01,
|
||||
ss_firing_mode_2 = 0x02,
|
||||
ss_firing_mode_3 = 0x04,
|
||||
ss_trigger = 0x40,
|
||||
ss_reload = 0x80,
|
||||
|
||||
// Racing Wheel
|
||||
rw_d_pad_up = 0x10,
|
||||
rw_d_pad_right = 0x20,
|
||||
rw_d_pad_down = 0x40,
|
||||
rw_d_pad_left = 0x80,
|
||||
rw_l1 = 0x04,
|
||||
rw_r1 = 0x08,
|
||||
rw_paddle_l = 0x01,
|
||||
rw_paddle_r = 0x02,
|
||||
};
|
||||
|
||||
enum battery_status : u8
|
||||
{
|
||||
charge_empty = 0x00,
|
||||
charge_1 = 0x01,
|
||||
charge_2 = 0x02,
|
||||
charge_3 = 0x03,
|
||||
charge_4 = 0x04,
|
||||
charge_full = 0x05,
|
||||
usb_charging = 0xEE,
|
||||
usb_charged = 0xEF,
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
zero_shift = 0x8000,
|
||||
};
|
||||
}
|
||||
|
||||
const ps_move_input_report_common& ps_move_device::input_report_common() const
|
||||
{
|
||||
switch (model)
|
||||
{
|
||||
default:
|
||||
case ps_move_model::ZCM1:
|
||||
{
|
||||
return input_report_ZCM1.common;
|
||||
}
|
||||
case ps_move_model::ZCM2:
|
||||
{
|
||||
return input_report_ZCM2.common;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ps_move_handler::ps_move_handler()
|
||||
: hid_pad_handler<ps_move_device>(pad_handler::move, { MOVE_ID_ZCM1, MOVE_ID_ZCM2 })
|
||||
{
|
||||
// Unique names for the config files and our pad settings dialog
|
||||
button_list =
|
||||
{
|
||||
{ ps_move_key_codes::none, "" },
|
||||
{ ps_move_key_codes::cross, "Cross" },
|
||||
{ ps_move_key_codes::square, "Square" },
|
||||
{ ps_move_key_codes::circle, "Circle" },
|
||||
{ ps_move_key_codes::triangle, "Triangle" },
|
||||
{ ps_move_key_codes::start, "Start" },
|
||||
{ ps_move_key_codes::select, "Select" },
|
||||
{ ps_move_key_codes::ps, "PS" },
|
||||
{ ps_move_key_codes::move, "Move" },
|
||||
{ ps_move_key_codes::t, "T" },
|
||||
{ ps_move_key_codes::firing_mode_1, "Firing Mode 1" },
|
||||
{ ps_move_key_codes::firing_mode_2, "Firing Mode 2" },
|
||||
{ ps_move_key_codes::firing_mode_3, "Firing Mode 3" },
|
||||
{ ps_move_key_codes::reload, "Reload" },
|
||||
{ ps_move_key_codes::dpad_up, "D-Pad Up" },
|
||||
{ ps_move_key_codes::dpad_down, "D-Pad Down" },
|
||||
{ ps_move_key_codes::dpad_left, "D-Pad Left" },
|
||||
{ ps_move_key_codes::dpad_right, "D-Pad Right" },
|
||||
{ ps_move_key_codes::L1, "L1" },
|
||||
{ ps_move_key_codes::R1, "R1" },
|
||||
{ ps_move_key_codes::L2, "L2" },
|
||||
{ ps_move_key_codes::R2, "R2" },
|
||||
{ ps_move_key_codes::throttle, "Throttle" },
|
||||
{ ps_move_key_codes::paddle_left, "Paddle Left" },
|
||||
{ ps_move_key_codes::paddle_right, "Paddle Right" },
|
||||
};
|
||||
|
||||
init_configs();
|
||||
|
||||
// Define border values
|
||||
thumb_max = 255;
|
||||
trigger_min = 0;
|
||||
trigger_max = 255;
|
||||
|
||||
// Set capabilities
|
||||
b_has_config = true;
|
||||
b_has_rumble = true;
|
||||
b_has_motion = true;
|
||||
b_has_deadzones = true;
|
||||
b_has_led = true;
|
||||
b_has_rgb = true;
|
||||
b_has_player_led = false;
|
||||
b_has_battery = true;
|
||||
b_has_battery_led = false;
|
||||
b_has_pressure_intensity_button = false;
|
||||
|
||||
m_name_string = "PS Move #";
|
||||
m_max_devices = 4; // CELL_GEM_MAX_NUM
|
||||
|
||||
m_trigger_threshold = trigger_max / 2;
|
||||
m_thumb_threshold = thumb_max / 2;
|
||||
}
|
||||
|
||||
ps_move_handler::~ps_move_handler()
|
||||
{
|
||||
}
|
||||
|
||||
void ps_move_handler::init_config(cfg_pad* cfg)
|
||||
{
|
||||
if (!cfg) return;
|
||||
|
||||
// Set default button mapping
|
||||
cfg->ls_left.def = ::at32(button_list, ps_move_key_codes::none);
|
||||
cfg->ls_down.def = ::at32(button_list, ps_move_key_codes::none);
|
||||
cfg->ls_right.def = ::at32(button_list, ps_move_key_codes::none);
|
||||
cfg->ls_up.def = ::at32(button_list, ps_move_key_codes::none);
|
||||
cfg->rs_left.def = ::at32(button_list, ps_move_key_codes::none);
|
||||
cfg->rs_down.def = ::at32(button_list, ps_move_key_codes::none);
|
||||
cfg->rs_right.def = ::at32(button_list, ps_move_key_codes::none);
|
||||
cfg->rs_up.def = ::at32(button_list, ps_move_key_codes::none);
|
||||
cfg->start.def = ::at32(button_list, ps_move_key_codes::start);
|
||||
cfg->select.def = ::at32(button_list, ps_move_key_codes::select);
|
||||
cfg->ps.def = ::at32(button_list, ps_move_key_codes::ps);
|
||||
cfg->square.def = ::at32(button_list, ps_move_key_codes::square);
|
||||
cfg->cross.def = ::at32(button_list, ps_move_key_codes::cross);
|
||||
cfg->circle.def = ::at32(button_list, ps_move_key_codes::circle);
|
||||
cfg->triangle.def = ::at32(button_list, ps_move_key_codes::triangle);
|
||||
cfg->left.def = ::at32(button_list, ps_move_key_codes::none);
|
||||
cfg->down.def = ::at32(button_list, ps_move_key_codes::none);
|
||||
cfg->right.def = ::at32(button_list, ps_move_key_codes::none);
|
||||
cfg->up.def = ::at32(button_list, ps_move_key_codes::none);
|
||||
cfg->r1.def = ::at32(button_list, ps_move_key_codes::move);
|
||||
cfg->r2.def = ::at32(button_list, ps_move_key_codes::t);
|
||||
cfg->r3.def = ::at32(button_list, ps_move_key_codes::none);
|
||||
cfg->l1.def = ::at32(button_list, ps_move_key_codes::none);
|
||||
cfg->l2.def = ::at32(button_list, ps_move_key_codes::none);
|
||||
cfg->l3.def = ::at32(button_list, ps_move_key_codes::none);
|
||||
|
||||
// Set default misc variables
|
||||
cfg->lstickdeadzone.def = 40; // between 0 and 255
|
||||
cfg->rstickdeadzone.def = 40; // between 0 and 255
|
||||
cfg->ltriggerthreshold.def = 0; // between 0 and 255
|
||||
cfg->rtriggerthreshold.def = 0; // between 0 and 255
|
||||
|
||||
// apply defaults
|
||||
cfg->from_default();
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
hid_device* ps_move_handler::connect_move_device(ps_move_device* device, std::string_view path)
|
||||
{
|
||||
if (!device)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Windows enumerates 3 ps move devices: Col01, Col02, and Col03.
|
||||
// We use Col01 for data and Col02 for bluetooth.
|
||||
// Our enumerated paths are filtered and only contain Col01.
|
||||
// We open Col02 first, and then Col01. Col02 is unused for now.
|
||||
|
||||
static const std::string col01 = "&Col01#";
|
||||
static const std::string number = "&0000#";
|
||||
|
||||
std::string col02_path { path };
|
||||
col02_path.replace(path.find(col01), col01.size(), "&Col02#");
|
||||
col02_path.replace(path.find(number), number.size(), "&0001#");
|
||||
|
||||
// Open Col02
|
||||
device->bt_device = hid_open_path(col02_path.c_str());
|
||||
if (!device->bt_device)
|
||||
{
|
||||
move_log.error("%s hid_open_path failed! error='%s', path='%s'", m_type, hid_error(device->bt_device), col02_path);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (const hid_device_info* info = hid_get_device_info(device->bt_device))
|
||||
{
|
||||
move_log.notice("%s adding bt device: vid=0x%x, pid=0x%x, path='%s'", m_type, info->vendor_id, info->product_id, col02_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
move_log.warning("%s adding bt device: vid=N/A, pid=N/A, path='%s', error='%s'", m_type, col02_path, hid_error(device->bt_device));
|
||||
}
|
||||
|
||||
if (hid_set_nonblocking(device->bt_device, 1) == -1)
|
||||
{
|
||||
move_log.error("check_add_device: hid_set_nonblocking failed! Reason: %s", hid_error(device->bt_device));
|
||||
device->close();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Open Col01
|
||||
device->hidDevice = hid_open_path(path.data());
|
||||
if (!device->hidDevice)
|
||||
{
|
||||
move_log.error("%s hid_open_path failed! error='%s', path='%s'", m_type, hid_error(device->bt_device), path);
|
||||
device->close();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (hid_set_nonblocking(device->hidDevice, 1) == -1)
|
||||
{
|
||||
move_log.error("check_add_device: hid_set_nonblocking failed! Reason: %s", hid_error(device->hidDevice));
|
||||
device->close();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (const hid_device_info* info = hid_get_device_info(device->hidDevice))
|
||||
{
|
||||
move_log.notice("%s adding device: vid=0x%x, pid=0x%x, path='%s'", m_type, info->vendor_id, info->product_id, col02_path);
|
||||
|
||||
switch (info->product_id)
|
||||
{
|
||||
default:
|
||||
case MOVE_ID_ZCM1.m_pid:
|
||||
device->model = ps_move_model::ZCM1;
|
||||
break;
|
||||
case MOVE_ID_ZCM2.m_pid:
|
||||
device->model = ps_move_model::ZCM2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
move_log.warning("%s adding device: vid=N/A, pid=N/A, path='%s', error='%s'", m_type, col02_path, hid_error(device->hidDevice));
|
||||
device->model = ps_move_model::ZCM1;
|
||||
}
|
||||
|
||||
return device->hidDevice;
|
||||
}
|
||||
#endif
|
||||
|
||||
void ps_move_handler::check_add_device(hid_device* hidDevice, std::string_view path, std::wstring_view wide_serial)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
if (!hidDevice)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
ps_move_device* device = nullptr;
|
||||
|
||||
for (auto& controller : m_controllers)
|
||||
{
|
||||
ensure(controller.second);
|
||||
|
||||
if (!controller.second->hidDevice)
|
||||
{
|
||||
device = controller.second.get();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!device)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
hidDevice = connect_move_device(device, path);
|
||||
if (!hidDevice)
|
||||
{
|
||||
device->close();
|
||||
return;
|
||||
}
|
||||
#else
|
||||
if (hid_set_nonblocking(hidDevice, 1) == -1)
|
||||
{
|
||||
move_log.error("check_add_device: hid_set_nonblocking failed! Reason: %s", hid_error(hidDevice));
|
||||
hid_close(hidDevice);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
device->hidDevice = hidDevice;
|
||||
device->path = path;
|
||||
|
||||
// Activate
|
||||
if (send_output_report(device) == -1)
|
||||
{
|
||||
move_log.error("check_add_device: send_output_report failed! Reason: %s", hid_error(hidDevice));
|
||||
}
|
||||
|
||||
std::string serial;
|
||||
for (wchar_t ch : wide_serial)
|
||||
serial += static_cast<uchar>(ch);
|
||||
|
||||
move_log.success("Added device: serial='%s', path='%s'", serial, device->path);
|
||||
}
|
||||
|
||||
ps_move_handler::DataStatus ps_move_handler::get_data(ps_move_device* device)
|
||||
{
|
||||
if (!device)
|
||||
return DataStatus::ReadError;
|
||||
|
||||
constexpr u8 reportId = 0x01;
|
||||
void* report = nullptr;
|
||||
usz report_size = 0;
|
||||
|
||||
switch (device->model)
|
||||
{
|
||||
case ps_move_model::ZCM1:
|
||||
report = &device->input_report_ZCM1;
|
||||
device->input_report_ZCM1.common.report_id = reportId;
|
||||
report_size = sizeof(ps_move_input_report_ZCM1);
|
||||
break;
|
||||
case ps_move_model::ZCM2:
|
||||
report = &device->input_report_ZCM2;
|
||||
device->input_report_ZCM2.common.report_id = reportId;
|
||||
report_size = sizeof(ps_move_input_report_ZCM2);
|
||||
break;
|
||||
}
|
||||
|
||||
std::vector<u8> buf(report_size);
|
||||
|
||||
int res = hid_read(device->hidDevice, buf.data(), report_size);
|
||||
if (res < 0)
|
||||
{
|
||||
// looks like controller disconnected or read error
|
||||
move_log.error("get_data: hid_read 0x%02x failed! result=%d, buf[0]=0x%x, error=%s", reportId, res, buf[0], hid_error(device->hidDevice));
|
||||
return DataStatus::ReadError;
|
||||
}
|
||||
|
||||
if (res != static_cast<int>(report_size))
|
||||
return DataStatus::NoNewData;
|
||||
|
||||
if (std::memcmp(report, buf.data(), report_size) == 0)
|
||||
return DataStatus::NoNewData;
|
||||
|
||||
// Get the new data
|
||||
std::memcpy(report, buf.data(), report_size);
|
||||
|
||||
//move_log.error("%s", fmt::buf_to_hexstring(buf.data(), buf.size(), 64));
|
||||
|
||||
return DataStatus::NewData;
|
||||
}
|
||||
|
||||
PadHandlerBase::connection ps_move_handler::update_connection(const std::shared_ptr<PadDevice>& device)
|
||||
{
|
||||
ps_move_device* move_device = static_cast<ps_move_device*>(device.get());
|
||||
if (!move_device || move_device->path.empty())
|
||||
return connection::disconnected;
|
||||
|
||||
if (move_device->hidDevice == nullptr)
|
||||
{
|
||||
// try to reconnect
|
||||
#ifdef _WIN32
|
||||
if (hid_device* dev = connect_move_device(move_device, move_device->path))
|
||||
{
|
||||
move_device->hidDevice = dev;
|
||||
}
|
||||
#else
|
||||
if (hid_device* dev = hid_open_path(move_device->path.c_str()))
|
||||
{
|
||||
if (hid_set_nonblocking(dev, 1) == -1)
|
||||
{
|
||||
move_log.error("Reconnecting Device %s: hid_set_nonblocking failed with error %s", move_device->path, hid_error(dev));
|
||||
}
|
||||
move_device->hidDevice = dev;
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
// nope, not there
|
||||
move_log.error("Device %s: disconnected", move_device->path);
|
||||
return connection::disconnected;
|
||||
}
|
||||
}
|
||||
|
||||
if (get_data(move_device) == DataStatus::ReadError)
|
||||
{
|
||||
// this also can mean disconnected, either way deal with it on next loop and reconnect
|
||||
move_device->close();
|
||||
|
||||
return connection::no_data;
|
||||
}
|
||||
|
||||
return connection::connected;
|
||||
}
|
||||
|
||||
void ps_move_handler::handle_external_device(const pad_ensemble& binding)
|
||||
{
|
||||
const auto& device = binding.device;
|
||||
const auto& pad = binding.pad;
|
||||
|
||||
ps_move_device* dev = static_cast<ps_move_device*>(device.get());
|
||||
if (!dev || !pad)
|
||||
return;
|
||||
|
||||
auto& move_data = pad->move_data;
|
||||
|
||||
if (dev->model != ps_move_model::ZCM1)
|
||||
{
|
||||
move_data.external_device_read_requested = false;
|
||||
move_data.external_device_write_requested = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const ps_move_input_report_common& input = dev->input_report_common();
|
||||
const u16 extra_buttons = input.sequence_number << 8 | input.buttons_3;
|
||||
|
||||
move_data.external_device_connected = !!(extra_buttons & button_flags::ext_dev);
|
||||
|
||||
if (!move_data.external_device_connected)
|
||||
{
|
||||
dev->external_device_id = move_data.external_device_id = 0;
|
||||
std::memset(move_data.external_device_data.data(), 0, move_data.external_device_data.size());
|
||||
move_data.external_device_read_requested = false;
|
||||
move_data.external_device_write_requested = false;
|
||||
return;
|
||||
}
|
||||
|
||||
std::memcpy(move_data.external_device_data.data(), dev->input_report_ZCM1.ext_device_data.data(), dev->input_report_ZCM1.ext_device_data.size());
|
||||
|
||||
if (move_data.external_device_read_requested || move_data.external_device_id == 0)
|
||||
{
|
||||
bool success = false;
|
||||
|
||||
std::array<u8, 49> ext_buf{};
|
||||
ext_buf[0x00] = 0xE0; // Report ID
|
||||
ext_buf[0x01] = 0x01; // Read flag
|
||||
ext_buf[0x02] = 0xA0; // Target extension device's I²C slave address
|
||||
ext_buf[0x03] = 0x00; // Offset
|
||||
ext_buf[0x04] = 0xFF; // Length
|
||||
|
||||
if (int res = hid_send_feature_report(dev->hidDevice, ext_buf.data(), ext_buf.size()); res != static_cast<int>(ext_buf.size()))
|
||||
{
|
||||
move_log.error("get_extended_info: hid_send_feature_report 0xE0 (external_device) failed! result=%d, ext_buf[0]=0x%x, error=%s", res, ext_buf[0], hid_error(dev->hidDevice));
|
||||
}
|
||||
else if (res = hid_get_feature_report(dev->hidDevice, ext_buf.data(), ext_buf.size()); res < 0)
|
||||
{
|
||||
move_log.error("get_extended_info: hid_get_feature_report 0xE0 (external_device) failed! result=%d, ext_buf[0]=0x%x, error=%s", res, ext_buf[0], hid_error(dev->hidDevice));
|
||||
}
|
||||
else if (ext_buf[0x01] != 0) // The result will hold an error flag at pos 0x01
|
||||
{
|
||||
move_log.error("get_extended_info: hid_get_feature_report 0xE0 (external_device) returned error: ext_buf[0x01]=0x%x, error=%s", ext_buf[0x01], hid_error(dev->hidDevice));
|
||||
}
|
||||
else
|
||||
{
|
||||
move_log.trace("get_extended_info: hid_get_feature_report 0xE0 got result: %s", fmt::buf_to_hexstring(ext_buf.data(), ext_buf.size(), 64));
|
||||
success = true;
|
||||
}
|
||||
|
||||
// Get device ID
|
||||
const u32 old_id = dev->external_device_id;
|
||||
|
||||
// The result will be stored starting at pos 0x09
|
||||
dev->external_device_id = move_data.external_device_id = (ext_buf[0x09] << 8) | ext_buf[0x0A];
|
||||
|
||||
if (dev->external_device_id != 0 && dev->external_device_id != old_id)
|
||||
{
|
||||
move_log.notice("get_extended_info: external device with ID 0x%x found", dev->external_device_id);
|
||||
}
|
||||
|
||||
if (move_data.external_device_read_requested)
|
||||
{
|
||||
auto& dst = move_data.external_device_read;
|
||||
|
||||
if (success)
|
||||
{
|
||||
// Copy everything except device ID starting at pos 0x0B
|
||||
ensure(ext_buf.size() == dst.size() + 0x0B);
|
||||
std::memcpy(dst.data(), &ext_buf[0x0B], dst.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memset(dst.data(), 0, dst.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (move_data.external_device_write_requested)
|
||||
{
|
||||
const auto& src = move_data.external_device_write;
|
||||
|
||||
std::array<u8, 49> ext_buf{};
|
||||
ext_buf[0x00] = 0xE0; // Report ID
|
||||
ext_buf[0x01] = 0x00; // Read flag
|
||||
ext_buf[0x02] = 0xA0; // Target extension device's I²C slave address
|
||||
ext_buf[0x03] = src[0]; // Control Byte
|
||||
ext_buf[0x04] = static_cast<u8>(src.size() - 1); // Length
|
||||
|
||||
std::memcpy(&ext_buf[0x09], &src[1], src.size() - 1);
|
||||
|
||||
move_log.trace("ps_move_handler: trying to send data to external device: %s", fmt::buf_to_hexstring(ext_buf.data(), ext_buf.size(), 64));
|
||||
|
||||
if (const int res = hid_send_feature_report(dev->hidDevice, ext_buf.data(), ext_buf.size()); res < 0)
|
||||
{
|
||||
move_log.error("get_extended_info: hid_send_feature_report 0xE0 (external_device) failed! result=%d, ext_buf[0]=0x%x, error=%s", res, ext_buf[0], hid_error(dev->hidDevice));
|
||||
}
|
||||
}
|
||||
|
||||
move_data.external_device_read_requested = false;
|
||||
move_data.external_device_write_requested = false;
|
||||
}
|
||||
|
||||
bool ps_move_handler::get_is_left_trigger(const std::shared_ptr<PadDevice>& /*device*/, u64 keyCode)
|
||||
{
|
||||
// We also report the T button as left trigger
|
||||
return keyCode == ps_move_key_codes::L2 || keyCode == ps_move_key_codes::t;
|
||||
}
|
||||
|
||||
bool ps_move_handler::get_is_right_trigger(const std::shared_ptr<PadDevice>& /*device*/, u64 keyCode)
|
||||
{
|
||||
// We also report the Throttle button as right trigger
|
||||
return keyCode == ps_move_key_codes::R2 || keyCode == ps_move_key_codes::throttle;
|
||||
}
|
||||
|
||||
std::unordered_map<u64, u16> ps_move_handler::get_button_values(const std::shared_ptr<PadDevice>& device)
|
||||
{
|
||||
std::unordered_map<u64, u16> key_buf;
|
||||
ps_move_device* dev = static_cast<ps_move_device*>(device.get());
|
||||
if (!dev)
|
||||
return key_buf;
|
||||
|
||||
const ps_move_input_report_common& input = dev->input_report_common();
|
||||
|
||||
key_buf[ps_move_key_codes::cross] = (input.buttons_2 & button_flags::cross) ? 255 : 0;
|
||||
key_buf[ps_move_key_codes::square] = (input.buttons_2 & button_flags::square) ? 255 : 0;
|
||||
key_buf[ps_move_key_codes::circle] = (input.buttons_2 & button_flags::circle) ? 255 : 0;
|
||||
key_buf[ps_move_key_codes::triangle] = (input.buttons_2 & button_flags::triangle) ? 255 : 0;
|
||||
key_buf[ps_move_key_codes::start] = (input.buttons_1 & button_flags::start) ? 255 : 0;
|
||||
key_buf[ps_move_key_codes::select] = (input.buttons_1 & button_flags::select) ? 255 : 0;
|
||||
|
||||
const u16 extra_buttons = input.sequence_number << 8 | input.buttons_3;
|
||||
key_buf[ps_move_key_codes::ps] = (extra_buttons & button_flags::ps) ? 255 : 0;
|
||||
key_buf[ps_move_key_codes::move] = (extra_buttons & button_flags::move) ? 255 : 0;
|
||||
key_buf[ps_move_key_codes::t] = (extra_buttons & button_flags::t) ? input.trigger_2 : 0;
|
||||
|
||||
dev->battery_level = input.battery_level;
|
||||
|
||||
// Handle external data
|
||||
if (dev->model == ps_move_model::ZCM1)
|
||||
{
|
||||
const bool external_device_connected = !!(extra_buttons & button_flags::ext_dev);
|
||||
|
||||
if (external_device_connected)
|
||||
{
|
||||
const std::array<u8, 5>& ext_data = dev->input_report_ZCM1.ext_device_data;
|
||||
|
||||
switch (dev->external_device_id)
|
||||
{
|
||||
case SHARP_SHOOTER_DEVICE_ID:
|
||||
key_buf[ps_move_key_codes::firing_mode_1] = (ext_data[0] & button_flags::ss_firing_mode_1) ? 255 : 0;
|
||||
key_buf[ps_move_key_codes::firing_mode_2] = (ext_data[0] & button_flags::ss_firing_mode_2) ? 255 : 0;
|
||||
key_buf[ps_move_key_codes::firing_mode_3] = (ext_data[0] & button_flags::ss_firing_mode_3) ? 255 : 0;
|
||||
key_buf[ps_move_key_codes::reload] = (ext_data[0] & button_flags::ss_reload) ? 255 : 0;
|
||||
//key_buf[ps_move_key_codes::t] = (ext_data[0] & button_flags::ss_trigger) ? 255 : 0; // This is already reported as normal trigger
|
||||
break;
|
||||
case RACING_WHEEL_DEVICE_ID:
|
||||
key_buf[ps_move_key_codes::dpad_up] = (input.buttons_1 & button_flags::rw_d_pad_up) ? 255 : 0;
|
||||
key_buf[ps_move_key_codes::dpad_right] = (input.buttons_1 & button_flags::rw_d_pad_right) ? 255 : 0;
|
||||
key_buf[ps_move_key_codes::dpad_down] = (input.buttons_1 & button_flags::rw_d_pad_down) ? 255 : 0;
|
||||
key_buf[ps_move_key_codes::dpad_left] = (input.buttons_1 & button_flags::rw_d_pad_left) ? 255 : 0;
|
||||
key_buf[ps_move_key_codes::L1] = (input.buttons_2 & button_flags::rw_l1) ? 255 : 0;
|
||||
key_buf[ps_move_key_codes::R1] = (input.buttons_2 & button_flags::rw_r1) ? 255 : 0;
|
||||
key_buf[ps_move_key_codes::throttle] = ext_data[0];
|
||||
key_buf[ps_move_key_codes::L2] = ext_data[1];
|
||||
key_buf[ps_move_key_codes::R2] = ext_data[2];
|
||||
key_buf[ps_move_key_codes::paddle_left] = (ext_data[3] & button_flags::rw_paddle_l) ? 255 : 0;
|
||||
key_buf[ps_move_key_codes::paddle_right] = (ext_data[3] & button_flags::rw_paddle_r) ? 255 : 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return key_buf;
|
||||
}
|
||||
|
||||
void ps_move_handler::get_extended_info(const pad_ensemble& binding)
|
||||
{
|
||||
const auto& device = binding.device;
|
||||
const auto& pad = binding.pad;
|
||||
|
||||
ps_move_device* dev = static_cast<ps_move_device*>(device.get());
|
||||
if (!dev || !pad)
|
||||
return;
|
||||
|
||||
const ps_move_input_report_common& input = dev->input_report_common();
|
||||
|
||||
constexpr f32 MOVE_ONE_G = 4096.0f; // This is just a rough estimate and probably depends on the device
|
||||
|
||||
// The default position is flat on the ground, pointing forward.
|
||||
// The accelerometers constantly measure G forces.
|
||||
// The gyros measure changes in orientation and will reset when the device isn't moved anymore.
|
||||
s16 accel_x = input.accel_x_2; // Increases if the device is rolled to the left
|
||||
s16 accel_y = input.accel_y_2; // Increases if the device is pitched upwards
|
||||
s16 accel_z = input.accel_z_2; // Increases if the device is moved upwards
|
||||
s16 gyro_x = input.gyro_x_2; // Increases if the device is pitched upwards
|
||||
s16 gyro_y = input.gyro_y_2; // Increases if the device is rolled to the right
|
||||
s16 gyro_z = input.gyro_z_2; // Increases if the device is yawed to the left
|
||||
|
||||
if (dev->model == ps_move_model::ZCM1)
|
||||
{
|
||||
accel_x -= zero_shift;
|
||||
accel_y -= zero_shift;
|
||||
accel_z -= zero_shift;
|
||||
gyro_x -= zero_shift;
|
||||
gyro_y -= zero_shift;
|
||||
gyro_z -= zero_shift;
|
||||
}
|
||||
|
||||
pad->m_sensors[0].m_value = Clamp0To1023(512.0f + (MOTION_ONE_G * (accel_x / MOVE_ONE_G) * -1.0f));
|
||||
pad->m_sensors[1].m_value = Clamp0To1023(512.0f + (MOTION_ONE_G * (accel_z / MOVE_ONE_G) * -1.0f));
|
||||
pad->m_sensors[2].m_value = Clamp0To1023(512.0f + (MOTION_ONE_G * (accel_y / MOVE_ONE_G)));
|
||||
pad->m_sensors[3].m_value = Clamp0To1023(512.0f + (MOTION_ONE_G * (gyro_z / MOVE_ONE_G) * -1.0f));
|
||||
|
||||
pad->move_data.accelerometer_x = accel_x;
|
||||
pad->move_data.accelerometer_y = accel_y;
|
||||
pad->move_data.accelerometer_z = accel_z;
|
||||
pad->move_data.gyro_x = gyro_x;
|
||||
pad->move_data.gyro_y = gyro_y;
|
||||
pad->move_data.gyro_z = gyro_z;
|
||||
pad->move_data.temperature = ((input.temperature << 4) | ((input.magnetometer_x & 0xF0) >> 4));
|
||||
|
||||
handle_external_device(binding);
|
||||
}
|
||||
|
||||
pad_preview_values ps_move_handler::get_preview_values(const std::unordered_map<u64, u16>& data)
|
||||
{
|
||||
return {
|
||||
std::max(::at32(data, ps_move_key_codes::L2), ::at32(data, ps_move_key_codes::t)),
|
||||
std::max(::at32(data, ps_move_key_codes::R2), ::at32(data, ps_move_key_codes::throttle)),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
};
|
||||
}
|
||||
|
||||
int ps_move_handler::send_output_report(ps_move_device* device)
|
||||
{
|
||||
if (!device || !device->hidDevice)
|
||||
return -2;
|
||||
|
||||
const cfg_pad* config = device->config;
|
||||
if (config == nullptr)
|
||||
return -2; // hid_write returns -1 on error
|
||||
|
||||
device->output_report.type = 0x06;
|
||||
device->output_report.rumble = device->large_motor;
|
||||
|
||||
// Override color if necessary (for example while we actually use the PS Move with cellGem)
|
||||
if (device->color_override_active)
|
||||
{
|
||||
device->output_report.r = device->color_override.r;
|
||||
device->output_report.g = device->color_override.g;
|
||||
device->output_report.b = device->color_override.b;
|
||||
}
|
||||
else
|
||||
{
|
||||
device->output_report.r = config->colorR;
|
||||
device->output_report.g = config->colorG;
|
||||
device->output_report.b = config->colorB;
|
||||
}
|
||||
|
||||
const auto now = steady_clock::now();
|
||||
const auto elapsed = now - device->last_output_report_time;
|
||||
|
||||
// Update LED at an interval or it will be disabled automatically
|
||||
if (elapsed >= 4000ms)
|
||||
{
|
||||
device->new_output_data = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use LED update rate of 120ms
|
||||
if (elapsed < 120ms)
|
||||
{
|
||||
return -3;
|
||||
}
|
||||
|
||||
device->new_output_data = std::memcmp(&device->output_report, &device->last_output_report, sizeof(ps_move_output_report));
|
||||
|
||||
if (!device->new_output_data)
|
||||
{
|
||||
return -3;
|
||||
}
|
||||
}
|
||||
|
||||
device->last_output_report_time = now;
|
||||
device->last_output_report = device->output_report;
|
||||
|
||||
return hid_write(device->hidDevice, reinterpret_cast<u8*>(&device->output_report), sizeof(ps_move_output_report));
|
||||
}
|
||||
|
||||
void ps_move_handler::apply_pad_data(const pad_ensemble& binding)
|
||||
{
|
||||
const auto& device = binding.device;
|
||||
const auto& pad = binding.pad;
|
||||
|
||||
ps_move_device* dev = static_cast<ps_move_device*>(device.get());
|
||||
if (!dev || !dev->hidDevice || !dev->config || !pad)
|
||||
return;
|
||||
|
||||
cfg_pad* config = dev->config;
|
||||
|
||||
const int idx_l = config->switch_vibration_motors ? 1 : 0;
|
||||
|
||||
const u8 speed_large = config->enable_vibration_motor_large ? pad->m_vibrateMotors[idx_l].m_value : 0;
|
||||
|
||||
dev->large_motor = speed_large;
|
||||
|
||||
if (send_output_report(dev) >= 0)
|
||||
{
|
||||
dev->new_output_data = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ps_move_handler::SetPadData(const std::string& padId, u8 player_id, u8 large_motor, u8 small_motor, s32 r, s32 g, s32 b, bool /*player_led*/, bool /*battery_led*/, u32 /*battery_led_brightness*/)
|
||||
{
|
||||
std::shared_ptr<ps_move_device> device = get_hid_device(padId);
|
||||
if (device == nullptr || device->hidDevice == nullptr)
|
||||
return;
|
||||
|
||||
device->large_motor = large_motor;
|
||||
device->small_motor = small_motor;
|
||||
device->player_id = player_id;
|
||||
device->config = get_config(padId);
|
||||
|
||||
ensure(device->config);
|
||||
|
||||
if (r >= 0 && g >= 0 && b >= 0 && r <= 255 && g <= 255 && b <= 255)
|
||||
{
|
||||
device->config->colorR.set(r);
|
||||
device->config->colorG.set(g);
|
||||
device->config->colorB.set(b);
|
||||
}
|
||||
|
||||
if (send_output_report(device.get()) >= 0)
|
||||
{
|
||||
device->new_output_data = false;
|
||||
}
|
||||
}
|
||||
|
||||
u32 ps_move_handler::get_battery_level(const std::string& padId)
|
||||
{
|
||||
const std::shared_ptr<ps_move_device> device = get_hid_device(padId);
|
||||
if (!device || !device->hidDevice)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (device->battery_level)
|
||||
{
|
||||
case battery_status::usb_charging:
|
||||
case battery_status::usb_charged:
|
||||
return 100;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// 0 to 5
|
||||
return std::clamp<u32>(device->battery_level * 20, 0, 100);
|
||||
}
|
166
rpcs3/Input/ps_move_handler.h
Normal file
166
rpcs3/Input/ps_move_handler.h
Normal file
@ -0,0 +1,166 @@
|
||||
#pragma once
|
||||
|
||||
#include "hid_pad_handler.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace reports
|
||||
{
|
||||
// NOTE: The 1st half-frame contains slightly older data than the 2nd half-frame
|
||||
#pragma pack(push, 1)
|
||||
struct ps_move_input_report_common
|
||||
{
|
||||
// ID Size Description
|
||||
u8 report_id{}; // 0x00 1 HID Report ID (always 0x01)
|
||||
u8 buttons_1{}; // 0x01 1 Buttons 1 (Start, Select)
|
||||
u8 buttons_2{}; // 0x02 1 Buttons 2 (X, Square, Circle, Triangle)
|
||||
u8 buttons_3{}; // 0x03 1+ Buttons 3 (PS, Move, T) and EXT
|
||||
u8 sequence_number{}; // 0x04 1- Sequence number
|
||||
u8 trigger_1{}; // 0x05 1 T button values (1st half-frame)
|
||||
u8 trigger_2{}; // 0x06 1 T button values (2nd half-frame)
|
||||
u32 magic{}; // 0x07 4 always 0x7F7F7F7F
|
||||
u8 timestamp_upper{}; // 0x0B 1 Timestamp (upper byte)
|
||||
u8 battery_level{}; // 0x0C 1 Battery level. 0x05 = max, 0xEE = USB charging
|
||||
s16 accel_x_1{}; // 0x0D 2 X-axis accelerometer (1st half-frame)
|
||||
s16 accel_y_1{}; // 0x0F 2 Z-axis accelerometer (1st half-frame)
|
||||
s16 accel_z_1{}; // 0x11 2 Y-axis accelerometer (1st half-frame)
|
||||
s16 accel_x_2{}; // 0x13 2 X-axis accelerometer (2nd half-frame)
|
||||
s16 accel_y_2{}; // 0x15 2 Z-axis accelerometer (2nd half-frame)
|
||||
s16 accel_z_2{}; // 0x17 2 Y-axis accelerometer (2nd half-frame)
|
||||
s16 gyro_x_1{}; // 0x19 2 X-axis gyroscope (1st half-frame)
|
||||
s16 gyro_y_1{}; // 0x1B 2 Z-axis gyroscope (1st half-frame)
|
||||
s16 gyro_z_1{}; // 0x1D 2 Y-axis gyroscope (1st half-frame)
|
||||
s16 gyro_x_2{}; // 0x1F 2 X-axis gyroscope (2nd half-frame)
|
||||
s16 gyro_y_2{}; // 0x21 2 Z-axis gyroscope (2nd half-frame)
|
||||
s16 gyro_z_2{}; // 0x23 2 Y-axis gyroscope (2nd half-frame)
|
||||
u8 temperature{}; // 0x25 1+ Temperature
|
||||
u8 magnetometer_x{}; // 0x26 1+ Temperature + X-axis magnetometer
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct ps_move_input_report_ZCM1
|
||||
{
|
||||
ps_move_input_report_common common{};
|
||||
|
||||
// ID Size Description
|
||||
u8 magnetometer_x2{}; // 0x27 1- X-axis magnetometer
|
||||
u8 magnetometer_y{}; // 0x28 1+ Z-axis magnetometer
|
||||
u16 magnetometer_z{}; // 0x29 1- Y-axis magnetometer
|
||||
u8 timestamp_lower{}; // 0x2B 1 Timestamp (lower byte)
|
||||
std::array<u8, 5> ext_device_data{}; // 0x2C 5 External device data
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct ps_move_input_report_ZCM2
|
||||
{
|
||||
ps_move_input_report_common common{};
|
||||
|
||||
// ID Size Description
|
||||
u16 timestamp2; // 0x27 2 same as common timestamp
|
||||
u16 unk; // 0x29 2 Unknown
|
||||
u8 timestamp_lower; // 0x2B 1 Timestamp (lower byte)
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
struct ps_move_output_report
|
||||
{
|
||||
u8 type{};
|
||||
u8 zero{};
|
||||
u8 r{};
|
||||
u8 g{};
|
||||
u8 b{};
|
||||
u8 zero2{};
|
||||
u8 rumble{};
|
||||
u8 padding[2];
|
||||
};
|
||||
|
||||
struct ps_move_feature_report
|
||||
{
|
||||
std::array<u8, 4> data{}; // TODO
|
||||
};
|
||||
}
|
||||
|
||||
enum class ps_move_model
|
||||
{
|
||||
ZCM1, // PS3
|
||||
ZCM2, // PS4
|
||||
};
|
||||
|
||||
class ps_move_device : public HidDevice
|
||||
{
|
||||
public:
|
||||
ps_move_model model = ps_move_model::ZCM1;
|
||||
reports::ps_move_input_report_ZCM1 input_report_ZCM1{};
|
||||
reports::ps_move_input_report_ZCM2 input_report_ZCM2{};
|
||||
reports::ps_move_output_report output_report{};
|
||||
reports::ps_move_output_report last_output_report{};
|
||||
steady_clock::time_point last_output_report_time;
|
||||
u32 external_device_id = 0;
|
||||
|
||||
const reports::ps_move_input_report_common& input_report_common() const;
|
||||
};
|
||||
|
||||
class ps_move_handler final : public hid_pad_handler<ps_move_device>
|
||||
{
|
||||
enum ps_move_key_codes
|
||||
{
|
||||
none = 0,
|
||||
cross,
|
||||
square,
|
||||
circle,
|
||||
triangle,
|
||||
start,
|
||||
select,
|
||||
ps,
|
||||
move,
|
||||
t,
|
||||
|
||||
// Available through external sharpshooter
|
||||
firing_mode_1,
|
||||
firing_mode_2,
|
||||
firing_mode_3,
|
||||
reload,
|
||||
|
||||
// Available through external racing wheel
|
||||
dpad_up,
|
||||
dpad_down,
|
||||
dpad_left,
|
||||
dpad_right,
|
||||
L1,
|
||||
R1,
|
||||
L2,
|
||||
R2,
|
||||
throttle,
|
||||
paddle_left,
|
||||
paddle_right,
|
||||
};
|
||||
|
||||
public:
|
||||
ps_move_handler();
|
||||
~ps_move_handler();
|
||||
|
||||
void SetPadData(const std::string& padId, u8 player_id, u8 large_motor, u8 small_motor, s32 r, s32 g, s32 b, bool player_led, bool battery_led, u32 battery_led_brightness) override;
|
||||
u32 get_battery_level(const std::string& padId) override;
|
||||
void init_config(cfg_pad* cfg) override;
|
||||
|
||||
private:
|
||||
#ifdef _WIN32
|
||||
hid_device* connect_move_device(ps_move_device* device, std::string_view path);
|
||||
#endif
|
||||
|
||||
DataStatus get_data(ps_move_device* device) override;
|
||||
void check_add_device(hid_device* hidDevice, std::string_view path, std::wstring_view wide_serial) override;
|
||||
int send_output_report(ps_move_device* device) override;
|
||||
|
||||
bool get_is_left_trigger(const std::shared_ptr<PadDevice>& device, u64 keyCode) override;
|
||||
bool get_is_right_trigger(const std::shared_ptr<PadDevice>& device, u64 keyCode) override;
|
||||
PadHandlerBase::connection update_connection(const std::shared_ptr<PadDevice>& device) override;
|
||||
std::unordered_map<u64, u16> get_button_values(const std::shared_ptr<PadDevice>& device) override;
|
||||
pad_preview_values get_preview_values(const std::unordered_map<u64, u16>& data) override;
|
||||
void get_extended_info(const pad_ensemble& binding) override;
|
||||
void apply_pad_data(const pad_ensemble& binding) override;
|
||||
|
||||
void handle_external_device(const pad_ensemble& binding);
|
||||
};
|
545
rpcs3/Input/ps_move_tracker.cpp
Normal file
545
rpcs3/Input/ps_move_tracker.cpp
Normal file
@ -0,0 +1,545 @@
|
||||
#include "stdafx.h"
|
||||
#include "Emu/Cell/Modules/cellCamera.h"
|
||||
#include "Emu/Cell/Modules/cellGem.h"
|
||||
#include "ps_move_tracker.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <opencv2/photo.hpp>
|
||||
|
||||
LOG_CHANNEL(ps_move);
|
||||
|
||||
namespace gem
|
||||
{
|
||||
extern bool convert_image_format(CellCameraFormat input_format, CellGemVideoConvertFormatEnum output_format,
|
||||
const std::vector<u8>& video_data_in, u32 width, u32 height,
|
||||
u8* video_data_out, u32 video_data_out_size);
|
||||
}
|
||||
|
||||
template class ps_move_tracker<false>;
|
||||
template class ps_move_tracker<true>;
|
||||
|
||||
template <bool DiagnosticsEnabled>
|
||||
ps_move_tracker<DiagnosticsEnabled>::ps_move_tracker()
|
||||
{
|
||||
init_workers();
|
||||
}
|
||||
|
||||
template <bool DiagnosticsEnabled>
|
||||
ps_move_tracker<DiagnosticsEnabled>::~ps_move_tracker()
|
||||
{
|
||||
for (u32 index = 0; index < CELL_GEM_MAX_NUM; index++)
|
||||
{
|
||||
if (auto& worker = m_workers[index])
|
||||
{
|
||||
auto& thread = *worker;
|
||||
thread = thread_state::aborting;
|
||||
m_wake_up_workers[index].release(1);
|
||||
m_wake_up_workers[index].notify_one();
|
||||
thread();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <bool DiagnosticsEnabled>
|
||||
void ps_move_tracker<DiagnosticsEnabled>::set_image_data(const void* buf, u64 size, u32 width, u32 height, s32 format)
|
||||
{
|
||||
if (!buf || !size || !width || !height || !format)
|
||||
{
|
||||
ps_move.error("ps_move_tracker got unexpected input: buf=*0x%x, size=%d, width=%d, height=%d, format=%d", buf, size, width, height, format);
|
||||
return;
|
||||
}
|
||||
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
m_format = format;
|
||||
m_image_data.resize(size);
|
||||
|
||||
std::memcpy(m_image_data.data(), buf, size);
|
||||
}
|
||||
|
||||
template <bool DiagnosticsEnabled>
|
||||
void ps_move_tracker<DiagnosticsEnabled>::set_active(u32 index, bool active)
|
||||
{
|
||||
ps_move_config& config = ::at32(m_config, index);
|
||||
config.active = active;
|
||||
}
|
||||
|
||||
template <bool DiagnosticsEnabled>
|
||||
void ps_move_tracker<DiagnosticsEnabled>::ps_move_config::calculate_values()
|
||||
{
|
||||
// The hue is a "circle", so we use modulo 360.
|
||||
max_hue = (hue + hue_threshold) % 360;
|
||||
min_hue = hue - hue_threshold;
|
||||
|
||||
if (min_hue < 0)
|
||||
{
|
||||
min_hue += 360;
|
||||
}
|
||||
else
|
||||
{
|
||||
min_hue %= 360;
|
||||
}
|
||||
|
||||
saturation_threshold = saturation_threshold_u8 / 255.0f;
|
||||
}
|
||||
|
||||
template <bool DiagnosticsEnabled>
|
||||
void ps_move_tracker<DiagnosticsEnabled>::set_hue(u32 index, u16 hue)
|
||||
{
|
||||
ps_move_config& config = ::at32(m_config, index);
|
||||
config.hue = hue;
|
||||
config.calculate_values();
|
||||
}
|
||||
|
||||
template <bool DiagnosticsEnabled>
|
||||
void ps_move_tracker<DiagnosticsEnabled>::set_hue_threshold(u32 index, u16 threshold)
|
||||
{
|
||||
ps_move_config& config = ::at32(m_config, index);
|
||||
config.hue_threshold = threshold;
|
||||
config.calculate_values();
|
||||
}
|
||||
|
||||
template <bool DiagnosticsEnabled>
|
||||
void ps_move_tracker<DiagnosticsEnabled>::set_saturation_threshold(u32 index, u16 threshold)
|
||||
{
|
||||
ps_move_config& config = ::at32(m_config, index);
|
||||
config.saturation_threshold_u8 = threshold;
|
||||
config.calculate_values();
|
||||
}
|
||||
|
||||
template <bool DiagnosticsEnabled>
|
||||
void ps_move_tracker<DiagnosticsEnabled>::init_workers()
|
||||
{
|
||||
for (u32 index = 0; index < CELL_GEM_MAX_NUM; index++)
|
||||
{
|
||||
if (m_workers[index])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
m_workers[index] = std::make_unique<named_thread<std::function<void()>>>(fmt::format("PS Move Worker %d", index), [this, index]()
|
||||
{
|
||||
while (thread_ctrl::state() != thread_state::aborting)
|
||||
{
|
||||
// Notify that all work is done
|
||||
m_workers_finished[index].release(1);
|
||||
m_workers_finished[index].notify_one();
|
||||
|
||||
// Wait for work
|
||||
m_wake_up_workers[index].wait(0);
|
||||
m_wake_up_workers[index].release(0);
|
||||
|
||||
if (thread_ctrl::state() == thread_state::aborting)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Find contours
|
||||
ps_move_info& info = m_info[index];
|
||||
ps_move_info new_info{};
|
||||
process_contours(new_info, index);
|
||||
|
||||
if (new_info.valid)
|
||||
{
|
||||
info = std::move(new_info);
|
||||
}
|
||||
else
|
||||
{
|
||||
info.valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Notify one last time that all work is done
|
||||
m_workers_finished[index].release(1);
|
||||
m_workers_finished[index].notify_one();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
template <bool DiagnosticsEnabled>
|
||||
void ps_move_tracker<DiagnosticsEnabled>::process_image()
|
||||
{
|
||||
// Convert image to RGBA
|
||||
convert_image(CELL_GEM_RGBA_640x480);
|
||||
|
||||
// Calculate hues
|
||||
process_hues();
|
||||
|
||||
// Get active devices
|
||||
std::vector<u32> active_devices;
|
||||
for (u32 index = 0; index < CELL_GEM_MAX_NUM; index++)
|
||||
{
|
||||
ps_move_config& config = m_config[index];
|
||||
|
||||
if (config.active)
|
||||
{
|
||||
active_devices.push_back(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
ps_move_info& info = m_info[index];
|
||||
info.valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Find contours on worker threads
|
||||
for (u32 index : active_devices)
|
||||
{
|
||||
// Clear old state
|
||||
m_workers_finished[index].release(0);
|
||||
|
||||
// Wake up worker
|
||||
m_wake_up_workers[index].release(1);
|
||||
m_wake_up_workers[index].notify_one();
|
||||
}
|
||||
|
||||
// Wait for worker threads (a couple of seconds so we don't deadlock)
|
||||
for (u32 index : active_devices)
|
||||
{
|
||||
// Wait for worker
|
||||
m_workers_finished[index].wait(0, atomic_wait_timeout{5000000000});
|
||||
m_workers_finished[index].release(0);
|
||||
}
|
||||
}
|
||||
|
||||
template <bool DiagnosticsEnabled>
|
||||
void ps_move_tracker<DiagnosticsEnabled>::convert_image(s32 output_format)
|
||||
{
|
||||
const u32 width = m_width;
|
||||
const u32 height = m_height;
|
||||
const u32 size = height * width;
|
||||
|
||||
m_image_rgba.resize(size * 4);
|
||||
m_image_hsv.resize(size * 3);
|
||||
|
||||
for (u32 index = 0; index < CELL_GEM_MAX_NUM; index++)
|
||||
{
|
||||
m_image_binary[index].resize(size);
|
||||
}
|
||||
|
||||
if (gem::convert_image_format(CellCameraFormat{m_format}, CellGemVideoConvertFormatEnum{output_format}, m_image_data, width, height, m_image_rgba.data(), ::size32(m_image_rgba)))
|
||||
{
|
||||
ps_move.trace("Converted video frame of format %s to %s", CellCameraFormat{m_format}, CellGemVideoConvertFormatEnum{output_format});
|
||||
}
|
||||
|
||||
if constexpr (DiagnosticsEnabled)
|
||||
{
|
||||
m_image_gray.resize(size);
|
||||
m_image_rgba_contours.resize(size * 4);
|
||||
|
||||
std::memcpy(m_image_rgba_contours.data(), m_image_rgba.data(), m_image_rgba.size());
|
||||
}
|
||||
}
|
||||
|
||||
template <bool DiagnosticsEnabled>
|
||||
void ps_move_tracker<DiagnosticsEnabled>::process_hues()
|
||||
{
|
||||
const u32 width = m_width;
|
||||
const u32 height = m_height;
|
||||
|
||||
static const double sqrt3 = sqrt(3);
|
||||
|
||||
if constexpr (DiagnosticsEnabled)
|
||||
{
|
||||
std::fill(m_hues.begin(), m_hues.end(), 0);
|
||||
}
|
||||
|
||||
u8* gray = nullptr;
|
||||
|
||||
for (u32 y = 0; y < height; y++)
|
||||
{
|
||||
const u8* rgba = &m_image_rgba[y * width * 4];
|
||||
u8* hsv = &m_image_hsv[y * width * 3];
|
||||
|
||||
if constexpr (DiagnosticsEnabled)
|
||||
{
|
||||
gray = &m_image_gray[y * width];
|
||||
}
|
||||
|
||||
for (u32 x = 0; x < width; x++, rgba += 4, hsv += 3)
|
||||
{
|
||||
const float r = rgba[0] / 255.0f;
|
||||
const float g = rgba[1] / 255.0f;
|
||||
const float b = rgba[2] / 255.0f;
|
||||
const auto [hue, saturation, value] = rgb_to_hsv(r, g, b);
|
||||
|
||||
hsv[0] = static_cast<u8>(hue / 2);
|
||||
hsv[1] = static_cast<u8>(saturation * 255.0f);
|
||||
hsv[2] = static_cast<u8>(value * 255.0f);
|
||||
|
||||
if constexpr (DiagnosticsEnabled)
|
||||
{
|
||||
*gray++ = static_cast<u8>(std::clamp((0.299f * r + 0.587f * g + 0.114f * b) * 255.0f, 0.0f, 255.0f));
|
||||
++m_hues[hue];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_circular_contour(const std::vector<cv::Point>& contour, float& area)
|
||||
{
|
||||
std::vector<cv::Point> approx;
|
||||
cv::approxPolyDP(contour, approx, 0.01 * cv::arcLength(contour, true), true);
|
||||
if (approx.size() < 8ULL) return false;
|
||||
|
||||
area = static_cast<float>(cv::contourArea(contour));
|
||||
if (area < 30.0f) return false;
|
||||
|
||||
cv::Point2f center;
|
||||
float radius;
|
||||
cv::minEnclosingCircle(contour, center, radius);
|
||||
if (radius < 5.0f) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <bool DiagnosticsEnabled>
|
||||
void ps_move_tracker<DiagnosticsEnabled>::process_contours(ps_move_info& info, u32 index)
|
||||
{
|
||||
const ps_move_config& config = ::at32(m_config, index);
|
||||
const std::vector<u8>& image_hsv = m_image_hsv;
|
||||
std::vector<u8>& image_binary = ::at32(m_image_binary, index);
|
||||
|
||||
const u32 width = m_width;
|
||||
const u32 height = m_height;
|
||||
const bool wrapped_hue = config.min_hue > config.max_hue; // e.g. min=355, max=5 (red)
|
||||
|
||||
info.valid = false;
|
||||
info.x_max = width;
|
||||
info.y_max = height;
|
||||
|
||||
// Map memory
|
||||
cv::Mat binary(cv::Size(width, height), CV_8UC1, image_binary.data(), 0);
|
||||
|
||||
// Filter image
|
||||
for (u32 y = 0; y < height; y++)
|
||||
{
|
||||
const u8* src = &image_hsv[y * width * 3];
|
||||
u8* dst = &image_binary[y * width];
|
||||
|
||||
for (u32 x = 0; x < width; x++, src += 3)
|
||||
{
|
||||
const u16 hue = src[0] * 2;
|
||||
const u8 saturation = src[1];
|
||||
const u8 value = src[2];
|
||||
|
||||
// Simply drop dark and colorless pixels as well as pixels that don't match our hue
|
||||
if ((wrapped_hue ? (hue < config.min_hue && hue > config.max_hue) : (hue < config.min_hue || hue > config.max_hue)) ||
|
||||
saturation < config.saturation_threshold_u8 || saturation > 200 ||
|
||||
value < 150 || value > 255)
|
||||
{
|
||||
dst[x] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
dst[x] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all small outer contours
|
||||
if (m_filter_small_contours)
|
||||
{
|
||||
std::vector<std::vector<cv::Point>> contours;
|
||||
cv::findContours(binary, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
|
||||
for (auto it = contours.begin(); it != contours.end();)
|
||||
{
|
||||
float area;
|
||||
if (is_circular_contour(*it, area))
|
||||
{
|
||||
it = contours.erase(it);
|
||||
continue;
|
||||
}
|
||||
it++;
|
||||
}
|
||||
if (!contours.empty())
|
||||
{
|
||||
cv::drawContours(binary, contours, -1, 0, cv::FILLED);
|
||||
}
|
||||
}
|
||||
|
||||
// Find best contour
|
||||
std::vector<std::vector<cv::Point>> all_contours;
|
||||
cv::findContours(binary, all_contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
|
||||
|
||||
if (all_contours.empty())
|
||||
return;
|
||||
|
||||
std::vector<std::vector<cv::Point>> contours;
|
||||
contours.reserve(all_contours.size());
|
||||
|
||||
std::vector<cv::Point2f> centers;
|
||||
centers.reserve(all_contours.size());
|
||||
|
||||
std::vector<float> radii;
|
||||
radii.reserve(all_contours.size());
|
||||
|
||||
const f32 min_radius = m_min_radius * width;
|
||||
const f32 max_radius = m_max_radius * width;
|
||||
|
||||
usz best_index = umax;
|
||||
float best_area = 0.0f;
|
||||
for (usz i = 0; i < all_contours.size(); i++)
|
||||
{
|
||||
const std::vector<cv::Point>& contour = all_contours[i];
|
||||
float area;
|
||||
if (!is_circular_contour(contour, area))
|
||||
continue;
|
||||
|
||||
// Get center and radius
|
||||
cv::Point2f center;
|
||||
float radius;
|
||||
cv::minEnclosingCircle(contour, center, radius);
|
||||
|
||||
// Filter radius
|
||||
if (radius < min_radius || radius > max_radius)
|
||||
continue;
|
||||
|
||||
contours.push_back(std::move(all_contours[i]));
|
||||
centers.push_back(std::move(center));
|
||||
radii.push_back(std::move(radius));
|
||||
|
||||
if (area > best_area)
|
||||
{
|
||||
best_area = area;
|
||||
best_index = contours.size() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (best_index == umax)
|
||||
return;
|
||||
|
||||
info.valid = true;
|
||||
info.distance = 1500.0f;
|
||||
info.radius = radii[best_index];
|
||||
info.x_pos = std::clamp(static_cast<u32>(centers[best_index].x), 0u, width);
|
||||
info.y_pos = std::clamp(static_cast<u32>(centers[best_index].y), 0u, height);
|
||||
|
||||
if constexpr (!DiagnosticsEnabled)
|
||||
return;
|
||||
|
||||
if (!m_draw_contours && !m_draw_overlays) [[likely]]
|
||||
return;
|
||||
|
||||
// Map memory
|
||||
cv::Mat rgba(cv::Size(width, height), CV_8UC4, m_image_rgba_contours.data(), 0);
|
||||
|
||||
if (!m_show_all_contours) [[likely]]
|
||||
{
|
||||
std::vector<cv::Point> contour = std::move(contours[best_index]);
|
||||
contours = { std::move(contour) };
|
||||
centers = { centers[best_index] };
|
||||
radii = { radii[best_index] };
|
||||
}
|
||||
|
||||
static const cv::Scalar contour_color(255, 0, 0, 255);
|
||||
static const cv::Scalar circle_color(0, 255, 0, 255);
|
||||
static const cv::Scalar center_color(0, 0, 255, 255);
|
||||
|
||||
// Draw contours
|
||||
if (m_draw_contours)
|
||||
{
|
||||
cv::drawContours(rgba, contours, -1, contour_color, cv::FILLED);
|
||||
}
|
||||
|
||||
// Draw overlays
|
||||
if (m_draw_overlays)
|
||||
{
|
||||
for (usz i = 0; i < centers.size(); i++)
|
||||
{
|
||||
const cv::Point2f& center = centers[i];
|
||||
const float radius = radii[i];
|
||||
|
||||
cv::circle(rgba, center, static_cast<int>(radius), circle_color, 1);
|
||||
cv::line(rgba, center + cv::Point2f(-2, 0), center + cv::Point2f(2, 0), center_color, 1);
|
||||
cv::line(rgba, center + cv::Point2f(0, -2), center + cv::Point2f(0, 2), center_color, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <bool DiagnosticsEnabled>
|
||||
std::tuple<u8, u8, u8> ps_move_tracker<DiagnosticsEnabled>::hsv_to_rgb(u16 hue, float saturation, float value)
|
||||
{
|
||||
const float h = hue / 60.0f;
|
||||
const float chroma = value * saturation;
|
||||
const float x = chroma * (1.0f - std::abs(std::fmod(h, 2.0f) - 1.0f));
|
||||
const float m = value - chroma;
|
||||
|
||||
float r = 0.0f;
|
||||
float g = 0.0f;
|
||||
float b = 0.0f;
|
||||
|
||||
switch (static_cast<int>(std::ceil(h)))
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
r = chroma + m;
|
||||
g = x + m;
|
||||
b = 0 + m;
|
||||
break;
|
||||
case 2:
|
||||
r = x + m;
|
||||
g = chroma + m;
|
||||
b = 0 + m;
|
||||
break;
|
||||
case 3:
|
||||
r = 0 + m;
|
||||
g = chroma + m;
|
||||
b = x + m;
|
||||
break;
|
||||
case 4:
|
||||
r = 0 + m;
|
||||
g = x + m;
|
||||
b = chroma + m;
|
||||
break;
|
||||
case 5:
|
||||
r = x + m;
|
||||
g = 0 + m;
|
||||
b = chroma + m;
|
||||
break;
|
||||
case 6:
|
||||
r = chroma + m;
|
||||
g = 0 + m;
|
||||
b = x + m;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const u8 red = static_cast<u8>(std::clamp(std::round(r * 255.0f), 0.0f, 255.0f));
|
||||
const u8 green = static_cast<u8>(std::clamp(std::round(g * 255.0f), 0.0f, 255.0f));
|
||||
const u8 blue = static_cast<u8>(std::clamp(std::round(b * 255.0f), 0.0f, 255.0f));
|
||||
|
||||
return { red, green, blue };
|
||||
}
|
||||
|
||||
template <bool DiagnosticsEnabled>
|
||||
std::tuple<s16, float, float> ps_move_tracker<DiagnosticsEnabled>::rgb_to_hsv(float r, float g, float b)
|
||||
{
|
||||
const float cmax = std::max({r, g, b}); // V (of HSV)
|
||||
const float cmin = std::min({r, g, b});
|
||||
const float delta = cmax - cmin;
|
||||
const float saturation = cmax ? (delta / cmax) : 0.0f; // S (of HSV)
|
||||
|
||||
s16 hue; // H (of HSV)
|
||||
|
||||
if (!delta)
|
||||
{
|
||||
hue = 0;
|
||||
}
|
||||
else if (cmax == r)
|
||||
{
|
||||
hue = (static_cast<s16>(60.0f * (g - b) / delta) + 360) % 360;
|
||||
}
|
||||
else if (cmax == g)
|
||||
{
|
||||
hue = (static_cast<s16>(60.0f * (b - r) / delta) + 120 + 360) % 360;
|
||||
}
|
||||
else
|
||||
{
|
||||
hue = (static_cast<s16>(60.0f * (r - g) / delta) + 240 + 360) % 360;
|
||||
}
|
||||
|
||||
return { hue, saturation, cmax };
|
||||
}
|
95
rpcs3/Input/ps_move_tracker.h
Normal file
95
rpcs3/Input/ps_move_tracker.h
Normal file
@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
|
||||
struct ps_move_info
|
||||
{
|
||||
bool valid = false;
|
||||
f32 radius = 0.0f;
|
||||
f32 distance = 0.0f;
|
||||
u32 x_pos = 0;
|
||||
u32 y_pos = 0;
|
||||
u32 x_max = 0;
|
||||
u32 y_max = 0;
|
||||
};
|
||||
|
||||
template <bool DiagnosticsEnabled = false>
|
||||
class ps_move_tracker
|
||||
{
|
||||
public:
|
||||
ps_move_tracker();
|
||||
virtual ~ps_move_tracker();
|
||||
|
||||
void set_image_data(const void* buf, u64 size, u32 width, u32 height, s32 format);
|
||||
|
||||
void init_workers();
|
||||
void process_image();
|
||||
void convert_image(s32 output_format);
|
||||
void process_hues();
|
||||
void process_contours(ps_move_info& info, u32 index);
|
||||
|
||||
void set_active(u32 index, bool active);
|
||||
void set_hue(u32 index, u16 hue);
|
||||
void set_hue_threshold(u32 index, u16 threshold);
|
||||
void set_saturation_threshold(u32 index, u16 threshold);
|
||||
|
||||
void set_min_radius(f32 radius) { m_min_radius = radius; }
|
||||
void set_max_radius(f32 radius) { m_max_radius = radius; }
|
||||
void set_filter_small_contours(bool enabled = false) { m_filter_small_contours = enabled; }
|
||||
void set_show_all_contours(bool enabled = false) { m_show_all_contours = enabled; }
|
||||
void set_draw_contours(bool enabled = false) { m_draw_contours = enabled; }
|
||||
void set_draw_overlays(bool enabled = false) { m_draw_overlays = enabled; }
|
||||
|
||||
const std::array<ps_move_info, CELL_GEM_MAX_NUM>& info() { return m_info; }
|
||||
const std::array<u32, 360>& hues() { return m_hues; }
|
||||
const std::vector<u8>& rgba() { return m_image_rgba; }
|
||||
const std::vector<u8>& rgba_contours() { return m_image_rgba_contours; }
|
||||
const std::vector<u8>& hsv() { return m_image_hsv; }
|
||||
const std::vector<u8>& gray() { return m_image_gray; }
|
||||
const std::vector<u8>& binary(u32 index) { return ::at32(m_image_binary, index); }
|
||||
|
||||
static std::tuple<u8, u8, u8> hsv_to_rgb(u16 hue, float saturation, float value);
|
||||
static std::tuple<s16, float, float> rgb_to_hsv(float r, float g, float b);
|
||||
|
||||
private:
|
||||
struct ps_move_config
|
||||
{
|
||||
bool active = false;
|
||||
|
||||
u16 hue = 0;
|
||||
u16 hue_threshold = 0;
|
||||
u16 saturation_threshold_u8 = 0;
|
||||
|
||||
s16 min_hue = 0;
|
||||
s16 max_hue = 0;
|
||||
f32 saturation_threshold = 0.0f;
|
||||
|
||||
void calculate_values();
|
||||
};
|
||||
|
||||
u32 m_width = 0;
|
||||
u32 m_height = 0;
|
||||
s32 m_format = 0;
|
||||
f32 m_min_radius = 0.0f;
|
||||
f32 m_max_radius = 1.0f;
|
||||
|
||||
bool m_filter_small_contours = true;
|
||||
bool m_show_all_contours = false;
|
||||
bool m_draw_contours = false;
|
||||
bool m_draw_overlays = false;
|
||||
|
||||
std::vector<u8> m_image_data;
|
||||
std::vector<u8> m_image_rgba;
|
||||
std::vector<u8> m_image_rgba_contours;
|
||||
std::vector<u8> m_image_hsv;
|
||||
std::vector<u8> m_image_gray;
|
||||
|
||||
std::array<std::vector<u8>, CELL_GEM_MAX_NUM> m_image_hue_filtered{};
|
||||
std::array<std::vector<u8>, CELL_GEM_MAX_NUM> m_image_binary{};
|
||||
|
||||
std::array<u32, 360> m_hues{};
|
||||
std::array<ps_move_info, CELL_GEM_MAX_NUM> m_info{};
|
||||
std::array<ps_move_config, CELL_GEM_MAX_NUM> m_config{};
|
||||
|
||||
std::array<std::unique_ptr<named_thread<std::function<void()>>>, CELL_GEM_MAX_NUM> m_workers{};
|
||||
std::array<atomic_t<u32>, CELL_GEM_MAX_NUM> m_wake_up_workers{};
|
||||
std::array<atomic_t<u32>, CELL_GEM_MAX_NUM> m_workers_finished{};
|
||||
};
|
@ -71,7 +71,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\SoundTouch\soundtouch\include;$(SolutionDir)3rdparty\cubeb\extra;$(SolutionDir)3rdparty\cubeb\cubeb\include\;$(SolutionDir)3rdparty\flatbuffers\include;$(SolutionDir)3rdparty\wolfssl\wolfssl;$(SolutionDir)3rdparty\curl\curl\include;$(SolutionDir)3rdparty\rtmidi\rtmidi;$(SolutionDir)3rdparty\libusb\libusb\libusb;$(VULKAN_SDK)\Include;$(SolutionDir)3rdparty\libsdl-org\SDL\include;$(QTDIR)\include;$(QTDIR)\include\QtCore;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtSvgWidgets;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtMultimedia;$(QTDIR)\mkspecs\win32-msvc;.\release;.\QTGeneratedFiles\$(ConfigurationName);.\QTGeneratedFiles;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\opencv\opencv410\build\include;$(SolutionDir)3rdparty\SoundTouch\soundtouch\include;$(SolutionDir)3rdparty\cubeb\extra;$(SolutionDir)3rdparty\cubeb\cubeb\include\;$(SolutionDir)3rdparty\flatbuffers\include;$(SolutionDir)3rdparty\wolfssl\wolfssl;$(SolutionDir)3rdparty\curl\curl\include;$(SolutionDir)3rdparty\rtmidi\rtmidi;$(SolutionDir)3rdparty\libusb\libusb\libusb;$(VULKAN_SDK)\Include;$(SolutionDir)3rdparty\libsdl-org\SDL\include;$(QTDIR)\include;$(QTDIR)\include\QtCore;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtSvgWidgets;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtMultimedia;$(QTDIR)\mkspecs\win32-msvc;.\release;.\QTGeneratedFiles\$(ConfigurationName);.\QTGeneratedFiles;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalOptions>/Zc:__cplusplus -Zc:strictStrings -Zc:throwingNew- -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions)</AdditionalOptions>
|
||||
<AssemblerListingLocation>release\</AssemblerListingLocation>
|
||||
<BrowseInformation>false</BrowseInformation>
|
||||
@ -89,8 +89,8 @@
|
||||
<ExternalWarningLevel>TurnOffAllWarnings</ExternalWarningLevel>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>DbgHelp.lib;Ole32.lib;gdi32.lib;hidapi.lib;libusb-1.0.lib;winmm.lib;miniupnpc_static.lib;rtmidi.lib;imm32.lib;ksuser.lib;version.lib;OpenAL32.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslang.lib;OSDependent.lib;OGLCompiler.lib;SPIRV.lib;MachineIndependent.lib;GenericCodeGen.lib;Advapi32.lib;user32.lib;zlib.lib;zstd.lib;libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;shell32.lib;Qt6Core.lib;Qt6Gui.lib;Qt6Widgets.lib;Qt6Concurrent.lib;Qt6Multimedia.lib;Qt6MultimediaWidgets.lib;Qt6Svg.lib;Qt6SvgWidgets.lib;7zip.lib;libcubeb.lib;cubeb.lib;soundtouch.lib;Avrt.lib;SDL.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)3rdparty\openal\openal-soft\build;$(SolutionDir)3rdparty\glslang\build\hlsl\$(CONFIGURATION);$(SolutionDir)3rdparty\glslang\build\SPIRV\$(CONFIGURATION);$(SolutionDir)3rdparty\glslang\build\OGLCompilersDLL\$(CONFIGURATION);$(SolutionDir)3rdparty\glslang\build\glslang\OSDependent\Windows\$(CONFIGURATION);$(SolutionDir)3rdparty\glslang\build\glslang\$(CONFIGURATION);$(SolutionDir)3rdparty\discord-rpc\lib;$(SolutionDir)lib\$(CONFIGURATION)-$(PLATFORM);$(QTDIR)\lib;$(VULKAN_SDK)\Lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>opencv_world4100.lib;DbgHelp.lib;Ole32.lib;gdi32.lib;hidapi.lib;libusb-1.0.lib;winmm.lib;miniupnpc_static.lib;rtmidi.lib;imm32.lib;ksuser.lib;version.lib;OpenAL32.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslang.lib;OSDependent.lib;OGLCompiler.lib;SPIRV.lib;MachineIndependent.lib;GenericCodeGen.lib;Advapi32.lib;user32.lib;zlib.lib;zstd.lib;libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;shell32.lib;Qt6Core.lib;Qt6Gui.lib;Qt6Widgets.lib;Qt6Concurrent.lib;Qt6Multimedia.lib;Qt6MultimediaWidgets.lib;Qt6Svg.lib;Qt6SvgWidgets.lib;7zip.lib;libcubeb.lib;cubeb.lib;soundtouch.lib;Avrt.lib;SDL.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)3rdparty\opencv\opencv410\build\x64\vc16\lib;$(SolutionDir)3rdparty\openal\openal-soft\build;$(SolutionDir)3rdparty\glslang\build\hlsl\$(CONFIGURATION);$(SolutionDir)3rdparty\glslang\build\SPIRV\$(CONFIGURATION);$(SolutionDir)3rdparty\glslang\build\OGLCompilersDLL\$(CONFIGURATION);$(SolutionDir)3rdparty\glslang\build\glslang\OSDependent\Windows\$(CONFIGURATION);$(SolutionDir)3rdparty\glslang\build\glslang\$(CONFIGURATION);$(SolutionDir)3rdparty\discord-rpc\lib;$(SolutionDir)lib\$(CONFIGURATION)-$(PLATFORM);$(QTDIR)\lib;$(VULKAN_SDK)\Lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<AdditionalOptions>"/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" %(AdditionalOptions)</AdditionalOptions>
|
||||
<DataExecutionPrevention>true</DataExecutionPrevention>
|
||||
<GenerateDebugInformation>Debug</GenerateDebugInformation>
|
||||
@ -123,7 +123,7 @@
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\SoundTouch\soundtouch\include;$(SolutionDir)3rdparty\cubeb\extra;$(SolutionDir)3rdparty\cubeb\cubeb\include\;$(SolutionDir)3rdparty\flatbuffers\include;$(SolutionDir)3rdparty\wolfssl\wolfssl;$(SolutionDir)3rdparty\curl\curl\include;$(SolutionDir)3rdparty\rtmidi\rtmidi;$(SolutionDir)3rdparty\libusb\libusb\libusb;$(VULKAN_SDK)\Include;$(SolutionDir)3rdparty\libsdl-org\SDL\include;$(QTDIR)\include;$(QTDIR)\include\QtCore;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtSvgWidgets;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtMultimedia;$(QTDIR)\mkspecs\win32-msvc;.\debug;.\QTGeneratedFiles\$(ConfigurationName);.\QTGeneratedFiles;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\opencv\opencv410\build\include;$(SolutionDir)3rdparty\SoundTouch\soundtouch\include;$(SolutionDir)3rdparty\cubeb\extra;$(SolutionDir)3rdparty\cubeb\cubeb\include\;$(SolutionDir)3rdparty\flatbuffers\include;$(SolutionDir)3rdparty\wolfssl\wolfssl;$(SolutionDir)3rdparty\curl\curl\include;$(SolutionDir)3rdparty\rtmidi\rtmidi;$(SolutionDir)3rdparty\libusb\libusb\libusb;$(VULKAN_SDK)\Include;$(SolutionDir)3rdparty\libsdl-org\SDL\include;$(QTDIR)\include;$(QTDIR)\include\QtCore;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtSvgWidgets;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtMultimedia;$(QTDIR)\mkspecs\win32-msvc;.\debug;.\QTGeneratedFiles\$(ConfigurationName);.\QTGeneratedFiles;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalOptions>/Zc:__cplusplus -Zc:strictStrings -Zc:throwingNew- -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions)</AdditionalOptions>
|
||||
<AssemblerListingLocation>debug\</AssemblerListingLocation>
|
||||
<BrowseInformation>false</BrowseInformation>
|
||||
@ -140,8 +140,8 @@
|
||||
<ProgramDataBaseFileName>$(IntDir)vc$(PlatformToolsetVersion).pdb</ProgramDataBaseFileName>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>DbgHelp.lib;Ole32.lib;gdi32.lib;hidapi.lib;libusb-1.0.lib;winmm.lib;miniupnpc_static.lib;rtmidi.lib;imm32.lib;ksuser.lib;version.lib;OpenAL32.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslangd.lib;OSDependentd.lib;OGLCompilerd.lib;SPIRVd.lib;MachineIndependentd.lib;GenericCodeGend.lib;Advapi32.lib;user32.lib;zlib.lib;zstd.lib;libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;shell32.lib;Qt6Cored.lib;Qt6Guid.lib;Qt6Widgetsd.lib;Qt6Concurrentd.lib;Qt6Multimediad.lib;Qt6MultimediaWidgetsd.lib;Qt6Svgd.lib;Qt6SvgWidgetsd.lib;7zip.lib;libcubeb.lib;cubeb.lib;soundtouch.lib;Avrt.lib;SDL.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)3rdparty\openal\openal-soft\build;$(SolutionDir)3rdparty\glslang\build\hlsl\$(CONFIGURATION);$(SolutionDir)3rdparty\glslang\build\SPIRV\$(CONFIGURATION);$(SolutionDir)3rdparty\glslang\build\OGLCompilersDLL\$(CONFIGURATION);$(SolutionDir)3rdparty\glslang\build\glslang\OSDependent\Windows\$(CONFIGURATION);$(SolutionDir)3rdparty\glslang\build\glslang\$(CONFIGURATION);$(SolutionDir)3rdparty\discord-rpc\lib;$(SolutionDir)lib\$(CONFIGURATION)-$(PLATFORM);$(QTDIR)\lib;$(VULKAN_SDK)\Lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>opencv_world4100.lib;DbgHelp.lib;Ole32.lib;gdi32.lib;hidapi.lib;libusb-1.0.lib;winmm.lib;miniupnpc_static.lib;rtmidi.lib;imm32.lib;ksuser.lib;version.lib;OpenAL32.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslangd.lib;OSDependentd.lib;OGLCompilerd.lib;SPIRVd.lib;MachineIndependentd.lib;GenericCodeGend.lib;Advapi32.lib;user32.lib;zlib.lib;zstd.lib;libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;shell32.lib;Qt6Cored.lib;Qt6Guid.lib;Qt6Widgetsd.lib;Qt6Concurrentd.lib;Qt6Multimediad.lib;Qt6MultimediaWidgetsd.lib;Qt6Svgd.lib;Qt6SvgWidgetsd.lib;7zip.lib;libcubeb.lib;cubeb.lib;soundtouch.lib;Avrt.lib;SDL.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)3rdparty\opencv\opencv410\build\x64\vc16\lib;$(SolutionDir)3rdparty\openal\openal-soft\build;$(SolutionDir)3rdparty\glslang\build\hlsl\$(CONFIGURATION);$(SolutionDir)3rdparty\glslang\build\SPIRV\$(CONFIGURATION);$(SolutionDir)3rdparty\glslang\build\OGLCompilersDLL\$(CONFIGURATION);$(SolutionDir)3rdparty\glslang\build\glslang\OSDependent\Windows\$(CONFIGURATION);$(SolutionDir)3rdparty\glslang\build\glslang\$(CONFIGURATION);$(SolutionDir)3rdparty\discord-rpc\lib;$(SolutionDir)lib\$(CONFIGURATION)-$(PLATFORM);$(QTDIR)\lib;$(VULKAN_SDK)\Lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<AdditionalOptions>"/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" /VERBOSE %(AdditionalOptions)</AdditionalOptions>
|
||||
<DataExecutionPrevention>true</DataExecutionPrevention>
|
||||
<GenerateDebugInformation>Debug</GenerateDebugInformation>
|
||||
@ -179,8 +179,11 @@
|
||||
<ClCompile Include="Input\evdev_gun_handler.cpp" />
|
||||
<ClCompile Include="Input\gui_pad_thread.cpp" />
|
||||
<ClCompile Include="Input\hid_pad_handler.cpp" />
|
||||
<ClCompile Include="Input\ps_move_config.cpp" />
|
||||
<ClCompile Include="Input\ps_move_tracker.cpp" />
|
||||
<ClCompile Include="Input\raw_mouse_config.cpp" />
|
||||
<ClCompile Include="Input\raw_mouse_handler.cpp" />
|
||||
<ClCompile Include="Input\ps_move_handler.cpp" />
|
||||
<ClCompile Include="Input\sdl_pad_handler.cpp" />
|
||||
<ClCompile Include="Input\skateboard_pad_handler.cpp" />
|
||||
<ClCompile Include="main.cpp" />
|
||||
@ -213,6 +216,9 @@
|
||||
<ClCompile Include="QTGeneratedFiles\Debug\moc_camera_settings_dialog.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Debug\moc_ps_move_tracker_dialog.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Debug\moc_cg_disasm_window.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
@ -486,6 +492,9 @@
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_camera_settings_dialog.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_ps_move_tracker_dialog.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_cg_disasm_window.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
@ -741,6 +750,7 @@
|
||||
<ClCompile Include="rpcs3qt\breakpoint_list.cpp" />
|
||||
<ClCompile Include="rpcs3qt\call_stack_list.cpp" />
|
||||
<ClCompile Include="rpcs3qt\camera_settings_dialog.cpp" />
|
||||
<ClCompile Include="rpcs3qt\ps_move_tracker_dialog.cpp" />
|
||||
<ClCompile Include="rpcs3qt\cheat_manager.cpp" />
|
||||
<ClCompile Include="rpcs3qt\config_adapter.cpp" />
|
||||
<ClCompile Include="rpcs3qt\config_checker.cpp" />
|
||||
@ -961,8 +971,11 @@
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
|
||||
</CustomBuild>
|
||||
<ClInclude Include="Input\ps_move_config.h" />
|
||||
<ClInclude Include="Input\ps_move_tracker.h" />
|
||||
<ClInclude Include="Input\raw_mouse_config.h" />
|
||||
<ClInclude Include="Input\raw_mouse_handler.h" />
|
||||
<ClInclude Include="Input\ps_move_handler.h" />
|
||||
<ClInclude Include="Input\sdl_pad_handler.h" />
|
||||
<ClInclude Include="Input\skateboard_pad_handler.h" />
|
||||
<ClInclude Include="main_application.h" />
|
||||
@ -1022,6 +1035,7 @@
|
||||
<ClInclude Include="module_verifier.hpp" />
|
||||
<ClInclude Include="QTGeneratedFiles\ui_about_dialog.h" />
|
||||
<ClInclude Include="QTGeneratedFiles\ui_camera_settings_dialog.h" />
|
||||
<ClInclude Include="QTGeneratedFiles\ui_ps_move_tracker_dialog.h" />
|
||||
<ClInclude Include="QTGeneratedFiles\ui_main_window.h" />
|
||||
<ClInclude Include="QTGeneratedFiles\ui_pad_led_settings_dialog.h" />
|
||||
<ClInclude Include="QTGeneratedFiles\ui_pad_motion_settings_dialog.h" />
|
||||
@ -1093,6 +1107,16 @@
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia"</Command>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="rpcs3qt\ps_move_tracker_dialog.h">
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing %(Identity)...</Message>
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia"</Command>
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Moc%27ing %(Identity)...</Message>
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia"</Command>
|
||||
</CustomBuild>
|
||||
<ClInclude Include="rpcs3qt\category.h" />
|
||||
<ClInclude Include="rpcs3qt\config_adapter.h" />
|
||||
<CustomBuild Include="rpcs3qt\config_checker.h">
|
||||
@ -1962,6 +1986,16 @@
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\ui_%(Filename).h;%(Outputs)</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)"</Command>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="rpcs3qt\ps_move_tracker_dialog.ui">
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\uic.exe;%(AdditionalInputs)</AdditionalInputs>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Uic%27ing %(Identity)...</Message>
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\QTGeneratedFiles\ui_%(Filename).h;%(Outputs)</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">"$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)"</Command>
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\uic.exe;%(AdditionalInputs)</AdditionalInputs>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Uic%27ing %(Identity)...</Message>
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\ui_%(Filename).h;%(Outputs)</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)"</Command>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="rpcs3qt\pad_motion_settings_dialog.ui">
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\uic.exe;%(AdditionalInputs)</AdditionalInputs>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Uic%27ing %(Identity)...</Message>
|
||||
|
@ -184,6 +184,12 @@
|
||||
<Filter Include="buildfiles\msvc">
|
||||
<UniqueIdentifier>{5cace00d-92fc-4b03-82a2-72706cd04e84}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Gui\ps_move_tracker_dialog">
|
||||
<UniqueIdentifier>{4a270c26-740b-46e8-880c-bb22e708b897}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Io\Move">
|
||||
<UniqueIdentifier>{f8a98f7b-dc23-47c0-8a5f-d0b76eaf0df5}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp">
|
||||
@ -879,6 +885,15 @@
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_camera_settings_dialog.cpp">
|
||||
<Filter>Generated Files\Release</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rpcs3qt\ps_move_tracker_dialog.cpp">
|
||||
<Filter>Gui\ps_move_tracker_dialog</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Debug\moc_ps_move_tracker_dialog.cpp">
|
||||
<Filter>Generated Files\Debug</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_ps_move_tracker_dialog.cpp">
|
||||
<Filter>Generated Files\Release</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rpcs3qt\shortcut_utils.cpp">
|
||||
<Filter>Gui\utils</Filter>
|
||||
</ClCompile>
|
||||
@ -1119,6 +1134,15 @@
|
||||
<ClCompile Include="rpcs3qt\vfs_tool_dialog.cpp">
|
||||
<Filter>Gui\vfs</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Input\ps_move_handler.cpp">
|
||||
<Filter>Io\Move</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Input\ps_move_tracker.cpp">
|
||||
<Filter>Io\Move</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Input\ps_move_config.cpp">
|
||||
<Filter>Io\Move</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Input\ds4_pad_handler.h">
|
||||
@ -1253,6 +1277,9 @@
|
||||
<ClInclude Include="QTGeneratedFiles\ui_camera_settings_dialog.h">
|
||||
<Filter>Generated Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="QTGeneratedFiles\ui_ps_move_tracker_dialog.h">
|
||||
<Filter>Generated Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="rpcs3qt\shortcut_utils.h">
|
||||
<Filter>Gui\utils</Filter>
|
||||
</ClInclude>
|
||||
@ -1307,6 +1334,15 @@
|
||||
<ClInclude Include="Input\raw_mouse_config.h">
|
||||
<Filter>Io\raw</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Input\ps_move_handler.h">
|
||||
<Filter>Io\Move</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Input\ps_move_tracker.h">
|
||||
<Filter>Io\Move</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Input\ps_move_config.h">
|
||||
<Filter>Io\Move</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<CustomBuild Include="debug\moc_predefs.h.cbt">
|
||||
@ -1546,6 +1582,12 @@
|
||||
<CustomBuild Include="rpcs3qt\camera_settings_dialog.h">
|
||||
<Filter>Gui\settings</Filter>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="rpcs3qt\ps_move_tracker_dialog.ui">
|
||||
<Filter>Form Files</Filter>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="rpcs3qt\ps_move_tracker_dialog.h">
|
||||
<Filter>Gui\ps_move_tracker_dialog</Filter>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="rpcs3qt\qt_music_handler.h">
|
||||
<Filter>Io\music</Filter>
|
||||
</CustomBuild>
|
||||
|
@ -1150,6 +1150,7 @@ QString emu_settings::GetLocalizedSetting(const QString& original, emu_settings_
|
||||
switch (static_cast<move_handler>(index))
|
||||
{
|
||||
case move_handler::null: return tr("Null", "Move handler");
|
||||
case move_handler::real: return tr("Real", "Move handler");
|
||||
case move_handler::fake: return tr("Fake", "Move handler");
|
||||
case move_handler::mouse: return tr("Mouse", "Move handler");
|
||||
case move_handler::raw_mouse: return tr("Raw Mouse", "Move handler");
|
||||
|
@ -84,6 +84,10 @@ emulated_pad_settings_dialog::emulated_pad_settings_dialog(pad_type type, QWidge
|
||||
setWindowTitle(tr("Configure Emulated USIO"));
|
||||
add_tabs<usio_btn>(tabs);
|
||||
break;
|
||||
case emulated_pad_settings_dialog::pad_type::gem:
|
||||
setWindowTitle(tr("Configure Emulated PS Move (Real)"));
|
||||
add_tabs<gem_btn>(tabs);
|
||||
break;
|
||||
case emulated_pad_settings_dialog::pad_type::ds3gem:
|
||||
setWindowTitle(tr("Configure Emulated PS Move (Fake)"));
|
||||
add_tabs<gem_btn>(tabs);
|
||||
@ -113,12 +117,7 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs)
|
||||
ensure(!!tabs);
|
||||
|
||||
constexpr u32 max_items_per_column = 6;
|
||||
int rows = static_cast<int>(T::count);
|
||||
|
||||
for (u32 cols = 1; utils::aligned_div(static_cast<u32>(T::count), cols) > max_items_per_column;)
|
||||
{
|
||||
rows = utils::aligned_div(static_cast<u32>(T::count), ++cols);
|
||||
}
|
||||
int count = static_cast<int>(T::count);
|
||||
|
||||
usz players = 0;
|
||||
switch (m_type)
|
||||
@ -135,8 +134,16 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs)
|
||||
case pad_type::usio:
|
||||
players = g_cfg_usio.players.size();
|
||||
break;
|
||||
case pad_type::gem:
|
||||
players = g_cfg_gem_real.players.size();
|
||||
|
||||
// Ignore x and y axis
|
||||
static_assert(static_cast<int>(gem_btn::y_axis) == static_cast<int>(gem_btn::count) - 1);
|
||||
static_assert(static_cast<int>(gem_btn::x_axis) == static_cast<int>(gem_btn::count) - 2);
|
||||
count -= 2;
|
||||
break;
|
||||
case pad_type::ds3gem:
|
||||
players = g_cfg_gem.players.size();
|
||||
players = g_cfg_gem_fake.players.size();
|
||||
break;
|
||||
case pad_type::guncon3:
|
||||
players = g_cfg_guncon3.players.size();
|
||||
@ -149,6 +156,13 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs)
|
||||
break;
|
||||
}
|
||||
|
||||
int rows = count;
|
||||
|
||||
for (u32 cols = 1; utils::aligned_div(static_cast<u32>(count), cols) > max_items_per_column;)
|
||||
{
|
||||
rows = utils::aligned_div(static_cast<u32>(count), ++cols);
|
||||
}
|
||||
|
||||
m_combos.resize(players);
|
||||
|
||||
for (usz player = 0; player < players; player++)
|
||||
@ -156,7 +170,7 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs)
|
||||
QWidget* widget = new QWidget(this);
|
||||
QGridLayout* grid_layout = new QGridLayout(this);
|
||||
|
||||
for (int i = 0, row = 0, col = 0; i < static_cast<int>(T::count); i++, row++)
|
||||
for (int i = 0, row = 0, col = 0; i < count; i++, row++)
|
||||
{
|
||||
const T id = static_cast<T>(i);
|
||||
const QString name = QString::fromStdString(fmt::format("%s", id));
|
||||
@ -201,8 +215,11 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs)
|
||||
case pad_type::usio:
|
||||
saved_btn_id = ::at32(g_cfg_usio.players, player)->get_pad_button(static_cast<usio_btn>(id));
|
||||
break;
|
||||
case pad_type::gem:
|
||||
saved_btn_id = ::at32(g_cfg_gem_real.players, player)->get_pad_button(static_cast<gem_btn>(id));
|
||||
break;
|
||||
case pad_type::ds3gem:
|
||||
saved_btn_id = ::at32(g_cfg_gem.players, player)->get_pad_button(static_cast<gem_btn>(id));
|
||||
saved_btn_id = ::at32(g_cfg_gem_fake.players, player)->get_pad_button(static_cast<gem_btn>(id));
|
||||
break;
|
||||
case pad_type::guncon3:
|
||||
saved_btn_id = ::at32(g_cfg_guncon3.players, player)->get_pad_button(static_cast<guncon3_btn>(id));
|
||||
@ -242,8 +259,11 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs)
|
||||
case pad_type::usio:
|
||||
::at32(g_cfg_usio.players, player)->set_button(static_cast<usio_btn>(id), btn_id);
|
||||
break;
|
||||
case pad_type::gem:
|
||||
::at32(g_cfg_gem_real.players, player)->set_button(static_cast<gem_btn>(id), btn_id);
|
||||
break;
|
||||
case pad_type::ds3gem:
|
||||
::at32(g_cfg_gem.players, player)->set_button(static_cast<gem_btn>(id), btn_id);
|
||||
::at32(g_cfg_gem_fake.players, player)->set_button(static_cast<gem_btn>(id), btn_id);
|
||||
break;
|
||||
case pad_type::guncon3:
|
||||
::at32(g_cfg_guncon3.players, player)->set_button(static_cast<guncon3_btn>(id), btn_id);
|
||||
@ -302,12 +322,18 @@ void emulated_pad_settings_dialog::load_config()
|
||||
cfg_log.notice("Could not load usio config. Using defaults.");
|
||||
}
|
||||
break;
|
||||
case emulated_pad_settings_dialog::pad_type::ds3gem:
|
||||
if (!g_cfg_gem.load())
|
||||
case emulated_pad_settings_dialog::pad_type::gem:
|
||||
if (!g_cfg_gem_real.load())
|
||||
{
|
||||
cfg_log.notice("Could not load gem config. Using defaults.");
|
||||
}
|
||||
break;
|
||||
case emulated_pad_settings_dialog::pad_type::ds3gem:
|
||||
if (!g_cfg_gem_fake.load())
|
||||
{
|
||||
cfg_log.notice("Could not load fake gem config. Using defaults.");
|
||||
}
|
||||
break;
|
||||
case emulated_pad_settings_dialog::pad_type::guncon3:
|
||||
if (!g_cfg_guncon3.load())
|
||||
{
|
||||
@ -345,8 +371,11 @@ void emulated_pad_settings_dialog::save_config()
|
||||
case emulated_pad_settings_dialog::pad_type::usio:
|
||||
g_cfg_usio.save();
|
||||
break;
|
||||
case emulated_pad_settings_dialog::pad_type::gem:
|
||||
g_cfg_gem_real.save();
|
||||
break;
|
||||
case emulated_pad_settings_dialog::pad_type::ds3gem:
|
||||
g_cfg_gem.save();
|
||||
g_cfg_gem_fake.save();
|
||||
break;
|
||||
case emulated_pad_settings_dialog::pad_type::guncon3:
|
||||
g_cfg_guncon3.save();
|
||||
@ -376,8 +405,11 @@ void emulated_pad_settings_dialog::reset_config()
|
||||
case emulated_pad_settings_dialog::pad_type::usio:
|
||||
g_cfg_usio.from_default();
|
||||
break;
|
||||
case emulated_pad_settings_dialog::pad_type::gem:
|
||||
g_cfg_gem_real.from_default();
|
||||
break;
|
||||
case emulated_pad_settings_dialog::pad_type::ds3gem:
|
||||
g_cfg_gem.from_default();
|
||||
g_cfg_gem_fake.from_default();
|
||||
break;
|
||||
case emulated_pad_settings_dialog::pad_type::guncon3:
|
||||
g_cfg_guncon3.from_default();
|
||||
@ -416,8 +448,11 @@ void emulated_pad_settings_dialog::reset_config()
|
||||
case pad_type::usio:
|
||||
def_btn_id = ::at32(g_cfg_usio.players, player)->default_pad_button(static_cast<usio_btn>(data.toInt()));
|
||||
break;
|
||||
case pad_type::gem:
|
||||
def_btn_id = ::at32(g_cfg_gem_real.players, player)->default_pad_button(static_cast<gem_btn>(data.toInt()));
|
||||
break;
|
||||
case pad_type::ds3gem:
|
||||
def_btn_id = ::at32(g_cfg_gem.players, player)->default_pad_button(static_cast<gem_btn>(data.toInt()));
|
||||
def_btn_id = ::at32(g_cfg_gem_fake.players, player)->default_pad_button(static_cast<gem_btn>(data.toInt()));
|
||||
break;
|
||||
case pad_type::guncon3:
|
||||
def_btn_id = ::at32(g_cfg_guncon3.players, player)->default_pad_button(static_cast<guncon3_btn>(data.toInt()));
|
||||
|
@ -19,6 +19,7 @@ public:
|
||||
turntable,
|
||||
ghltar,
|
||||
usio,
|
||||
gem,
|
||||
ds3gem,
|
||||
guncon3,
|
||||
topshotelite,
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include "gui_settings.h"
|
||||
#include "input_dialog.h"
|
||||
#include "camera_settings_dialog.h"
|
||||
#include "ps_move_tracker_dialog.h"
|
||||
#include "ipc_settings_dialog.h"
|
||||
#include "shortcut_utils.h"
|
||||
#include "config_checker.h"
|
||||
@ -1990,6 +1991,7 @@ void main_window::OnEmuStop()
|
||||
|
||||
ui->actionManage_Users->setEnabled(true);
|
||||
ui->confCamerasAct->setEnabled(true);
|
||||
ui->actionPS_Move_Tracker->setEnabled(true);
|
||||
|
||||
// Refresh game list in order to update time played
|
||||
if (m_game_list_frame && m_is_list_mode)
|
||||
@ -2032,6 +2034,7 @@ void main_window::OnEmuReady() const
|
||||
|
||||
ui->actionManage_Users->setEnabled(false);
|
||||
ui->confCamerasAct->setEnabled(false);
|
||||
ui->actionPS_Move_Tracker->setEnabled(false);
|
||||
|
||||
ui->batchRemoveShaderCachesAct->setEnabled(false);
|
||||
ui->batchRemovePPUCachesAct->setEnabled(false);
|
||||
@ -2808,6 +2811,12 @@ void main_window::CreateConnects()
|
||||
dlg->show();
|
||||
});
|
||||
|
||||
connect(ui->confPSMoveAct, &QAction::triggered, this, [this]
|
||||
{
|
||||
emulated_pad_settings_dialog* dlg = new emulated_pad_settings_dialog(emulated_pad_settings_dialog::pad_type::gem, this);
|
||||
dlg->show();
|
||||
});
|
||||
|
||||
connect(ui->confGunCon3Act, &QAction::triggered, this, [this]
|
||||
{
|
||||
emulated_pad_settings_dialog* dlg = new emulated_pad_settings_dialog(emulated_pad_settings_dialog::pad_type::guncon3, this);
|
||||
@ -2961,6 +2970,12 @@ void main_window::CreateConnects()
|
||||
viewer->show_log();
|
||||
});
|
||||
|
||||
connect(ui->actionPS_Move_Tracker, &QAction::triggered, this, [this]
|
||||
{
|
||||
ps_move_tracker_dialog* dlg = new ps_move_tracker_dialog(this);
|
||||
dlg->open();
|
||||
});
|
||||
|
||||
connect(ui->toolsCheckConfigAct, &QAction::triggered, this, [this]
|
||||
{
|
||||
const QString path_last_cfg = m_gui_settings->GetValue(gui::fd_cfg_check).toString();
|
||||
|
@ -242,6 +242,7 @@
|
||||
<addaction name="confGHLtarAct"/>
|
||||
<addaction name="confTurntableAct"/>
|
||||
<addaction name="confUSIOAct"/>
|
||||
<addaction name="confPSMoveAct"/>
|
||||
<addaction name="confPSMoveDS3Act"/>
|
||||
<addaction name="confGunCon3Act"/>
|
||||
<addaction name="confTopShotEliteAct"/>
|
||||
@ -261,6 +262,7 @@
|
||||
<addaction name="menuMice"/>
|
||||
<addaction name="menuEmulatedPads"/>
|
||||
<addaction name="confCamerasAct"/>
|
||||
<addaction name="actionPS_Move_Tracker"/>
|
||||
<addaction name="confAudioAct"/>
|
||||
<addaction name="confIOAct"/>
|
||||
<addaction name="confSystemAct"/>
|
||||
@ -1390,6 +1392,16 @@
|
||||
<string>VFS Tool</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionPS_Move_Tracker">
|
||||
<property name="text">
|
||||
<string>PS Move Tracker</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="confPSMoveAct">
|
||||
<property name="text">
|
||||
<string>PS Move</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<resources>
|
||||
|
@ -1416,6 +1416,7 @@ void pad_settings_dialog::ChangeHandler()
|
||||
break;
|
||||
case pad_handler::keyboard: m_description = tooltips.gamepad_settings.keyboard; break;
|
||||
case pad_handler::skateboard: m_description = tooltips.gamepad_settings.skateboard; break;
|
||||
case pad_handler::move: m_description = tooltips.gamepad_settings.move; break;
|
||||
#ifdef _WIN32
|
||||
case pad_handler::xinput: m_description = tooltips.gamepad_settings.xinput; break;
|
||||
case pad_handler::mm: m_description = tooltips.gamepad_settings.mmjoy; break;
|
||||
@ -1486,6 +1487,7 @@ void pad_settings_dialog::ChangeHandler()
|
||||
case pad_handler::ds4:
|
||||
case pad_handler::dualsense:
|
||||
case pad_handler::skateboard:
|
||||
case pad_handler::move:
|
||||
{
|
||||
const QString name_string = qstr(m_handler->name_string());
|
||||
for (usz i = 1; i <= m_handler->max_devices(); i++) // Controllers 1-n in GUI
|
||||
@ -1957,6 +1959,7 @@ QString pad_settings_dialog::GetLocalizedPadHandler(const QString& original, pad
|
||||
case pad_handler::ds4: return tr("DualShock 4");
|
||||
case pad_handler::dualsense: return tr("DualSense");
|
||||
case pad_handler::skateboard: return tr("Skateboard");
|
||||
case pad_handler::move: return tr("PS Move");
|
||||
#ifdef _WIN32
|
||||
case pad_handler::xinput: return tr("XInput");
|
||||
case pad_handler::mm: return tr("MMJoystick");
|
||||
|
595
rpcs3/rpcs3qt/ps_move_tracker_dialog.cpp
Normal file
595
rpcs3/rpcs3qt/ps_move_tracker_dialog.cpp
Normal file
@ -0,0 +1,595 @@
|
||||
#include "stdafx.h"
|
||||
#include "ps_move_tracker_dialog.h"
|
||||
#include "ui_ps_move_tracker_dialog.h"
|
||||
#include "Emu/Cell/Modules/cellCamera.h"
|
||||
#include "qt_camera_handler.h"
|
||||
#include "Input/ps_move_handler.h"
|
||||
#include "Input/ps_move_config.h"
|
||||
#include "Input/ps_move_tracker.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QImage>
|
||||
#include <QPainter>
|
||||
#include <QPushButton>
|
||||
#include <QSlider>
|
||||
|
||||
LOG_CHANNEL(ps_move);
|
||||
|
||||
extern u32 get_buffer_size_by_format(s32, s32, s32);
|
||||
|
||||
static constexpr bool tie_hue_to_color = true;
|
||||
static constexpr int radius_range = 1000;
|
||||
static const constexpr f64 min_radius_conversion = radius_range / g_cfg_move.min_radius.max;
|
||||
static const constexpr f64 max_radius_conversion = radius_range / g_cfg_move.max_radius.max;
|
||||
|
||||
ps_move_tracker_dialog::ps_move_tracker_dialog(QWidget* parent)
|
||||
: QDialog(parent)
|
||||
, ui(new Ui::ps_move_tracker_dialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
if (!g_cfg_move.load())
|
||||
{
|
||||
ps_move.notice("Could not load PS Move config. Using defaults.");
|
||||
}
|
||||
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, [this](QAbstractButton* button)
|
||||
{
|
||||
if (button == ui->buttonBox->button(QDialogButtonBox::Save))
|
||||
{
|
||||
g_cfg_move.save();
|
||||
}
|
||||
else if (button == ui->buttonBox->button(QDialogButtonBox::Apply))
|
||||
{
|
||||
g_cfg_move.save();
|
||||
}
|
||||
else if (button == ui->buttonBox->button(QDialogButtonBox::Close))
|
||||
{
|
||||
if (!g_cfg_move.load())
|
||||
{
|
||||
ps_move.notice("Could not load PS Move config. Using defaults.");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
m_format = CELL_CAMERA_RGBA;
|
||||
ui->inputFormatCombo->addItem(tr("RGBA"), static_cast<int>(CELL_CAMERA_RGBA));
|
||||
ui->inputFormatCombo->addItem(tr("RAW8"), static_cast<int>(CELL_CAMERA_RAW8));
|
||||
ui->inputFormatCombo->setCurrentIndex(ui->inputFormatCombo->findData(m_format));
|
||||
|
||||
connect(ui->inputFormatCombo, &QComboBox::currentIndexChanged, this, [this](int index)
|
||||
{
|
||||
if (index < 0) return;
|
||||
if (const auto qvar = ui->inputFormatCombo->currentData(); qvar.canConvert<int>())
|
||||
{
|
||||
m_format = qvar.toInt();
|
||||
reset_camera();
|
||||
}
|
||||
});
|
||||
|
||||
ui->viewCombo->addItem(tr("Image"), static_cast<int>(view_mode::image));
|
||||
ui->viewCombo->addItem(tr("Grayscale"), static_cast<int>(view_mode::grayscale));
|
||||
ui->viewCombo->addItem(tr("HSV Hue"), static_cast<int>(view_mode::hsv_hue));
|
||||
ui->viewCombo->addItem(tr("HSV Saturation"), static_cast<int>(view_mode::hsv_saturation));
|
||||
ui->viewCombo->addItem(tr("HSV Value"), static_cast<int>(view_mode::hsv_value));
|
||||
ui->viewCombo->addItem(tr("Binary"), static_cast<int>(view_mode::binary));
|
||||
ui->viewCombo->addItem(tr("Contours"), static_cast<int>(view_mode::contours));
|
||||
ui->viewCombo->setCurrentIndex(ui->viewCombo->findData(static_cast<int>(m_view_mode)));
|
||||
|
||||
connect(ui->viewCombo, &QComboBox::currentIndexChanged, this, [this](int index)
|
||||
{
|
||||
if (index < 0) return;
|
||||
if (const auto qvar = ui->viewCombo->currentData(); qvar.canConvert<int>())
|
||||
{
|
||||
m_view_mode = static_cast<view_mode>(qvar.toInt());
|
||||
}
|
||||
});
|
||||
|
||||
ui->histoCombo->addItem(tr("Hues"), static_cast<int>(histo_mode::unfiltered_hues));
|
||||
ui->histoCombo->setCurrentIndex(ui->viewCombo->findData(static_cast<int>(m_histo_mode)));
|
||||
|
||||
connect(ui->histoCombo, &QComboBox::currentIndexChanged, this, [this](int index)
|
||||
{
|
||||
if (index < 0) return;
|
||||
if (const auto qvar = ui->histoCombo->currentData(); qvar.canConvert<int>())
|
||||
{
|
||||
m_histo_mode = static_cast<histo_mode>(qvar.toInt());
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->hueSlider, &QSlider::valueChanged, this, [this](int value)
|
||||
{
|
||||
cfg_ps_move* config = ::at32(g_cfg_move.move, m_index);
|
||||
const u16 hue = std::clamp<u16>(value, config->hue.min, config->hue.max);
|
||||
config->hue.set(hue);
|
||||
update_hue();
|
||||
});
|
||||
ui->hueSlider->setRange(g_cfg_move.move1.hue.min, g_cfg_move.move1.hue.max);
|
||||
|
||||
connect(ui->hueThresholdSlider, &QSlider::valueChanged, this, [this](int value)
|
||||
{
|
||||
cfg_ps_move* config = ::at32(g_cfg_move.move, m_index);
|
||||
const u16 hue_threshold = std::clamp<u16>(value, config->hue_threshold.min, config->hue_threshold.max);
|
||||
config->hue_threshold.set(hue_threshold);
|
||||
update_hue_threshold();
|
||||
});
|
||||
ui->hueThresholdSlider->setRange(g_cfg_move.move1.hue_threshold.min, g_cfg_move.move1.hue_threshold.max);
|
||||
|
||||
connect(ui->saturationThresholdSlider, &QSlider::valueChanged, this, [this](int value)
|
||||
{
|
||||
cfg_ps_move* config = ::at32(g_cfg_move.move, m_index);
|
||||
const u16 saturation_threshold = std::clamp<u16>(value, config->saturation_threshold.min, config->saturation_threshold.max);
|
||||
config->saturation_threshold.set(saturation_threshold);
|
||||
update_saturation_threshold();
|
||||
});
|
||||
ui->saturationThresholdSlider->setRange(g_cfg_move.move1.saturation_threshold.min, g_cfg_move.move1.saturation_threshold.max);
|
||||
|
||||
connect(ui->minRadiusSlider, &QSlider::valueChanged, this, [this](int value)
|
||||
{
|
||||
const f32 min_radius = std::clamp(value / min_radius_conversion, g_cfg_move.min_radius.min, g_cfg_move.min_radius.max);
|
||||
g_cfg_move.min_radius.set(min_radius);
|
||||
update_min_radius();
|
||||
});
|
||||
ui->minRadiusSlider->setRange(0, radius_range);
|
||||
|
||||
connect(ui->maxRadiusSlider, &QSlider::valueChanged, this, [this](int value)
|
||||
{
|
||||
const f32 max_radius = std::clamp(value / max_radius_conversion, g_cfg_move.max_radius.min, g_cfg_move.max_radius.max);
|
||||
g_cfg_move.max_radius.set(max_radius);
|
||||
update_max_radius();
|
||||
});
|
||||
ui->maxRadiusSlider->setRange(0, radius_range);
|
||||
|
||||
connect(ui->colorSliderR, &QSlider::valueChanged, this, [this](int value)
|
||||
{
|
||||
cfg_ps_move* config = ::at32(g_cfg_move.move, m_index);
|
||||
config->r.set(std::clamp<u8>(value, config->r.min, config->r.max));
|
||||
update_color();
|
||||
});
|
||||
connect(ui->colorSliderG, &QSlider::valueChanged, this, [this](int value)
|
||||
{
|
||||
cfg_ps_move* config = ::at32(g_cfg_move.move, m_index);
|
||||
config->g.set(std::clamp<u8>(value, config->g.min, config->g.max));
|
||||
update_color();
|
||||
});
|
||||
connect(ui->colorSliderB, &QSlider::valueChanged, this, [this](int value)
|
||||
{
|
||||
cfg_ps_move* config = ::at32(g_cfg_move.move, m_index);
|
||||
config->b.set(std::clamp<u8>(value, config->b.min, config->b.max));
|
||||
update_color();
|
||||
});
|
||||
ui->colorSliderR->setRange(g_cfg_move.move1.r.min, g_cfg_move.move1.r.max);
|
||||
ui->colorSliderG->setRange(g_cfg_move.move1.g.min, g_cfg_move.move1.g.max);
|
||||
ui->colorSliderB->setRange(g_cfg_move.move1.b.min, g_cfg_move.move1.b.max);
|
||||
|
||||
connect(ui->filterSmallContoursBox, &QCheckBox::toggled, [this](bool checked)
|
||||
{
|
||||
m_filter_small_contours = checked;
|
||||
});
|
||||
ui->filterSmallContoursBox->setChecked(m_filter_small_contours);
|
||||
|
||||
connect(ui->freezeFrameBox, &QCheckBox::toggled, [this](bool checked)
|
||||
{
|
||||
m_freeze_frame = checked;
|
||||
});
|
||||
ui->freezeFrameBox->setChecked(m_freeze_frame);
|
||||
|
||||
connect(ui->showAllContoursBox, &QCheckBox::toggled, [this](bool checked)
|
||||
{
|
||||
m_show_all_contours = checked;
|
||||
});
|
||||
ui->showAllContoursBox->setChecked(m_show_all_contours);
|
||||
|
||||
connect(ui->drawContoursBox, &QCheckBox::toggled, [this](bool checked)
|
||||
{
|
||||
m_draw_contours = checked;
|
||||
});
|
||||
ui->drawContoursBox->setChecked(m_draw_contours);
|
||||
|
||||
connect(ui->drawOverlaysBox, &QCheckBox::toggled, [this](bool checked)
|
||||
{
|
||||
m_draw_overlays = checked;
|
||||
});
|
||||
ui->drawOverlaysBox->setChecked(m_draw_overlays);
|
||||
|
||||
for (u32 index = 0; index < CELL_GEM_MAX_NUM; index++)
|
||||
{
|
||||
ui->comboSelectDevice->addItem(tr("PS Move #%0").arg(index + 1), index);
|
||||
}
|
||||
ui->comboSelectDevice->setCurrentIndex(ui->comboSelectDevice->findData(m_index));
|
||||
|
||||
connect(ui->comboSelectDevice, &QComboBox::currentIndexChanged, this, [this](int index)
|
||||
{
|
||||
if (index < 0) return;
|
||||
if (const auto qvar = ui->comboSelectDevice->currentData(); qvar.canConvert<int>())
|
||||
{
|
||||
m_index = qvar.toInt();
|
||||
update_color(true);
|
||||
update_hue(true);
|
||||
update_hue_threshold(true);
|
||||
update_saturation_threshold(true);
|
||||
}
|
||||
});
|
||||
|
||||
m_ps_move_tracker = std::make_unique<ps_move_tracker<true>>();
|
||||
|
||||
m_update_timer = new QTimer(this);
|
||||
connect(m_update_timer, &QTimer::timeout, this, [this]()
|
||||
{
|
||||
std::lock_guard lock(m_image_mutex);
|
||||
|
||||
if (m_image.isNull() || m_histogram.isNull())
|
||||
return;
|
||||
|
||||
ui->imageLabel->setPixmap(m_image);
|
||||
ui->histogramLabel->setPixmap(m_histogram);
|
||||
});
|
||||
|
||||
reset_camera();
|
||||
|
||||
m_input_thread = std::make_unique<named_thread<pad_thread>>(thread(), window(), "");
|
||||
while (!pad::g_started) QApplication::processEvents();
|
||||
|
||||
adjustSize();
|
||||
|
||||
update_color(true);
|
||||
update_hue(true);
|
||||
update_hue_threshold(true);
|
||||
update_saturation_threshold(true);
|
||||
update_min_radius(true);
|
||||
update_max_radius(true);
|
||||
}
|
||||
|
||||
ps_move_tracker_dialog::~ps_move_tracker_dialog()
|
||||
{
|
||||
m_update_timer->stop();
|
||||
|
||||
if (m_tracker_thread)
|
||||
{
|
||||
m_stop_threads = 1;
|
||||
m_tracker_thread->wait();
|
||||
}
|
||||
|
||||
if (m_camera_handler)
|
||||
{
|
||||
m_camera_handler->close_camera();
|
||||
}
|
||||
|
||||
if (m_input_thread)
|
||||
{
|
||||
auto& thread = *m_input_thread;
|
||||
thread = thread_state::aborting;
|
||||
thread();
|
||||
m_input_thread.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void ps_move_tracker_dialog::update_color(bool update_sliders)
|
||||
{
|
||||
cfg_ps_move* config = ::at32(g_cfg_move.move, m_index);
|
||||
|
||||
ui->colorGb->setTitle(tr("Color: R=%0, G=%1, B=%2").arg(config->r.get()).arg(config->g.get()).arg(config->b.get()));
|
||||
|
||||
if (update_sliders)
|
||||
{
|
||||
ui->colorSliderR->setValue(config->r.get());
|
||||
ui->colorSliderG->setValue(config->g.get());
|
||||
ui->colorSliderB->setValue(config->b.get());
|
||||
}
|
||||
else if (tie_hue_to_color)
|
||||
{
|
||||
const auto [hue, saturation, value] = ps_move_tracker<true>::rgb_to_hsv(config->r.get() / 255.0f, config->g.get() / 255.0f, config->b.get() / 255.0f);
|
||||
config->hue.set(std::clamp<u32>(hue, config->hue.min, config->hue.max));
|
||||
update_hue(true);
|
||||
}
|
||||
|
||||
if (!m_input_thread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard lock(pad::g_pad_mutex);
|
||||
auto& handlers = m_input_thread->get_handlers();
|
||||
if (auto it = handlers.find(pad_handler::move); it != handlers.end())
|
||||
{
|
||||
for (auto& binding : it->second->bindings())
|
||||
{
|
||||
if (binding.device)
|
||||
{
|
||||
binding.device->color_override_active = true;
|
||||
binding.device->color_override.r = config->r.get();
|
||||
binding.device->color_override.g = config->g.get();
|
||||
binding.device->color_override.b = config->b.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ps_move_tracker_dialog::update_hue(bool update_slider)
|
||||
{
|
||||
const u32 hue = ::at32(g_cfg_move.move, m_index)->hue.get();
|
||||
ui->hueGb->setTitle(tr("Hue: %0").arg(hue));
|
||||
|
||||
if (update_slider)
|
||||
{
|
||||
ui->hueSlider->setValue(hue);
|
||||
}
|
||||
else if (tie_hue_to_color)
|
||||
{
|
||||
cfg_ps_move* config = ::at32(g_cfg_move.move, m_index);
|
||||
const auto [r, g, b] = ps_move_tracker<true>::hsv_to_rgb(hue, 1.0f, 1.0f);
|
||||
config->r.set(r / 100);
|
||||
config->g.set(g / 100);
|
||||
config->b.set(b / 100);
|
||||
update_color(true);
|
||||
}
|
||||
}
|
||||
|
||||
void ps_move_tracker_dialog::update_hue_threshold(bool update_slider)
|
||||
{
|
||||
const u32 hue_threshold = ::at32(g_cfg_move.move, m_index)->hue_threshold.get();
|
||||
ui->hueThresholdGb->setTitle(tr("Hue Threshold: %0").arg(hue_threshold));
|
||||
|
||||
if (update_slider)
|
||||
{
|
||||
ui->hueThresholdSlider->setValue(hue_threshold);
|
||||
}
|
||||
}
|
||||
|
||||
void ps_move_tracker_dialog::update_saturation_threshold(bool update_slider)
|
||||
{
|
||||
const u32 saturation_threshold = ::at32(g_cfg_move.move, m_index)->saturation_threshold.get();
|
||||
ui->saturationThresholdGb->setTitle(tr("Saturation Threshold: %0").arg(saturation_threshold));
|
||||
|
||||
if (update_slider)
|
||||
{
|
||||
ui->saturationThresholdSlider->setValue(saturation_threshold);
|
||||
}
|
||||
}
|
||||
void ps_move_tracker_dialog::update_min_radius(bool update_slider)
|
||||
{
|
||||
const f32 min_radius = std::clamp(g_cfg_move.min_radius / min_radius_conversion, g_cfg_move.min_radius.min, g_cfg_move.min_radius.max);
|
||||
ui->minRadiusGb->setTitle(tr("Min Radius: %0 %").arg(min_radius));
|
||||
|
||||
if (update_slider)
|
||||
{
|
||||
ui->minRadiusSlider->setValue(g_cfg_move.min_radius * min_radius_conversion);
|
||||
}
|
||||
}
|
||||
|
||||
void ps_move_tracker_dialog::update_max_radius(bool update_slider)
|
||||
{
|
||||
const f32 max_radius = std::clamp(g_cfg_move.max_radius / max_radius_conversion, g_cfg_move.max_radius.min, g_cfg_move.max_radius.max);
|
||||
ui->maxRadiusGb->setTitle(tr("Max Radius: %0 %").arg(max_radius));
|
||||
|
||||
if (update_slider)
|
||||
{
|
||||
ui->maxRadiusSlider->setValue(g_cfg_move.max_radius * max_radius_conversion);
|
||||
}
|
||||
}
|
||||
|
||||
void ps_move_tracker_dialog::reset_camera()
|
||||
{
|
||||
m_update_timer->stop();
|
||||
|
||||
if (m_tracker_thread)
|
||||
{
|
||||
m_stop_threads = 1;
|
||||
m_tracker_thread->wait();
|
||||
m_stop_threads = 0;
|
||||
}
|
||||
|
||||
std::lock_guard camera_lock(m_camera_handler_mutex);
|
||||
|
||||
const u64 size = get_buffer_size_by_format(m_format, width, height);
|
||||
m_image_data_frozen.resize(size);
|
||||
m_image_data.resize(size);
|
||||
m_frame_number = 0;
|
||||
|
||||
m_camera_handler = std::make_unique<qt_camera_handler>();
|
||||
m_camera_handler->set_resolution(width, height);
|
||||
m_camera_handler->set_format(m_format, size);
|
||||
m_camera_handler->set_mirrored(true);
|
||||
m_camera_handler->open_camera();
|
||||
m_camera_handler->start_camera();
|
||||
|
||||
m_update_timer->start(1000 / 60);
|
||||
|
||||
m_tracker_thread.reset(QThread::create([this]()
|
||||
{
|
||||
while (!m_stop_threads)
|
||||
{
|
||||
process_camera_frame();
|
||||
}
|
||||
}));
|
||||
m_tracker_thread->start();
|
||||
}
|
||||
|
||||
void ps_move_tracker_dialog::process_camera_frame()
|
||||
{
|
||||
std::lock_guard camera_lock(m_camera_handler_mutex);
|
||||
|
||||
if (!m_camera_handler)
|
||||
{
|
||||
// Wait some time
|
||||
std::this_thread::sleep_for(100us);
|
||||
return;
|
||||
}
|
||||
|
||||
u32 width = 0;
|
||||
u32 height = 0;
|
||||
u64 frame_number = 0;
|
||||
u64 bytes_read = 0;
|
||||
|
||||
const camera_handler_base::camera_handler_state state = m_camera_handler->get_image(m_image_data.data(), m_image_data.size(), width, height, frame_number, bytes_read);
|
||||
|
||||
if (state != camera_handler_base::camera_handler_state::running || frame_number <= m_frame_number)
|
||||
{
|
||||
// Wait some time
|
||||
std::this_thread::sleep_for(100us);
|
||||
return;
|
||||
}
|
||||
|
||||
m_frame_number = frame_number;
|
||||
|
||||
if (m_frame_frozen != m_freeze_frame)
|
||||
{
|
||||
m_frame_frozen = m_freeze_frame;
|
||||
|
||||
if (m_frame_frozen)
|
||||
{
|
||||
std::memcpy(m_image_data_frozen.data(), m_image_data.data(), m_image_data.size());
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 index = 0; index < CELL_GEM_MAX_NUM; index++)
|
||||
{
|
||||
const cfg_ps_move* config = g_cfg_move.move[index];
|
||||
|
||||
m_ps_move_tracker->set_active(index, m_index == index);
|
||||
m_ps_move_tracker->set_hue(index, config->hue);
|
||||
m_ps_move_tracker->set_hue_threshold(index, config->hue_threshold);
|
||||
m_ps_move_tracker->set_saturation_threshold(index, config->saturation_threshold);
|
||||
}
|
||||
|
||||
m_ps_move_tracker->set_image_data(m_frame_frozen ? m_image_data_frozen.data() : m_image_data.data(), m_image_data.size(), width, height, m_camera_handler->format());
|
||||
m_ps_move_tracker->set_min_radius(static_cast<f32>(g_cfg_move.min_radius.get() / g_cfg_move.min_radius.max));
|
||||
m_ps_move_tracker->set_max_radius(static_cast<f32>(g_cfg_move.max_radius.get() / g_cfg_move.max_radius.max));
|
||||
m_ps_move_tracker->set_filter_small_contours(m_filter_small_contours);
|
||||
m_ps_move_tracker->set_show_all_contours(m_show_all_contours);
|
||||
m_ps_move_tracker->set_draw_contours(m_draw_contours);
|
||||
m_ps_move_tracker->set_draw_overlays(m_draw_overlays);
|
||||
m_ps_move_tracker->process_image();
|
||||
|
||||
const std::vector<u8>* result = nullptr;
|
||||
QImage::Format format = QImage::Format::Format_Invalid;
|
||||
qsizetype bytes_per_line = 0;
|
||||
|
||||
switch (m_view_mode)
|
||||
{
|
||||
case view_mode::image:
|
||||
{
|
||||
result = &m_ps_move_tracker->rgba();
|
||||
format = QImage::Format::Format_RGBA8888;
|
||||
bytes_per_line = width * 4;
|
||||
break;
|
||||
}
|
||||
case view_mode::grayscale:
|
||||
{
|
||||
result = &m_ps_move_tracker->gray();
|
||||
format = QImage::Format::Format_Grayscale8;
|
||||
bytes_per_line = width;
|
||||
break;
|
||||
}
|
||||
case view_mode::hsv_hue:
|
||||
case view_mode::hsv_saturation:
|
||||
case view_mode::hsv_value:
|
||||
{
|
||||
const int index = static_cast<int>(m_view_mode) - static_cast<int>(view_mode::hsv_hue);
|
||||
const std::vector<u8>& hsv = m_ps_move_tracker->hsv();
|
||||
static std::vector<u8> hsv_single;
|
||||
hsv_single.resize(hsv.size() / 3);
|
||||
for (int i = 0; i < static_cast<int>(hsv_single.size()); i++)
|
||||
{
|
||||
hsv_single[i] = hsv[i * 3 + index];
|
||||
}
|
||||
result = &hsv_single;
|
||||
format = QImage::Format::Format_Grayscale8;
|
||||
bytes_per_line = width;
|
||||
break;
|
||||
}
|
||||
case view_mode::binary:
|
||||
{
|
||||
result = &m_ps_move_tracker->binary(m_index);
|
||||
format = QImage::Format::Format_Grayscale8;
|
||||
bytes_per_line = width;
|
||||
break;
|
||||
}
|
||||
case view_mode::contours:
|
||||
{
|
||||
result = &m_ps_move_tracker->rgba_contours();
|
||||
format = QImage::Format::Format_RGBA8888;
|
||||
bytes_per_line = width * 4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QPixmap histogram;
|
||||
|
||||
switch (m_histo_mode)
|
||||
{
|
||||
case histo_mode::unfiltered_hues:
|
||||
{
|
||||
histogram = get_histogram(m_ps_move_tracker->hues(), false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const QImage image(result->data(), width, height, bytes_per_line, format, nullptr, nullptr);
|
||||
|
||||
std::lock_guard lock(m_image_mutex);
|
||||
m_image = QPixmap::fromImage(image);
|
||||
m_histogram = std::move(histogram);
|
||||
}
|
||||
|
||||
QPixmap ps_move_tracker_dialog::get_histogram(const std::array<u32, 360>& hues, bool ignore_zero) const
|
||||
{
|
||||
// Create image
|
||||
const int height = ui->histogramLabel->height();
|
||||
static QPixmap background = [&]()
|
||||
{
|
||||
// Paint background
|
||||
QPixmap pxmap(static_cast<int>(hues.size()), height);
|
||||
pxmap.fill(Qt::white);
|
||||
return pxmap;
|
||||
}();
|
||||
|
||||
QPixmap histo = background;
|
||||
QPainter painter(&histo);
|
||||
|
||||
const cfg_ps_move* config = ::at32(g_cfg_move.move, m_index);
|
||||
const u16 hue = config->hue;
|
||||
const u16 hue_threshold = config->hue_threshold;
|
||||
const int min_hue = hue - hue_threshold;
|
||||
const int max_hue = hue + hue_threshold;
|
||||
|
||||
// Paint hue threshold
|
||||
painter.fillRect(min_hue, 0, hue_threshold * 2 + 1, histo.height(), Qt::lightGray);
|
||||
|
||||
if (min_hue < 0)
|
||||
{
|
||||
painter.fillRect(min_hue + 360, 0, hue_threshold * 2 + 1, histo.height(), Qt::lightGray);
|
||||
}
|
||||
else if (max_hue >= 360)
|
||||
{
|
||||
painter.fillRect(0, 0, max_hue - 360, histo.height(), Qt::lightGray);
|
||||
}
|
||||
|
||||
// Paint target hue
|
||||
const auto [r, g, b] = ps_move_tracker<true>::hsv_to_rgb(hue, 1.0f, 1.0f);
|
||||
painter.setPen(QColor(r, g, b));
|
||||
painter.drawLine(hue, 0, hue, histo.height() - 1);
|
||||
|
||||
// Paint histogram
|
||||
painter.setPen(Qt::black);
|
||||
|
||||
const u32 zero_offset = (ignore_zero ? 1 : 0);
|
||||
|
||||
const auto max_elem = std::max_element(hues.begin() + zero_offset, hues.end());
|
||||
const u32 max_value = max_elem != hues.end() ? *max_elem : 0u;
|
||||
|
||||
if (!max_value)
|
||||
return histo;
|
||||
|
||||
for (int i = zero_offset; i < static_cast<int>(hues.size()); i++)
|
||||
{
|
||||
const int bar_height = (hues[i] / static_cast<float>(max_value)) * height;
|
||||
if (bar_height <= 0) continue;
|
||||
painter.drawLine(i, height - 1, i, height - bar_height);
|
||||
}
|
||||
|
||||
return histo;
|
||||
}
|
89
rpcs3/rpcs3qt/ps_move_tracker_dialog.h
Normal file
89
rpcs3/rpcs3qt/ps_move_tracker_dialog.h
Normal file
@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
|
||||
#include "Utilities/mutex.h"
|
||||
#include "Utilities/Thread.h"
|
||||
#include "Input/pad_thread.h"
|
||||
#include "Emu/Cell/Modules/cellGem.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QPixmap>
|
||||
#include <QTimer>
|
||||
#include <QThread>
|
||||
|
||||
class qt_camera_handler;
|
||||
class pad_thread;
|
||||
template <bool> class ps_move_tracker;
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class ps_move_tracker_dialog;
|
||||
}
|
||||
|
||||
class ps_move_tracker_dialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ps_move_tracker_dialog(QWidget* parent = nullptr);
|
||||
virtual ~ps_move_tracker_dialog();
|
||||
|
||||
private:
|
||||
void update_color(bool update_sliders = false);
|
||||
void update_hue(bool update_slider = false);
|
||||
void update_hue_threshold(bool update_slider = false);
|
||||
void update_saturation_threshold(bool update_slider = false);
|
||||
void update_min_radius(bool update_slider = false);
|
||||
void update_max_radius(bool update_slider = false);
|
||||
|
||||
void reset_camera();
|
||||
void process_camera_frame();
|
||||
|
||||
QPixmap get_histogram(const std::array<u32, 360>& hues, bool ignore_zero) const;
|
||||
|
||||
enum class view_mode
|
||||
{
|
||||
image,
|
||||
grayscale,
|
||||
hsv_hue,
|
||||
hsv_saturation,
|
||||
hsv_value,
|
||||
binary,
|
||||
contours
|
||||
};
|
||||
view_mode m_view_mode = view_mode::contours;
|
||||
|
||||
enum class histo_mode
|
||||
{
|
||||
unfiltered_hues
|
||||
};
|
||||
histo_mode m_histo_mode = histo_mode::unfiltered_hues;
|
||||
|
||||
std::unique_ptr<Ui::ps_move_tracker_dialog> ui;
|
||||
std::unique_ptr<qt_camera_handler> m_camera_handler;
|
||||
std::unique_ptr<ps_move_tracker<true>> m_ps_move_tracker;
|
||||
std::vector<u8> m_image_data_frozen;
|
||||
std::vector<u8> m_image_data;
|
||||
std::array<u32, 360> m_hues{};
|
||||
static constexpr s32 width = 640;
|
||||
static constexpr s32 height = 480;
|
||||
s32 m_format = 0;
|
||||
u64 m_frame_number = 0;
|
||||
u32 m_index = 0;
|
||||
bool m_filter_small_contours = true;
|
||||
bool m_freeze_frame = false;
|
||||
bool m_frame_frozen = false;
|
||||
bool m_show_all_contours = false;
|
||||
bool m_draw_contours = true;
|
||||
bool m_draw_overlays = true;
|
||||
|
||||
QPixmap m_image;
|
||||
QPixmap m_histogram;
|
||||
|
||||
// Thread control
|
||||
QTimer* m_update_timer = nullptr;
|
||||
std::unique_ptr<QThread> m_tracker_thread;
|
||||
atomic_t<u32> m_stop_threads = 0;
|
||||
std::mutex m_camera_handler_mutex;
|
||||
std::mutex m_image_mutex;
|
||||
std::shared_ptr<named_thread<pad_thread>> m_input_thread;
|
||||
};
|
393
rpcs3/rpcs3qt/ps_move_tracker_dialog.ui
Normal file
393
rpcs3/rpcs3qt/ps_move_tracker_dialog.ui
Normal file
@ -0,0 +1,393 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ps_move_tracker_dialog</class>
|
||||
<widget class="QDialog" name="ps_move_tracker_dialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>988</width>
|
||||
<height>710</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="main_layout" stretch="0,0">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="mainLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="previewLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gbPreview">
|
||||
<property name="title">
|
||||
<string>Preview</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="gb_view_layout">
|
||||
<item>
|
||||
<widget class="QLabel" name="imageLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>640</width>
|
||||
<height>480</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gistoGb">
|
||||
<property name="title">
|
||||
<string>Histogram</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="gistoGbLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="histogramLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::MinimumExpanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="rightLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="settingsGb">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>300</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="minRadiusGb">
|
||||
<property name="title">
|
||||
<string>Min Radius</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="minRadiusGbLayout">
|
||||
<item>
|
||||
<widget class="QSlider" name="minRadiusSlider">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="maxRadiusGb">
|
||||
<property name="title">
|
||||
<string>Max Radius</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="maxRadiusGbLayout">
|
||||
<item>
|
||||
<widget class="QSlider" name="maxRadiusSlider">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="psMoveGb">
|
||||
<property name="title">
|
||||
<string>PS Move</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="psMoveGbLayout">
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboSelectDevice"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="colorGb">
|
||||
<property name="title">
|
||||
<string>Color</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="colorGbLayout">
|
||||
<item>
|
||||
<widget class="QSlider" name="colorSliderR">
|
||||
<property name="maximum">
|
||||
<number>255</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="colorSliderG">
|
||||
<property name="maximum">
|
||||
<number>255</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="colorSliderB">
|
||||
<property name="maximum">
|
||||
<number>255</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="hueGb">
|
||||
<property name="title">
|
||||
<string>Hue</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="hueGbLayout">
|
||||
<item>
|
||||
<widget class="QSlider" name="hueSlider">
|
||||
<property name="maximum">
|
||||
<number>359</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="hueThresholdGb">
|
||||
<property name="title">
|
||||
<string>Hue Threshold</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="hueThresholdLayout">
|
||||
<item>
|
||||
<widget class="QSlider" name="hueThresholdSlider">
|
||||
<property name="maximum">
|
||||
<number>359</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="saturationThresholdGb">
|
||||
<property name="title">
|
||||
<string>Saturation Threshold</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="saturationThresholdGbLayout">
|
||||
<item>
|
||||
<widget class="QSlider" name="saturationThresholdSlider">
|
||||
<property name="maximum">
|
||||
<number>255</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="inputFormatGb">
|
||||
<property name="title">
|
||||
<string>Input Format</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="input_format_layout">
|
||||
<item>
|
||||
<widget class="QComboBox" name="inputFormatCombo"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="viewGb">
|
||||
<property name="title">
|
||||
<string>View</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="view_layout">
|
||||
<item>
|
||||
<widget class="QComboBox" name="viewCombo"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="histoSettingsGb">
|
||||
<property name="title">
|
||||
<string>Histogram</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="histoSettingsGbLayout">
|
||||
<item>
|
||||
<widget class="QComboBox" name="histoCombo"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="displayGb">
|
||||
<property name="title">
|
||||
<string>Display</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="filterGbLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="filterSmallContoursBox">
|
||||
<property name="text">
|
||||
<string>Filter Small Contours</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="freezeFrameBox">
|
||||
<property name="text">
|
||||
<string>Freeze Frame</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showAllContoursBox">
|
||||
<property name="text">
|
||||
<string>Show All Contours</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="drawContoursBox">
|
||||
<property name="text">
|
||||
<string>Draw Contours</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="drawOverlaysBox">
|
||||
<property name="text">
|
||||
<string>Draw Overlays</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::MinimumExpanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Apply|QDialogButtonBox::Close|QDialogButtonBox::Save</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>ps_move_tracker_dialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>ps_move_tracker_dialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -279,6 +279,7 @@ public:
|
||||
const QString dualsense_linux = tr("The DualSense handler is recommended for official DualSense controllers.");
|
||||
const QString dualsense_other = tr("The DualSense handler is recommended for official DualSense controllers.");
|
||||
const QString skateboard = tr("The Skateboard handler is recommended for official RIDE skateboard controllers.");
|
||||
const QString move = tr("The PS Move handler is recommended for official PS Move controllers.");
|
||||
const QString xinput = tr("The XInput handler will work with Xbox controllers and many third-party PC-compatible controllers. Pressure sensitive buttons from SCP are supported when SCP's XInput1_3.dll is placed in the main RPCS3 directory. For more details, see the <a %0 href=\"https://wiki.rpcs3.net/index.php?title=Help:Controller_Configuration\">RPCS3 Wiki</a>.").arg(gui::utils::get_link_style());
|
||||
const QString evdev = tr("The evdev handler should work with any controller that has Linux support.<br>If your joystick is not being centered properly, read the <a %0 href=\"https://wiki.rpcs3.net/index.php?title=Help:Controller_Configuration\">RPCS3 Wiki</a> for instructions.").arg(gui::utils::get_link_style());
|
||||
const QString mmjoy = tr("The MMJoystick handler should work with almost any controller recognized by Windows. However, it is recommended that you use the more specific handlers if you have a controller that supports them.");
|
||||
|
Loading…
Reference in New Issue
Block a user