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