Implement Marcin Bulandra's suggestion of only automatically updating the
[u/mdw/putty] / windows / winser.c
index f50eea1..ab88406 100644 (file)
@@ -2,17 +2,6 @@
  * Serial back end (Windows-specific).
  */
 
-/*
- * TODO:
- * 
- *  - sending breaks?
- *     + looks as if you do this by calling SetCommBreak(handle),
- *      then waiting a bit, then doing ClearCommBreak(handle). A
- *      small job for timing.c, methinks.
- *
- *  - why are we dropping data when talking to judicator?
- */
-
 #include <stdio.h>
 #include <stdlib.h>
 #include <limits.h>
@@ -26,6 +15,8 @@ typedef struct serial_backend_data {
     struct handle *out, *in;
     void *frontend;
     int bufsize;
+    long clearbreak_time;
+    int break_in_progress;
 } *Serial;
 
 static void serial_terminate(Serial serial)
@@ -38,9 +29,11 @@ static void serial_terminate(Serial serial)
        handle_free(serial->in);
        serial->in = NULL;
     }
-    if (serial->port) {
+    if (serial->port != INVALID_HANDLE_VALUE) {
+       if (serial->break_in_progress)
+           ClearCommBreak(serial->port);
        CloseHandle(serial->port);
-       serial->port = NULL;
+       serial->port = INVALID_HANDLE_VALUE;
     }
 }
 
@@ -215,9 +208,10 @@ static const char *serial_init(void *frontend_handle, void **backend_handle,
     const char *err;
 
     serial = snew(struct serial_backend_data);
-    serial->port = NULL;
+    serial->port = INVALID_HANDLE_VALUE;
     serial->out = serial->in = NULL;
     serial->bufsize = 0;
+    serial->break_in_progress = FALSE;
     *backend_handle = serial;
 
     serial->frontend = frontend_handle;
@@ -227,8 +221,39 @@ static const char *serial_init(void *frontend_handle, void **backend_handle,
        logevent(serial->frontend, msg);
     }
 
-    serport = CreateFile(cfg->serline, GENERIC_READ | GENERIC_WRITE, 0, NULL,
-                        OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
+    {
+       /*
+        * Munge the string supplied by the user into a Windows filename.
+        *
+        * Windows supports opening a few "legacy" devices (including
+        * COM1-9) by specifying their names verbatim as a filename to
+        * open. (Thus, no files can ever have these names. See
+        * <http://msdn2.microsoft.com/en-us/library/aa365247.aspx>
+        * ("Naming a File") for the complete list of reserved names.)
+        *
+        * However, this doesn't let you get at devices COM10 and above.
+        * For that, you need to specify a filename like "\\.\COM10".
+        * This is also necessary for special serial and serial-like
+        * devices such as \\.\WCEUSBSH001. It also works for the "legacy"
+        * names, so you can do \\.\COM1 (verified as far back as Win95).
+        * See <http://msdn2.microsoft.com/en-us/library/aa363858.aspx>
+        * (CreateFile() docs).
+        *
+        * So, we believe that prepending "\\.\" should always be the
+        * Right Thing. However, just in case someone finds something to
+        * talk to that doesn't exist under there, if the serial line
+        * contains a backslash, we use it verbatim. (This also lets
+        * existing configurations using \\.\ continue working.)
+        */
+       char *serfilename =
+           dupprintf("%s%s",
+                     strchr(cfg->serline, '\\') ? "" : "\\\\.\\",
+                     cfg->serline);
+       serport = CreateFile(serfilename, GENERIC_READ | GENERIC_WRITE, 0, NULL,
+                            OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
+       sfree(serfilename);
+    }
+
     if (serport == INVALID_HANDLE_VALUE)
        return "Unable to open serial port";
 
@@ -241,10 +266,16 @@ static const char *serial_init(void *frontend_handle, void **backend_handle,
                                    HANDLE_FLAG_OVERLAPPED);
     serial->in = handle_input_new(serport, serial_gotdata, serial,
                                  HANDLE_FLAG_OVERLAPPED |
-                                 HANDLE_FLAG_IGNOREEOF);
+                                 HANDLE_FLAG_IGNOREEOF |
+                                 HANDLE_FLAG_UNITBUFFER);
 
     *realhost = dupstr(cfg->serline);
 
+    /*
+     * Specials are always available.
+     */
+    update_specials_menu(serial->frontend);
+
     return NULL;
 }
 
@@ -253,6 +284,7 @@ static void serial_free(void *handle)
     Serial serial = (Serial) handle;
 
     serial_terminate(serial);
+    expire_timer_context(serial);
     sfree(serial);
 }
 
@@ -300,14 +332,42 @@ static void serial_size(void *handle, int width, int height)
     return;
 }
 
+static void serbreak_timer(void *ctx, long now)
+{
+    Serial serial = (Serial)ctx;
+
+    if (now >= serial->clearbreak_time && serial->port) {
+       ClearCommBreak(serial->port);
+       serial->break_in_progress = FALSE;
+       logevent(serial->frontend, "Finished serial break");
+    }
+}
+
 /*
  * Send serial special codes.
  */
 static void serial_special(void *handle, Telnet_Special code)
 {
-    /*
-     * FIXME: serial break? XON? XOFF?
-     */
+    Serial serial = (Serial) handle;
+
+    if (serial->port && code == TS_BRK) {
+       logevent(serial->frontend, "Starting serial break at user request");
+       SetCommBreak(serial->port);
+       /*
+        * To send a serial break on Windows, we call SetCommBreak
+        * to begin the break, then wait a bit, and then call
+        * ClearCommBreak to finish it. Hence, I must use timing.c
+        * to arrange a callback when it's time to do the latter.
+        * 
+        * SUS says that a default break length must be between 1/4
+        * and 1/2 second. FreeBSD apparently goes with 2/5 second,
+        * and so will I. 
+        */
+       serial->clearbreak_time =
+           schedule_timer(TICKSPERSEC * 2 / 5, serbreak_timer, serial);
+       serial->break_in_progress = TRUE;
+    }
+
     return;
 }
 
@@ -317,10 +377,11 @@ static void serial_special(void *handle, Telnet_Special code)
  */
 static const struct telnet_special *serial_get_specials(void *handle)
 {
-    /*
-     * FIXME: serial break? XON? XOFF?
-     */
-    return NULL;
+    static const struct telnet_special specials[] = {
+       {"Break", TS_BRK},
+       {NULL, TS_EXITMENU}
+    };
+    return specials;
 }
 
 static int serial_connected(void *handle)
@@ -361,7 +422,7 @@ static void serial_provide_logctx(void *handle, void *logctx)
 static int serial_exitcode(void *handle)
 {
     Serial serial = (Serial) handle;
-    if (serial->port != NULL)
+    if (serial->port != INVALID_HANDLE_VALUE)
         return -1;                     /* still connected */
     else
         /* Exit codes are a meaningless concept with serial ports */
@@ -393,5 +454,7 @@ Backend serial_backend = {
     serial_provide_logctx,
     serial_unthrottle,
     serial_cfg_info,
-    1
+    "serial",
+    PROT_SERIAL,
+    0
 };