Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Logitech MX Master side (horizontal) scroll wheel #118

Open
cornfeedhobo opened this issue Jul 2, 2024 · 14 comments · May be fixed by #174
Open

Logitech MX Master side (horizontal) scroll wheel #118

cornfeedhobo opened this issue Jul 2, 2024 · 14 comments · May be fixed by #174

Comments

@cornfeedhobo
Copy link

cornfeedhobo commented Jul 2, 2024

I have a logitech plugged in and all the buttons work except the horizontal scroll.

I'll happily tackle this once I can figure out how to build for arm from my local setup.

Note: this is a placeholder issue for myself until I get time to investigate over a weekend. I will update this issue with more detail soon.

@jalmeroth
Copy link
Contributor

Hi @cornfeedhobo,

I started to work around my Logitech related issues in a seperate project over here: https://github.com/jalmeroth/deskhopl.

Would be good to know, if it solves your issue too.

@cornfeedhobo
Copy link
Author

@jalmeroth Interesting. What prevents this from being merged upstream? Why a dedicated fork?

@jalmeroth
Copy link
Contributor

jalmeroth commented Aug 23, 2024 via email

@FawazAT
Copy link

FawazAT commented Sep 30, 2024

Hi, I’m experiencing the same issue with the horizontal scroll. Did you manage to find a solution?

@cornfeedhobo
Copy link
Author

Nope. I'm waiting on @hrvach to have some free time to review @jalmeroth's work.

@Abdulaziz-Alsughaier
Copy link

This works for me, hope it helps.

Files Modified

  • src/include/hid_parser.h
  • src/include/main.h
  • src/include/usb_descriptors.h
  • src/hid_report.c
  • src/mouse.c
  • src/usb_descriptors.c

Changes Detail

File: src/include/hid_parser.h

Code Changes:

typedef struct {
  int32_t move_x;
  int32_t move_y;
  int32_t wheel;
- int32_t pan;
+ int32_t h_wheel;  // New field for horizontal scrolling
  uint32_t buttons;
} mouse_values_t;

typedef struct {
  report_val_t buttons;
  report_val_t move_x;
  report_val_t move_y;
  report_val_t wheel;
+ report_val_t h_wheel;  // Add horizontal wheel report value
  uint8_t report_id;
  bool is_found;
  bool uses_report_id;
} mouse_t;

File: src/include/main.h

Code Changes:

- #define MOUSE_REPORT_LENGTH 7
+ #define MOUSE_REPORT_LENGTH 8

 typedef struct TU_ATTR_PACKED {
   uint8_t buttons;
   int16_t x;
   int16_t y;
   int8_t wheel;
+ int8_t h_wheel; // New field for horizontal wheel
   uint8_t mode;
 } mouse_report_t;



- bool tud_mouse_report(uint8_t mode, uint8_t buttons, int16_t x, int16_t y, int8_t wheel);
+ bool tud_mouse_report(uint8_t mode, uint8_t buttons, int16_t x, int16_t y, int8_t wheel, int8_t h_wheel);

File: src/include/usb_descriptors.h

Code Changes:

