1 /* A front-end using readline to "cook" input lines for Kawa.
3 * Copyright (C) 1999 Per Bothner
5 * This front-end program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as published
7 * by the Free Software Foundation; either version 2, or (at your option)
10 * Some code from Johnson & Troan: "Linux Application Development"
11 * (Addison-Wesley, 1998) was used directly or for inspiration.
16 * Only tested under Linux; needs to be ported.
18 * When running mc -c under the Linux console, mc does not recognize
19 * mouse clicks, which mc does when not running under fep.
21 * Pasting selected text containing tabs is like hitting the tab character,
22 * which invokes readline completion. We don't want this. I don't know
23 * if this is fixable without integrating fep into a terminal emulator.
25 * Echo suppression is a kludge, but can only be avoided with better kernel
26 * support: We need a tty mode to disable "real" echoing, while still
27 * letting the inferior think its tty driver to doing echoing.
28 * Stevens's book claims SCR$ and BSD4.3+ have TIOCREMOTE.
30 * The latest readline may have some hooks we can use to avoid having
31 * to back up the prompt.
33 * Desirable readline feature: When in cooked no-echo mode (e.g. password),
34 * echo characters are they are types with '*', but remove them when done.
36 * A synchronous output while we're editing an input line should be
37 * inserted in the output view *before* the input line, so that the
38 * lines being edited (with the prompt) float at the end of the input.
40 * A "page mode" option to emulate more/less behavior: At each page of
41 * output, pause for a user command. This required parsing the output
42 * to keep track of line lengths. It also requires remembering the
43 * output, if we want an option to scroll back, which suggests that
44 * this should be integrated with a terminal emulator like xterm.
53 #include <sys/types.h>
54 #include <sys/socket.h>
55 #include <netinet/in.h>
56 #include <arpa/inet.h>
65 #include <sys/ioctl.h>
68 #ifdef READLINE_LIBRARY
69 # include "readline.h"
72 # include <readline/readline.h>
73 # include <readline/history.h>
77 #define COMMAND "/bin/sh"
80 #define COMMAND_ARGS COMMAND
85 # define memmove(d, s, n) __builtin_memcpy(d, s, n)
87 # define memmove(d, s, n) memcpy(d, s, n)
90 # define memmove(d, s, n) memcpy(d, s, n)
93 #define APPLICATION_NAME "Fep"
95 static int in_from_inferior_fd
;
96 static int out_to_inferior_fd
;
98 /* Unfortunately, we cannot safely display echo from the inferior process.
99 The reason is that the echo bit in the pty is "owned" by the inferior,
100 and if we try to turn it off, we could confuse the inferior.
101 Thus, when echoing, we get echo twice: First readline echoes while
102 we're actually editing. Then we send the line to the inferior, and the
103 terminal driver send back an extra echo.
104 The work-around is to remember the input lines, and when we see that
105 line come back, we supress the output.
106 A better solution (supposedly available on SVR4) would be a smarter
107 terminal driver, with more flags ... */
108 #define ECHO_SUPPRESS_MAX 1024
109 char echo_suppress_buffer
[ECHO_SUPPRESS_MAX
];
110 int echo_suppress_start
= 0;
111 int echo_suppress_limit
= 0;
116 FILE *logfile
= NULL
;
117 #define DPRINT0(FMT) (fprintf(logfile, FMT), fflush(logfile))
118 #define DPRINT1(FMT, V1) (fprintf(logfile, FMT, V1), fflush(logfile))
119 #define DPRINT2(FMT, V1, V2) (fprintf(logfile, FMT, V1, V2), fflush(logfile))
121 #define DPRINT0(FMT) /* Do nothing */
122 #define DPRINT1(FMT, V1) /* Do nothing */
123 #define DPRINT2(FMT, V1, V2) /* Do nothing */
126 struct termios orig_term
;
128 /* Pid of child process. */
129 static pid_t child
= -1;
132 sig_child (int signo
)
136 DPRINT0 ("(Child process died.)\n");
137 tcsetattr(STDIN_FILENO
, TCSANOW
, &orig_term
);
141 volatile int propagate_sigwinch
= 0;
144 * propagate window size changes from input file descriptor to
145 * master side of pty.
147 void sigwinch_handler(int signal
) {
148 propagate_sigwinch
= 1;
151 /* get_master_pty() takes a double-indirect character pointer in which
152 * to put a slave name, and returns an integer file descriptor.
153 * If it returns < 0, an error has occurred.
154 * Otherwise, it has returned the master pty file descriptor, and fills
155 * in *name with the name of the corresponding slave pty.
156 * Once the slave pty has been opened, you are responsible to free *name.
159 int get_master_pty(char **name
) {
161 /* default to returning error */
164 /* create a dummy name to fill in */
165 *name
= strdup("/dev/ptyXX");
167 /* search for an unused pty */
168 for (i
=0; i
<16 && master
<= 0; i
++) {
169 for (j
=0; j
<16 && master
<= 0; j
++) {
171 (*name
)[8] = "pqrstuvwxyzPQRST"[i
];
172 (*name
)[9] = "0123456789abcdef"[j
];
173 /* open the master pty */
174 if ((master
= open(*name
, O_RDWR
)) < 0) {
175 if (errno
== ENOENT
) {
176 /* we are out of pty devices */
182 /* By substituting a letter, we change the master pty
183 * name into the slave pty name.
186 if (access(*name
, R_OK
|W_OK
) != 0)
194 if ((master
< 0) && (i
== 16) && (j
== 16)) {
195 /* must have tried every pty unsuccessfully */
205 /* get_slave_pty() returns an integer file descriptor.
206 * If it returns < 0, an error has occurred.
207 * Otherwise, it has returned the slave file descriptor.
210 int get_slave_pty(char *name
) {
215 /* chown/chmod the corresponding pty, if possible.
216 * This will only work if the process has root permissions.
217 * Alternatively, write and exec a small setuid program that
220 if ((gptr
= getgrnam("tty")) != 0) {
223 /* if the tty group does not exist, don't change the
224 * group on the slave pty, only the owner
229 /* Note that we do not check for errors here. If this is code
230 * where these actions are critical, check for errors!
232 chown(name
, getuid(), gid
);
233 /* This code only makes the slave read/writeable for the user.
234 * If this is for an interactive shell that will want to
235 * receive "write" and "wall" messages, OR S_IWGRP into the
236 * second argument below.
238 chmod(name
, S_IRUSR
|S_IWUSR
);
240 /* open the corresponding slave pty */
241 slave
= open(name
, O_RDWR
);
245 /* Certain special characters, such as ctrl/C, we want to pass directly
246 to the inferior, rather than letting readline handle them. */
248 static char special_chars
[20];
249 static int special_chars_count
;
252 add_special_char(int ch
)
255 special_chars
[special_chars_count
++] = ch
;
261 is_special_char(int ch
)
265 if (ch
== eof_char
&& rl_point
== rl_end
)
268 for (i
= special_chars_count
; --i
>= 0; )
269 if (special_chars
[i
] == ch
)
274 static char buf
[1024];
275 /* buf[0 .. buf_count-1] is the what has been emitted on the current line.
276 It is used as the readline prompt. */
277 static int buf_count
= 0;
282 null_prep_terminal (int meta
)
287 null_deprep_terminal ()
291 char pending_special_char
;
294 line_handler (char *line
)
299 DPRINT0("saw eof!\n");
300 buf
[0] = '\004'; /* ctrl/d */
301 write (out_to_inferior_fd
, buf
, 1);
305 static char enter
[] = "\r";
306 /* Send line to inferior: */
307 int length
= strlen (line
);
308 if (length
> ECHO_SUPPRESS_MAX
-2)
310 echo_suppress_start
= 0;
311 echo_suppress_limit
= 0;
315 if (echo_suppress_limit
+ length
> ECHO_SUPPRESS_MAX
- 2)
317 if (echo_suppress_limit
- echo_suppress_start
+ length
318 <= ECHO_SUPPRESS_MAX
- 2)
320 memmove (echo_suppress_buffer
,
321 echo_suppress_buffer
+ echo_suppress_start
,
322 echo_suppress_limit
- echo_suppress_start
);
323 echo_suppress_limit
-= echo_suppress_start
;
324 echo_suppress_start
= 0;
328 echo_suppress_limit
= 0;
330 echo_suppress_start
= 0;
332 memcpy (echo_suppress_buffer
+ echo_suppress_limit
,
334 echo_suppress_limit
+= length
;
335 echo_suppress_buffer
[echo_suppress_limit
++] = '\r';
336 echo_suppress_buffer
[echo_suppress_limit
++] = '\n';
338 write (out_to_inferior_fd
, line
, length
);
339 if (pending_special_char
== 0)
341 write (out_to_inferior_fd
, enter
, sizeof(enter
)-1);
347 rl_callback_handler_remove ();
350 if (pending_special_char
!= 0)
352 write (out_to_inferior_fd
, &pending_special_char
, 1);
353 pending_special_char
= 0;
357 /* Value of rl_getc_function.
358 Use this because readline should read from stdin, not rl_instream,
359 points to the pty (so readline has monitor its terminal modes). */
362 my_rl_getc (FILE *dummy
)
364 int ch
= rl_getc (stdin
);
365 if (is_special_char (ch
))
367 pending_special_char
= ch
;
374 main(int argc
, char** argv
)
381 struct sigaction act
;
386 static char empty_string
[1] = "";
387 char *prompt
= empty_string
;
391 logfile
= fopen("LOG", "w");
394 rl_readline_name
= APPLICATION_NAME
;
396 if ((master
= get_master_pty(&name
)) < 0)
398 perror("ptypair: could not open master pty");
402 DPRINT1("pty name: '%s'\n", name
);
404 /* set up SIGWINCH handler */
405 act
.sa_handler
= sigwinch_handler
;
406 sigemptyset(&(act
.sa_mask
));
408 if (sigaction(SIGWINCH
, &act
, NULL
) < 0)
410 perror("ptypair: could not handle SIGWINCH ");
414 if (ioctl(STDIN_FILENO
, TIOCGWINSZ
, &ws
) < 0)
416 perror("ptypair: could not get window size");
420 if ((child
= fork()) < 0)
422 perror("cannot fork");
428 int slave
; /* file descriptor for slave pty */
430 /* We are in the child process */
434 if ((slave
= get_slave_pty(name
)) < 0)
436 perror("ptypair: could not open slave pty");
442 /* We need to make this process a session group leader, because
443 * it is on a new PTY, and things like job control simply will
444 * not work correctly unless there is a session group leader
445 * and process group leader (which a session group leader
446 * automatically is). This also disassociates us from our old
451 perror("could not set session leader");
454 /* Tie us to our new controlling tty. */
456 if (ioctl(slave
, TIOCSCTTY
, NULL
))
458 perror("could not set new controlling tty");
461 if ((slave
= get_slave_pty(name
)) < 0)
463 perror("ptypair: could not open slave pty");
469 /* make slave pty be standard in, out, and error */
470 dup2(slave
, STDIN_FILENO
);
471 dup2(slave
, STDOUT_FILENO
);
472 dup2(slave
, STDERR_FILENO
);
474 /* at this point the slave pty should be standard input */
480 /* Try to restore window size; failure isn't critical */
481 if (ioctl(STDOUT_FILENO
, TIOCSWINSZ
, &ws
) < 0)
483 perror("could not restore window size");
486 /* now start the shell */
488 static char* command_args
[] = { COMMAND_ARGS
, NULL
};
490 execvp(COMMAND
, command_args
);
492 execvp(argv
[1], &argv
[1]);
495 /* should never be reached */
500 signal (SIGCHLD
, sig_child
);
503 /* Note that we only set termios settings for standard input;
504 * the master side of a pty is NOT a tty.
506 tcgetattr(STDIN_FILENO
, &orig_term
);
509 eof_char
= t
.c_cc
[VEOF
];
510 /* add_special_char(t.c_cc[VEOF]);*/
511 add_special_char(t
.c_cc
[VINTR
]);
512 add_special_char(t
.c_cc
[VQUIT
]);
513 add_special_char(t
.c_cc
[VSUSP
]);
514 #if defined (VDISCARD)
515 add_special_char(t
.c_cc
[VDISCARD
]);
519 t
.c_lflag
|= (ICANON
| ISIG
| ECHO
| ECHOCTL
| ECHOE
| \
520 ECHOK
| ECHOKE
| ECHONL
| ECHOPRT
);
522 t
.c_lflag
&= ~(ICANON
| ISIG
| ECHO
| ECHOCTL
| ECHOE
| \
523 ECHOK
| ECHOKE
| ECHONL
| ECHOPRT
);
528 tcsetattr(STDIN_FILENO
, TCSANOW
, &t
);
529 in_from_inferior_fd
= master
;
530 out_to_inferior_fd
= master
;
531 rl_instream
= fdopen (master
, "r");
532 rl_getc_function
= my_rl_getc
;
534 rl_prep_term_function
= null_prep_terminal
;
535 rl_deprep_term_function
= null_deprep_terminal
;
536 rl_callback_handler_install (prompt
, line_handler
);
538 in_from_tty_fd
= STDIN_FILENO
;
540 maxfd
= in_from_inferior_fd
> in_from_tty_fd
? in_from_inferior_fd
545 FD_SET (in_from_inferior_fd
, &in_set
);
546 FD_SET (in_from_tty_fd
, &in_set
);
548 num
= select(maxfd
+1, &in_set
, NULL
, NULL
, NULL
);
550 if (propagate_sigwinch
)
553 if (ioctl (STDIN_FILENO
, TIOCGWINSZ
, &ws
) >= 0)
555 ioctl (master
, TIOCSWINSZ
, &ws
);
557 propagate_sigwinch
= 0;
566 if (FD_ISSET (in_from_tty_fd
, &in_set
))
568 extern int readline_echoing_p
;
569 struct termios term_master
;
573 DPRINT1("[tty avail num_keys:%d]\n", num_keys
);
575 /* If we can't get tty modes for the master side of the pty, we
576 can't handle non-canonical-mode programs. Always assume the
577 master is in canonical echo mode if we can't tell. */
578 ioctl_ret
= tcgetattr(master
, &term_master
);
582 DPRINT2 ("echo:%d, canon:%d\n",
583 (term_master
.c_lflag
& ECHO
) != 0,
584 (term_master
.c_lflag
& ICANON
) != 0);
585 do_canon
= (term_master
.c_lflag
& ICANON
) != 0;
586 readline_echoing_p
= (term_master
.c_lflag
& ECHO
) != 0;
591 DPRINT1("tcgetattr on master fd failed: errno = %d\n", errno
);
595 if (do_canon
== 0 && num_keys
== 0)
598 int count
= read (STDIN_FILENO
, ch
, sizeof(ch
));
599 write (out_to_inferior_fd
, ch
, count
);
606 /* Re-install callback handler for new prompt. */
607 if (prompt
!= empty_string
)
609 prompt
= malloc (buf_count
+ 1);
611 prompt
= empty_string
;
614 memcpy (prompt
, buf
, buf_count
);
615 prompt
[buf_count
] = '\0';
616 DPRINT1("New prompt '%s'\n", prompt
);
617 #if 0 /* ifdef HAVE_RL_ALREADY_PROMPTED -- doesn't work */
618 rl_already_prompted
= buf_count
> 0;
624 rl_callback_handler_install (prompt
, line_handler
);
627 rl_callback_read_char ();
630 else /* input from inferior. */
635 if (buf_count
> (sizeof(buf
) >> 2))
637 count
= read (in_from_inferior_fd
, buf
+buf_count
,
638 sizeof(buf
) - buf_count
);
641 DPRINT0 ("(Connection closed by foreign host.)\n");
642 tcsetattr(STDIN_FILENO
, TCSANOW
, &orig_term
);
645 old_count
= buf_count
;
647 /* Look for any pending echo that we need to suppress. */
648 while (echo_suppress_start
< echo_suppress_limit
650 && buf
[buf_count
] == echo_suppress_buffer
[echo_suppress_start
])
654 echo_suppress_start
++;
657 /* Write to the terminal anything that was not suppressed. */
659 write (1, buf
+ buf_count
, count
);
661 /* Finally, look for a prompt candidate.
662 * When we get around to going input (from the keyboard),
663 * we will consider the prompt to be anything since the last
664 * line terminator. So we need to save that text in the
665 * initial part of buf. However, anything before the
666 * most recent end-of-line is not interesting. */
669 for (i
= buf_count
; --i
>= old_count
; )
671 for (i
= buf_count
- 1; i
-- >= buf_count
- count
; )
674 if (buf
[i
] == '\n' || buf
[i
] == '\r')
677 memmove (buf
, buf
+i
, buf_count
- i
);
682 DPRINT2("-> i: %d, buf_count: %d\n", i
, buf_count
);
This page took 0.044278 seconds and 4 git commands to generate.