mirror of https://github.com/nirenjan/libx52.git
				
				
				
			
		
			
				
	
	
		
			307 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			C
		
	
	
			
		
		
	
	
			307 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			C
		
	
	
| /*
 | |
|  * Saitek X52 IO driver - report parser
 | |
|  *
 | |
|  * Copyright (C) 2012-2020 Nirenjan Krishnan (nirenjan@nirenjan.org)
 | |
|  *
 | |
|  * SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
 | |
|  */
 | |
| 
 | |
| #include <stdint.h>
 | |
| #include "io_common.h"
 | |
| #include "usb-ids.h"
 | |
| 
 | |
| static void map_hat(uint8_t hat, libx52io_report *report)
 | |
| {
 | |
|     /*
 | |
|      * Hat reports values from 0-8, but just to account for any spurious
 | |
|      * values, leave the remaining 7 entries blank.
 | |
|      *
 | |
|      * Pushing the hat North reports 1, and it increases to 2 for NE, 3 for
 | |
|      * East, 4 for SE and so on in a clockwise fashion until it hits 8 for NW.
 | |
|      *
 | |
|      * According to the USB spec, Y axis increases as it is pulled from front
 | |
|      * to back, i.e., further from the user to closer to the user, and X axis
 | |
|      * increases left to right. Therefore NE is X=+1, Y=-1.
 | |
|      */
 | |
|     static const int32_t hat_to_axis[16][2] = {
 | |
|         {0, 0},
 | |
|         {0, -1},
 | |
|         {1, -1},
 | |
|         {1, 0},
 | |
|         {1, 1},
 | |
|         {0, 1},
 | |
|         {-1, 1},
 | |
|         {-1, 0},
 | |
|         {-1, -1},
 | |
|     };
 | |
| 
 | |
|     report->axis[LIBX52IO_AXIS_HATX] = hat_to_axis[hat][0];
 | |
|     report->axis[LIBX52IO_AXIS_HATY] = hat_to_axis[hat][1];
 | |
| }
 | |
| 
 | |
| static void map_axis(unsigned char *data, int thumb_pos, libx52io_report *report)
 | |
| {
 | |
|     /*
 | |
|      * The bytes containing the throttle axes are the same, with only the
 | |
|      * position of the thumbstick report varying between the X52 and X52Pro.
 | |
|      * Therefore, we can share the code between the different parsers
 | |
|      */
 | |
|     report->axis[LIBX52IO_AXIS_Z] = data[4];
 | |
|     report->axis[LIBX52IO_AXIS_RX] = data[5];
 | |
|     report->axis[LIBX52IO_AXIS_RY] = data[6];
 | |
|     report->axis[LIBX52IO_AXIS_SLIDER] = data[7];
 | |
|     report->axis[LIBX52IO_AXIS_THUMBX] = data[thumb_pos] & 0xf;
 | |
|     report->axis[LIBX52IO_AXIS_THUMBY] = data[thumb_pos] >> 4;
 | |
| 
 | |
|     /*
 | |
|      * The hat report is in the upper 4 bits of the byte preceding the
 | |
|      * thumbstick report. Use that to map to the axis values
 | |
|      */
 | |
|     report->hat = data[thumb_pos-1] >> 4;
 | |
|     map_hat(report->hat, report);
 | |
| }
 | |
| 
 | |
| static void map_buttons(unsigned char *data, const int *button_map, libx52io_report *report)
 | |
| {
 | |
|     /*
 | |
|      * The bytes containing the buttons are the same between the X52 and X52Pro.
 | |
|      * Therefore, we can share the code between the two parsers, and we just
 | |
|      * need a different button map for each device.
 | |
|      */
 | |
|     uint64_t buttons = 0;
 | |
|     int i;
 | |
|     buttons |= data[12]; buttons <<= 8;
 | |
|     buttons |= data[11]; buttons <<= 8;
 | |
|     buttons |= data[10]; buttons <<= 8;
 | |
|     buttons |= data[9]; buttons <<= 8;
 | |
|     buttons |= data[8];
 | |
| 
 | |
|     for (i = 0; button_map[i] != -1; i++) {
 | |
|         int btn = button_map[i];
 | |
|         report->button[btn] = !!(buttons & ((uint64_t)1 << i));
 | |
|     }
 | |
| 
 | |
|     if (report->button[LIBX52IO_BTN_MODE_1]) {
 | |
|         report->mode = 1;
 | |
|     } else if (report->button[LIBX52IO_BTN_MODE_2]) {
 | |
|         report->mode = 2;
 | |
|     } else if (report->button[LIBX52IO_BTN_MODE_3]) {
 | |
|         report->mode = 3;
 | |
|     }
 | |
|     /*
 | |
|      * NOTE: It is possible to hold the mode selector in a position such that
 | |
|      * none of the mode buttons actually report as selected. It is also
 | |
|      * possible that it could be in a transient state between two adjacent
 | |
|      * modes. Either way, we don't want to modify the report, so leave it. It
 | |
|      * is up to the application to handle the case where mode doesn't change.
 | |
|      */
 | |
| }
 | |
