Support for Windows PuTTY connecting straight to a local serial port
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Mon, 28 Aug 2006 10:35:12 +0000 (10:35 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Mon, 28 Aug 2006 10:35:12 +0000 (10:35 +0000)
in place of making a network connection. This has involved a couple
of minor infrastructure changes:
 - New dlg_label_change() function in the dialog.h interface, which
   alters the label on a control. Only used, at present, to switch
   the Host Name and Port boxes into Serial Line and Speed, which
   means that any platform not implementing serial connections (i.e.
   currently all but Windows) does not need to actually do anything
   in this function. Yet.
 - New small piece of infrastructure: cfg_launchable() determines
   whether a Config structure describes a session ready to be
   launched. This was previously determined by seeing if it had a
   non-empty host name, but it has to check the serial line as well
   so there's a centralised function for it. I haven't gone through
   all front ends and arranged for this function to be used
   everywhere it needs to be; so far I've only checked Windows.
 - Similarly, cfg_dest() returns the destination of a connection
   (host name or serial line) in a text format suitable for putting
   into messages such as `Unable to connect to %s'.

git-svn-id: svn://svn.tartarus.org/sgt/putty@6815 cda61777-01e9-0310-a592-d414129be87e

23 files changed:
Recipe
be_all_s.c [new file with mode: 0644]
be_nos_s.c [new file with mode: 0644]
config.c
dialog.h
doc/config.but
doc/gs.but
doc/using.but
mac/macctrls.c
macosx/osxctrls.m
misc.c
putty.h
sercfg.c [new file with mode: 0644]
settings.c
unix/gtkdlg.c
windows/wincfg.c
windows/winctrls.c
windows/windefs.c
windows/window.c
windows/winhelp.h
windows/winplink.c
windows/winser.c [new file with mode: 0644]
windows/winstuff.h

diff --git a/Recipe b/Recipe
index 779995f..31a6b5d 100644 (file)
--- a/Recipe
+++ b/Recipe
@@ -220,7 +220,7 @@ TERMINAL = terminal wcwidth ldiscucs logging tree234 minibidi
 
 # GUI front end and terminal emulator (putty, puttytel).
 GUITERM  = TERMINAL window windlg winctrls sizetip winucs winprint
-         + winutils wincfg
+         + winutils wincfg sercfg
 
 # Same thing on Unix.
 UXTERM   = TERMINAL uxcfg uxucs uxprint timing
@@ -263,6 +263,9 @@ BE_ALL   = be_all cproxy
 BE_NOSSH = be_nossh nocproxy
 BE_SSH   = be_none cproxy
 BE_NONE  = be_none nocproxy
+# More backend sets, with the additional Windows serial-port module.
+W_BE_ALL = be_all_s winser cproxy
+W_BE_NOSSH = be_nos_s winser nocproxy
 
 # ------------------------------------------------------------
 # Definitions of actual programs. The program name, followed by a
@@ -270,9 +273,9 @@ BE_NONE  = be_none nocproxy
 # keywords [G] for Windows GUI app, [C] for Console app, [X] for
 # X/GTK Unix app, [U] for command-line Unix app, [M] for Macintosh app.
 
-putty    : [G] GUITERM NONSSH WINSSH BE_ALL WINMISC putty.res LIBS
-puttytel : [G] GUITERM NONSSH BE_NOSSH WINMISC puttytel.res LIBS
-plink    : [C] winplink wincons NONSSH WINSSH BE_ALL logging WINMISC
+putty    : [G] GUITERM NONSSH WINSSH W_BE_ALL WINMISC putty.res LIBS
+puttytel : [G] GUITERM NONSSH W_BE_NOSSH WINMISC puttytel.res LIBS
+plink    : [C] winplink wincons NONSSH WINSSH W_BE_ALL logging WINMISC
          + plink.res LIBS
 pscp     : [C] pscp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC
          + pscp.res LIBS
diff --git a/be_all_s.c b/be_all_s.c
new file mode 100644 (file)
index 0000000..4b3c198
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Linking module for PuTTY proper: list the available backends
+ * including ssh, plus the serial backend.
+ */
+
+#include <stdio.h>
+#include "putty.h"
+
+/*
+ * This appname is not strictly in the right place, since Plink
+ * also uses this module. However, Plink doesn't currently use any
+ * of the dialog-box sorts of things that make use of appname, so
+ * it shouldn't do any harm here. I'm trying to avoid having to
+ * have tiny little source modules containing nothing but
+ * declarations of appname, for as long as I can...
+ */
+const char *const appname = "PuTTY";
+
+#ifdef TELNET_DEFAULT
+const int be_default_protocol = PROT_TELNET;
+#else
+const int be_default_protocol = PROT_SSH;
+#endif
+
+struct backend_list backends[] = {
+    {PROT_SSH, "ssh", &ssh_backend},
+    {PROT_TELNET, "telnet", &telnet_backend},
+    {PROT_RLOGIN, "rlogin", &rlogin_backend},
+    {PROT_RAW, "raw", &raw_backend},
+    {PROT_SERIAL, "serial", &serial_backend},
+    {0, NULL}
+};
diff --git a/be_nos_s.c b/be_nos_s.c
new file mode 100644 (file)
index 0000000..ea8cf14
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Linking module for PuTTYtel: list the available backends not
+ * including ssh.
+ */
+
+#include <stdio.h>
+#include "putty.h"
+
+const int be_default_protocol = PROT_TELNET;
+
+const char *const appname = "PuTTYtel";
+
+struct backend_list backends[] = {
+    {PROT_TELNET, "telnet", &telnet_backend},
+    {PROT_RLOGIN, "rlogin", &rlogin_backend},
+    {PROT_RAW, "raw", &raw_backend},
+    {PROT_SERIAL, "serial", &serial_backend},
+    {0, NULL}
+};
+
+/*
+ * Stub implementations of functions not used in non-ssh versions.
+ */
+void random_save_seed(void)
+{
+}
+
+void random_destroy_seed(void)
+{
+}
+
+void noise_ultralight(unsigned long data)
+{
+}
index a2f660f..7992e27 100644 (file)
--- a/config.c
+++ b/config.c
 
 #define PRINTER_DISABLED_STRING "None (printing disabled)"
 
-static void protocolbuttons_handler(union control *ctrl, void *dlg,
+#define HOST_BOX_TITLE "Host Name (or IP address)"
+#define PORT_BOX_TITLE "Port"
+
+static void config_host_handler(union control *ctrl, void *dlg,
+                               void *data, int event)
+{
+    Config *cfg = (Config *)data;
+
+    /*
+     * This function works just like the standard edit box handler,
+     * only it has to choose the control's label and text from two
+     * different places depending on the protocol.
+     */
+    if (event == EVENT_REFRESH) {
+       if (cfg->protocol == PROT_SERIAL) {
+           /*
+            * This label text is carefully chosen to contain an n,
+            * since that's the shortcut for the host name control.
+            */
+           dlg_label_change(ctrl, dlg, "Serial line");
+           dlg_editbox_set(ctrl, dlg, cfg->serline);
+       } else {
+           dlg_label_change(ctrl, dlg, HOST_BOX_TITLE);
+           dlg_editbox_set(ctrl, dlg, cfg->host);
+       }
+    } else if (event == EVENT_VALCHANGE) {
+       if (cfg->protocol == PROT_SERIAL)
+           dlg_editbox_get(ctrl, dlg, cfg->serline, lenof(cfg->serline));
+       else
+           dlg_editbox_get(ctrl, dlg, cfg->host, lenof(cfg->host));
+    }
+}
+
+static void config_port_handler(union control *ctrl, void *dlg,
+                               void *data, int event)
+{
+    Config *cfg = (Config *)data;
+    char buf[80];
+
+    /*
+     * This function works just like the standard edit box handler,
+     * only it has to choose the control's label and text from two
+     * different places depending on the protocol.
+     */
+    if (event == EVENT_REFRESH) {
+       if (cfg->protocol == PROT_SERIAL) {
+           /*
+            * This label text is carefully chosen to contain a p,
+            * since that's the shortcut for the port control.
+            */
+           dlg_label_change(ctrl, dlg, "Speed");
+           sprintf(buf, "%d", cfg->serspeed);
+       } else {
+           dlg_label_change(ctrl, dlg, PORT_BOX_TITLE);
+           sprintf(buf, "%d", cfg->port);
+       }
+       dlg_editbox_set(ctrl, dlg, buf);
+    } else if (event == EVENT_VALCHANGE) {
+       dlg_editbox_get(ctrl, dlg, buf, lenof(buf));
+       if (cfg->protocol == PROT_SERIAL)
+           cfg->serspeed = atoi(buf);
+       else
+           cfg->port = atoi(buf);
+    }
+}
+
+struct hostport {
+    union control *host, *port;
+};
+
+/*
+ * We export this function so that platform-specific config
+ * routines can use it to conveniently identify the protocol radio
+ * buttons in order to add to them.
+ */
+void config_protocolbuttons_handler(union control *ctrl, void *dlg,
                                    void *data, int event)
 {
     int button, defport;
     Config *cfg = (Config *)data;
+    struct hostport *hp = (struct hostport *)ctrl->radio.context.p;
+
     /*
      * This function works just like the standard radio-button
      * handler, except that it also has to change the setting of
-     * the port box. We expect the context parameter to point at
-     * the `union control' structure for the port box.
+     * the port box, and refresh both host and port boxes when. We
+     * expect the context parameter to point at a hostport
+     * structure giving the `union control's for both.
      */
     if (event == EVENT_REFRESH) {
        for (button = 0; button < ctrl->radio.nbuttons; button++)
@@ -44,9 +122,10 @@ static void protocolbuttons_handler(union control *ctrl, void *dlg,
            }
            if (defport > 0 && cfg->port != defport) {
                cfg->port = defport;
-               dlg_refresh((union control *)ctrl->radio.context.p, dlg);
            }
        }
+       dlg_refresh(hp->host, dlg);
+       dlg_refresh(hp->port, dlg);
     }
 }
 
@@ -382,7 +461,7 @@ static void sessionsaver_handler(union control *ctrl, void *dlg,
             * contains a hostname.
             */
            if (load_selected_session(ssd, savedsession, dlg, cfg) &&
-               (ctrl == ssd->listbox && cfg->host[0])) {
+               (ctrl == ssd->listbox && cfg_launchable(cfg))) {
                dlg_end(dlg, 1);       /* it's all over, and succeeded */
            }
        } else if (ctrl == ssd->savebutton) {
@@ -437,7 +516,8 @@ static void sessionsaver_handler(union control *ctrl, void *dlg,
             * there was a session selected in that which had a
             * valid host name in it, then load it and go.
             */
-           if (dlg_last_focused(ctrl, dlg) == ssd->listbox && !*cfg->host) {
+           if (dlg_last_focused(ctrl, dlg) == ssd->listbox &&
+               !cfg_launchable(cfg)) {
                Config cfg2;
                if (!load_selected_session(ssd, savedsession, dlg, &cfg2)) {
                    dlg_beep(dlg);
@@ -457,7 +537,7 @@ static void sessionsaver_handler(union control *ctrl, void *dlg,
             * Otherwise, do the normal thing: if we have a valid
             * session, get going.
             */
-           if (*cfg->host) {
+           if (cfg_launchable(cfg)) {
                dlg_end(dlg, 1);
            } else
                dlg_beep(dlg);
@@ -977,31 +1057,43 @@ void setup_config_box(struct controlbox *b, int midsession,
     sfree(str);
 
     if (!midsession) {
+       struct hostport *hp = (struct hostport *)
+           ctrl_alloc(b, sizeof(struct hostport));
+       int i, gotssh;
+
        s = ctrl_getset(b, "Session", "hostport",
-                       "Specify your connection by host name or IP address");
+                       "Specify the destination you want to connect to");
        ctrl_columns(s, 2, 75, 25);
-       c = ctrl_editbox(s, "Host Name (or IP address)", 'n', 100,
+       c = ctrl_editbox(s, HOST_BOX_TITLE, 'n', 100,
                         HELPCTX(session_hostname),
-                        dlg_stdeditbox_handler, I(offsetof(Config,host)),
-                        I(sizeof(((Config *)0)->host)));
+                        config_host_handler, I(0), I(0));
        c->generic.column = 0;
-       c = ctrl_editbox(s, "Port", 'p', 100, HELPCTX(session_hostname),
-                        dlg_stdeditbox_handler,
-                        I(offsetof(Config,port)), I(-1));
+       hp->host = c;
+       c = ctrl_editbox(s, PORT_BOX_TITLE, 'p', 100,
+                        HELPCTX(session_hostname),
+                        config_port_handler, I(0), I(0));
        c->generic.column = 1;
+       hp->port = c;
        ctrl_columns(s, 1, 100);
-       if (backends[3].name == NULL) {
-           ctrl_radiobuttons(s, "Protocol:", NO_SHORTCUT, 3,
+
+       gotssh = FALSE;
+       for (i = 0; backends[i].name; i++)
+           if (backends[i].protocol == PROT_SSH) {
+               gotssh = TRUE;
+               break;
+           }
+       if (!gotssh) {
+           ctrl_radiobuttons(s, "Connection type:", NO_SHORTCUT, 3,
                              HELPCTX(session_hostname),
-                             protocolbuttons_handler, P(c),
+                             config_protocolbuttons_handler, P(hp),
                              "Raw", 'r', I(PROT_RAW),
                              "Telnet", 't', I(PROT_TELNET),
                              "Rlogin", 'i', I(PROT_RLOGIN),
                              NULL);
        } else {
-           ctrl_radiobuttons(s, "Protocol:", NO_SHORTCUT, 4,
+           ctrl_radiobuttons(s, "Connection type:", NO_SHORTCUT, 4,
                              HELPCTX(session_hostname),
-                             protocolbuttons_handler, P(c),
+                             config_protocolbuttons_handler, P(hp),
                              "Raw", 'r', I(PROT_RAW),
                              "Telnet", 't', I(PROT_TELNET),
                              "Rlogin", 'i', I(PROT_RLOGIN),
index 8daa332..a695c85 100644 (file)
--- a/dialog.h
+++ b/dialog.h
@@ -611,6 +611,10 @@ void dlg_update_done(union control *ctrl, void *dlg);
  */
 void dlg_set_focus(union control *ctrl, void *dlg);
 /*
+ * Change the label text on a control.
+ */
+void dlg_label_change(union control *ctrl, void *dlg, char const *text);
+/*
  * Return the `ctrl' structure for the most recent control that had
  * the input focus apart from the one mentioned. This is NOT
  * GUARANTEED to work on all platforms, so don't base any critical
index 9bbd36d..42fa3aa 100644 (file)
@@ -25,18 +25,25 @@ filled in before PuTTY can open a session at all.
 \b The \q{Host Name} box is where you type the name, or the \i{IP
 address}, of the server you want to connect to.
 
-\b The \q{Protocol} radio buttons let you choose what type of
+\b The \q{Connection type} radio buttons let you choose what type of
 connection you want to make: a \I{raw TCP connections}raw
-connection, a \i{Telnet} connection, an \i{Rlogin} connection
-or an \i{SSH} connection. (See \k{which-one} for a
-summary of the differences between SSH, Telnet and rlogin, and
-\k{using-rawprot} for an explanation of \q{raw} connections.)
-
-\b The \q{Port} box lets you specify which \i{port number} on the server
-to connect to. If you select Telnet, Rlogin, or SSH, this box will
-be filled in automatically to the usual value, and you will only
-need to change it if you have an unusual server. If you select Raw
-mode, you will almost certainly need to fill in the \q{Port} box.
+connection, a \i{Telnet} connection, an \i{Rlogin} connection, an
+\i{SSH} connection, or a connection to a local \i{serial line}. (See
+\k{which-one} for a summary of the differences between SSH, Telnet
+and rlogin; see \k{using-rawprot} for an explanation of \q{raw}
+connections; see \k{using-serial} for information about using a
+serial line.)
+
+\b The \q{Port} box lets you specify which \i{port number} on the
+server to connect to. If you select Telnet, Rlogin, or SSH, this box
+will be filled in automatically to the usual value, and you will
+only need to change it if you have an unusual server. If you select
+Raw mode, you will almost certainly need to fill in the \q{Port} box
+yourself.
+
+If you select \q{Serial} from the \q{Connection type} radio buttons,
+the \q{Host Name} and \q{Port} boxes are replaced by \q{Serial line}
+and \q{Speed}; see \k{config-serial} for more details of these.
 
 \S{config-saving} \ii{Loading and storing saved sessions}
 
@@ -2912,6 +2919,95 @@ would expect.
 
 This is an SSH-2-specific bug.
 
+\H{config-serial} The Serial panel
+
+The \i{Serial} panel allows you to configure options that only apply
+when PuTTY is connecting to a local \I{serial port}\i{serial line}.
+
+\S{config-serial-line} Selecting a serial line to connect to
+
+\cfg{winhelp-topic}{serial.line}
+
+The \q{Serial line to connect to} box allows you to choose which
+serial line you want PuTTY to talk to, if your computer has more
+than one serial port.
+
+On Windows, the first serial line is called \cw{COM1}, and if there
+is a second it is called \cw{COM2}, and so on.
+
+This configuration setting is also visible on the Session panel,
+where it replaces the \q{Host Name} box (see \k{config-hostname}) if
+the connection type is set to \q{Serial}.
+
+\S{config-serial-speed} Selecting the speed of your serial line
+
+\cfg{winhelp-topic}{serial.speed}
+
+The \q{Speed} box allows you to choose the speed (or \q{baud rate})
+at which to talk to the serial line. Typical values might be 9600,
+19200, 38400 or 57600. Which one you need will depend on the device
+at the other end of the serial cable; consult the manual for that
+device if you are in doubt.
+
+This configuration setting is also visible on the Session panel,
+where it replaces the \q{Port} box (see \k{config-hostname}) if the
+connection type is set to \q{Serial}.
+
+\S{config-serial-databits} Selecting the number of data bits
+
+\cfg{winhelp-topic}{serial.databits}
+
+The \q{Data bits} box allows you to choose how many data bits are
+transmitted in each byte sent or received through the serial line.
+Typical values are 7 or 8.
+
+\S{config-serial-stopbits} Selecting the number of stop bits
+
+\cfg{winhelp-topic}{serial.stopbits}
+
+The \q{Stop bits} box allows you to choose how many stop bits are
+used in the serial line protocol. Typical values are 1, 1.5 or 2.
+
+\S{config-serial-parity} Selecting the serial parity checking scheme
+
+\cfg{winhelp-topic}{serial.parity}
+
+The \q{Parity} box allows you to choose what type of parity checking
+is used on the serial line. The settings are:
+
+\b \q{None}: no parity bit is sent at all.
+
+\b \q{Odd}: an extra parity bit is sent alongside each byte, and
+arranged so that the total number of 1 bits is odd.
+
+\b \q{Even}: an extra parity bit is sent alongside each byte, and
+arranged so that the total number of 1 bits is even.
+
+\b \q{Mark}: an extra parity bit is sent alongside each byte, and
+always set to 1.
+
+\b \q{Space}: an extra parity bit is sent alongside each byte, and
+always set to 0.
+
+\S{config-serial-flow} Selecting the serial flow control scheme
+
+\cfg{winhelp-topic}{serial.flow}
+
+The \q{Flow control} box allows you to choose what type of flow
+control checking is used on the serial line. The settings are:
+
+\b \q{None}: no flow control is done. Data may be lost if either
+side attempts to send faster than the serial line permits.
+
+\b \q{XON/XOFF}: flow control is done by sending XON and XOFF
+characters within the data stream.
+
+\b \q{RTS/CTS}: flow control is done using the RTS and CTS wires on
+the serial line.
+
+\b \q{DSR/DTR}: flow control is done using the DSR and DTR wires on
+the serial line.
+
 \H{config-file} \ii{Storing configuration in a file}
 
 PuTTY does not currently support storing its configuration in a file
index 63cca5d..eae9958 100644 (file)
@@ -19,13 +19,16 @@ In the \q{Host Name} box, enter the Internet \i{host name} of the server
 you want to connect to. You should have been told this by the
 provider of your login account.
 
-Now select a login \i{protocol} to use, from the \q{Protocol}
+Now select a login \i{protocol} to use, from the \q{Connection type}
 buttons. For a login session, you should select \i{Telnet},
 \i{Rlogin} or \i{SSH}. See \k{which-one} for a description of the
 differences between the three protocols, and advice on which one to
 use. The fourth protocol, \I{raw protocol}\e{Raw}, is not used for
 interactive login sessions; you would usually use this for debugging
-other Internet services (see \k{using-rawprot}).
+other Internet services (see \k{using-rawprot}). The fifth option,
+\e{Serial}, is used for connecting to a local serial line, and works
+somewhat differently: see \k{using-serial} for more information on
+this.
 
 When you change the selected protocol, the number in the \q{Port}
 box will change. This is normal: it happens because the various
index d0b03c4..0fb7127 100644 (file)
@@ -491,6 +491,37 @@ protocol}\q{Raw}, from the \q{Protocol} buttons in the \q{Session}
 configuration panel. (See \k{config-hostname}.) You can then enter a
 host name and a port number, and make the connection.
 
