OSDN Git Service

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