Replace TUD_HID_REPORT_DESC_ABS_MOUSE With:
#define TUD_HID_REPORT_DESC_ABS_MOUSE(...) \
  HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP      )                   ,\
  HID_USAGE      ( HID_USAGE_DESKTOP_MOUSE     )                   ,\
  HID_COLLECTION ( HID_COLLECTION_APPLICATION  )                   ,\
    /* Report ID */\
    __VA_ARGS__ \
    HID_USAGE      ( HID_USAGE_DESKTOP_POINTER )                   ,\
    HID_COLLECTION ( HID_COLLECTION_PHYSICAL   )                   ,\
      HID_USAGE_PAGE  ( HID_USAGE_PAGE_BUTTON  )                   ,\
        HID_USAGE_MIN   ( 1                                      ) ,\
        HID_USAGE_MAX   ( 5                                      ) ,\
        HID_LOGICAL_MIN ( 0                                      ) ,\
        HID_LOGICAL_MAX ( 1                                      ) ,\
        /* Left, Right, Middle, Backward, Forward buttons */ \
        HID_REPORT_COUNT( 5                                      ) ,\
        HID_REPORT_SIZE ( 1                                      ) ,\
        HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\
        /* 3 bit padding */ \
        HID_REPORT_COUNT( 1                                      ) ,\
        HID_REPORT_SIZE ( 3                                      ) ,\
        HID_INPUT       ( HID_CONSTANT                           ) ,\
      HID_USAGE_PAGE  ( HID_USAGE_PAGE_DESKTOP )                   ,\
        /* X, Y position [0, 32767] */ \
        HID_USAGE       ( HID_USAGE_DESKTOP_X                    ) ,\
        HID_USAGE       ( HID_USAGE_DESKTOP_Y                    ) ,\
        HID_LOGICAL_MIN  ( 0x00                                ) ,\
        HID_LOGICAL_MAX_N( 0x7FFF, 2                           ) ,\
        HID_REPORT_SIZE  ( 16                                  ) ,\
        HID_REPORT_COUNT ( 2                                   ) ,\
        HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\
        /* Vertical wheel [-127, 127] */ \
        HID_USAGE       ( HID_USAGE_DESKTOP_WHEEL                )  ,\
        HID_LOGICAL_MIN ( 0x81                                   )  ,\
        HID_LOGICAL_MAX ( 0x7f                                   )  ,\
        HID_REPORT_COUNT( 1                                      )  ,\
        HID_REPORT_SIZE ( 8                                      )  ,\
        HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_RELATIVE )  ,\
        /* Horizontal wheel (AC Pan) */ \
        HID_USAGE_PAGE  ( HID_USAGE_PAGE_CONSUMER               )   ,\
        HID_LOGICAL_MIN ( 0x81                                   )   ,\
        HID_LOGICAL_MAX ( 0x7f                                   )   ,\
        HID_REPORT_COUNT( 1                                      )   ,\
        HID_REPORT_SIZE ( 8                                      )   ,\
        HID_USAGE_N     ( HID_USAGE_CONSUMER_AC_PAN, 2          )   ,\
        HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_RELATIVE )   ,\
        /* Mode byte */ \
        HID_REPORT_COUNT( 1                                      ) ,\
        HID_REPORT_SIZE ( 8                                      ) ,\
        HID_INPUT       ( HID_CONSTANT                           ) ,\
    HID_COLLECTION_END                                            , \
  HID_COLLECTION_END \
Replace TUD_HID_REPORT_DESC_MOUSEHELP With:
HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP      )                   ,\
HID_USAGE      ( HID_USAGE_DESKTOP_MOUSE     )                   ,\
HID_COLLECTION ( HID_COLLECTION_APPLICATION  )                   ,\
  /* Report ID if any */\
  __VA_ARGS__ \
  HID_USAGE      ( HID_USAGE_DESKTOP_POINTER )                   ,\
  HID_COLLECTION ( HID_COLLECTION_PHYSICAL   )                   ,\
    HID_USAGE_PAGE  ( HID_USAGE_PAGE_BUTTON  )                   ,\
      HID_USAGE_MIN   ( 1                                      ) ,\
      HID_USAGE_MAX   ( 5                                      ) ,\
      HID_LOGICAL_MIN ( 0                                      ) ,\
      HID_LOGICAL_MAX ( 1                                      ) ,\
      /* Left, Right, Middle, Backward, Forward buttons */ \
      HID_REPORT_COUNT( 5                                      ) ,\
      HID_REPORT_SIZE ( 1                                      ) ,\
      HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\
      /* 3 bit padding */ \
      HID_REPORT_COUNT( 1                                      ) ,\
      HID_REPORT_SIZE ( 3                                      ) ,\
      HID_INPUT       ( HID_CONSTANT                           ) ,\
    HID_USAGE_PAGE  ( HID_USAGE_PAGE_DESKTOP )                   ,\
      /* X, Y position [-32767, 32767] */ \
      HID_USAGE       ( HID_USAGE_DESKTOP_X                    ) ,\
      HID_USAGE       ( HID_USAGE_DESKTOP_Y                    ) ,\
      HID_LOGICAL_MIN_N ( 0x8000, 2                            ) ,\
      HID_LOGICAL_MAX_N ( 0x7fff, 2                            ) ,\
      HID_REPORT_SIZE ( 16                                     ) ,\
      HID_REPORT_COUNT( 2                                      ) ,\
      HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_RELATIVE ) ,\
      /* Vertical wheel [-127, 127] */ \
      HID_USAGE       ( HID_USAGE_DESKTOP_WHEEL                )  ,\
      HID_LOGICAL_MIN ( 0x81                                   )  ,\
      HID_LOGICAL_MAX ( 0x7f                                   )  ,\
      HID_REPORT_COUNT( 1                                      )  ,\
      HID_REPORT_SIZE ( 8                                      )  ,\
      HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_RELATIVE )  ,\
      /* Horizontal wheel (AC Pan) */ \
      HID_USAGE_PAGE  ( HID_USAGE_PAGE_CONSUMER               )   ,\
      HID_LOGICAL_MIN ( 0x81                                   )   ,\
      HID_LOGICAL_MAX ( 0x7f                                   )   ,\
      HID_REPORT_COUNT( 1                                      )   ,\
      HID_REPORT_SIZE ( 8                                      )   ,\
      HID_USAGE_N     ( HID_USAGE_CONSUMER_AC_PAN, 2          )   ,\
      HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_RELATIVE )   ,\
      /* Mode byte */ \
      HID_REPORT_COUNT( 1                                      ) ,\
      HID_REPORT_SIZE ( 8                                      ) ,\
      HID_INPUT       ( HID_CONSTANT                           ) ,\
  HID_COLLECTION_END                                            , \
