From e72af998f5382092726cfdb4d57c3b5f8b7d5401 Mon Sep 17 00:00:00 2001
From: Andrew Church <achurch@achurch.org>
Date: Mon, 25 Feb 2019 11:23:12 +1100
Subject: [PATCH] dinput: Allow reconnecting to disconnected joysticks

Wine-bug: https://bugs.winehq.org/show_bug.cgi?id=34297
---
 dlls/dinput/joystick_linuxinput.c | 148 +++++++++++++++++++++++-------
 1 file changed, 113 insertions(+), 35 deletions(-)

diff --git a/dlls/dinput/joystick_linuxinput.c b/dlls/dinput/joystick_linuxinput.c
index b5418d805cc..2434af600ac 100644
--- a/dlls/dinput/joystick_linuxinput.c
+++ b/dlls/dinput/joystick_linuxinput.c
@@ -84,6 +84,13 @@ struct wine_input_absinfo {
     LONG flat;
 };
 
+enum wine_joystick_linuxinput_fd_state {
+    WINE_FD_STATE_CLOSED = 0,    /* No device has been opened yet */
+    WINE_FD_STATE_OK,            /* File descriptor is open and ready for reading */
+    WINE_FD_STATE_DISCONNECTED,  /* Read error occurred; might be able to reopen later */
+    WINE_FD_STATE_INVALID,       /* Device is no longer available at original pathname */
+};
+
 /* implemented in effect_linuxinput.c */
 HRESULT linuxinput_create_effect(int* fd, REFGUID rguid, struct list *parent_list_entry, LPDIRECTINPUTEFFECT* peff);
 HRESULT linuxinput_get_info_A(int fd, REFGUID rguid, LPDIEFFECTINFOA info);
@@ -123,6 +130,7 @@ struct JoystickImpl
 
 	/* joystick private */
 	int				joyfd;
+	enum wine_joystick_linuxinput_fd_state joyfd_state;
 
 	int                             dev_axes_to_di[ABS_MAX];
         POINTL                          povs[4];
@@ -473,6 +481,7 @@ static JoystickImpl *alloc_device(REFGUID rguid, IDirectInputImpl *dinput, unsig
     newDevice->generic.base.dinput = dinput;
     newDevice->generic.joy_polldev = joy_polldev;
     newDevice->joyfd       = -1;
+    newDevice->joyfd_state = WINE_FD_STATE_CLOSED;
     newDevice->joydev      = &joydevs[index];
     newDevice->generic.name        = newDevice->joydev->name;
     list_init(&newDevice->ff_effects);
@@ -680,38 +689,15 @@ static HRESULT joydev_create_device(IDirectInputImpl *dinput, REFGUID rguid, REF
     return DIERR_DEVICENOTREG;
 }
 
-
-const struct dinput_device joystick_linuxinput_device = {
-  "Wine Linux-input joystick driver",
-  joydev_enum_deviceA,
-  joydev_enum_deviceW,
-  joydev_create_device
-};
-
-/******************************************************************************
-  *     Acquire : gets exclusive control of the joystick
-  */
-static HRESULT WINAPI JoystickWImpl_Acquire(LPDIRECTINPUTDEVICE8W iface)
+static int joydev_open_evdev(JoystickImpl *This)
 {
-    JoystickImpl *This = impl_from_IDirectInputDevice8W(iface);
-    HRESULT res;
-
-    TRACE("(this=%p)\n",This);
+    int fd;
 
-    if ((res = IDirectInputDevice2WImpl_Acquire(iface)) != DI_OK)
+    if ((fd = open(This->joydev->device, O_RDWR)) == -1)
     {
-        WARN("Failed to acquire: %x\n", res);
-        return res;
-    }
-
-    if ((This->joyfd = open(This->joydev->device, O_RDWR)) == -1)
-    {
-        if ((This->joyfd = open(This->joydev->device, O_RDONLY)) == -1)
+        if ((fd = open(This->joydev->device, O_RDONLY)) == -1)
         {
             /* Couldn't open the device at all */
-            ERR("Failed to open device %s: %d %s\n", This->joydev->device, errno, strerror(errno));
-            IDirectInputDevice2WImpl_Unacquire(iface);
-            return DIERR_NOTFOUND;
         }
         else
         {
@@ -726,18 +712,53 @@ static HRESULT WINAPI JoystickWImpl_Acquire(LPDIRECTINPUTDEVICE8W iface)
         event.type = EV_FF;
         event.code = FF_GAIN;
         event.value = This->ff_gain;
-        if (write(This->joyfd, &event, sizeof(event)) == -1)
+        if (write(fd, &event, sizeof(event)) == -1)
             ERR("Failed to set gain (%i): %d %s\n", This->ff_gain, errno, strerror(errno));
         if (!This->ff_autocenter)
         {
             /* Disable autocenter. */
             event.code = FF_AUTOCENTER;
             event.value = 0;
-            if (write(This->joyfd, &event, sizeof(event)) == -1)
+            if (write(fd, &event, sizeof(event)) == -1)
                 ERR("Failed disabling autocenter: %d %s\n", errno, strerror(errno));
         }
     }
 
+    return fd;
+}
+
+
+const struct dinput_device joystick_linuxinput_device = {
+  "Wine Linux-input joystick driver",
+  joydev_enum_deviceA,
+  joydev_enum_deviceW,
+  joydev_create_device
+};
+
+/******************************************************************************
+  *     Acquire : gets exclusive control of the joystick
+  */
+static HRESULT WINAPI JoystickWImpl_Acquire(LPDIRECTINPUTDEVICE8W iface)
+{
+    JoystickImpl *This = impl_from_IDirectInputDevice8W(iface);
+    HRESULT res;
+
+    TRACE("(this=%p)\n",This);
+
+    if ((res = IDirectInputDevice2WImpl_Acquire(iface)) != DI_OK)
+    {
+        WARN("Failed to acquire: %x\n", res);
+        return res;
+    }
+
+    if ((This->joyfd = joydev_open_evdev(This)) == -1)
+    {
+        ERR("Failed to open device %s: %d %s\n", This->joydev->device, errno, strerror(errno));
+        IDirectInputDevice2WImpl_Unacquire(iface);
+        return DIERR_NOTFOUND;
+    }
+
+    This->joyfd_state = WINE_FD_STATE_OK;
     return DI_OK;
 }
 
@@ -775,6 +796,7 @@ static HRESULT WINAPI JoystickWImpl_Unacquire(LPDIRECTINPUTDEVICE8W iface)
 
       close(This->joyfd);
       This->joyfd = -1;
+      This->joyfd_state = WINE_FD_STATE_CLOSED;
     }
     return res;
 }
