FIX #1198: hWaitForInput on Windows
authorSimon Marlow <marlowsd@gmail.com>
Tue, 8 Jul 2008 13:42:54 +0000 (13:42 +0000)
committerSimon Marlow <marlowsd@gmail.com>
Tue, 8 Jul 2008 13:42:54 +0000 (13:42 +0000)
Now we do the appropriate magic in fdReady() to detect when there is
real input available, as opposed to uninteresting console events.

cbits/inputReady.c

index 947cc4f..51f278f 100644 (file)
@@ -56,42 +56,113 @@ fdReady(int fd, int write, int msecs, int isSock)
        HANDLE hFile = (HANDLE)_get_osfhandle(fd);
        DWORD avail;
 
-       // WaitForMultipleObjects() works for Console input, but it
-       // doesn't work for pipes (it always returns WAIT_OBJECT_0
-       // even when no data is available).  There doesn't seem to be
-       // an easy way to distinguish the two kinds of HANDLE, so we
-       // try to detect pipe input first, and if that fails we try
-       // WaitForMultipleObjects().
-       //
-       rc = PeekNamedPipe( hFile, NULL, 0, NULL, &avail, NULL );
-       if (rc != 0) {
-           if (avail != 0) {
-               return 1;
-           } else {
-               return 0;
-           }
-       } else {
-           rc = GetLastError();
-           if (rc == ERROR_BROKEN_PIPE) {
-               return 1; // this is probably what we want
-           }
-           if (rc != ERROR_INVALID_HANDLE && rc != ERROR_INVALID_FUNCTION) {
-                maperrno();
-               return -1;
-           }
-       }
+        switch (GetFileType(hFile)) {
 
-       rc = WaitForMultipleObjects( 1,
-                                    &hFile,
-                                    TRUE,   /* wait all */
-                                    msecs); /*millisecs*/
-       
-       /* 1 => Input ready, 0 => not ready, -1 => error */
-       switch (rc) {
-       case WAIT_TIMEOUT: return 0;
-       case WAIT_OBJECT_0: return 1;
-       default: return -1;
-       }
+        case FILE_TYPE_CHAR:
+        {
+            INPUT_RECORD buf[1];
+            DWORD count;
+
+            // nightmare.  A Console Handle will appear to be ready
+            // (WaitForSingleObject() returned WAIT_OBJECT_0) when
+            // it has events in its input buffer, but these events might
+            // not be keyboard events, so when we read from the Handle the
+            // read() will block.  So here we try to discard non-keyboard
+            // events from a console handle's input buffer and then try
+            // the WaitForSingleObject() again.
+
+            while (1) // keep trying until we find a real key event
+            {
+                rc = WaitForSingleObject( hFile, msecs );
+                switch (rc) {
+                case WAIT_TIMEOUT: return 0;
+                case WAIT_OBJECT_0: break;
+                default: /* WAIT_FAILED */ maperrno(); return -1;
+                }
+
+                while (1) // discard non-key events
+                {
+                    rc = PeekConsoleInput(hFile, buf, 1, &count);
+                    // printf("peek, rc=%d, count=%d, type=%d\n", rc, count, buf[0].EventType);
+                    if (rc == 0) {
+                        rc = GetLastError();
+                        if (rc == ERROR_INVALID_HANDLE || rc == ERROR_INVALID_FUNCTION) {
+                            return 1;
+                        } else {
+                            maperrno();
+                            return -1;
+                        }
+                    }
+
+                    if (count == 0) break; // no more events => wait again
+
+                    // discard console events that are not "key down", because
+                    // these will also be discarded by ReadFile().
+                    if (buf[0].EventType == KEY_EVENT &&
+                        buf[0].Event.KeyEvent.bKeyDown &&
+                        buf[0].Event.KeyEvent.uChar.AsciiChar != '\0')
+                    {
+                        // it's a proper keypress:
+                        return 1;
+                    }
+                    else
+                    {
+                        // it's a non-key event, a key up event, or a
+                        // non-character key (e.g. shift).  discard it.
+                        rc = ReadConsoleInput(hFile, buf, 1, &count);
+                        if (rc == 0) {
+                            rc = GetLastError();
+                            if (rc == ERROR_INVALID_HANDLE || rc == ERROR_INVALID_FUNCTION) {
+                                return 1;
+                            } else {
+                                maperrno();
+                                return -1;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        case FILE_TYPE_DISK:
+            // assume that disk files are always ready:
+            return 1;
+
+        case FILE_TYPE_PIPE:
+            // WaitForMultipleObjects() doesn't work for pipes (it
+            // always returns WAIT_OBJECT_0 even when no data is
+            // available).  If the HANDLE is a pipe, therefore, we try
+            // PeekNamedPipe:
+            //
+            rc = PeekNamedPipe( hFile, NULL, 0, NULL, &avail, NULL );
+            if (rc != 0) {
+                if (avail != 0) {
+                    return 1;
+                } else {
+                    return 0;
+                }
+            } else {
+                rc = GetLastError();
+                if (rc == ERROR_BROKEN_PIPE) {
+                    return 1; // this is probably what we want
+                }
+                if (rc != ERROR_INVALID_HANDLE && rc != ERROR_INVALID_FUNCTION) {
+                    maperrno();
+                    return -1;
+                }
+            }
+            /* PeekNamedPipe didn't work - fall through to the general case */
+
+        default:
+            rc = WaitForSingleObject( hFile, msecs );
+
+            /* 1 => Input ready, 0 => not ready, -1 => error */
+            switch (rc) {
+            case WAIT_TIMEOUT: return 0;
+            case WAIT_OBJECT_0: return 1;
+            default: /* WAIT_FAILED */ maperrno(); return -1;
+            }
+        }
     }
 #endif
 }