+\H{using-serial} Connecting to a local serial line
+
+PuTTY can connect directly to a local serial line as an alternative
+to making a network connection. In this mode, text typed into the
+PuTTY window will be sent straight out of your computer's serial
+port, and data received through that port will be displayed in the
+PuTTY window. You might use this mode, for example, if your serial
+port is connected to another computer which has a serial connection.
+
+To make a connection of this type, simply select \q{Serial} from the
+\q{Connection type} radio buttons on the \q{Session} configuration
+panel (see \k{config-hostname}). The \q{Host Name} and \q{Port}
+boxes will transform into \q{Serial line} and \q{Speed}, allowing
+you to specify which serial line to use (if your computer has more
+than one) and what speed (baud rate) to use when transferring data.
+For further configuration options (data bits, stop bits, parity,
+flow control), you can use the \q{Serial} configuration panel (see
+\k{config-serial}).
+
+After you start up PuTTY in serial mode, you might find that you
+have to make the first move, by sending some data out of the serial
+line in order to notify the device at the other end that someone is
+there for it to talk to. This probably depends on the device. If you
+start up a PuTTY serial session and nothing appears in the window,
+try pressing Return a few times and see if that helps.
+
+A serial line provides no well defined means for one end of the
+connection to notify the other that the connection is finished.
+Therefore, PuTTY in serial mode will remain connected until you
+close the window using the close button.
+
 \H{using-cmdline} The PuTTY command line
 
 PuTTY can be made to do various things without user intervention by
index ea182c0..e5e774d 100644 (file)
@@ -2327,6 +2327,17 @@ int dlg_coloursel_results(union control *ctrl, void *dlg,
        return 0;
 }
 
+void dlg_label_change(union control *ctrl, void *dlg, char const *text)
+{
+    /*
+     * This function is currently only used by the config box to
+     * switch the labels on the host and port boxes between serial
+     * and network modes. Since the Mac port does not have a serial
+     * back end, this function can safely do nothing.
+     */
+}
+
+
 /*
  * Local Variables:
  * c-file-style: "simon"
index e3780ff..12e813c 100644 (file)
@@ -1686,6 +1686,17 @@ void dlg_text_set(union control *ctrl, void *dv, char const *text)
     [c->textview setString:[NSString stringWithCString:text]];
 }
 
+void dlg_label_change(union control *ctrl, void *dlg, char const *text)
+{
+    /*
+     * This function is currently only used by the config box to
+     * switch the labels on the host and port boxes between serial
+     * and network modes. Since OS X does not (yet?) have a serial
+     * back end, this function can safely do nothing for the
+     * moment.
+     */
+}
+
 void dlg_filesel_set(union control *ctrl, void *dv, Filename fn)
 {
     /* FIXME */
diff --git a/misc.c b/misc.c
index 37ac0a1..09506de 100644 (file)
--- a/misc.c
+++ b/misc.c
@@ -625,3 +625,23 @@ void debug_memdump(void *buf, int len, int L)
 }
 
 #endif                         /* def DEBUG */