HID_COLLECTION_END

File: src/hid_report.c

Code Changes:

int32_t get_report_value(uint8_t *report, report_val_t *val) {
    /* Calculate the bit offset within the byte */
    uint16_t offset_in_bits = val->offset % 8;

    /* Calculate the remaining bits in the first byte */
    uint16_t remaining_bits = 8 - offset_in_bits;

    /* Calculate the byte offset in the array */
    uint16_t byte_offset = val->offset >> 3;

    /* Create a mask for the specified number of bits */
    uint32_t mask = (1u << val->size) - 1;

    /* Initialize the result value with the bits from the first byte */
    int32_t result = report[byte_offset] >> offset_in_bits;

    /* Move to the next byte and continue fetching bits until the desired length is reached */
    while (val->size > remaining_bits) {
        result |= report[++byte_offset] << remaining_bits;
        remaining_bits += 8;
    }

    /* Apply the mask to retain only the desired number of bits */
    result = result & mask;

    /* Special case if our result is negative.
       Check if the most significant bit of 'val' is set */
-    if (result & ((mask >> 1) + 1)) {
+    /* Handle signed values (like wheel scrolling) */
+    if ((val->usage == HID_USAGE_DESKTOP_WHEEL || val->usage == HID_USAGE_CONSUMER_AC_PAN) && 
+        (result & 0x80)) {
+        // Sign extend
+        result |= 0xFFFFFF00;
+    }
+    else if (result & ((mask >> 1) + 1)) {
        /* If it is set, sign-extend 'val' by filling the higher bits with 1s */
        result |= (0xFFFFFFFFU << val->size);
    }

    return result;
}
Add this to extract_data:
        // Horizontal wheel (AC Pan)
        {.usage_page   = HID_USAGE_PAGE_CONSUMER,
        .global_usage = HID_USAGE_DESKTOP_MOUSE,
        .usage        = HID_USAGE_CONSUMER_AC_PAN,
        .handler      = _store,
        .receiver     = process_mouse_report,
        .dst          = &iface->mouse.h_wheel,
        .id           = &iface->mouse.report_id},

File: src/mouse.c

void extract_report_values(uint8_t *raw_report, device_t *state, mouse_values_t *values, hid_interface_t *iface) {
    /* Interpret values depending on the current protocol used. */
    if (iface->protocol == HID_PROTOCOL_BOOT) {
        hid_mouse_report_t *mouse_report = (hid_mouse_report_t *)raw_report;

        values->move_x  = mouse_report->x;
        values->move_y  = mouse_report->y;
        values->wheel   = mouse_report->wheel;
+        values->h_wheel = 0;  
        values->buttons = mouse_report->buttons;
        return;
    }

    /* If HID Report ID is used, the report is prefixed by the report ID so we have to move by 1 byte */
    if (iface->mouse.report_id)
        raw_report++;

    values->move_x  = get_report_value(raw_report, &iface->mouse.move_x);
    values->move_y  = get_report_value(raw_report, &iface->mouse.move_y);
    values->wheel   = get_report_value(raw_report, &iface->mouse.wheel);
+    values->h_wheel = get_report_value(raw_report, &iface->mouse.h_wheel); // Add horizontal wheel
    values->buttons = get_report_value(raw_report, &iface->mouse.buttons);
}

mouse_report_t create_mouse_report(device_t *state, mouse_values_t *values) {
    mouse_report_t mouse_report = {
        .buttons = values->buttons,
        .x       = state->pointer_x,
        .y       = state->pointer_y,
        .wheel   = values->wheel,
+        .h_wheel = values->h_wheel,  // Add horizontal wheel
        .mode    = ABSOLUTE,
    };

    /* Workaround for Windows multiple desktops */
    if (state->relative_mouse || state->gaming_mode) {
        mouse_report.x = values->move_x;
        mouse_report.y = values->move_y;
        mouse_report.mode = RELATIVE;
    }

    return mouse_report;
}
-    bool succeeded = tud_mouse_report(report.mode, report.buttons, report.x, report.y, report.wheel);
+    bool succeeded = tud_mouse_report(report.mode, report.buttons, report.x, report.y, report.wheel, report.h_wheel);

File: src/usb_descriptors.c

- bool tud_mouse_report(uint8_t mode, uint8_t buttons, int16_t x, int16_t y, int8_t wheel) {
-     mouse_report_t report = {.buttons = buttons, .wheel = wheel, .x = x, .y = y, .mode = mode};
+ bool tud_mouse_report(uint8_t mode, uint8_t buttons, int16_t x, int16_t y, int8_t wheel, int8_t h_wheel) {
+    mouse_report_t report = {.buttons = buttons, .wheel = wheel,.h_wheel = h_wheel, .x = x, .y = y, .mode = mode};

    uint8_t instance = ITF_NUM_HID;
    uint8_t report_id = REPORT_ID_MOUSE;

    if (mode == RELATIVE) {
        instance = ITF_NUM_HID_REL_M;
        report_id = REPORT_ID_RELMOUSE;
    }

    return tud_hid_n_report(instance, report_id, &report, sizeof(report));
}

@cornfeedhobo
Copy link
Author

@Abdulaziz-Alsughaier can you open a PR for this?
cc @hrvach

@hrvach
Copy link
Owner

hrvach commented Nov 18, 2024

I haven't properly understood this issue until reading the patch suggested. So, this "side scroll" is actually the horizontal wheel you're missing? Yeah, I can look into this but don't have any means to test so I'd kindly require somebody to try it out afterwards.

@jalmeroth
Copy link
Contributor

I haven't properly understood this issue until reading the patch suggested. So, this "side scroll" is actually the horizontal wheel you're missing? Yeah, I can look into this but don't have any means to test so I'd kindly require somebody to try it out afterwards.

In my setup it actually behaves exactly as the wheel field, just that it is the next byte (AC Pan).
The only thing thats different, is that it is using HID_USAGE_PAGE_CONSUMER.

Let me know, if you've something to test

@hrvach
Copy link
Owner

hrvach commented Nov 18, 2024

I haven't properly understood this issue until reading the patch suggested. So, this "side scroll" is actually the horizontal wheel you're missing? Yeah, I can look into this but don't have any means to test so I'd kindly require somebody to try it out afterwards.

In my setup it actually behaves exactly as the wheel field, just that it is the next byte (AC Pan). The only thing thats different, is that it is using HID_USAGE_PAGE_CONSUMER.

Let me know, if you've something to test

My only wheel not attached to a car is the vertical wheel on my mouse, so please excuse me asking dumb questions - how does AC Pan differ from horizontal scroll in behavior?

@jalmeroth
Copy link
Contributor

It's actually the same here: horizontal scrolling is sent via AC Pan field

I can send some reports later if you want

@jalmeroth
Copy link
Contributor

Here's the descriptor for starters:

0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
0x09, 0x02,        // Usage (Mouse)
0xA1, 0x01,        // Collection (Application)
0x85, 0x02,        //   Report ID (2)
0x09, 0x01,        //   Usage (Pointer)
0xA1, 0x00,        //   Collection (Physical)
0x95, 0x10,        //     Report Count (16)
0x75, 0x01,        //     Report Size (1)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0x01,        //     Logical Maximum (1)
0x05, 0x09,        //     Usage Page (Button)
0x19, 0x01,        //     Usage Minimum (0x01)
0x29, 0x10,        //     Usage Maximum (0x10)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x02,        //     Report Count (2)
0x75, 0x10,        //     Report Size (16)
0x16, 0x01, 0x80,  //     Logical Minimum (-32767)
0x26, 0xFF, 0x7F,  //     Logical Maximum (32767)
0x05, 0x01,        //     Usage Page (Generic Desktop Ctrls)
0x09, 0x30,        //     Usage (X)
0x09, 0x31,        //     Usage (Y)
0x81, 0x06,        //     Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01,        //     Report Count (1)
0x75, 0x08,        //     Report Size (8)
0x15, 0x81,        //     Logical Minimum (-127)
0x25, 0x7F,        //     Logical Maximum (127)
0x09, 0x38,        //     Usage (Wheel)
0x81, 0x06,        //     Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01,        //     Report Count (1)
0x05, 0x0C,        //     Usage Page (Consumer)
0x0A, 0x38, 0x02,  //     Usage (AC Pan)
0x81, 0x06,        //     Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              //   End Collection
0xC0,              // End Collection
0x05, 0x0C,        // Usage Page (Consumer)
0x09, 0x01,        // Usage (Consumer Control)
0xA1, 0x01,        // Collection (Application)
0x85, 0x03,        //   Report ID (3)
0x95, 0x02,        //   Report Count (2)
0x75, 0x10,        //   Report Size (16)
0x15, 0x01,        //   Logical Minimum (1)
0x26, 0xFF, 0x02,  //   Logical Maximum (767)
0x19, 0x01,        //   Usage Minimum (Consumer Control)
0x2A, 0xFF, 0x02,  //   Usage Maximum (0x02FF)
0x81, 0x00,        //   Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              // End Collection
0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
0x09, 0x80,        // Usage (Sys Control)
0xA1, 0x01,        // Collection (Application)
0x85, 0x04,        //   Report ID (4)
0x95, 0x01,        //   Report Count (1)
0x75, 0x02,        //   Report Size (2)
0x15, 0x01,        //   Logical Minimum (1)
0x25, 0x03,        //   Logical Maximum (3)
0x09, 0x82,        //   Usage (Sys Sleep)
0x09, 0x81,        //   Usage (Sys Power Down)
0x09, 0x83,        //   Usage (Sys Wake Up)
0x81, 0x00,        //   Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x75, 0x01,        //   Report Size (1)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x01,        //   Logical Maximum (1)
0x09, 0x9B,        //   Usage (0x9B)
0x81, 0x06,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x75, 0x05,        //   Report Size (5)
0x81, 0x03,        //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              // End Collection

// 133 bytes

A typical report looks like this:

020000000000000001

Notice the 0x01 which is scrolling horizontally one direction

and here in the other direction 0xff:

0200000000000000ff

Here parsed by wireshark:
swappy-20241118_140158
swappy-20241118_140219

Hope this helps

@cornfeedhobo
Copy link
Author

@hrvach I had some issues compiling the last time I tried, but assuming I can get over that hurdle, yes happy to test!

@cornfeedhobo cornfeedhobo changed the title Logitech MX Master side scroll Logitech MX Master side (horizontal) scroll wheel Nov 18, 2024
@jalmeroth jalmeroth linked a pull request Nov 18, 2024 that will close this issue
@jalmeroth
Copy link
Contributor

Hi @hrvach,

I think I nailed it. 😅 At least it works on my machine (see PR#174).

Let me know what you think,
Jan

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants