]> asedeno.scripts.mit.edu Git - PuTTY_svn.git/blob - ldisc.c
Enable xterm mouse reporting of wheel actions in GTK.
[PuTTY_svn.git] / ldisc.c
1 /*
2  * ldisc.c: PuTTY line discipline. Sits between the input coming
3  * from keypresses in the window, and the output channel leading to
4  * the back end. Implements echo and/or local line editing,
5  * depending on what's currently configured.
6  */
7
8 #include <stdio.h>
9 #include <ctype.h>
10
11 #include "putty.h"
12 #include "terminal.h"
13 #include "ldisc.h"
14
15 #define ECHOING (ldisc->localecho == FORCE_ON || \
16                  (ldisc->localecho == AUTO && \
17                       (ldisc->back->ldisc(ldisc->backhandle, LD_ECHO) || \
18                            term_ldisc(ldisc->term, LD_ECHO))))
19 #define EDITING (ldisc->localedit == FORCE_ON || \
20                  (ldisc->localedit == AUTO && \
21                       (ldisc->back->ldisc(ldisc->backhandle, LD_EDIT) || \
22                            term_ldisc(ldisc->term, LD_EDIT))))
23
24 static void c_write(Ldisc ldisc, char *buf, int len)
25 {
26     from_backend(ldisc->frontend, 0, buf, len);
27 }
28
29 static int plen(Ldisc ldisc, unsigned char c)
30 {
31     if ((c >= 32 && c <= 126) || (c >= 160 && !in_utf(ldisc->term)))
32         return 1;
33     else if (c < 128)
34         return 2;                      /* ^x for some x */
35     else if (in_utf(ldisc->term) && c >= 0xC0)
36         return 1;                      /* UTF-8 introducer character
37                                         * (FIXME: combining / wide chars) */
38     else if (in_utf(ldisc->term) && c >= 0x80 && c < 0xC0)
39         return 0;                      /* UTF-8 followup character */
40     else
41         return 4;                      /* <XY> hex representation */
42 }
43
44 static void pwrite(Ldisc ldisc, unsigned char c)
45 {
46     if ((c >= 32 && c <= 126) ||
47         (!in_utf(ldisc->term) && c >= 0xA0) ||
48         (in_utf(ldisc->term) && c >= 0x80)) {
49         c_write(ldisc, (char *)&c, 1);
50     } else if (c < 128) {
51         char cc[2];
52         cc[1] = (c == 127 ? '?' : c + 0x40);
53         cc[0] = '^';
54         c_write(ldisc, cc, 2);
55     } else {
56         char cc[5];
57         sprintf(cc, "<%02X>", c);
58         c_write(ldisc, cc, 4);
59     }
60 }
61
62 static int char_start(Ldisc ldisc, unsigned char c)
63 {
64     if (in_utf(ldisc->term))
65         return (c < 0x80 || c >= 0xC0);
66     else
67         return 1;
68 }
69
70 static void bsb(Ldisc ldisc, int n)
71 {
72     while (n--)
73         c_write(ldisc, "\010 \010", 3);
74 }
75
76 #define CTRL(x) (x^'@')
77 #define KCTRL(x) ((x^'@') | 0x100)
78
79 void *ldisc_create(Conf *conf, Terminal *term,
80                    Backend *back, void *backhandle,
81                    void *frontend)
82 {
83     Ldisc ldisc = snew(struct ldisc_tag);
84
85     ldisc->buf = NULL;
86     ldisc->buflen = 0;
87     ldisc->bufsiz = 0;
88     ldisc->quotenext = 0;
89
90     ldisc->back = back;
91     ldisc->backhandle = backhandle;
92     ldisc->term = term;
93     ldisc->frontend = frontend;
94
95     ldisc_configure(ldisc, conf);
96
97     /* Link ourselves into the backend and the terminal */
98     if (term)
99         term->ldisc = ldisc;
100     if (back)
101         back->provide_ldisc(backhandle, ldisc);
102
103     return ldisc;
104 }
105
106 void ldisc_configure(void *handle, Conf *conf)
107 {
108     Ldisc ldisc = (Ldisc) handle;
109
110     ldisc->telnet_keyboard = conf_get_int(conf, CONF_telnet_keyboard);
111     ldisc->telnet_newline = conf_get_int(conf, CONF_telnet_newline);
112     ldisc->protocol = conf_get_int(conf, CONF_protocol);
113     ldisc->localecho = conf_get_int(conf, CONF_localecho);
114     ldisc->localedit = conf_get_int(conf, CONF_localedit);
115 }
116
117 void ldisc_free(void *handle)
118 {
119     Ldisc ldisc = (Ldisc) handle;
120
121     if (ldisc->term)
122         ldisc->term->ldisc = NULL;
123     if (ldisc->back)
124         ldisc->back->provide_ldisc(ldisc->backhandle, NULL);
125     if (ldisc->buf)
126         sfree(ldisc->buf);
127     sfree(ldisc);
128 }
129
130 void ldisc_send(void *handle, char *buf, int len, int interactive)
131 {
132     Ldisc ldisc = (Ldisc) handle;
133     int keyflag = 0;
134     /*
135      * Called with len=0 when the options change. We must inform
136      * the front end in case it needs to know.
137      */
138     if (len == 0) {
139         ldisc_update(ldisc->frontend, ECHOING, EDITING);
140         return;
141     }
142     /*
143      * Notify the front end that something was pressed, in case
144      * it's depending on finding out (e.g. keypress termination for
145      * Close On Exit). 
146      */
147     frontend_keypress(ldisc->frontend);
148
149     if (interactive && ldisc->term) {
150         /*
151          * Interrupt a paste from the clipboard, if one was in
152          * progress when the user pressed a key. This is easier than
153          * buffering the current piece of data and saving it until the
154          * terminal has finished pasting, and has the potential side
155          * benefit of permitting a user to cancel an accidental huge
156          * paste.
157          */
158         term_nopaste(ldisc->term);
159     }
160
161     /*
162      * Less than zero means null terminated special string.
163      */
164     if (len < 0) {
165         len = strlen(buf);
166         keyflag = KCTRL('@');
167     }
168     /*
169      * Either perform local editing, or just send characters.
170      */
171     if (EDITING) {
172         while (len--) {
173             int c;
174             c = (unsigned char)(*buf++) + keyflag;
175             if (!interactive && c == '\r')
176                 c += KCTRL('@');
177             switch (ldisc->quotenext ? ' ' : c) {
178                 /*
179                  * ^h/^?: delete, and output BSBs, to return to
180                  * last character boundary (in UTF-8 mode this may
181                  * be more than one byte)
182                  * ^w: delete, and output BSBs, to return to last
183                  * space/nonspace boundary
184                  * ^u: delete, and output BSBs, to return to BOL
185                  * ^c: Do a ^u then send a telnet IP
186                  * ^z: Do a ^u then send a telnet SUSP
187                  * ^\: Do a ^u then send a telnet ABORT
188                  * ^r: echo "^R\n" and redraw line
189                  * ^v: quote next char
190                  * ^d: if at BOL, end of file and close connection,
191                  * else send line and reset to BOL
192                  * ^m: send line-plus-\r\n and reset to BOL
193                  */
194               case KCTRL('H'):
195               case KCTRL('?'):         /* backspace/delete */
196                 if (ldisc->buflen > 0) {
197                     do {
198                         if (ECHOING)
199                             bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));
200                         ldisc->buflen--;
201                     } while (!char_start(ldisc, ldisc->buf[ldisc->buflen]));
202                 }
203                 break;
204               case CTRL('W'):          /* delete word */
205                 while (ldisc->buflen > 0) {
206                     if (ECHOING)
207                         bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));
208                     ldisc->buflen--;
209                     if (ldisc->buflen > 0 &&
210                         isspace((unsigned char)ldisc->buf[ldisc->buflen-1]) &&
211                         !isspace((unsigned char)ldisc->buf[ldisc->buflen]))
212                         break;
213                 }
214                 break;
215               case CTRL('U'):          /* delete line */
216               case CTRL('C'):          /* Send IP */
217               case CTRL('\\'):         /* Quit */
218               case CTRL('Z'):          /* Suspend */
219                 while (ldisc->buflen > 0) {
220                     if (ECHOING)
221                         bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));
222                     ldisc->buflen--;
223                 }
224                 ldisc->back->special(ldisc->backhandle, TS_EL);
225                 /*
226                  * We don't send IP, SUSP or ABORT if the user has
227                  * configured telnet specials off! This breaks
228                  * talkers otherwise.
229                  */
230                 if (!ldisc->telnet_keyboard)
231                     goto default_case;
232                 if (c == CTRL('C'))
233                     ldisc->back->special(ldisc->backhandle, TS_IP);
234                 if (c == CTRL('Z'))
235                     ldisc->back->special(ldisc->backhandle, TS_SUSP);
236                 if (c == CTRL('\\'))
237                     ldisc->back->special(ldisc->backhandle, TS_ABORT);
238                 break;
239               case CTRL('R'):          /* redraw line */
240                 if (ECHOING) {
241                     int i;
242                     c_write(ldisc, "^R\r\n", 4);
243                     for (i = 0; i < ldisc->buflen; i++)
244                         pwrite(ldisc, ldisc->buf[i]);
245                 }
246                 break;
247               case CTRL('V'):          /* quote next char */
248                 ldisc->quotenext = TRUE;
249                 break;
250               case CTRL('D'):          /* logout or send */
251                 if (ldisc->buflen == 0) {
252                     ldisc->back->special(ldisc->backhandle, TS_EOF);
253                 } else {
254                     ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen);
255                     ldisc->buflen = 0;
256                 }
257                 break;
258                 /*
259                  * This particularly hideous bit of code from RDB
260                  * allows ordinary ^M^J to do the same thing as
261                  * magic-^M when in Raw protocol. The line `case
262                  * KCTRL('M'):' is _inside_ the if block. Thus:
263                  * 
264                  *  - receiving regular ^M goes straight to the
265                  *    default clause and inserts as a literal ^M.
266                  *  - receiving regular ^J _not_ directly after a
267                  *    literal ^M (or not in Raw protocol) fails the
268                  *    if condition, leaps to the bottom of the if,
269                  *    and falls through into the default clause
270                  *    again.
271                  *  - receiving regular ^J just after a literal ^M
272                  *    in Raw protocol passes the if condition,
273                  *    deletes the literal ^M, and falls through
274                  *    into the magic-^M code
275                  *  - receiving a magic-^M empties the line buffer,
276                  *    signals end-of-line in one of the various
277                  *    entertaining ways, and _doesn't_ fall out of
278                  *    the bottom of the if and through to the
279                  *    default clause because of the break.
280                  */
281               case CTRL('J'):
282                 if (ldisc->protocol == PROT_RAW &&
283                     ldisc->buflen > 0 && ldisc->buf[ldisc->buflen - 1] == '\r') {
284                     if (ECHOING)
285                         bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));
286                     ldisc->buflen--;
287                     /* FALLTHROUGH */
288               case KCTRL('M'):         /* send with newline */
289                     if (ldisc->buflen > 0)
290                         ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen);
291                     if (ldisc->protocol == PROT_RAW)
292                         ldisc->back->send(ldisc->backhandle, "\r\n", 2);
293                     else if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline)
294                         ldisc->back->special(ldisc->backhandle, TS_EOL);
295                     else
296                         ldisc->back->send(ldisc->backhandle, "\r", 1);
297                     if (ECHOING)
298                         c_write(ldisc, "\r\n", 2);
299                     ldisc->buflen = 0;
300                     break;
301                 }
302                 /* FALLTHROUGH */
303               default:                 /* get to this label from ^V handler */
304                 default_case:
305                 if (ldisc->buflen >= ldisc->bufsiz) {
306                     ldisc->bufsiz = ldisc->buflen + 256;
307                     ldisc->buf = sresize(ldisc->buf, ldisc->bufsiz, char);
308                 }
309                 ldisc->buf[ldisc->buflen++] = c;
310                 if (ECHOING)
311                     pwrite(ldisc, (unsigned char) c);
312                 ldisc->quotenext = FALSE;
313                 break;
314             }
315         }
316     } else {
317         if (ldisc->buflen != 0) {
318             ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen);
319             while (ldisc->buflen > 0) {
320                 bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));
321                 ldisc->buflen--;
322             }
323         }
324         if (len > 0) {
325             if (ECHOING)
326                 c_write(ldisc, buf, len);
327             if (keyflag && ldisc->protocol == PROT_TELNET && len == 1) {
328                 switch (buf[0]) {
329                   case CTRL('M'):
330                     if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline)
331                         ldisc->back->special(ldisc->backhandle, TS_EOL);
332                     else
333                         ldisc->back->send(ldisc->backhandle, "\r", 1);
334                     break;
335                   case CTRL('?'):
336                   case CTRL('H'):
337                     if (ldisc->telnet_keyboard) {
338                         ldisc->back->special(ldisc->backhandle, TS_EC);
339                         break;
340                     }
341                   case CTRL('C'):
342                     if (ldisc->telnet_keyboard) {
343                         ldisc->back->special(ldisc->backhandle, TS_IP);
344                         break;
345                     }
346                   case CTRL('Z'):
347                     if (ldisc->telnet_keyboard) {
348                         ldisc->back->special(ldisc->backhandle, TS_SUSP);
349                         break;
350                     }
351
352                   default:
353                     ldisc->back->send(ldisc->backhandle, buf, len);
354                     break;
355                 }
356             } else
357                 ldisc->back->send(ldisc->backhandle, buf, len);
358         }
359     }
360 }