On Sat, Feb 24, 2007 at 01:39:25AM -0500, Rich Felker wrote:
> > using luit for this sounds appealing, but in my experience luit (a)
> > crashes frequently and (b) is easily confused by escape sequences and
> > has no user interface for resetting all its iso-2022 state, so in
> > practice it works for only a few apps.
> 
> Hmm, maybe a replacement for luit is in order then.. If I omit
> iso-2022 support (which IMO is a big plus) then it should just be ~100
> lines of C.. I'll see if I can whip up a prototype sometime soon.

And here it is. Ugly but simple. Syntax is:
tconv [-i inner_encoding] [-o outer_encoding] [-e command ...]

Both encodings default to nl_langinfo(CODESET). Command defaults to
$SHELL. Bad things(tm) may happen if you set either encoding to
something stateful or ascii-incompatible (e.g. non-EUC legacy CJK
encodings) or a transliterating converter.

Actual usage to fix rxvt:
rxvt -e ./tconv -o iso-8859-1

Known bugs: termios handling is somewhat wrong and something should be
done to ensure that replacements made by iconv match the column width
of the correct character, to avoid corrupting the terminal. Maybe
deadlock situations when terminal blocks..? Other bugs?

Rich
/* Written in 2007 by Rich Felker; released to the public domain */

#define _XOPEN_SOURCE 500

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <signal.h>
#include <locale.h>
#include <langinfo.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/select.h>
#include <termios.h>
#include <iconv.h>

static void dummy(int sig)
{
}

static void print(int fd, ...)
{
        va_list ap;
        const char *s;
        va_start(ap, fd);
        while ((s = va_arg(ap, const char *)))
                write(fd, s, strlen(s));
}

int main(int argc, char **argv)
{
        int i, j;
        const char *o_enc, *i_enc;
        char **cmd = 0;
        int pty;
        fd_set rfds, wfds;
        char buf[512], buf2[1536];
        static struct termios tio, tio_old;
        iconv_t itoo, otoi;
        char *in, *out;
        size_t inb, outb;

#ifdef TIOCSWINSZ
        struct winsize ws = { };
        
        signal(SIGWINCH, dummy);
        ioctl(0, TIOCGWINSZ, &ws);
#endif

        tcgetattr(0, &tio);
        tio_old = tio;
        tio.c_cflag &= CBAUD;
        tio.c_cflag |= CS8 | CLOCAL | CREAD;
        tio.c_iflag = 0;
        tio.c_oflag = 0;
        tio.c_lflag = 0;
        tcsetattr(0, TCSANOW, &tio);

        setlocale(LC_CTYPE, "");
        i_enc = o_enc = nl_langinfo(CODESET);

        for (i=1; i<argc && !cmd; i++) {
                if (argv[i][0] != '-') {
                        print(2, argv[0], ": unrecognized option: '",
                                argv[i], "'\n", (char*)0);
                        continue;
                }
                for (j=1; argv[i][j]; j++) switch (argv[i][1]) {
                case 'o':
                        if (argv[i][j+1]) o_enc = argv[i]+j+1;
                        else if (i+1 < argc) o_enc = argv[++i];
                        else print(2, argv[0],
                                ": outer encoding omitted\n", (char *)0);
                        break;
                case 'i':
                        if (argv[i][j+1]) i_enc = argv[i]+j+1;
                        else if (i+1 < argc) i_enc = argv[++i];
                        else print(2, argv[0],
                                ": inner encoding omitted\n", (char *)0);
                        break;
                case 'e':
                        if (argv[i][j+1]) argv[i] += j+1;
                        else if (i+1 < argc) i++;
                        else print(2, argv[0],
                                ": command omitted, using SHELL\n", (char *)0);
                        /* null terminate our exec arglist */
                        for (j=0; j<argc-i; j++)
                                argv[j] = argv[j+i];
                        argv[j] = 0;
                        cmd = argv;
                }
        }
        
        itoo = iconv_open(o_enc, i_enc);
        otoi = iconv_open(i_enc, o_enc);
        if (!itoo || !otoi) {
                print(2, argv[0], ": failed to open iconv between ",
                        o_enc, " and ", i_enc, "\n", (char*)0);
                goto die;
        }
        
        if ((pty = posix_openpt(O_RDWR|O_NOCTTY)) < 0
          || grantpt(pty) < 0 || unlockpt(pty) < 0) {
                print(2, argv[0], ": failed to get pty: ",
                        strerror(errno), "\n", (char *)0);
                goto die;
        }

        switch(fork()) {
        case -1:
                print(2, argv[0], ": failed to fork child: ",
                        strerror(errno), "\n", (char *)0);
                goto die;
        case 0:
                setsid();
                i = open(ptsname(pty), O_RDWR);
                close(pty);
                dup2(i, 0);
                dup2(i, 1);
                dup2(i, 2);
                if (i > 2) close(i);
                if (cmd) execvp(cmd[0], cmd);
                else {
                        const char *s = getenv("SHELL");
                        if (!s) s = "/bin/sh";
                        execl(s, s, (char *)0);
                }
                _exit(1);
        }

        goto resize;
        for (;;) {
                FD_ZERO(&rfds);
                FD_ZERO(&wfds);
                FD_SET(0,&rfds);
                FD_SET(pty,&rfds);
                switch (select(pty+1, &rfds, &wfds, NULL, 0)) {
                case 0:
                        continue;
                case -1:
                        if (errno != EINTR) {
                                print(2, argv[0], ": error: ",
                                        strerror(errno), "\n", (char *)0);
                                goto die;
                        }
resize:
#ifdef TIOCSWINSZ
                        ioctl(0, TIOCGWINSZ, &ws);
                        ioctl(pty, TIOCSWINSZ, &ws);
#endif
                        continue;
                }
                if (FD_ISSET(pty, &rfds)) {
                        ssize_t l = read(pty, buf, sizeof buf);
                        if (l <= 0) exit(0);
                        in = buf; inb = l;
                        out = buf2; outb = sizeof buf2;
                        while (inb && outb) {
                                iconv(itoo, &in, &inb, &out, &outb);
                                if (inb) { in++; inb--; }
                        }
                        write(1, buf2, out-buf2);
                }
                /* fixme: account for blocked pty..? */
                if (FD_ISSET(0, &rfds)) {
                        ssize_t l = read(0, buf, sizeof buf);
                        if (l <= 0) exit(0);
                        in = buf; inb = l;
                        out = buf2; outb = sizeof buf2;
                        while (inb && outb) {
                                iconv(otoi, &in, &inb, &out, &outb);
                                if (inb) { in++; inb--; }
                        }
                        write(pty, buf, l);
                }
        }
die:
        tcsetattr(0, TCSAFLUSH, &tio_old);
        return 1;
}

Reply via email to