+
+/*
+ * Determine whether or not a Config structure represents a session
+ * which can sensibly be launched right now.
+ */
+int cfg_launchable(const Config *cfg)
+{
+    if (cfg->protocol == PROT_SERIAL)
+       return cfg->serline[0] != 0;
+    else
+       return cfg->host[0] != 0;
+}
+
+char const *cfg_dest(const Config *cfg)
+{
+    if (cfg->protocol == PROT_SERIAL)
+       return cfg->serline;
+    else
+       return cfg->host;
+}
diff --git a/putty.h b/putty.h
index 8277bbc..e43f97f 100644 (file)
--- a/putty.h
+++ b/putty.h
@@ -298,7 +298,10 @@ enum {
 
 enum {
     /* Protocol back ends. (cfg.protocol) */
-    PROT_RAW, PROT_TELNET, PROT_RLOGIN, PROT_SSH
+    PROT_RAW, PROT_TELNET, PROT_RLOGIN, PROT_SSH,
+    /* PROT_SERIAL is supported on a subset of platforms, but it doesn't
+     * hurt to define it globally. */
+    PROT_SERIAL
 };
 
 enum {
@@ -330,6 +333,14 @@ enum {
     FQ_DEFAULT, FQ_ANTIALIASED, FQ_NONANTIALIASED, FQ_CLEARTYPE
 };
 
+enum {
+    SER_PAR_NONE, SER_PAR_ODD, SER_PAR_EVEN, SER_PAR_MARK, SER_PAR_SPACE
+};
+
+enum {
+    SER_FLOW_NONE, SER_FLOW_XONXOFF, SER_FLOW_RTSCTS, SER_FLOW_DSRDTR
+};
+
 extern const char *const ttymodes[];
 
 enum {
@@ -456,6 +467,12 @@ struct config_tag {
     char localusername[100];
     int rfc_environ;
     int passive_telnet;
+    /* Serial port options */
+    char serline[256];
+    int serspeed;
+    int serdatabits, serstopbits;
+    int serparity;
+    int serflow;
     /* Keyboard options */
     int bksp_is_delete;
     int rxvt_homeend;
@@ -908,6 +925,13 @@ void pinger_free(Pinger);
  */
 
 #include "misc.h"
+int cfg_launchable(const Config *cfg);
+char const *cfg_dest(const Config *cfg);
+
+/*
+ * Exports from sercfg.c.
+ */
+void ser_setup_config_box(struct controlbox *b, int midsession);
 
 /*
  * Exports from version.c.
diff --git a/sercfg.c b/sercfg.c
new file mode 100644 (file)
index 0000000..c75879c
--- /dev/null
+++ b/sercfg.c
@@ -0,0 +1,167 @@
+/*\r
+ * sercfg.c - the serial-port specific parts of the PuTTY\r
+ * configuration box. Centralised as cross-platform code because\r
+ * more than one platform will want to use it, but not part of the\r
+ * main configuration. The expectation is that each platform's\r
+ * local config function will call out to ser_setup_config_box() if\r
+ * it needs to set up the standard serial stuff. (Of course, it can\r
+ * then apply local tweaks after ser_setup_config_box() returns, if\r
+ * it needs to.)\r
+ */\r
+\r
+#include <assert.h>\r
+#include <stdlib.h>\r
+\r
+#include "putty.h"\r
+#include "dialog.h"\r
+#include "storage.h"\r
+\r
+static void serial_parity_handler(union control *ctrl, void *dlg,\r
+                                 void *data, int event)\r
+{\r
+    static const struct {\r
+       const char *name;\r
+       int val;\r
+    } parities[] = {\r
+       {"None", SER_PAR_NONE},\r
+       {"Odd", SER_PAR_ODD},\r
+       {"Even", SER_PAR_EVEN},\r
+       {"Mark", SER_PAR_MARK},\r
+       {"Space", SER_PAR_SPACE},\r
+    };\r
+    int i;\r
+    Config *cfg = (Config *)data;\r
+\r
+    if (event == EVENT_REFRESH) {\r
+       dlg_update_start(ctrl, dlg);\r
+       dlg_listbox_clear(ctrl, dlg);\r
+       for (i = 0; i < lenof(parities); i++)\r
+           dlg_listbox_addwithid(ctrl, dlg, parities[i].name,\r
+                                 parities[i].val);\r
+       for (i = 0; i < lenof(parities); i++)\r
+           if (cfg->serparity == parities[i].val)\r
+               dlg_listbox_select(ctrl, dlg, i);\r
+       dlg_update_done(ctrl, dlg);\r
+    } else if (event == EVENT_SELCHANGE) {\r
+       int i = dlg_listbox_index(ctrl, dlg);\r
+       if (i < 0)\r
+           i = SER_PAR_NONE;\r
+       else\r
+           i = dlg_listbox_getid(ctrl, dlg, i);\r
+       cfg->serparity = i;\r
+    }\r
+}\r
+\r
+static void serial_flow_handler(union control *ctrl, void *dlg,\r
+                               void *data, int event)\r
+{\r
+    static const struct {\r
+       const char *name;\r
+       int val;\r
+    } flows[] = {\r
+       {"None", SER_FLOW_NONE},\r
+       {"XON/XOFF", SER_FLOW_XONXOFF},\r
+       {"RTS/CTS", SER_FLOW_RTSCTS},\r
+       {"DSR/DTR", SER_FLOW_DSRDTR},\r
+    };\r
+    int i;\r
+    Config *cfg = (Config *)data;\r
+\r
+    if (event == EVENT_REFRESH) {\r
+       dlg_update_start(ctrl, dlg);\r
+       dlg_listbox_clear(ctrl, dlg);\r
+       for (i = 0; i < lenof(flows); i++)\r
+           dlg_listbox_addwithid(ctrl, dlg, flows[i].name,\r
+                                 flows[i].val);\r
+       for (i = 0; i < lenof(flows); i++)\r
+           if (cfg->serflow == flows[i].val)\r
+               dlg_listbox_select(ctrl, dlg, i);\r
+       dlg_update_done(ctrl, dlg);\r
+    } else if (event == EVENT_SELCHANGE) {\r
+       int i = dlg_listbox_index(ctrl, dlg);\r
+       if (i < 0)\r
+           i = SER_PAR_NONE;\r
+       else\r
+           i = dlg_listbox_getid(ctrl, dlg, i);\r
+       cfg->serflow = i;\r
+    }\r
+}\r
+\r
+void ser_setup_config_box(struct controlbox *b, int midsession)\r
+{\r
+    struct controlset *s;\r
+    union control *c;\r
+\r
+    /*\r
+     * Add the serial back end to the protocols list at the top of\r
+     * the config box.\r
+     */\r
+    s = ctrl_getset(b, "Session", "hostport",\r
+                   "Specify your connection by host name or IP address");\r
+    {\r
+       int i;\r
+       extern void config_protocolbuttons_handler(union control *, void *,\r
+                                                  void *, int);\r
+        for (i = 0; i < s->ncontrols; i++) {\r
+            c = s->ctrls[i];\r
+           if (c->generic.type == CTRL_RADIO &&\r
+               c->generic.handler == config_protocolbuttons_handler) {\r
+               c->radio.nbuttons++;\r
+               c->radio.ncolumns++;\r
+               c->radio.buttons =\r
+                   sresize(c->radio.buttons, c->radio.nbuttons, char *);\r
+               c->radio.buttons[c->radio.nbuttons-1] =\r
+                   dupstr("Serial");\r
+               c->radio.buttondata =\r
+                   sresize(c->radio.buttondata, c->radio.nbuttons, intorptr);\r
+               c->radio.buttondata[c->radio.nbuttons-1] = I(PROT_SERIAL);\r
+               if (c->radio.shortcuts) {\r
+                   c->radio.shortcuts =\r
+                       sresize(c->radio.shortcuts, c->radio.nbuttons, char);\r
+                   c->radio.shortcuts[c->radio.nbuttons-1] = NO_SHORTCUT;\r
+               }\r
+           }\r
+       }\r
+    }\r
+\r
+    /*\r
+     * Entirely new Connection/Serial panel for serial port\r
+     * configuration.\r
+     */\r
+    ctrl_settitle(b, "Connection/Serial",\r
+                 "Options controlling local serial lines");\r
+\r
+    if (!midsession) {\r
+       /*\r
+        * We don't permit switching to a different serial port in\r
+        * midflight, although we do allow all other\r
+        * reconfiguration.\r
+        */\r
+       s = ctrl_getset(b, "Connection/Serial", "serline",\r
+                       "Select a serial line");\r
+       ctrl_editbox(s, "Serial line to connect to", 'l', 40,\r
+                    HELPCTX(serial_line),\r
+                    dlg_stdeditbox_handler, I(offsetof(Config,serline)),\r
+                    I(sizeof(((Config *)0)->serline)));\r
+    }\r
+\r
+    s = ctrl_getset(b, "Connection/Serial", "sercfg", "Configure the serial line");\r
+    ctrl_editbox(s, "Speed (baud)", 's', 40,\r
+                HELPCTX(serial_speed),\r
+                dlg_stdeditbox_handler, I(offsetof(Config,serspeed)), I(-1));\r
+    ctrl_editbox(s, "Data bits", 'b', 40,\r
+                HELPCTX(serial_databits),\r
+                dlg_stdeditbox_handler,I(offsetof(Config,serdatabits)),I(-1));\r
+    /*\r
+     * Stop bits come in units of one half.\r
+     */\r
+    ctrl_editbox(s, "Stop bits", 't', 40,\r
+                HELPCTX(serial_stopbits),\r
+                dlg_stdeditbox_handler,I(offsetof(Config,serstopbits)),I(-2));\r
+    ctrl_droplist(s, "Parity", 'p', 40,\r
+                 HELPCTX(serial_parity),\r
+                 serial_parity_handler, I(0));\r
+    ctrl_droplist(s, "Flow control", 'f', 40,\r
+                 HELPCTX(serial_flow),\r
+                 serial_flow_handler, I(0));\r
+}\r
index 76d02c0..9aeaf53 100644 (file)
@@ -439,6 +439,12 @@ void save_open_settings(void *sesskey, int do_host, Config *cfg)
     write_setting_fontspec(sesskey, "WideBoldFont", cfg->wideboldfont);
     write_setting_i(sesskey, "ShadowBold", cfg->shadowbold);
     write_setting_i(sesskey, "ShadowBoldOffset", cfg->shadowboldoffset);
+    write_setting_s(sesskey, "SerialLine", cfg->serline);
+    write_setting_i(sesskey, "SerialSpeed", cfg->serspeed);
+    write_setting_i(sesskey, "SerialDataBits", cfg->serdatabits);
+    write_setting_i(sesskey, "SerialStopHalfbits", cfg->serstopbits);
+    write_setting_i(sesskey, "SerialParity", cfg->serparity);
+    write_setting_i(sesskey, "SerialFlowControl", cfg->serflow);
 }
 
 void load_settings(char *section, int do_host, Config * cfg)
@@ -758,6 +764,12 @@ void load_open_settings(void *sesskey, int do_host, Config *cfg)
     gppfont(sesskey, "WideFont", &cfg->widefont);
     gppfont(sesskey, "WideBoldFont", &cfg->wideboldfont);
     gppi(sesskey, "ShadowBoldOffset", 1, &cfg->shadowboldoffset);
+    gpps(sesskey, "SerialLine", "", cfg->serline, sizeof(cfg->serline));
+    gppi(sesskey, "SerialSpeed", 9600, &cfg->serspeed);
+    gppi(sesskey, "SerialDataBits", 8, &cfg->serdatabits);
+    gppi(sesskey, "SerialStopHalfbits", 2, &cfg->serstopbits);
+    gppi(sesskey, "SerialParity", 0, &cfg->serparity);
+    gppi(sesskey, "SerialFlowControl", 0, &cfg->serflow);
 }
 
 void do_defaults(char *session, Config * cfg)
index 35c8d67..19546d9 100644 (file)
@@ -592,6 +592,17 @@ void dlg_text_set(union control *ctrl, void *dlg, char const *text)
     gtk_label_set_text(GTK_LABEL(uc->text), text);
 }
 
+void dlg_label_change(union control *ctrl, void *dlg, char const *text)
+{
+    /*
+     * This function is currently only used by the config box to
+     * switch the labels on the host and port boxes between serial
+     * and network modes. Since Unix does not (yet) have a serial
+     * back end, this function can safely do nothing for the
+     * moment.
+     */
+}
+
 void dlg_filesel_set(union control *ctrl, void *dlg, Filename fn)
 {
     struct dlgparam *dp = (struct dlgparam *)dlg;
index 6a970a7..ff5739b 100644 (file)
@@ -371,4 +371,9 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, int has_help,
            }
        }
     }
