diff --git a/Recipe b/Recipe index 779995fe..31a6b5d7 100644 --- 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 index 00000000..4b3c1983 --- /dev/null +++ b/be_all_s.c @@ -0,0 +1,32 @@ +/* + * Linking module for PuTTY proper: list the available backends + * including ssh, plus the serial backend. + */ + +#include +#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 index 00000000..ea8cf144 --- /dev/null +++ b/be_nos_s.c @@ -0,0 +1,34 @@ +/* + * Linking module for PuTTYtel: list the available backends not + * including ssh. + */ + +#include +#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) +{ +} diff --git a/config.c b/config.c index a2f660f4..7992e27d 100644 --- a/config.c +++ b/config.c @@ -12,16 +12,94 @@ #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), diff --git a/dialog.h b/dialog.h index 8daa3320..a695c852 100644 --- a/dialog.h +++ b/dialog.h @@ -610,6 +610,10 @@ void dlg_update_done(union control *ctrl, void *dlg); * Set input focus into a particular control. */ 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 diff --git a/doc/config.but b/doc/config.but index 9bbd36d2..42fa3aa0 100644 --- a/doc/config.but +++ b/doc/config.but @@ -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.) +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. +\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 diff --git a/doc/gs.but b/doc/gs.but index 63cca5d9..eae9958d 100644 --- a/doc/gs.but +++ b/doc/gs.but @@ -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 diff --git a/doc/using.but b/doc/using.but index d0b03c4a..0fb7127b 100644 --- a/doc/using.but +++ b/doc/using.but @@ -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 diff --git a/mac/macctrls.c b/mac/macctrls.c index ea182c0f..e5e774d9 100644 --- a/mac/macctrls.c +++ b/mac/macctrls.c @@ -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" diff --git a/macosx/osxctrls.m b/macosx/osxctrls.m index e3780ff6..12e813c9 100644 --- a/macosx/osxctrls.m +++ b/macosx/osxctrls.m @@ -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 37ac0a18..09506de2 100644 --- 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 8277bbc2..e43f97f1 100644 --- 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 index 00000000..c75879c5 --- /dev/null +++ b/sercfg.c @@ -0,0 +1,167 @@ +/* + * sercfg.c - the serial-port specific parts of the PuTTY + * configuration box. Centralised as cross-platform code because + * more than one platform will want to use it, but not part of the + * main configuration. The expectation is that each platform's + * local config function will call out to ser_setup_config_box() if + * it needs to set up the standard serial stuff. (Of course, it can + * then apply local tweaks after ser_setup_config_box() returns, if + * it needs to.) + */ + +#include +#include + +#include "putty.h" +#include "dialog.h" +#include "storage.h" + +static void serial_parity_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + static const struct { + const char *name; + int val; + } parities[] = { + {"None", SER_PAR_NONE}, + {"Odd", SER_PAR_ODD}, + {"Even", SER_PAR_EVEN}, + {"Mark", SER_PAR_MARK}, + {"Space", SER_PAR_SPACE}, + }; + int i; + Config *cfg = (Config *)data; + + if (event == EVENT_REFRESH) { + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; i < lenof(parities); i++) + dlg_listbox_addwithid(ctrl, dlg, parities[i].name, + parities[i].val); + for (i = 0; i < lenof(parities); i++) + if (cfg->serparity == parities[i].val) + dlg_listbox_select(ctrl, dlg, i); + dlg_update_done(ctrl, dlg); + } else if (event == EVENT_SELCHANGE) { + int i = dlg_listbox_index(ctrl, dlg); + if (i < 0) + i = SER_PAR_NONE; + else + i = dlg_listbox_getid(ctrl, dlg, i); + cfg->serparity = i; + } +} + +static void serial_flow_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + static const struct { + const char *name; + int val; + } flows[] = { + {"None", SER_FLOW_NONE}, + {"XON/XOFF", SER_FLOW_XONXOFF}, + {"RTS/CTS", SER_FLOW_RTSCTS}, + {"DSR/DTR", SER_FLOW_DSRDTR}, + }; + int i; + Config *cfg = (Config *)data; + + if (event == EVENT_REFRESH) { + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; i < lenof(flows); i++) + dlg_listbox_addwithid(ctrl, dlg, flows[i].name, + flows[i].val); + for (i = 0; i < lenof(flows); i++) + if (cfg->serflow == flows[i].val) + dlg_listbox_select(ctrl, dlg, i); + dlg_update_done(ctrl, dlg); + } else if (event == EVENT_SELCHANGE) { + int i = dlg_listbox_index(ctrl, dlg); + if (i < 0) + i = SER_PAR_NONE; + else + i = dlg_listbox_getid(ctrl, dlg, i); + cfg->serflow = i; + } +} + +void ser_setup_config_box(struct controlbox *b, int midsession) +{ + struct controlset *s; + union control *c; + + /* + * Add the serial back end to the protocols list at the top of + * the config box. + */ + s = ctrl_getset(b, "Session", "hostport", + "Specify your connection by host name or IP address"); + { + int i; + extern void config_protocolbuttons_handler(union control *, void *, + void *, int); + for (i = 0; i < s->ncontrols; i++) { + c = s->ctrls[i]; + if (c->generic.type == CTRL_RADIO && + c->generic.handler == config_protocolbuttons_handler) { + c->radio.nbuttons++; + c->radio.ncolumns++; + c->radio.buttons = + sresize(c->radio.buttons, c->radio.nbuttons, char *); + c->radio.buttons[c->radio.nbuttons-1] = + dupstr("Serial"); + c->radio.buttondata = + sresize(c->radio.buttondata, c->radio.nbuttons, intorptr); + c->radio.buttondata[c->radio.nbuttons-1] = I(PROT_SERIAL); + if (c->radio.shortcuts) { + c->radio.shortcuts = + sresize(c->radio.shortcuts, c->radio.nbuttons, char); + c->radio.shortcuts[c->radio.nbuttons-1] = NO_SHORTCUT; + } + } + } + } + + /* + * Entirely new Connection/Serial panel for serial port + * configuration. + */ + ctrl_settitle(b, "Connection/Serial", + "Options controlling local serial lines"); + + if (!midsession) { + /* + * We don't permit switching to a different serial port in + * midflight, although we do allow all other + * reconfiguration. + */ + s = ctrl_getset(b, "Connection/Serial", "serline", + "Select a serial line"); + ctrl_editbox(s, "Serial line to connect to", 'l', 40, + HELPCTX(serial_line), + dlg_stdeditbox_handler, I(offsetof(Config,serline)), + I(sizeof(((Config *)0)->serline))); + } + + s = ctrl_getset(b, "Connection/Serial", "sercfg", "Configure the serial line"); + ctrl_editbox(s, "Speed (baud)", 's', 40, + HELPCTX(serial_speed), + dlg_stdeditbox_handler, I(offsetof(Config,serspeed)), I(-1)); + ctrl_editbox(s, "Data bits", 'b', 40, + HELPCTX(serial_databits), + dlg_stdeditbox_handler,I(offsetof(Config,serdatabits)),I(-1)); + /* + * Stop bits come in units of one half. + */ + ctrl_editbox(s, "Stop bits", 't', 40, + HELPCTX(serial_stopbits), + dlg_stdeditbox_handler,I(offsetof(Config,serstopbits)),I(-2)); + ctrl_droplist(s, "Parity", 'p', 40, + HELPCTX(serial_parity), + serial_parity_handler, I(0)); + ctrl_droplist(s, "Flow control", 'f', 40, + HELPCTX(serial_flow), + serial_flow_handler, I(0)); +} diff --git a/settings.c b/settings.c index 76d02c0f..9aeaf530 100644 --- a/settings.c +++ b/settings.c @@ -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) diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index 35c8d679..19546d9c 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -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; diff --git a/windows/wincfg.c b/windows/wincfg.c index 6a970a75..ff5739be 100644 --- a/windows/wincfg.c +++ b/windows/wincfg.c @@ -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); } diff --git a/windows/winctrls.c b/windows/winctrls.c index 9ef09b7d..35f5f2d4 100644 --- a/windows/winctrls.c +++ b/windows/winctrls.c @@ -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; diff --git a/windows/windefs.c b/windows/windefs.c index 2acc5e36..6fd6f0c9 100644 --- a/windows/windefs.c +++ b/windows/windefs.c @@ -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; } diff --git a/windows/window.c b/windows/window.c index 0b928bd1..42aa7fb7 100644 --- a/windows/window.c +++ b/windows/window.c @@ -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); diff --git a/windows/winhelp.h b/windows/winhelp.h index 40fcf724..c9d05b03 100644 --- a/windows/winhelp.h +++ b/windows/winhelp.h @@ -131,6 +131,12 @@ #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. */ diff --git a/windows/winplink.c b/windows/winplink.c index 86ad00ff..562d26af 100644 --- a/windows/winplink.c +++ b/windows/winplink.c @@ -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 index 00000000..f6def7aa --- /dev/null +++ b/windows/winser.c @@ -0,0 +1,396 @@ +/* + * 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 +#include +#include + +#include "putty.h" + +#define SERIAL_MAX_BACKLOG 4096 + +typedef struct serial_backend_data { + HANDLE port; + struct handle *out, *in; + void *frontend; + int bufsize; +} *Serial; + +static void serial_terminate(Serial serial) +{ + if (serial->out) { + handle_free(serial->out); + serial->out = NULL; + } + if (serial->in) { + handle_free(serial->in); + serial->in = NULL; + } + if (serial->port) { + CloseHandle(serial->port); + serial->port = NULL; + } +} + +static int serial_gotdata(struct handle *h, void *data, int len) +{ + Serial serial = (Serial)handle_get_privdata(h); + if (len <= 0) { + const char *error_msg; + + /* + * Currently, len==0 should never happen because we're + * ignoring EOFs. However, it seems not totally impossible + * that this same back end might be usable to talk to named + * pipes or some other non-serial device, in which case EOF + * may become meaningful here. + */ + if (len == 0) + error_msg = "End of file reading from serial device"; + else + error_msg = "Error reading from serial device"; + + serial_terminate(serial); + + notify_remote_exit(serial->frontend); + + logevent(serial->frontend, error_msg); + + connection_fatal(serial->frontend, "%s", error_msg); + + return 0; /* placate optimiser */ + } else { + return from_backend(serial->frontend, 0, data, len); + } +} + +static void serial_sentdata(struct handle *h, int new_backlog) +{ + Serial serial = (Serial)handle_get_privdata(h); + if (new_backlog < 0) { + const char *error_msg = "Error writing to serial device"; + + serial_terminate(serial); + + notify_remote_exit(serial->frontend); + + logevent(serial->frontend, error_msg); + + connection_fatal(serial->frontend, "%s", error_msg); + } else { + serial->bufsize = new_backlog; + } +} + +static const char *serial_configure(Serial serial, HANDLE serport, Config *cfg) +{ + DCB dcb; + COMMTIMEOUTS timeouts; + + /* + * Set up the serial port parameters. If we can't even + * GetCommState, we ignore the problem on the grounds that the + * user might have pointed us at some other type of two-way + * device instead of a serial port. + */ + if (GetCommState(serport, &dcb)) { + char *msg; + const char *str; + + /* + * Boilerplate. + */ + dcb.fBinary = TRUE; + dcb.fDtrControl = DTR_CONTROL_ENABLE; + dcb.fDsrSensitivity = FALSE; + dcb.fTXContinueOnXoff = FALSE; + dcb.fOutX = FALSE; + dcb.fInX = FALSE; + dcb.fErrorChar = FALSE; + dcb.fNull = FALSE; + dcb.fRtsControl = RTS_CONTROL_ENABLE; + dcb.fAbortOnError = FALSE; + dcb.fOutxCtsFlow = FALSE; + dcb.fOutxDsrFlow = FALSE; + + /* + * Configurable parameters. + */ + dcb.BaudRate = cfg->serspeed; + msg = dupprintf("Configuring baud rate %d", cfg->serspeed); + logevent(serial->frontend, msg); + sfree(msg); + + dcb.ByteSize = cfg->serdatabits; + msg = dupprintf("Configuring %d data bits", cfg->serdatabits); + logevent(serial->frontend, msg); + sfree(msg); + + switch (cfg->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; + default: return "Invalid number of stop bits (need 1, 1.5 or 2)"; + } + msg = dupprintf("Configuring %s data bits", str); + logevent(serial->frontend, msg); + sfree(msg); + + switch (cfg->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; + case SER_PAR_MARK: dcb.Parity = MARKPARITY; str = "mark"; break; + case SER_PAR_SPACE: dcb.Parity = SPACEPARITY; str = "space"; break; + } + msg = dupprintf("Configuring %s parity", str); + logevent(serial->frontend, msg); + sfree(msg); + + switch (cfg->serflow) { + case SER_FLOW_NONE: + str = "no"; + break; + case SER_FLOW_XONXOFF: + dcb.fOutX = dcb.fInX = TRUE; + str = "XON/XOFF"; + break; + case SER_FLOW_RTSCTS: + dcb.fRtsControl = RTS_CONTROL_HANDSHAKE; + dcb.fOutxCtsFlow = TRUE; + str = "RTS/CTS"; + break; + case SER_FLOW_DSRDTR: + dcb.fDtrControl = DTR_CONTROL_HANDSHAKE; + dcb.fOutxDsrFlow = TRUE; + str = "DSR/DTR"; + break; + } + msg = dupprintf("Configuring %s flow control", str); + logevent(serial->frontend, msg); + sfree(msg); + + if (!SetCommState(serport, &dcb)) + return "Unable to configure serial port"; + + timeouts.ReadIntervalTimeout = 1; + timeouts.ReadTotalTimeoutMultiplier = 0; + timeouts.ReadTotalTimeoutConstant = 0; + timeouts.WriteTotalTimeoutMultiplier = 0; + timeouts.WriteTotalTimeoutConstant = 0; + if (!SetCommTimeouts(serport, &timeouts)) + return "Unable to configure serial timeouts"; + } + + return NULL; +} + +/* + * Called to set up the serial connection. + * + * Returns an error message, or NULL on success. + * + * Also places the canonical host name into `realhost'. It must be + * 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) +{ + Serial serial; + HANDLE serport; + const char *err; + + serial = snew(struct serial_backend_data); + serial->port = NULL; + serial->out = serial->in = NULL; + *backend_handle = serial; + + serial->frontend = frontend_handle; + + { + char *msg = dupprintf("Opening serial device %s", host); + logevent(serial->frontend, msg); + } + + serport = CreateFile(cfg->serline, GENERIC_READ | GENERIC_WRITE, 0, NULL, + OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); + if (serport == INVALID_HANDLE_VALUE) + return "Unable to open serial port"; + + err = serial_configure(serial, serport, cfg); + if (err) + return err; + + serial->port = serport; + serial->out = handle_output_new(serport, serial_sentdata, serial, + HANDLE_FLAG_OVERLAPPED); + serial->in = handle_input_new(serport, serial_gotdata, serial, + HANDLE_FLAG_OVERLAPPED | + HANDLE_FLAG_IGNOREEOF); + + *realhost = dupstr(cfg->serline); + + return NULL; +} + +static void serial_free(void *handle) +{ + Serial serial = (Serial) handle; + + serial_terminate(serial); + sfree(serial); +} + +static void serial_reconfig(void *handle, Config *cfg) +{ + Serial serial = (Serial) handle; + const char *err; + + err = serial_configure(serial, serial->port, cfg); + + /* + * FIXME: what should we do if err returns something? + */ +} + +/* + * Called to send data down the serial connection. + */ +static int serial_send(void *handle, char *buf, int len) +{ + Serial serial = (Serial) handle; + + if (serial->out == NULL) + return 0; + + serial->bufsize = handle_write(serial->out, buf, len); + return serial->bufsize; +} + +/* + * Called to query the current sendability status. + */ +static int serial_sendbuffer(void *handle) +{ + Serial serial = (Serial) handle; + return serial->bufsize; +} + +/* + * Called to set the size of the window + */ +static void serial_size(void *handle, int width, int height) +{ + /* Do nothing! */ + return; +} + +/* + * Send serial special codes. + */ +static void serial_special(void *handle, Telnet_Special code) +{ + /* + * FIXME: serial break? XON? XOFF? + */ + return; +} + +/* + * Return a list of the special codes that make sense in this + * protocol. + */ +static const struct telnet_special *serial_get_specials(void *handle) +{ + /* + * FIXME: serial break? XON? XOFF? + */ + return NULL; +} + +static int serial_connected(void *handle) +{ + return 1; /* always connected */ +} + +static int serial_sendok(void *handle) +{ + return 1; +} + +static void serial_unthrottle(void *handle, int backlog) +{ + Serial serial = (Serial) handle; + if (serial->in) + handle_unthrottle(serial->in, backlog); +} + +static int serial_ldisc(void *handle, int option) +{ + /* + * Local editing and local echo are off by default. + */ + return 0; +} + +static void serial_provide_ldisc(void *handle, void *ldisc) +{ + /* This is a stub. */ +} + +static void serial_provide_logctx(void *handle, void *logctx) +{ + /* This is a stub. */ +} + +static int serial_exitcode(void *handle) +{ + Serial serial = (Serial) handle; + if (serial->port != NULL) + return -1; /* still connected */ + else + /* Exit codes are a meaningless concept with serial ports */ + return INT_MAX; +} + +/* + * cfg_info for Serial does nothing at all. + */ +static int serial_cfg_info(void *handle) +{ + return 0; +} + +Backend serial_backend = { + serial_init, + serial_free, + serial_reconfig, + serial_send, + serial_sendbuffer, + serial_size, + serial_special, + serial_get_specials, + serial_connected, + serial_exitcode, + serial_sendok, + serial_ldisc, + serial_provide_ldisc, + serial_provide_logctx, + serial_unthrottle, + serial_cfg_info, + 1 +}; diff --git a/windows/winstuff.h b/windows/winstuff.h index 85c99f43..9244c75c 100644 --- a/windows/winstuff.h +++ b/windows/winstuff.h @@ -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