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