diff --git a/rpcs3/Emu/Io/PadHandler.cpp b/rpcs3/Emu/Io/PadHandler.cpp index d97f7a0642..9c22f2d857 100644 --- a/rpcs3/Emu/Io/PadHandler.cpp +++ b/rpcs3/Emu/Io/PadHandler.cpp @@ -278,6 +278,11 @@ bool PadHandlerBase::has_led() const return b_has_led; } +bool PadHandlerBase::has_rgb() const +{ + return b_has_rgb; +} + bool PadHandlerBase::has_battery() const { return b_has_battery; diff --git a/rpcs3/Emu/Io/PadHandler.h b/rpcs3/Emu/Io/PadHandler.h index 4cf95af00c..b9dd6d2601 100644 --- a/rpcs3/Emu/Io/PadHandler.h +++ b/rpcs3/Emu/Io/PadHandler.h @@ -75,6 +75,7 @@ protected: int m_thumb_threshold = 0; bool b_has_led = false; + bool b_has_rgb = false; bool b_has_battery = false; bool b_has_deadzones = false; bool b_has_rumble = false; @@ -145,6 +146,7 @@ public: bool has_rumble() const; bool has_deadzones() const; bool has_led() const; + bool has_rgb() const; bool has_battery() const; void set_player(u32 player_id) { m_player_id = player_id; } diff --git a/rpcs3/Input/ds3_pad_handler.cpp b/rpcs3/Input/ds3_pad_handler.cpp index efd3bcf187..439734861e 100644 --- a/rpcs3/Input/ds3_pad_handler.cpp +++ b/rpcs3/Input/ds3_pad_handler.cpp @@ -35,6 +35,8 @@ struct ds3_output_report ds3_led led_5; // reserved for another LED }; +constexpr u8 battery_capacity[] = {0, 1, 25, 50, 75, 100}; + constexpr u16 DS3_VID = 0x054C; constexpr u16 DS3_PID = 0x0268; @@ -76,6 +78,9 @@ ds3_pad_handler::ds3_pad_handler() b_has_config = true; b_has_rumble = true; b_has_deadzones = true; + b_has_battery = true; + b_has_led = true; + b_has_rgb = false; m_name_string = "DS3 Pad #"; m_max_devices = CELL_PAD_MAX_PORT_NUM; @@ -98,6 +103,16 @@ ds3_pad_handler::~ds3_pad_handler() } } +u32 ds3_pad_handler::get_battery_level(const std::string& padId) +{ + std::shared_ptr device = get_hid_device(padId); + if (!device || !device->hidDevice) + { + return 0; + } + return std::clamp(device->battery_level, 0, 100); +} + void ds3_pad_handler::SetPadData(const std::string& padId, u32 largeMotor, u32 smallMotor, s32/* r*/, s32/* g*/, s32 /* b*/, bool /*battery_led*/, u32 /*battery_led_brightness*/) { std::shared_ptr device = get_hid_device(padId); @@ -123,30 +138,53 @@ void ds3_pad_handler::SetPadData(const std::string& padId, u32 largeMotor, u32 s } } + ensure(device->config); + // Start/Stop the engines :) send_output_report(device.get()); } int ds3_pad_handler::send_output_report(ds3_device* ds3dev) { - if (!ds3dev || !ds3dev->hidDevice) + if (!ds3dev || !ds3dev->hidDevice || !ds3dev->config) return -2; ds3_output_report output_report; output_report.rumble.small_motor_on = ds3dev->small_motor; output_report.rumble.large_motor_force = ds3dev->large_motor; - switch (m_player_id) + if (ds3dev->config->led_battery_indicator) { - case 0: output_report.led_enabled = 0b00000010; break; - case 1: output_report.led_enabled = 0b00000100; break; - case 2: output_report.led_enabled = 0b00001000; break; - case 3: output_report.led_enabled = 0b00010000; break; - case 4: output_report.led_enabled = 0b00010010; break; - case 5: output_report.led_enabled = 0b00010100; break; - case 6: output_report.led_enabled = 0b00011000; break; - default: - fmt::throw_exception("DS3 is using forbidden player id %d", m_player_id); + if (ds3dev->battery_level >= 75) + output_report.led_enabled = 0b00010000; + else if (ds3dev->battery_level >= 50) + output_report.led_enabled = 0b00001000; + else if (ds3dev->battery_level >= 25) + output_report.led_enabled = 0b00000100; + else + output_report.led_enabled = 0b00000010; + } + else + { + switch (m_player_id) + { + case 0: output_report.led_enabled = 0b00000010; break; + case 1: output_report.led_enabled = 0b00000100; break; + case 2: output_report.led_enabled = 0b00001000; break; + case 3: output_report.led_enabled = 0b00010000; break; + case 4: output_report.led_enabled = 0b00010010; break; + case 5: output_report.led_enabled = 0b00010100; break; + case 6: output_report.led_enabled = 0b00011000; break; + default: + fmt::throw_exception("DS3 is using forbidden player id %d", m_player_id); + } + } + + if (ds3dev->config->led_low_battery_blink && ds3dev->battery_level < 25) + { + output_report.led[3].interval_duration = 0x14; // 2 seconds + output_report.led[3].interval_portion_on = ds3dev->led_delay_on; + output_report.led[3].interval_portion_off = ds3dev->led_delay_off; } return hid_write(ds3dev->hidDevice, &output_report.report_id, sizeof(output_report)); @@ -194,10 +232,9 @@ void ds3_pad_handler::init_config(pad_config* cfg, const std::string& name) cfg->lpadsquircling.def = 0; cfg->rpadsquircling.def = 0; - // Set color value - cfg->colorR.def = 0; - cfg->colorG.def = 0; - cfg->colorB.def = 0; + // Set default LED options + cfg->led_battery_indicator.def = false; + cfg->led_low_battery_blink.def = true; // apply defaults cfg->from_default(); @@ -386,6 +423,23 @@ void ds3_pad_handler::get_extended_info(const std::shared_ptr& device if (!ds3dev || !pad) return; + const u8 battery_status = ds3dev->padData[12 + DS3_HID_OFFSET]; + + if (battery_status >= 0xEE) + { + // Charging (0xEE) or full (0xEF). Let's set the level to 100%. + ds3dev->battery_level = 100; + ds3dev->cable_state = 1; + } + else + { + ds3dev->battery_level = battery_capacity[std::min(battery_status, 5)]; + ds3dev->cable_state = 0; + } + + pad->m_battery_level = ds3dev->battery_level; + pad->m_cable_state = ds3dev->cable_state; + // For unknown reasons the sixaxis values seem to be in little endian on linux #ifdef _WIN32 @@ -507,6 +561,38 @@ void ds3_pad_handler::apply_pad_data(const std::shared_ptr& device, c const int speed_large = config->enable_vibration_motor_large ? pad->m_vibrateMotors[idx_l].m_value : vibration_min; const int speed_small = config->enable_vibration_motor_small ? pad->m_vibrateMotors[idx_s].m_value : vibration_min; + const bool wireless = dev->cable_state == 0; + const bool low_battery = dev->battery_level < 25; + const bool is_blinking = dev->led_delay_on > 0 || dev->led_delay_off > 0; + + // Blink LED when battery is low + if (config->led_low_battery_blink) + { + // we are now wired or have okay battery level -> stop blinking + if (is_blinking && !(wireless && low_battery)) + { + dev->led_delay_on = 0; + dev->led_delay_off = 0; + dev->new_output_data = true; + } + // we are now wireless and low on battery -> blink + else if (!is_blinking && wireless && low_battery) + { + dev->led_delay_on = 0x80; + dev->led_delay_off = 0xFF - dev->led_delay_on; + dev->new_output_data = true; + } + } + + // Use LEDs to indicate battery level + if (config->led_battery_indicator) + { + if (dev->last_battery_level != dev->battery_level) + { + dev->new_output_data = true; + } + } + dev->new_output_data |= dev->large_motor != speed_large || dev->small_motor != speed_small; dev->large_motor = speed_large; diff --git a/rpcs3/Input/ds3_pad_handler.h b/rpcs3/Input/ds3_pad_handler.h index c05f174fa1..59dd34a61f 100644 --- a/rpcs3/Input/ds3_pad_handler.h +++ b/rpcs3/Input/ds3_pad_handler.h @@ -81,6 +81,7 @@ public: ~ds3_pad_handler(); void SetPadData(const std::string& padId, u32 largeMotor, u32 smallMotor, s32 r, s32 g, s32 b, bool battery_led, u32 battery_led_brightness) override; + u32 get_battery_level(const std::string& padId) override; void init_config(pad_config* cfg, const std::string& name) override; private: diff --git a/rpcs3/Input/ds4_pad_handler.cpp b/rpcs3/Input/ds4_pad_handler.cpp index e121b9a5a6..18949797b1 100644 --- a/rpcs3/Input/ds4_pad_handler.cpp +++ b/rpcs3/Input/ds4_pad_handler.cpp @@ -118,6 +118,7 @@ ds4_pad_handler::ds4_pad_handler() b_has_rumble = true; b_has_deadzones = true; b_has_led = true; + b_has_rgb = true; b_has_battery = true; m_name_string = "DS4 Pad #"; diff --git a/rpcs3/Input/dualsense_pad_handler.cpp b/rpcs3/Input/dualsense_pad_handler.cpp index e2439e22b3..270fa619d4 100644 --- a/rpcs3/Input/dualsense_pad_handler.cpp +++ b/rpcs3/Input/dualsense_pad_handler.cpp @@ -137,6 +137,7 @@ dualsense_pad_handler::dualsense_pad_handler() b_has_rumble = true; b_has_deadzones = true; b_has_led = true; + b_has_rgb = true; b_has_battery = false; m_name_string = "DualSense Pad #"; diff --git a/rpcs3/rpcs3qt/pad_led_settings_dialog.cpp b/rpcs3/rpcs3qt/pad_led_settings_dialog.cpp index 8b5af140d2..a0f46cb370 100644 --- a/rpcs3/rpcs3qt/pad_led_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/pad_led_settings_dialog.cpp @@ -5,7 +5,7 @@ #include #include -pad_led_settings_dialog::pad_led_settings_dialog(QDialog* parent, int colorR, int colorG, int colorB, bool has_battery, bool led_low_battery_blink, bool led_battery_indicator, int led_battery_indicator_brightness) +pad_led_settings_dialog::pad_led_settings_dialog(QDialog* parent, int colorR, int colorG, int colorB, bool has_rgb, bool has_battery, bool led_low_battery_blink, bool led_battery_indicator, int led_battery_indicator_brightness) : QDialog(parent) , ui(new Ui::pad_led_settings_dialog) , m_initial{colorR, colorG, colorB, led_low_battery_blink, led_battery_indicator, led_battery_indicator_brightness} @@ -18,11 +18,11 @@ pad_led_settings_dialog::pad_led_settings_dialog(QDialog* parent, int colorR, in ui->cb_led_blink->setChecked(m_new.low_battery_blink); ui->cb_led_indicate->setChecked(m_new.battery_indicator); - switch_groupboxes(m_new.battery_indicator); update_slider_label(m_new.battery_indicator_brightness); + ui->gb_led_color->setEnabled(has_rgb); ui->gb_battery_status->setEnabled(has_battery); - ui->gb_indicator_brightness->setEnabled(has_battery); + ui->gb_indicator_brightness->setEnabled(has_battery && has_rgb); // Let's restrict this to rgb capable devices for now connect(ui->bb_dialog_buttons, &QDialogButtonBox::clicked, [this](QAbstractButton* button) { @@ -42,22 +42,26 @@ pad_led_settings_dialog::pad_led_settings_dialog(QDialog* parent, int colorR, in } Q_EMIT pass_led_settings(m_new.cR, m_new.cG, m_new.cB, m_new.low_battery_blink, m_new.battery_indicator, m_new.battery_indicator_brightness); }); - connect(ui->b_colorpicker, &QPushButton::clicked, [this]() + + if (has_rgb) { - const QColor led_color(m_new.cR, m_new.cG, m_new.cB); - QColorDialog dlg(led_color, this); - dlg.setWindowTitle(tr("LED Color")); - if (dlg.exec() == QColorDialog::Accepted) + connect(ui->b_colorpicker, &QPushButton::clicked, [this]() { - const QColor new_color = dlg.selectedColor(); - m_new.cR = new_color.red(); - m_new.cG = new_color.green(); - m_new.cB = new_color.blue(); - redraw_color_sample(); - } - }); - connect(ui->hs_indicator_brightness, &QAbstractSlider::valueChanged, this, &pad_led_settings_dialog::update_slider_label); - connect(ui->cb_led_indicate, &QCheckBox::toggled, this, &pad_led_settings_dialog::switch_groupboxes); + const QColor led_color(m_new.cR, m_new.cG, m_new.cB); + QColorDialog dlg(led_color, this); + dlg.setWindowTitle(tr("LED Color")); + if (dlg.exec() == QColorDialog::Accepted) + { + const QColor new_color = dlg.selectedColor(); + m_new.cR = new_color.red(); + m_new.cG = new_color.green(); + m_new.cB = new_color.blue(); + redraw_color_sample(); + } + }); + connect(ui->hs_indicator_brightness, &QAbstractSlider::valueChanged, this, &pad_led_settings_dialog::update_slider_label); + connect(ui->cb_led_indicate, &QCheckBox::toggled, this, &pad_led_settings_dialog::battery_indicator_checked); + } // Draw color sample after showing the dialog, in order to have proper dimensions show(); @@ -107,9 +111,9 @@ void pad_led_settings_dialog::update_slider_label(int val) ui->l_indicator_brightness->setText(label); } -void pad_led_settings_dialog::switch_groupboxes(bool tick) +void pad_led_settings_dialog::battery_indicator_checked(bool checked) { - ui->gb_indicator_brightness->setEnabled(tick); + ui->gb_indicator_brightness->setEnabled(checked); } void pad_led_settings_dialog::read_form_values() diff --git a/rpcs3/rpcs3qt/pad_led_settings_dialog.h b/rpcs3/rpcs3qt/pad_led_settings_dialog.h index 4717a71402..79c8fa383b 100644 --- a/rpcs3/rpcs3qt/pad_led_settings_dialog.h +++ b/rpcs3/rpcs3qt/pad_led_settings_dialog.h @@ -14,7 +14,7 @@ class pad_led_settings_dialog : public QDialog Q_OBJECT public: - explicit pad_led_settings_dialog(QDialog* parent, int colorR, int colorG, int colorB, bool has_battery, bool led_low_battery_blink, bool led_battery_indicator, int led_battery_indicator_brightness); + explicit pad_led_settings_dialog(QDialog* parent, int colorR, int colorG, int colorB, bool has_rgb, bool has_battery, bool led_low_battery_blink, bool led_battery_indicator, int led_battery_indicator_brightness); ~pad_led_settings_dialog(); Q_SIGNALS: @@ -22,7 +22,7 @@ Q_SIGNALS: private Q_SLOTS: void update_slider_label(int val); - void switch_groupboxes(bool tick); + void battery_indicator_checked(bool checked); private: void redraw_color_sample(); diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.cpp b/rpcs3/rpcs3qt/pad_settings_dialog.cpp index 94961710d9..1ef24117fe 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/pad_settings_dialog.cpp @@ -378,7 +378,7 @@ void pad_settings_dialog::InitButtons() { // Allow LED battery indication while the dialog is open m_handler->SetPadData(m_device_name, 0, 0, m_handler_cfg.colorR, m_handler_cfg.colorG, m_handler_cfg.colorB, m_handler_cfg.led_battery_indicator.get(), m_handler_cfg.led_battery_indicator_brightness); - pad_led_settings_dialog dialog(this, m_handler_cfg.colorR, m_handler_cfg.colorG, m_handler_cfg.colorB, m_handler->has_battery(), m_handler_cfg.led_low_battery_blink.get(), m_handler_cfg.led_battery_indicator.get(), m_handler_cfg.led_battery_indicator_brightness); + pad_led_settings_dialog dialog(this, m_handler_cfg.colorR, m_handler_cfg.colorG, m_handler_cfg.colorB, m_handler->has_rgb(), m_handler->has_battery(), m_handler_cfg.led_low_battery_blink.get(), m_handler_cfg.led_battery_indicator.get(), m_handler_cfg.led_battery_indicator_brightness); connect(&dialog, &pad_led_settings_dialog::pass_led_settings, this, &pad_settings_dialog::apply_led_settings); dialog.exec(); m_handler->SetPadData(m_device_name, 0, 0, m_handler_cfg.colorR, m_handler_cfg.colorG, m_handler_cfg.colorB, false, m_handler_cfg.led_battery_indicator_brightness);