[PATCH v4 4/5] winebus.sys: Process device reports for linux event devices

Aric Stewart <[email protected]> Tue, 7 Nov 2017 08:18:03 -0600
Newsgroups gmane.comp.emulators.wine.patches
Message-ID <[email protected]>
v2: Updates from Sebastian Lackner
v3: Suggestions from Roderick Colenbrander
v3: Do not force axis ranges, handle SYN_DROPPED

Signed-off-by: Aric Stewart <[email protected]>
---
 dlls/winebus.sys/bus_udev.c | 235 ++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 229 insertions(+), 6 deletions(-)
v4-0004-winebus.sys-Process-device-reports-for-linux-event-.txt (text/x-patch, 10.3 KB)
diff --git a/dlls/winebus.sys/bus_udev.c b/dlls/winebus.sys/bus_udev.c
index ab03122908..faae2b61f0 100644
--- a/dlls/winebus.sys/bus_udev.c
+++ b/dlls/winebus.sys/bus_udev.c
@@ -251,9 +251,16 @@ struct wine_input_absinfo {
     BYTE report_index;
 };
 
+enum {FIRST=0, NORMAL, DROPPED};
+
 struct wine_input_private {
     struct platform_private base;
 
+    int buffer_length;
+    BYTE *last_report_buffer;
+    BYTE *current_report_buffer;
+    BYTE report_state;
+
     int report_descriptor_size;
     BYTE *report_descriptor;
 
@@ -359,7 +366,82 @@ static const BYTE* what_am_I(struct udev_device *dev)
     return Unknown;
 }
 