| 
 | |
| #define B(x) LIBX52IO_BTN_ ## x
 | |
| 
 | |
| static int parse_x52(unsigned char *data, int length, libx52io_report *report)
 | |
| {
 | |
|     /*
 | |
|      * Report layout for X52
 | |
|      * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | |
|      * |  X axis data        |  Y axis data        |  Rz axis data     |
 | |
|      * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | |
|      * |   Throttle    |  Rx axis data | Ry axis data  | Slider data   |
 | |
|      * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | |
|      * | Buttons 7-0   | Buttons 15-8  | Buttons 23-16 | Buttons 31-24 |
 | |
|      * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | |
|      * |  Hat  |///|Btn| MouseY| MouseX|
 | |
|      * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | |
|      */
 | |
|     uint32_t axis;
 | |
| 
 | |
|     static const int button_map[] = {
 | |
|         B(TRIGGER),
 | |
|         B(FIRE),
 | |
|         B(A),
 | |
|         B(B),
 | |
|         B(C),
 | |
|         B(PINKY),
 | |
|         B(D),
 | |
|         B(E),
 | |
|         B(T1_UP),
 | |
|         B(T1_DN),
 | |
|         B(T2_UP),
 | |
|         B(T2_DN),
 | |
|         B(T3_UP),
 | |
|         B(T3_DN),
 | |
|         B(TRIGGER_2),
 | |
|         B(POV_1_N),
 | |
|         B(POV_1_E),
 | |
|         B(POV_1_S),
 | |
|         B(POV_1_W),
 | |
|         B(POV_2_N),
 | |
|         B(POV_2_E),
 | |
|         B(POV_2_S),
 | |
|         B(POV_2_W),
 | |
|         B(MODE_1),
 | |
|         B(MODE_2),
 | |
|         B(MODE_3),
 | |
|         B(FUNCTION),
 | |
|         B(START_STOP),
 | |
|         B(RESET),
 | |
|         B(CLUTCH),
 | |
|         B(MOUSE_PRIMARY),
 | |
|         B(MOUSE_SECONDARY),
 | |
|         B(MOUSE_SCROLL_DN),
 | |
|         B(MOUSE_SCROLL_UP),
 | |
|         -1
 | |
|     };
 | |
| 
 | |
|     if (length != 14) {
 | |
|         return LIBX52IO_ERROR_IO;
 | |
|     }
 | |
| 
 | |
|     axis = (data[3] << 24) |
 | |
|            (data[2] << 16) |
 | |
|            (data[1] <<  8) |
 | |
|            data[0];
 | |
| 
 | |
|     report->axis[LIBX52IO_AXIS_X] = axis & 0x7ff;
 | |
|     report->axis[LIBX52IO_AXIS_Y] = (axis >> 11) & 0x7ff;
 | |
|     report->axis[LIBX52IO_AXIS_RZ] = (axis >> 22) & 0x3ff;
 | |
|     map_axis(data, 13, report);
 | |
| 
 | |
|     map_buttons(data, button_map, report);
 | |
| 
 | |
|     return LIBX52IO_SUCCESS;
 | |
| }
 | |
| 
 | |
| static int parse_x52pro(unsigned char *data, int length, libx52io_report *report)
 | |