@@ -819,23 +841,79 @@ static void joy_polldev(LPDIRECTINPUTDEVICE8A iface)
     struct input_event ie;
     JoystickImpl *This = impl_from_IDirectInputDevice8A(iface);
 
-    if (This->joyfd==-1)
-	return;
+    if (This->joyfd == -1)
+    {
+        int fd;
+        char namebuf[MAX_PATH + 8];  /* 8 == strlen(EVDEVDRIVER) */
+
+        if (This->joyfd_state != WINE_FD_STATE_DISCONNECTED)
+            return;
+        /* Try to reconnect to the device. */
+        fd = joydev_open_evdev(This);
+        if (fd == -1)
+            return;
+        namebuf[sizeof(namebuf) - strlen(EVDEVDRIVER) - 1] = 0;
+        if (ioctl(fd, EVIOCGNAME(sizeof(namebuf) - strlen(EVDEVDRIVER) - 1), namebuf) == -1)
+        {
+            /* Couldn't get the name; assume it's a different device. */
+            ERR("EVIOCGNAME(%s) failed: %d %s", This->joydev->device, errno, strerror(errno));
+            This->joyfd_state = WINE_FD_STATE_INVALID;
+            return;
+        }
+        strcat(namebuf, EVDEVDRIVER);  /* Guaranteed to be safe. */
+        if (strcmp(namebuf, This->joydev->name) != 0)
+        {
+            ERR("Device %s changed from \"%s\" to \"%s\"!  Can't reconnect.\n", This->joydev->device, This->joydev->name, namebuf);
+            This->joyfd_state = WINE_FD_STATE_INVALID;
+            return;
+        }
+        if (InterlockedCompareExchange(&This->joyfd, fd, -1) == -1)
+        {
+            This->joyfd_state = WINE_FD_STATE_OK;
+            TRACE("Reconnected to \"%s\" on %s", This->joydev->name, This->joydev->device);
+        }
+        else
+        {
+            /* Somebody beat us to it!  Throw away our fd and use theirs. */
+            close(fd);
+        }
+    }
 
     while (1)
     {
         LONG value = 0;
         int inst_id = -1;
+        int result;
 
 	plfd.fd = This->joyfd;
 	plfd.events = POLLIN;
 
-	if (poll(&plfd,1,0) != 1)
-	    return;
+        result = poll(&plfd,1,0);
+        if (result != 1)
+        {
+            if (result == -1)
+            {
+                ERR("poll failed: %d %s\n", errno, strerror(errno));
+                close(This->joyfd);
+                This->joyfd = -1;
+                This->joyfd_state = WINE_FD_STATE_DISCONNECTED;
+            }
+            return;
+        }
 
 	/* we have one event, so we can read */
-	if (sizeof(ie)!=read(This->joyfd,&ie,sizeof(ie)))
-	    return;
+        result = read(This->joyfd,&ie,sizeof(ie));
+        if (result != sizeof(ie))
+        {
+            if (result == -1)
+            {
+                ERR("read failed: %d %s\n", errno, strerror(errno));
+                close(This->joyfd);
+                This->joyfd = -1;
+                This->joyfd_state = WINE_FD_STATE_DISCONNECTED;
+            }
+            return;
+        }
 
 	TRACE("input_event: type %d, code %d, value %d\n",ie.type,ie.code,ie.value);
 	switch (ie.type) {
-- 
2.17.1