-static VOID build_report_descriptor(struct wine_input_private *ext, struct udev_device *dev)
+static void set_button_value(struct wine_input_private *ext, int code, int value)
+{
+    int index = ext->button_map[code];
+    int bindex = index / 8;
+    int b = index % 8;
+    BYTE mask;
+
+    mask = 1<<b;
+    if (value)
+        ext->current_report_buffer[bindex] = ext->current_report_buffer[bindex] | mask;
+    else
+    {
+        mask = ~mask;
+        ext->current_report_buffer[bindex] = ext->current_report_buffer[bindex] & mask;
+    }
+}
+
+static void set_abs_axis_value(struct wine_input_private *ext, int code, int value)
+{
+    int index;
+    /* check for hatswitches */
+    if (code <= ABS_HAT3Y && code >= ABS_HAT0X)
+    {
+        index = code - ABS_HAT0X;
+        ext->hat_values[index] = value;
+        if ((code - ABS_HAT0X) % 2)
+            index--;
+        if (ext->hat_values[index] == 0)
+        {
+            if (ext->hat_values[index+1] == 0)
+                value = 8;
+            else if (ext->hat_values[index+1] < 0)
+                value = 0;
+            else
+                value = 4;
+        }
+        else if (ext->hat_values[index] > 0)
+        {
+            if (ext->hat_values[index+1] == 0)
+                value = 2;
+            else if (ext->hat_values[index+1] < 0)
+                value = 1;
+            else
+                value = 3;
+        }
+        else
+        {
+            if (ext->hat_values[index+1] == 0)
+                value = 6;
+            else if (ext->hat_values[index+1] < 0)
+                value = 7;
+            else
+                value = 5;
+        }
+        ext->current_report_buffer[ext->hat_map[index]] = value;
+    }
+    else if (code < HID_ABS_MAX && ABS_to_HID_map[code][0] != 0)
+    {
+        index = ext->abs_map[code].report_index;
+        *((DWORD*)&ext->current_report_buffer[index]) = LE_DWORD(value);
+    }
+}
+
+static void set_rel_axis_value(struct wine_input_private *ext, int code, int value)
+{
+    int index;
+    if (code < HID_REL_MAX && REL_to_HID_map[code][0] != 0)
+    {
+        index = ext->rel_map[code];
+        if (value > 127) value = 127;
+        if (value < -127) value = -127;
+        ext->current_report_buffer[index] = value;
+    }
+}
+
+static BOOL build_report_descriptor(struct wine_input_private *ext, struct udev_device *dev)
 {
     int abs_pages[TOP_ABS_PAGE][HID_ABS_MAX+1];
     int rel_pages[TOP_REL_PAGE][HID_REL_MAX+1];
@@ -375,17 +457,17 @@ static VOID build_report_descriptor(struct wine_input_private *ext, struct udev_
     if (ioctl(ext->base.device_fd, EVIOCGBIT(EV_REL, sizeof(relbits)), relbits) == -1)
     {
         WARN("ioctl(EVIOCGBIT, EV_REL) failed: %d %s\n", errno, strerror(errno));
-        return;
+        return FALSE;
     }
     if (ioctl(ext->base.device_fd, EVIOCGBIT(EV_ABS, sizeof(absbits)), absbits) == -1)
     {
         WARN("ioctl(EVIOCGBIT, EV_ABS) failed: %d %s\n", errno, strerror(errno));
-        return;
+        return FALSE;
     }
     if (ioctl(ext->base.device_fd, EVIOCGBIT(EV_KEY, sizeof(keybits)), keybits) == -1)
     {
         WARN("ioctl(EVIOCGBIT, EV_KEY) failed: %d %s\n", errno, strerror(errno));
-        return;
+        return FALSE;
     }
 
     descript_size = sizeof(report_header) + sizeof(report_tail);
@@ -480,6 +562,11 @@ static VOID build_report_descriptor(struct wine_input_private *ext, struct udev_
     TRACE("Report will be %i bytes\n", report_size);
 
     ext->report_descriptor = HeapAlloc(GetProcessHeap(), 0, descript_size);
+    if (!ext->report_descriptor)
+    {
+        ERR("Failed to alloc report descriptor\n");
+        return FALSE;
+    }
     report_ptr = ext->report_descriptor;
 
     memcpy(report_ptr, report_header, sizeof(report_header));
@@ -529,6 +616,76 @@ static VOID build_report_descriptor(struct wine_input_private *ext, struct udev_
     memcpy(report_ptr, report_tail, sizeof(report_tail));
 
     ext->report_descriptor_size = descript_size;
+    ext->buffer_length = report_size;
+    ext->current_report_buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, report_size);
+    if (ext->current_report_buffer == NULL)
+    {
+        ERR("Failed to alloc report buffer\n");
+        HeapFree(GetProcessHeap(), 0, ext->report_descriptor);
+        return FALSE;
+    }
+    ext->last_report_buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, report_size);
+    if (ext->last_report_buffer == NULL)
+    {
+        ERR("Failed to alloc report buffer\n");
+        HeapFree(GetProcessHeap(), 0, ext->report_descriptor);
+        HeapFree(GetProcessHeap(), 0, ext->current_report_buffer);
+        return FALSE;
+    }
+    ext->report_state = FIRST;
+
+    /* Initialize axis in the report */
+    for (i = 0; i < HID_ABS_MAX; i++)
+        if (test_bit(absbits, i))
+            set_abs_axis_value(ext, i, ext->abs_map[i].info.value);
+
+    return TRUE;
+}
+
+static BOOL set_report_from_event(struct wine_input_private *ext, struct input_event *ie)
+{
+    switch(ie->type)
+    {
+#ifdef EV_SYN
+        case EV_SYN:
+            switch (ie->code)
+            {
+                case SYN_REPORT:
+                    if (ext->report_state == NORMAL)
+                    {
+                        memcpy(ext->last_report_buffer, ext->current_report_buffer, ext->buffer_length);
+                        return TRUE;
+                    }
+                    else
+                    {
+                        if (ext->report_state == DROPPED)
+                            memcpy(ext->current_report_buffer, ext->last_report_buffer, ext->buffer_length);
+                        ext->report_state = NORMAL;
+                    }
+                    break;
+                case SYN_DROPPED:
+                    TRACE_(hid_report)("received SY_DROPPED\n");
+                    ext->report_state = DROPPED;
+            }
+            return FALSE;
+#endif
+#ifdef EV_MSC
+        case EV_MSC:
+            return FALSE;
+#endif
+        case EV_KEY:
+            set_button_value(ext, ie->code, ie->value);
+            return FALSE;
+        case EV_ABS:
+            set_abs_axis_value(ext, ie->code, ie->value);
+            return FALSE;
+        case EV_REL:
+            set_rel_axis_value(ext, ie->code, ie->value);
+            return FALSE;
+        default:
+            ERR("TODO: Process Report (%i, %i)\n",ie->type, ie->code);
+            return FALSE;
+    }
 }
 #endif
 
@@ -888,9 +1045,60 @@ static NTSTATUS lnxev_get_string(DEVICE_OBJECT *device, DWORD index, WCHAR *buff
     return STATUS_SUCCESS;
 }
 
+static DWORD CALLBACK lnxev_device_report_thread(void *args)
+{
+    DEVICE_OBJECT *device = (DEVICE_OBJECT*)args;
+    struct wine_input_private *private = input_impl_from_DEVICE_OBJECT(device);
+    struct pollfd plfds[2];
+
+    plfds[0].fd = private->base.device_fd;
+    plfds[0].events = POLLIN;
+    plfds[0].revents = 0;
+    plfds[1].fd = private->base.control_pipe[0];
+    plfds[1].events = POLLIN;
+    plfds[1].revents = 0;
+
+    while (1)
+    {
+        int size;
+        struct input_event ie;
+
+        if (poll(plfds, 2, -1) <= 0) continue;
+        if (plfds[1].revents || !private->current_report_buffer || private->buffer_length == 0)
+            break;
+        size = read(plfds[0].fd, &ie, sizeof(ie));
+        if (size == -1)
+            TRACE_(hid_report)("Read failed. Likely an unplugged device\n");
+        else if (size == 0)
+            TRACE_(hid_report)("Failed to read report\n");
+        else if (set_report_from_event(private, &ie))
+            process_hid_report(device, private->current_report_buffer, private->buffer_length);
+    }
+    return 0;
+}
+
 static NTSTATUS lnxev_begin_report_processing(DEVICE_OBJECT *device)
 {
-    return STATUS_NOT_IMPLEMENTED;
+    struct wine_input_private *private = input_impl_from_DEVICE_OBJECT(device);
+
+    if (private->base.report_thread)
+        return STATUS_SUCCESS;
+
+    if (pipe(private->base.control_pipe) != 0)
+    {
+        ERR("Control pipe creation failed\n");
+        return STATUS_UNSUCCESSFUL;
+    }
+
+    private->base.report_thread = CreateThread(NULL, 0, lnxev_device_report_thread, device, 0, NULL);
+    if (!private->base.report_thread)
+    {
+        ERR("Unable to create device report thread\n");
+        close(private->base.control_pipe[0]);
+        close(private->base.control_pipe[1]);
+        return STATUS_UNSUCCESSFUL;
+    }
+    return STATUS_SUCCESS;
 }
 
 static NTSTATUS lnxev_set_output_report(DEVICE_OBJECT *device, UCHAR id, BYTE *report, DWORD length, ULONG_PTR *written)
@@ -1017,7 +1225,15 @@ static void try_add_device(struct udev_device *dev)
         private->device_fd = fd;
 #ifdef HAS_PROPER_INPUT_HEADER
         if (strcmp(subsystem, "input") == 0)
-            build_report_descriptor((struct wine_input_private*)private, dev);
+            if (!build_report_descriptor((struct wine_input_private*)private, dev))
+            {
+                ERR("Building report descriptor failed, removing device\n");
+                close(fd);
+                udev_device_unref(dev);
+                bus_remove_hid_device(device);
+                HeapFree(GetProcessHeap(), 0, serial);
+                return;
+            }
 #endif
         IoInvalidateDeviceRelations(device, BusRelations);
     }
@@ -1053,6 +1269,13 @@ static void try_remove_device(struct udev_device *dev)
         close(private->control_pipe[0]);
         close(private->control_pipe[1]);
         CloseHandle(private->report_thread);
+#ifdef HAS_PROPER_INPUT_HEADER
+        if (strcmp(udev_device_get_subsystem(dev), "input") == 0)
+        {
+            HeapFree(GetProcessHeap(), 0, ((struct wine_input_private*)private)->current_report_buffer);
+            HeapFree(GetProcessHeap(), 0, ((struct wine_input_private*)private)->last_report_buffer);
+        }
+#endif
     }
 
     dev = private->udev_device;