+
+    /*
+     * Serial back end is available on Windows.
+     */
+    ser_setup_config_box(b, midsession);
 }
index 9ef09b7..35f5f2d 100644 (file)
@@ -1161,10 +1161,11 @@ void progressbar(struct ctlpos *cp, int id)
  * Return value is a malloc'ed copy of the processed version of the
  * string.
  */
-static char *shortcut_escape(char *text, char shortcut)
+static char *shortcut_escape(const char *text, char shortcut)
 {
     char *ret;
-    char *p, *q;
+    char const *p;
+    char *q;
 
     if (!text)
        return NULL;                   /* sfree won't choke on this */
@@ -2236,6 +2237,53 @@ void dlg_text_set(union control *ctrl, void *dlg, char const *text)
     SetDlgItemText(dp->hwnd, c->base_id, text);
 }
 
+void dlg_label_change(union control *ctrl, void *dlg, char const *text)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+    char *escaped = NULL;
+    int id = -1;
+
+    assert(c);
+    switch (c->ctrl->generic.type) {
+      case CTRL_EDITBOX:
+       escaped = shortcut_escape(text, c->ctrl->editbox.shortcut);
+       id = c->base_id;
+       break;
+      case CTRL_RADIO:
+       escaped = shortcut_escape(text, c->ctrl->radio.shortcut);
+       id = c->base_id;
+       break;
+      case CTRL_CHECKBOX:
+       escaped = shortcut_escape(text, ctrl->checkbox.shortcut);
+       id = c->base_id;
+       break;
+      case CTRL_BUTTON:
+       escaped = shortcut_escape(text, ctrl->button.shortcut);
+       id = c->base_id;
+       break;
+      case CTRL_LISTBOX:
+       escaped = shortcut_escape(text, ctrl->listbox.shortcut);
+       id = c->base_id;
+       break;
+      case CTRL_FILESELECT:
+       escaped = shortcut_escape(text, ctrl->fileselect.shortcut);
+       id = c->base_id;
+       break;
+      case CTRL_FONTSELECT:
+       escaped = shortcut_escape(text, ctrl->fontselect.shortcut);
+       id = c->base_id;
+       break;
+      default:
+       assert(!"Can't happen");
+       break;
+    }
+    if (escaped) {
+       SetDlgItemText(dp->hwnd, id, escaped);
+       sfree(escaped);
+    }
+}
+
 void dlg_filesel_set(union control *ctrl, void *dlg, Filename fn)
 {
     struct dlgparam *dp = (struct dlgparam *)dlg;
index 2acc5e3..6fd6f0c 100644 (file)
@@ -32,6 +32,8 @@ Filename platform_default_filename(const char *name)
 
 char *platform_default_s(const char *name)
 {
+    if (!strcmp(name, "SerialLine"))
+       return dupstr("COM1");
     return NULL;
 }
 
index 0b928bd..42aa7fb 100644 (file)
@@ -235,7 +235,7 @@ static void start_backend(void)
     if (error) {
        char *str = dupprintf("%s Error", appname);
        sprintf(msg, "Unable to open connection to\n"
-               "%.800s\n" "%s", cfg.host, error);
+               "%.800s\n" "%s", cfg_dest(&cfg), error);
        MessageBox(NULL, msg, str, MB_ICONERROR | MB_OK);
        sfree(str);
        exit(0);
index 40fcf72..c9d05b0 100644 (file)
 #define WINHELP_CTX_ssh_bugs_rsapad2 "ssh.bugs.rsapad2"
 #define WINHELP_CTX_ssh_bugs_pksessid2 "ssh.bugs.pksessid2"
 #define WINHELP_CTX_ssh_bugs_rekey2 "ssh.bugs.rekey2"
+#define WINHELP_CTX_serial_line "serial.line"
+#define WINHELP_CTX_serial_speed "serial.speed"
+#define WINHELP_CTX_serial_databits "serial.databits"
+#define WINHELP_CTX_serial_stopbits "serial.stopbits"
+#define WINHELP_CTX_serial_parity "serial.parity"
+#define WINHELP_CTX_serial_flow "serial.flow"
 
 /* These are used in Windows-specific bits of the frontend.
  * We (ab)use "help context identifiers" (dwContextId) to identify them. */
index 86ad00f..562d26a 100644 (file)
@@ -319,7 +319,7 @@ int main(int argc, char **argv)
                errors = 1;
            }
        } else if (*p) {
-           if (!*cfg.host) {
+           if (!cfg_launchable(&cfg)) {
                char *q = p;
                /*
                 * If the hostname starts with "telnet:", set the
@@ -392,7 +392,7 @@ int main(int argc, char **argv)
                    {
                        Config cfg2;
                        do_defaults(host, &cfg2);
-                       if (loaded_session || cfg2.host[0] == '\0') {
+                       if (loaded_session || !cfg_launchable(&cfg2)) {
                            /* No settings for this host; use defaults */
                            /* (or session was already loaded with -load) */
                            strncpy(cfg.host, host, sizeof(cfg.host) - 1);
@@ -446,7 +446,7 @@ int main(int argc, char **argv)
     if (errors)
        return 1;
 
-    if (!*cfg.host) {
+    if (!cfg_launchable(&cfg)) {
        usage();
     }
 
@@ -459,7 +459,7 @@ int main(int argc, char **argv)
     }
 
     /* See if host is of the form user@host */
-    if (cfg.host[0] != '\0') {
+    if (cfg_launchable(&cfg)) {
        char *atsign = strrchr(cfg.host, '@');
        /* Make sure we're not overflowing the user field */
        if (atsign) {
diff --git a/windows/winser.c b/windows/winser.c
new file mode 100644 (file)
index 0000000..f6def7a
--- /dev/null
@@ -0,0 +1,396 @@
+/*\r
+ * Serial back end (Windows-specific).\r
+ */\r
+\r
+/*\r
+ * TODO:\r
+ * \r
+ *  - sending breaks?\r
+ *     + looks as if you do this by calling SetCommBreak(handle),\r
+ *      then waiting a bit, then doing ClearCommBreak(handle). A\r
+ *      small job for timing.c, methinks.\r
+ *\r
+ *  - why are we dropping data when talking to judicator?\r
+ */\r
+\r
+#include <stdio.h>\r
+#include <stdlib.h>\r
+#include <limits.h>\r
+\r
+#include "putty.h"\r
+\r
+#define SERIAL_MAX_BACKLOG 4096\r
+\r
+typedef struct serial_backend_data {\r
+    HANDLE port;\r
+    struct handle *out, *in;\r
+    void *frontend;\r
+    int bufsize;\r
+} *Serial;\r
+\r
+static void serial_terminate(Serial serial)\r
+{\r
+    if (serial->out) {\r
+       handle_free(serial->out);\r
+       serial->out = NULL;\r
+    }\r
+    if (serial->in) {\r
+       handle_free(serial->in);\r
+       serial->in = NULL;\r
+    }\r
+    if (serial->port) {\r
+       CloseHandle(serial->port);\r
+       serial->port = NULL;\r
+    }\r
+}\r
+\r
+static int serial_gotdata(struct handle *h, void *data, int len)\r
+{\r
+    Serial serial = (Serial)handle_get_privdata(h);\r
+    if (len <= 0) {\r
+       const char *error_msg;\r
+\r
+       /*\r
+        * Currently, len==0 should never happen because we're\r
+        * ignoring EOFs. However, it seems not totally impossible\r
+        * that this same back end might be usable to talk to named\r
+        * pipes or some other non-serial device, in which case EOF\r
+        * may become meaningful here.\r
+        */\r
+       if (len == 0)\r
+           error_msg = "End of file reading from serial device";\r
+       else\r
+           error_msg = "Error reading from serial device";\r
+\r
+       serial_terminate(serial);\r
+\r
+       notify_remote_exit(serial->frontend);\r
+\r
+       logevent(serial->frontend, error_msg);\r
+\r
+       connection_fatal(serial->frontend, "%s", error_msg);\r
+\r
+       return 0;                      /* placate optimiser */\r
+    } else {\r
+       return from_backend(serial->frontend, 0, data, len);\r
+    }\r
+}\r
+\r
+static void serial_sentdata(struct handle *h, int new_backlog)\r
+{\r
+    Serial serial = (Serial)handle_get_privdata(h);\r
+    if (new_backlog < 0) {\r
+       const char *error_msg = "Error writing to serial device";\r
+\r
+       serial_terminate(serial);\r
+\r
+       notify_remote_exit(serial->frontend);\r
+\r
+       logevent(serial->frontend, error_msg);\r
+\r
+       connection_fatal(serial->frontend, "%s", error_msg);\r
+    } else {\r
+       serial->bufsize = new_backlog;\r
+    }\r
+}\r
+\r
+static const char *serial_configure(Serial serial, HANDLE serport, Config *cfg)\r
+{\r
+    DCB dcb;\r
+    COMMTIMEOUTS timeouts;\r
+\r
+    /*\r
+     * Set up the serial port parameters. If we can't even\r
+     * GetCommState, we ignore the problem on the grounds that the\r
+     * user might have pointed us at some other type of two-way\r
+     * device instead of a serial port.\r
+     */\r
+    if (GetCommState(serport, &dcb)) {\r
+       char *msg;\r
+       const char *str;\r
+\r
+       /*\r
+        * Boilerplate.\r
+        */\r
+       dcb.fBinary = TRUE;\r
+       dcb.fDtrControl = DTR_CONTROL_ENABLE;\r
+       dcb.fDsrSensitivity = FALSE;\r
+       dcb.fTXContinueOnXoff = FALSE;\r
+       dcb.fOutX = FALSE;\r
+       dcb.fInX = FALSE;\r
+       dcb.fErrorChar = FALSE;\r
+       dcb.fNull = FALSE;\r
+       dcb.fRtsControl = RTS_CONTROL_ENABLE;\r
+       dcb.fAbortOnError = FALSE;\r
+       dcb.fOutxCtsFlow = FALSE;\r
+       dcb.fOutxDsrFlow = FALSE;\r
+\r
+       /*\r
+        * Configurable parameters.\r
+        */\r
+       dcb.BaudRate = cfg->serspeed;\r
+       msg = dupprintf("Configuring baud rate %d", cfg->serspeed);\r
+       logevent(serial->frontend, msg);\r
+       sfree(msg);\r
+\r
+       dcb.ByteSize = cfg->serdatabits;\r
+       msg = dupprintf("Configuring %d data bits", cfg->serdatabits);\r
+       logevent(serial->frontend, msg);\r
+       sfree(msg);\r
+\r
+       switch (cfg->serstopbits) {\r
+         case 2: dcb.StopBits = ONESTOPBIT; str = "1"; break;\r
+         case 3: dcb.StopBits = ONE5STOPBITS; str = "1.5"; break;\r
+         case 4: dcb.StopBits = TWOSTOPBITS; str = "2"; break;\r
+         default: return "Invalid number of stop bits (need 1, 1.5 or 2)";\r
+       }\r
+       msg = dupprintf("Configuring %s data bits", str);\r
+       logevent(serial->frontend, msg);\r
+       sfree(msg);\r
+\r
+       switch (cfg->serparity) {\r
+         case SER_PAR_NONE: dcb.Parity = NOPARITY; str = "no"; break;\r
+         case SER_PAR_ODD: dcb.Parity = ODDPARITY; str = "odd"; break;\r
+         case SER_PAR_EVEN: dcb.Parity = EVENPARITY; str = "even"; break;\r
+         case SER_PAR_MARK: dcb.Parity = MARKPARITY; str = "mark"; break;\r
+         case SER_PAR_SPACE: dcb.Parity = SPACEPARITY; str = "space"; break;\r
+       }\r
+       msg = dupprintf("Configuring %s parity", str);\r
+       logevent(serial->frontend, msg);\r
+       sfree(msg);\r
+\r
+       switch (cfg->serflow) {\r
+         case SER_FLOW_NONE:\r
+           str = "no";\r
+           break;\r
+         case SER_FLOW_XONXOFF:\r
+           dcb.fOutX = dcb.fInX = TRUE;\r
+           str = "XON/XOFF";\r
+           break;\r
+         case SER_FLOW_RTSCTS:\r
+           dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;\r
+           dcb.fOutxCtsFlow = TRUE;\r
+           str = "RTS/CTS";\r
+           break;\r
+         case SER_FLOW_DSRDTR:\r
+           dcb.fDtrControl = DTR_CONTROL_HANDSHAKE;\r
+           dcb.fOutxDsrFlow = TRUE;\r
+           str = "DSR/DTR";\r
+           break;\r
+       }\r
+       msg = dupprintf("Configuring %s flow control", str);\r
+       logevent(serial->frontend, msg);\r
+       sfree(msg);\r
+\r
+       if (!SetCommState(serport, &dcb))\r
+           return "Unable to configure serial port";\r
+\r
+       timeouts.ReadIntervalTimeout = 1;\r
+       timeouts.ReadTotalTimeoutMultiplier = 0;\r
+       timeouts.ReadTotalTimeoutConstant = 0;\r
+       timeouts.WriteTotalTimeoutMultiplier = 0;\r
+       timeouts.WriteTotalTimeoutConstant = 0;\r
+       if (!SetCommTimeouts(serport, &timeouts))\r
+           return "Unable to configure serial timeouts";\r
+    }\r
+\r
+    return NULL;\r
+}\r
+\r
+/*\r
+ * Called to set up the serial connection.\r
+ * \r
+ * Returns an error message, or NULL on success.\r
+ *\r
+ * Also places the canonical host name into `realhost'. It must be\r
+ * freed by the caller.\r
+ */\r
+static const char *serial_init(void *frontend_handle, void **backend_handle,\r
+                              Config *cfg,\r
+                              char *host, int port, char **realhost, int nodelay,\r
+                              int keepalive)\r
+{\r
+    Serial serial;\r
+    HANDLE serport;\r
+    const char *err;\r
+\r
+    serial = snew(struct serial_backend_data);\r
+    serial->port = NULL;\r
+    serial->out = serial->in = NULL;\r
+    *backend_handle = serial;\r
+\r
+    serial->frontend = frontend_handle;\r
+\r
+    {\r
+       char *msg = dupprintf("Opening serial device %s", host);\r
+       logevent(serial->frontend, msg);\r
+    }\r
+\r
+    serport = CreateFile(cfg->serline, GENERIC_READ | GENERIC_WRITE, 0, NULL,\r
+                        OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);\r
+    if (serport == INVALID_HANDLE_VALUE)\r
+       return "Unable to open serial port";\r
+\r
+    err = serial_configure(serial, serport, cfg);\r
+    if (err)\r
+       return err;\r
+\r
+    serial->port = serport;\r
+    serial->out = handle_output_new(serport, serial_sentdata, serial,\r
+                                   HANDLE_FLAG_OVERLAPPED);\r
+    serial->in = handle_input_new(serport, serial_gotdata, serial,\r
+                                 HANDLE_FLAG_OVERLAPPED |\r
+                                 HANDLE_FLAG_IGNOREEOF);\r
+\r
+    *realhost = dupstr(cfg->serline);\r
+\r
+    return NULL;\r
+}\r
+\r
+static void serial_free(void *handle)\r
+{\r
+    Serial serial = (Serial) handle;\r
+\r
+    serial_terminate(serial);\r
+    sfree(serial);\r
+}\r
+\r
+static void serial_reconfig(void *handle, Config *cfg)\r
+{\r
+    Serial serial = (Serial) handle;\r
+    const char *err;\r
+\r
+    err = serial_configure(serial, serial->port, cfg);\r
+\r
+    /*\r
+     * FIXME: what should we do if err returns something?\r
+     */\r
+}\r
+\r
+/*\r
+ * Called to send data down the serial connection.\r
+ */\r
+static int serial_send(void *handle, char *buf, int len)\r
+{\r
+    Serial serial = (Serial) handle;\r
+\r
+    if (serial->out == NULL)\r
+       return 0;\r
+\r
+    serial->bufsize = handle_write(serial->out, buf, len);\r
+    return serial->bufsize;\r
+}\r
+\r
+/*\r
+ * Called to query the current sendability status.\r
+ */\r
+static int serial_sendbuffer(void *handle)\r
+{\r
+    Serial serial = (Serial) handle;\r
+    return serial->bufsize;\r
+}\r
+\r
+/*\r
+ * Called to set the size of the window\r
+ */\r
+static void serial_size(void *handle, int width, int height)\r
+{\r
+    /* Do nothing! */\r
+    return;\r
+}\r
+\r
+/*\r
+ * Send serial special codes.\r
+ */\r
+static void serial_special(void *handle, Telnet_Special code)\r
+{\r
+    /*\r
+     * FIXME: serial break? XON? XOFF?\r
+     */\r
+    return;\r
+}\r
+\r
+/*\r
+ * Return a list of the special codes that make sense in this\r
+ * protocol.\r
+ */\r
+static const struct telnet_special *serial_get_specials(void *handle)\r
+{\r
+    /*\r
+     * FIXME: serial break? XON? XOFF?\r
+     */\r
+    return NULL;\r
+}\r
+\r
+static int serial_connected(void *handle)\r
+{\r
+    return 1;                         /* always connected */\r
+}\r
+\r
+static int serial_sendok(void *handle)\r
+{\r
+    return 1;\r
+}\r
+\r
+static void serial_unthrottle(void *handle, int backlog)\r
+{\r
+    Serial serial = (Serial) handle;\r
+    if (serial->in)\r
+       handle_unthrottle(serial->in, backlog);\r
+}\r
+\r
+static int serial_ldisc(void *handle, int option)\r
+{\r
+    /*\r
+     * Local editing and local echo are off by default.\r
+     */\r
+    return 0;\r
+}\r
+\r
+static void serial_provide_ldisc(void *handle, void *ldisc)\r
+{\r
+    /* This is a stub. */\r
+}\r
+\r
+static void serial_provide_logctx(void *handle, void *logctx)\r
+{\r
+    /* This is a stub. */\r
+}\r
+\r
+static int serial_exitcode(void *handle)\r
+{\r
+    Serial serial = (Serial) handle;\r
+    if (serial->port != NULL)\r
+        return -1;                     /* still connected */\r
+    else\r
+        /* Exit codes are a meaningless concept with serial ports */\r
+        return INT_MAX;\r
+}\r
+\r
+/*\r
+ * cfg_info for Serial does nothing at all.\r
+ */\r
+static int serial_cfg_info(void *handle)\r
+{\r
+    return 0;\r
+}\r
+\r
+Backend serial_backend = {\r
+    serial_init,\r
+    serial_free,\r
+    serial_reconfig,\r
+    serial_send,\r
+    serial_sendbuffer,\r
+    serial_size,\r
+    serial_special,\r
+    serial_get_specials,\r
+    serial_connected,\r
+    serial_exitcode,\r
+    serial_sendok,\r
+    serial_ldisc,\r
+    serial_provide_ldisc,\r
+    serial_provide_logctx,\r
+    serial_unthrottle,\r
+    serial_cfg_info,\r
+    1\r
+};\r
index 85c99f4..9244c75 100644 (file)
@@ -437,4 +437,9 @@ void agent_schedule_callback(void (*callback)(void *, void *, int),
                             void *callback_ctx, void *data, int len);
 #define FLAG_SYNCAGENT 0x1000
 
+/*
+ * Exports from winser.c.
+ */
+extern Backend serial_backend;
+
 #endif