diff --git a/src/core/generic/config.hpp b/src/core/generic/config.hpp index fb90d6f..dfeb0c2 100644 --- a/src/core/generic/config.hpp +++ b/src/core/generic/config.hpp @@ -57,6 +57,8 @@ class Config { usize dft_button_min_mag = 1000; usize dft_freq_min_mag = 10000; usize dft_tilt_min_mag = 10000; + usize dft_mpp2_button_min_mag = 50000; + usize dft_mpp2_contact_min_mag = 50000; f64 dft_tilt_distance = 0.6; public: diff --git a/src/core/generic/dft.hpp b/src/core/generic/dft.hpp index ee297b0..33e72c9 100644 --- a/src/core/generic/dft.hpp +++ b/src/core/generic/dft.hpp @@ -28,6 +28,20 @@ class DftStylus { i32 m_imag = 0; std::optional m_group = std::nullopt; + // For the MPP v2 button detection we only care about the first binary + // (0x0a) dft window, but there's two in the group, we keep track of the + // group when 0x0a was encountered, this allows comparing against this + // value to use only the first window in the group. + std::optional m_mppv2_binary_group = std::nullopt; + + // Boolean to track whether a button is held according to mpp v2. + // This is used instead of the button threshold detection. + std::optional m_mppv2_button_or_eraser = std::nullopt; + + // Boolean to track whether the pen is in contact according to mpp v2. + // This is used to override the contact state in the pressure frame handling. + std::optional m_mppv2_in_contact = std::nullopt; + public: DftStylus(Config config, const std::optional &metadata) : m_config {std::move(config)}, @@ -50,6 +64,12 @@ class DftStylus { case ipts::protocol::dft::Type::Pressure: this->handle_pressure(dft); break; + case ipts::protocol::dft::Type::PositionMPP_2: + this->handle_position_mpp_2(dft); + break; + case ipts::protocol::dft::Type::BinaryMPP_2: + this->handle_dft_binary_mpp_2(dft); + break; default: // Ignored break; @@ -171,8 +191,10 @@ class DftStylus { bool button = false; bool rubber = false; - if (dft.x[0].magnitude > m_config.dft_button_min_mag && - dft.y[0].magnitude > m_config.dft_button_min_mag) { + // If mppv2 has decided on a button state use that, else use the magnitude decision. + if (m_mppv2_button_or_eraser.value_or( + dft.x[0].magnitude > m_config.dft_button_min_mag && + dft.y[0].magnitude > m_config.dft_button_min_mag)) { const i32 real = dft.x[0].real[ipts::protocol::dft::NUM_COMPONENTS / 2] + dft.y[0].real[ipts::protocol::dft::NUM_COMPONENTS / 2]; const i32 imag = dft.x[0].imag[ipts::protocol::dft::NUM_COMPONENTS / 2] + @@ -206,11 +228,72 @@ class DftStylus { m_stylus.contact = true; m_stylus.pressure = std::clamp(p, 0.0, 1.0); } else { - m_stylus.contact = false; + m_stylus.contact = m_mppv2_in_contact.value_or(false); m_stylus.pressure = 0; } } + /*! + * Determines the current button state from the 0x0a frame, it can + * only be used for MPP v2 pens. The eraser is still obtained from the + * phase using the button frame. + */ + void handle_dft_binary_mpp_2(const ipts::DftWindow &dft) + { + if (dft.x.size() <= 5) { // not sure if this can happen? + return; + } + + // Second time we see this dft window in this group, skip it. + if (!dft.group.has_value() || m_mppv2_binary_group == dft.group) + return; + m_mppv2_binary_group = dft.group; + + // Clearing the state in case we can't determine it. + m_mppv2_button_or_eraser = std::nullopt; + + // Now, we can process the frame to determine button state. + // First, collapse x and y, they convey the same information. + const auto mag_4 = dft.x[4].magnitude + dft.y[4].magnitude; + const auto mag_5 = dft.x[5].magnitude + dft.y[5].magnitude; + const auto threshold = 2 * m_config.dft_mpp2_button_min_mag; + + if (mag_4 < threshold && mag_5 < threshold) { + // Not enough signal to make a decision. + return; + } + + // One of them is above the threshold, if 5 is higher than 4, button + // is held. + m_mppv2_button_or_eraser = mag_4 < mag_5; + } + + /*! + * Determines whether the pen is making contact with the screen, it can + * only be used for MPP v2 pens. + */ + void handle_position_mpp_2(const ipts::DftWindow &dft) + { + // Clearing the state in case we can't determine it. + m_mppv2_in_contact = std::nullopt; + + if (dft.x.size() <= 3) { // not sure if this can happen? + return; + } + + const auto mag_2 = dft.x[2].magnitude + dft.y[2].magnitude; + const auto mag_3 = dft.x[3].magnitude + dft.y[3].magnitude; + + const auto threshold = 2 * m_config.dft_mpp2_contact_min_mag; + if (mag_2 < threshold && mag_3 < threshold) { + // Not enough signal to make a decision. + return; + } + + // The pen switches the row from two to three when there's contact. + m_mppv2_in_contact = mag_2 < mag_3; + } + /*! * Interpolates the current stylus position from a list of antenna measurements. * @@ -335,6 +418,9 @@ class DftStylus { m_stylus.contact = false; m_stylus.button = false; m_stylus.rubber = false; + + m_mppv2_in_contact = std::nullopt; + m_mppv2_button_or_eraser = std::nullopt; } }; diff --git a/src/core/linux/config-loader.hpp b/src/core/linux/config-loader.hpp index 61b57a3..93eea34 100644 --- a/src/core/linux/config-loader.hpp +++ b/src/core/linux/config-loader.hpp @@ -27,6 +27,8 @@ class ConfigLoader { Config m_config {}; DeviceInfo m_info; + bool m_loaded_config = false; + public: ConfigLoader(const DeviceInfo &info, const std::optional &metadata) : m_info {info} @@ -56,6 +58,9 @@ class ConfigLoader { this->load_file(common::buildopts::ConfigFile); this->load_dir(common::buildopts::ConfigDir, false); + + if (!m_loaded_config) + spdlog::info("No config file loaded, using default values."); } /*! @@ -127,6 +132,8 @@ class ConfigLoader { */ void load_file(const std::filesystem::path &path) { + spdlog::info("Loading config {}.", path.c_str()); + const INIReader ini {path}; if (ini.ParseError() != 0) @@ -169,12 +176,15 @@ class ConfigLoader { this->get(ini, "DFT", "FreqMinMag", m_config.dft_freq_min_mag); this->get(ini, "DFT", "TiltMinMag", m_config.dft_tilt_min_mag); this->get(ini, "DFT", "TiltDistance", m_config.dft_tilt_distance); + this->get(ini, "DFT", "Mpp2ContactMinMag", m_config.dft_mpp2_contact_min_mag); + this->get(ini, "DFT", "Mpp2ButtonMinMag", m_config.dft_mpp2_button_min_mag); // Legacy options that are kept for compatibility this->get(ini, "DFT", "TipDistance", m_config.stylus_tip_distance); this->get(ini, "Contacts", "SizeThreshold", m_config.contacts_size_thresh_max); // clang-format on + m_loaded_config = true; } /*! diff --git a/src/ipts/protocol/dft.hpp b/src/ipts/protocol/dft.hpp index f6267bc..c85db86 100644 --- a/src/ipts/protocol/dft.hpp +++ b/src/ipts/protocol/dft.hpp @@ -14,9 +14,11 @@ constexpr u8 MAX_ROWS = 16; constexpr u8 PRESSURE_ROWS = 6; enum class Type : u8 { - Position = 6, - Button = 9, - Pressure = 11, + Position = 0x6, + PositionMPP_2 = 0x7, + Button = 0x9, + BinaryMPP_2 = 0xA, + Pressure = 0xB, }; struct [[gnu::packed]] Metadata {