Skip to content

Commit

Permalink
Merge pull request #156 from iwanders/slim-pen2-surface-pro-9-button-…
Browse files Browse the repository at this point in the history
…glitch-fix

MPP v2+, Slim Pen 2, Surface Pro 9 improvements
  • Loading branch information
StollD committed Mar 3, 2024
2 parents f59b726 + 4895549 commit a34eaaa
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 6 deletions.
2 changes: 2 additions & 0 deletions src/core/generic/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
92 changes: 89 additions & 3 deletions src/core/generic/dft.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,20 @@ class DftStylus {
i32 m_imag = 0;
std::optional<u32> 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<u32> 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<bool> 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<bool> m_mppv2_in_contact = std::nullopt;

public:
DftStylus(Config config, const std::optional<const ipts::Metadata> &metadata)
: m_config {std::move(config)},
Expand All @@ -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;
Expand Down Expand Up @@ -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] +
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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;
}
};

Expand Down
10 changes: 10 additions & 0 deletions src/core/linux/config-loader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<const ipts::Metadata> &metadata)
: m_info {info}
Expand Down Expand Up @@ -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.");
}

/*!
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
}

/*!
Expand Down
8 changes: 5 additions & 3 deletions src/ipts/protocol/dft.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit a34eaaa

Please sign in to comment.