| {
 | |
|     /*
 | |
|      * Report layout for X52Pro
 | |
|      * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | |
|      * |  X axis data      |  Y axis data      |///|  Rz axis data     |
 | |
|      * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | |
|      * |   Throttle    |  Rx axis data | Ry axis data  | Slider data   |
 | |
|      * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | |
|      * | Buttons 7-0   | Buttons 15-8  | Buttons 23-16 | Buttons 31-24 |
 | |
|      * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | |
|      * |/| Btns 38-32  |  Hat  |///////| MouseY| MouseX|
 | |
|      * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | |
|      */
 | |
|     uint32_t axis;
 | |
| 
 | |
|     static const int button_map[] = {
 | |
|         B(TRIGGER),
 | |
|         B(FIRE),
 | |
|         B(A),
 | |
|         B(B),
 | |
|         B(C),
 | |
|         B(PINKY),
 | |
|         B(D),
 | |
|         B(E),
 | |
|         B(T1_UP),
 | |
|         B(T1_DN),
 | |
|         B(T2_UP),
 | |
|         B(T2_DN),
 | |
|         B(T3_UP),
 | |
|         B(T3_DN),
 | |
|         B(TRIGGER_2),
 | |
|         B(MOUSE_PRIMARY),
 | |
|         B(MOUSE_SCROLL_DN),
 | |
|         B(MOUSE_SCROLL_UP),
 | |
|         B(MOUSE_SECONDARY),
 | |
|         B(POV_1_N),
 | |
|         B(POV_1_E),
 | |
|         B(POV_1_S),
 | |
|         B(POV_1_W),
 | |
|         B(POV_2_N),
 | |
|         B(POV_2_E),
 | |
|         B(POV_2_S),
 | |
|         B(POV_2_W),
 | |
|         B(MODE_1),
 | |
|         B(MODE_2),
 | |
|         B(MODE_3),
 | |
|         B(CLUTCH),
 | |
|         B(FUNCTION),
 | |
|         B(START_STOP),
 | |
|         B(RESET),
 | |
|         B(PG_UP),
 | |
|         B(PG_DN),
 | |
|         B(UP),
 | |
|         B(DN),
 | |
|         B(SELECT),
 | |
|         -1
 | |
|     };
 | |
| 
 | |
|     if (length != 15) {
 | |
|         return LIBX52IO_ERROR_IO;
 | |
|     }
 | |
| 
 | |
|     axis = (data[3] << 24) |
 | |
|            (data[2] << 16) |
 | |
|            (data[1] <<  8) |
 | |
|            data[0];
 | |
| 
 | |
|     report->axis[LIBX52IO_AXIS_X] = axis & 0x3ff;
 | |
|     report->axis[LIBX52IO_AXIS_Y] = (axis >> 10) & 0x3ff;
 | |
|     report->axis[LIBX52IO_AXIS_RZ] = (axis >> 22) & 0x3ff;
 | |
|     map_axis(data, 14, report);
 | |
| 
 | |
|     map_buttons(data, button_map, report);
 | |
| 
 | |
|     return LIBX52IO_SUCCESS;
 | |
| }
 | |
| 
 | |
| void _x52io_set_report_parser(libx52io_context *ctx)
 | |
| {
 | |
|     switch (ctx->pid) {
 | |
|     case X52_PROD_X52_1:
 | |
|     case X52_PROD_X52_2:
 | |
|         ctx->parser = parse_x52;
 | |
|         break;
 | |
| 
 | |
|     case X52_PROD_X52PRO:
 | |
|         ctx->parser = parse_x52pro;
 | |
| 
 | |
|     default:
 | |
|         break;
 | |
|     }
 | |
| }
 | |
| 
 | |
| int _x52io_parse_report(libx52io_context *ctx, libx52io_report *report,
 | |
|                         unsigned char *data, int length)
 | |
| {
 | |
|     if (ctx->parser == NULL) {
 | |
|         return LIBX52IO_ERROR_NO_DEVICE;
 | |
|     }
 | |
| 
 | |
|     return (ctx->parser)(data, length, report);
 | |
| }
 | |
| 
 | |
| int libx52io_read(libx52io_context *ctx, libx52io_report *report)
 | |
| {
 | |
|     return libx52io_read_timeout(ctx, report, -1);
 | |
| }
 | |
| 
 | |
| int libx52io_read_timeout(libx52io_context *ctx, libx52io_report *report, int timeout)
 | |
| {
 | |
|     int rc;
 | |
|     unsigned char data[16];
 | |
| 
 | |
|     if (ctx == NULL || report == NULL) {
 | |
|         return LIBX52IO_ERROR_INVALID;
 | |
|     }
 | |
| 
 | |
|     if (ctx->handle == NULL) {
 | |
|         return LIBX52IO_ERROR_NO_DEVICE;
 | |
|     }
 | |
| 
 | |
|     rc = hid_read_timeout(ctx->handle, data, sizeof(data), timeout);
 | |
|     if (rc == 0) {
 | |
|         return LIBX52IO_ERROR_TIMEOUT;
 | |
|     } else if (rc < 0) {
 | |
|         return LIBX52IO_ERROR_IO;
 | |
|     }
 | |
| 
 | |
|     // rc > 0
 | |
|     return _x52io_parse_report(ctx, report, data, rc);
 | |
| }
 |