Raise the default scrollback from 200 to 2000 lines. The former was
[sgt/putty] / windows / winser.c
index f50eea1..086d3e5 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;
     }
 }
 
@@ -94,7 +87,7 @@ static void serial_sentdata(struct handle *h, int new_backlog)
     }
 }
 
-static const char *serial_configure(Serial serial, HANDLE serport, Config *cfg)
+static const char *serial_configure(Serial serial, HANDLE serport, Conf *conf)
 {
     DCB dcb;
     COMMTIMEOUTS timeouts;
@@ -128,17 +121,17 @@ static const char *serial_configure(Serial serial, HANDLE serport, Config *cfg)
        /*
         * Configurable parameters.
         */
-       dcb.BaudRate = cfg->serspeed;
-       msg = dupprintf("Configuring baud rate %d", cfg->serspeed);
+       dcb.BaudRate = conf_get_int(conf, CONF_serspeed);
+       msg = dupprintf("Configuring baud rate %d", dcb.BaudRate);
        logevent(serial->frontend, msg);
        sfree(msg);
 
-       dcb.ByteSize = cfg->serdatabits;
-       msg = dupprintf("Configuring %d data bits", cfg->serdatabits);
+       dcb.ByteSize = conf_get_int(conf, CONF_serdatabits);
+       msg = dupprintf("Configuring %d data bits", dcb.ByteSize);
        logevent(serial->frontend, msg);
        sfree(msg);
 
-       switch (cfg->serstopbits) {
+       switch (conf_get_int(conf, CONF_serstopbits)) {
          case 2: dcb.StopBits = ONESTOPBIT; str = "1"; break;
          case 3: dcb.StopBits = ONE5STOPBITS; str = "1.5"; break;
          case 4: dcb.StopBits = TWOSTOPBITS; str = "2"; break;
@@ -148,7 +141,7 @@ static const char *serial_configure(Serial serial, HANDLE serport, Config *cfg)
        logevent(serial->frontend, msg);
        sfree(msg);
 
-       switch (cfg->serparity) {
+       switch (conf_get_int(conf, CONF_serparity)) {
          case SER_PAR_NONE: dcb.Parity = NOPARITY; str = "no"; break;
          case SER_PAR_ODD: dcb.Parity = ODDPARITY; str = "odd"; break;
          case SER_PAR_EVEN: dcb.Parity = EVENPARITY; str = "even"; break;
@@ -159,7 +152,7 @@ static const char *serial_configure(Serial serial, HANDLE serport, Config *cfg)
        logevent(serial->frontend, msg);
        sfree(msg);
 
-       switch (cfg->serflow) {
+       switch (conf_get_int(conf, CONF_serflow)) {
          case SER_FLOW_NONE:
            str = "no";
            break;
@@ -206,33 +199,64 @@ static const char *serial_configure(Serial serial, HANDLE serport, Config *cfg)
  * freed by the caller.
  */
 static const char *serial_init(void *frontend_handle, void **backend_handle,
-                              Config *cfg,
-                              char *host, int port, char **realhost, int nodelay,
-                              int keepalive)
+                              Conf *conf, char *host, int port,
+                              char **realhost, int nodelay, int keepalive)
 {
     Serial serial;
     HANDLE serport;
     const char *err;
+    char *serline;
 
     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;
 
+    serline = conf_get_str(conf, CONF_serline);
     {
-       char *msg = dupprintf("Opening serial device %s", cfg->serline);
+       char *msg = dupprintf("Opening serial device %s", serline);
        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(serline, '\\') ? "" : "\\\\.\\", 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";
 
-    err = serial_configure(serial, serport, cfg);
+    err = serial_configure(serial, serport, conf);
     if (err)
        return err;
 
@@ -241,9 +265,15 @@ 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);
+    *realhost = dupstr(serline);
+
+    /*
+     * Specials are always available.
+     */
+    update_specials_menu(serial->frontend);
 
     return NULL;
 }
@@ -253,15 +283,16 @@ static void serial_free(void *handle)
     Serial serial = (Serial) handle;
 
     serial_terminate(serial);
+    expire_timer_context(serial);
     sfree(serial);
 }
 
-static void serial_reconfig(void *handle, Config *cfg)
+static void serial_reconfig(void *handle, Conf *conf)
 {
     Serial serial = (Serial) handle;
     const char *err;
 
-    err = serial_configure(serial, serial->port, cfg);
+    err = serial_configure(serial, serial->port, conf);
 
     /*
      * FIXME: what should we do if err returns something?
@@ -300,14 +331,42 @@ static void serial_size(void *handle, int width, int height)
     return;
 }
 
+static void serbreak_timer(void *ctx, unsigned 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 +376,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 +421,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 +453,7 @@ Backend serial_backend = {
     serial_provide_logctx,
     serial_unthrottle,
     serial_cfg_info,
-    1
+    "serial",
+    PROT_SERIAL,
+    0
 };