Skip to content

Rotation not being applied when moving along one axis #90

@AndyFilter

Description

@AndyFilter

Hey!
It seems as though events (EV_REL) with null values are being skipped by the Linux kernel. This creates an issue when combined with rotation, where sometimes an additional axis of movement has to be "injected" as it was missing in the original frame.

How to reproduce?

  • Set rotation to a non-zero value (preferably something significant like 45 deg.)
  • Move the mouse either exactly vertical or horizontal.

Expected behaviour

Mouse moves at the set angle from one of the axes.

Observed behaviour

Mouse moves in a straight line along one of the axes as if the rotation wasn't applied at all.

Possible solutions

On Kernel newer than 6.11.0

It is technically possible to inject new events into the frame by simply writing into the input_value * array. This can be done like so:

  /* Inject missing axes if needed (transformed non-NONE but not seen in this frame) */
  if (x != NONE_EVENT_VALUE && !seen_x && ksize(vals) > (end - vals + 1) * sizeof(*vals)) {
      end->type = EV_REL;
      end->code = REL_X;
      end->value = x;
      end++;
  }
  // Same for the other axis

(where x is the horizontal move delta after acceleration, seen_x denotes whether a REL_X was spotted within this frame and vals/end is the input_value * array)

The problem

The more "sharp-eyed" will notice a quite obvious problem, we're writing into an end pointer, which causes a OOB write inside the kernel. Or does it? (VSauce music starts playing)
The even more "sharp-eyed" will also notice a "bounds check" of sorts ksize(vals) > (end - vals + 1) * sizeof(*vals). This should indeed ensure that we never write outside the allocated space for the buffer. But is it enough? From what I've checked this buffer usually is quite large (128 bytes - big enough for 16 frames). The problem is that perhaps not all of this was meant to be modified by the driver. This is unlikely, but possible and it's better not to take chances like this within the kernel. Though I've run tests with this code and it works just fine. After all, there'a a reason why one can return the number of events in a given frame.

#if __cleanup_events
return _count;
#endif

The bigger problem (older kernel versions)

Unfortunately, this cannot be replicated on older versions of the kernel as it requires the ability to return the number of events in the frame. Which was introduced in 6.11.0. I've tried to modify the buffer and hope for the best but the kernel just ignores it (as to be expected). There also is something interesting within the input_dev structure:

struct input_dev {
  // ...
  unsigned int num_vals;
  unsigned int max_vals;
  struct input_value *vals;
  // ...
}

But modifying this (and only this) seems to be giving very inconsistent results.

In Conclusion

It’s better to support newer kernels than to support nothing. But let's not give up without a fight.

This issue is also tracked on my repo here.

Metadata

Metadata

Assignees

Labels

bugSomething isn't workinggood first issueGood for newcomershelp wantedExtra attention is needed

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions