1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00:00

Support for Windows PuTTY connecting straight to a local serial port

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'.

[originally from svn r6815]
This commit is contained in:
Simon Tatham 2006-08-28 10:35:12 +00:00
parent 38f003dbe9
commit 34f747421d
23 changed files with 1056 additions and 43 deletions

11
Recipe
View File

@ -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

32
be_all_s.c Normal file
View File

@ -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}
};

34
be_nos_s.c Normal file
View File

@ -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)
{
}

130
config.c
View File

@ -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),

View File

@ -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

View 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.)
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

View 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

View 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

View 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"

View 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 */

20
misc.c
View File

@ -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;
}

26
putty.h
View File

@ -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.

167
sercfg.c Normal file
View File

@ -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 <assert.h>
#include <stdlib.h>
#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));
}

View 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)

View 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;

View 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);
}

View 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;

View 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;
}

View 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);

View File

@ -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. */

View 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) {

396
windows/winser.c Normal file
View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <limits.h>
#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
};

View 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