// Quick and dirty code to implement drag lock independent of the X11
// libinput code I've been using to do this:
//
//    xinput --set-button-map "$nm" 1 8 3 4 5 6 7 2
//    xinput --set-prop --type=int --format=8 "$nm" \
//       'libinput Drag Lock Buttons' 2 1
//
// Since no Wayland compositor seems to have any support for such
// "complicated" mouse settings, I figure doing it in microcode
// before it gets to linux would free me up from dependence on
// pitiful "new and improved" software.
//
// This version is just a proof of concept. It doesn't bother checking to
// see that the real trackball was plugged in or provide any way to do
// anything other than just emulate exactly what I've always done with X11.
// I used this a while now, and it appears to behave identically to
// the X11 libinput tweaks above, only with no tweaks required.

#include "USBHost_t36.h"

class mouse_state {
public:
   mouse_state();

   mouse_state(MouseController & mouse);

   mouse_state &
   operator=(const mouse_state & cpy);

   bool
   same_state(const mouse_state & cmp) const;

   uint32_t
   get_buttons() const {
         return cur_buttons;
      }

   int
   get_x() const {
         return cur_x;
      }

   int
   get_y() const {
         return cur_y;
      }

   int
   get_wheel() const {
         return cur_wheel;
      }

   int
   get_wheelh() const {
         return cur_wheelh;
      }

   void
   set_buttons(uint32_t new_buttons) {
         cur_buttons = new_buttons;
      }

private:
  uint32_t cur_buttons;
  int cur_x;
  int cur_y;
  int cur_wheel;
  int cur_wheelh;
};

mouse_state::mouse_state()
 : cur_buttons(0),
   cur_x(0),
   cur_y(0),
   cur_wheel(0),
   cur_wheelh(0) {
}

mouse_state &
mouse_state::operator=(const mouse_state & cpy) {
   cur_buttons = cpy.cur_buttons;
   cur_x = cpy.cur_x;
   cur_y = cpy.cur_y;
   cur_wheel = cpy.cur_wheel;
   cur_wheelh = cpy.cur_wheelh;
   return *this;
}

mouse_state::mouse_state(MouseController & mouse) {
   cur_buttons = mouse.getButtons();
   cur_x = mouse.getMouseX();
   cur_y = mouse.getMouseY();
   cur_wheel = mouse.getWheel();
   cur_wheelh = mouse.getWheelH();
}

bool
mouse_state::same_state(const mouse_state & cmp) const {
   return (cur_buttons == cmp.cur_buttons) &&
          (cur_x == cmp.cur_x) &&
          (cur_y == cmp.cur_y) &&
          (cur_wheel == cmp.cur_wheel) &&
          (cur_wheelh == cmp.cur_wheelh);
}

USBHost myusb;
USBHIDParser hid(myusb);
MouseController mouse(myusb);
bool dragging = false;

void ProcessMouseData() {
  mouse_state prev_state;

  if (mouse.available()) {
    mouse_state cur_state(mouse);
    uint32_t mask = cur_state.get_buttons();
    if ((mask & 0x04) == 0x04) {
       dragging = ! dragging; // toggle dragging flag when A is pressed
       mask ^= 0x04;          // turn off button A in mask
    }
    if (dragging) {
       mask |= 0x01;          // while dragging report button 1 is pressed
    }
    if ((mask & 0x08) == 0x08) {
       mask ^= 0x08;          // always change button D to button A
       mask |= 0x04;
    }
    cur_state.set_buttons(mask);
    Mouse.mask_and_move(cur_state.get_buttons(),
                        cur_state.get_x(),
                        cur_state.get_y(),
                        cur_state.get_wheel(),
                        cur_state.get_wheelh());
    prev_state = cur_state;
    mouse.mouseDataClear();
  }
}

void setup()
{
  myusb.begin();
}

void loop()
{
  myusb.Task();
  ProcessMouseData();
}
