Updated 2008-11-21 14:04:57 by dkf

GPS - Thu May 9, 2002: I wrote this little extension for my file server. My file server is a console application that prompts the user for a password. Being the pedantic programmer that I am I decided I didn't want someone to be able to see the password while the user enters it. I also wanted to receive a character at a time, so that I could display * for each entered char. This extension provides four commands. Please feel free to add to this. I place the code in the public domain.

KBK - 10 May 2002 - George, would you consider writing a TIP on this? This code could slide very easily into tclUnixChan.c (in the functions TtyGetOptionProc and TtySetOptionProc) as additional 'fconfigure' options that apply to ttys. Perhaps one could spell them something like:
    fconfigure stdin -echo false -canonicalize false

It's slightly messy that configuring these flags might apply to several channels at once, but that's no worse than the bucket of options we already have on ttys:
    -mode / -handshake / -xchar / -timeout / -ttycontrol

all potentially have the same cross-channel effects.
  #include <stdio.h>
  #include <stdlib.h>
  #include <termios.h>
  #include <string.h>
  #include <sys/syslimits.h> /*For MAX_CANON -- Linux: use <limits.h> instead */
  #include <tcl.h>

  #define CMD_ARGS (ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])

  int TerminalEchoOff CMD_ARGS {
    struct termios terminal;
    int fid = fileno (stdout);

    if (objc != 1) {
      Tcl_WrongNumArgs (interp, 0, NULL, "terminal:echoOff");
      return TCL_ERROR;
    }

    tcgetattr (fid, &terminal);
    terminal.c_lflag &= (~ECHO);
    tcsetattr (fid, TCSANOW, &terminal);

    return TCL_OK;
  }

  int TerminalEchoOn CMD_ARGS {
    struct termios terminal;
    int fid = fileno (stdout);

    if (objc != 1) {
      Tcl_WrongNumArgs (interp, 0, NULL, "terminal:echoOn");
      return TCL_ERROR;
    }

    tcgetattr (fid, &terminal);
    terminal.c_lflag |= (ECHO);
    tcsetattr (fid, TCSANOW, &terminal);

    return TCL_OK;
  }

  int TerminalCanonicalOff CMD_ARGS {
    struct termios terminal;
    int fid = fileno (stdin);

    if (objc != 1) {
      Tcl_WrongNumArgs (interp, 0, NULL, "terminal:canonicalOff");
      return TCL_ERROR;
    }

    tcgetattr (fid, &terminal);
    terminal.c_lflag &= (~ICANON);
    terminal.c_cc[VTIME] = 0;
    terminal.c_cc[VMIN] = 1;
    tcsetattr (fid, TCSANOW, &terminal);

    return TCL_OK;
  }

  int TerminalCanonicalOn CMD_ARGS {
    struct termios terminal;
    int fid = fileno (stdin);

    if (objc != 1) {
      Tcl_WrongNumArgs (interp, 0, NULL, "terminal:canonicalOn");
      return TCL_ERROR;
    }

    tcgetattr (fid, &terminal);
    terminal.c_lflag |= (ICANON);
    terminal.c_cc[VTIME] = 0;
    terminal.c_cc[VMIN] = MAX_CANON;
    tcsetattr (fid, TCSANOW, &terminal);

    return TCL_OK;
  }

  int Terminal_Init (Tcl_Interp *interp) {
    #define OBJ_CMD(name,func) Tcl_CreateObjCommand(interp, name, func, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL)

    OBJ_CMD ("terminal:echoOff", TerminalEchoOff);
    OBJ_CMD ("terminal:echoOn", TerminalEchoOn);
    OBJ_CMD ("terminal:canonicalOff", TerminalCanonicalOff);
    OBJ_CMD ("terminal:canonicalOn", TerminalCanonicalOn);

    #undef OBJ_CMD
    return TCL_OK;
  }

GPS Oct 23, 2003 - Compile with:
 cc -shared terminal.c -o terminal.so

You may also need to add a -l flag to link with the terminal library needed. Use nm in /usr/lib to findout which library has the required symbols. Then load it into your tclsh like so:
 load ./terminal.so

Example usage:
 terminal:echoOff
 #now we can read characters or lines without having them echo
 terminal:echoOn
 #now we see each character entered
 terminal:canonicalOff
 #now we can read a character at a time
 terminal:canonicalOn
 #now we read only when \r is received