Download
jcs
/subtext
/console.c
(View History)
jcs console: Minor fixes suggested by cppcheck | Latest amendment: 372 on 2023-03-08 |
1 | /* |
2 | * Copyright (c) 2021 joshua stein <jcs@jcs.org> |
3 | * |
4 | * Permission to use, copy, modify, and distribute this software for any |
5 | * purpose with or without fee is hereby granted, provided that the above |
6 | * copyright notice and this permission notice appear in all copies. |
7 | * |
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
15 | */ |
16 | |
17 | #include <stdio.h> |
18 | #include <string.h> |
19 | |
20 | #include "subtext.h" |
21 | #include "ansi.h" |
22 | #include "console.h" |
23 | #include "focusable.h" |
24 | #include "session.h" |
25 | #include "user.h" |
26 | #include "util.h" |
27 | |
28 | /* for monaco 9 */ |
29 | #define TEXT_FONT monaco |
30 | #define TEXT_SIZE 9 |
31 | #define FONT_WIDTH 6 |
32 | #define FONT_HEIGHT 11 |
33 | |
34 | #define CONSOLE_FORCE_REDRAW 1 |
35 | #define CONSOLE_PADDING 6 |
36 | #define CONSOLE_SMOOTH_SCROLLING 0 |
37 | |
38 | void console_idle(struct focusable *focusable, EventRecord *event); |
39 | void console_close(struct focusable *focusable, EventRecord *event); |
40 | void console_suspend(struct focusable *focusable, EventRecord *event); |
41 | void console_resume(struct focusable *focusable, EventRecord *event); |
42 | void console_update(struct focusable *focusable, EventRecord *event); |
43 | void console_mouse_down(struct focusable *focusable, EventRecord *event); |
44 | void console_key_down(struct focusable *focusable, EventRecord *event); |
45 | |
46 | void console_setup(struct session *session); |
47 | void console_redraw(struct console *console, short force); |
48 | short console_bound(struct console *console); |
49 | void console_erase_chars(struct console *console, short start, short count); |
50 | void console_shift_chars(struct console *console, short start, |
51 | short offset); |
52 | void console_parse_csi(struct console *console); |
53 | |
54 | struct node_funcs console_node_funcs = { |
55 | console_setup, |
56 | console_input, |
57 | console_output, |
58 | console_close_from_session |
59 | }; |
60 | |
61 | struct console * |
62 | console_init(void) |
63 | { |
64 | char title[64]; |
65 | char *sysop_username = NULL; |
66 | struct console *console; |
67 | struct focusable *focusable; |
68 | Rect bounds; |
69 | short width, height; |
70 | |
71 | console = xmalloczero(sizeof(struct console)); |
72 | if (console == NULL) |
73 | return NULL; |
74 | |
75 | console->session = session_create("console", "console", |
76 | &console_node_funcs); |
77 | if (console->session == NULL) { |
78 | xfree(&console); |
79 | warn("No free nodes for a console"); |
80 | return NULL; |
81 | } |
82 | |
83 | memset(console->chars, ' ', sizeof(console->chars)); |
84 | console->ncolumns = DEFAULT_TERMINAL_COLUMNS; |
85 | console->nlines = DEFAULT_TERMINAL_LINES; |
86 | |
87 | width = CONSOLE_PADDING + (FONT_WIDTH * console->ncolumns) + |
88 | CONSOLE_PADDING; |
89 | height = CONSOLE_PADDING + (FONT_HEIGHT * console->nlines) + |
90 | CONSOLE_PADDING; |
91 | |
92 | bounds.left = (screenBits.bounds.right - width) / 2; |
93 | bounds.right = bounds.left + width; |
94 | bounds.top = GetMBarHeight() + |
95 | ((screenBits.bounds.bottom - height) / 2); |
96 | bounds.bottom = bounds.top + height; |
97 | |
98 | snprintf(title, sizeof(title), "%s: %s: console", PROGRAM_NAME, |
99 | db->config.hostname); |
100 | CtoPstr(title); |
101 | |
102 | console->win = NewWindow(0L, &bounds, title, false, noGrowDocProc, |
103 | (WindowPtr)-1L, true, 0); |
104 | if (!console->win) { |
105 | warn("Can't create window"); |
106 | xfree(&console); |
107 | return NULL; |
108 | } |
109 | |
110 | focusable = xmalloczero(sizeof(struct focusable)); |
111 | if (focusable == NULL) { |
112 | xfree(&console); |
113 | return NULL; |
114 | } |
115 | focusable->win = console->win; |
116 | focusable->cookie = console; |
117 | focusable->update = console_update; |
118 | focusable->key_down = console_key_down; |
119 | focusable->close = console_close; |
120 | console->focusable = focusable; |
121 | if (!add_focusable(focusable)) { |
122 | xfree(&console); |
123 | return NULL; |
124 | } |
125 | |
126 | console->session->cookie = (void *)console; |
127 | console->session->vt100 = 1; |
128 | console->session->cp437 = 1; |
129 | console->session->tspeed = 19200; |
130 | |
131 | if ((sysop_username = user_first_sysop_username()) != NULL) { |
132 | strlcpy(console->session->autologin_username, sysop_username, |
133 | sizeof(console->session->autologin_username)); |
134 | xfree(&sysop_username); |
135 | } |
136 | |
137 | return console; |
138 | } |
139 | |
140 | void |
141 | console_idle(struct focusable *focusable, EventRecord *event) |
142 | { |
143 | struct console *console = (struct console *)focusable->cookie; |
144 | struct session *session = console->session; |
145 | GrafPtr old_port; |
146 | short n, cursor; |
147 | |
148 | if (session->obuflen == 0) |
149 | return; |
150 | |
151 | GetPort(&old_port); |
152 | SetPort(console->win); |
153 | |
154 | /* uncursor */ |
155 | cursor = (console->cursor_line * console->ncolumns) + |
156 | console->cursor_column; |
157 | if (cursor >= sizeof(console->attrs) - 1) |
158 | panic("console_idle: cursor out of bounds!"); |
159 | console->attrs[cursor] &= ~ATTR_CURSOR; |
160 | console->attrs[cursor] |= ATTR_DIRTY; |
161 | |
162 | for (n = 0; n < session->obuflen; n++) { |
163 | if (console->in_csi) { |
164 | if (session->obuf[n] == '\33' || |
165 | console->csilen >= nitems(console->csi) - 1) { |
166 | console_parse_csi(console); |
167 | console->in_csi = 0; |
168 | console->csi[0] = '\0'; |
169 | console->csilen = 0; |
170 | } else { |
171 | console->csi[console->csilen] = session->obuf[n]; |
172 | console->csilen++; |
173 | console_parse_csi(console); |
174 | } |
175 | |
176 | continue; |
177 | } |
178 | |
179 | switch (session->obuf[n]) { |
180 | case '\r': |
181 | console->cursor_column = 0; |
182 | break; |
183 | case '\n': |
184 | console->cursor_line++; |
185 | console->cursor_column = 0; /* \r should do this, but JIC */ |
186 | console_bound(console); |
187 | break; |
188 | case '\33': /* \e */ |
189 | if (session->obuflen <= n + 1) { |
190 | /* lone \e at end of buffer, keep it until we see next */ |
191 | session->obuflen = 1; |
192 | session->obuf[0] = '\33'; |
193 | goto output_done; |
194 | } |
195 | if (session->obuf[n + 1] == '[') { |
196 | console->in_csi = 1; |
197 | n++; |
198 | continue; |
199 | } |
200 | /* escape but not CSI, fall through */ |
201 | default: |
202 | console_bound(console); |
203 | cursor = (console->cursor_line * console->ncolumns) + |
204 | console->cursor_column; |
205 | console->chars[cursor] = session->obuf[n]; |
206 | console->attrs[cursor] = console->cur_attr | ATTR_DIRTY; |
207 | console->cursor_column++; |
208 | break; |
209 | } |
210 | } |
211 | |
212 | session->obuflen = 0; |
213 | |
214 | output_done: |
215 | console_bound(console); |
216 | console_redraw(console, 0); |
217 | SetPort(old_port); |
218 | } |
219 | |
220 | void |
221 | console_suspend(struct focusable *focusable, EventRecord *event) |
222 | { |
223 | } |
224 | |
225 | void |
226 | console_resume(struct focusable *focusable, EventRecord *event) |
227 | { |
228 | } |
229 | |
230 | void |
231 | console_update(struct focusable *focusable, EventRecord *event) |
232 | { |
233 | struct console *console = (struct console *)focusable->cookie; |
234 | short what = -1; |
235 | |
236 | if (event != NULL) |
237 | what = event->what; |
238 | |
239 | switch (what) { |
240 | case -1: |
241 | case updateEvt: |
242 | console_redraw(console, CONSOLE_FORCE_REDRAW); |
243 | break; |
244 | case activateEvt: |
245 | break; |
246 | } |
247 | } |
248 | |
249 | void |
250 | console_mouse_down(struct focusable *focusable, EventRecord *event) |
251 | { |
252 | } |
253 | |
254 | void |
255 | console_key_down(struct focusable *focusable, EventRecord *event) |
256 | { |
257 | struct console *console = (struct console *)focusable->cookie; |
258 | unsigned char k; |
259 | |
260 | if (console->session->ibuflen >= nitems(console->session->ibuf)) |
261 | return; |
262 | |
263 | console->session->last_input_at = Time; |
264 | |
265 | k = (event->message & charCodeMask); |
266 | if (event->modifiers & optionKey) { |
267 | /* these are only correct on a US keyboard */ |
268 | switch (k) { |
269 | case 0x8d: /* opt+C */ |
270 | /* ^C */ |
271 | console->session->ibuf[console->session->ibuflen++] = CONTROL_C; |
272 | break; |
273 | case 0xb6: /* opt+D */ |
274 | /* ^D */ |
275 | console->session->ibuf[console->session->ibuflen++] = CONTROL_D; |
276 | break; |
277 | case 0xc2: /* opt+L */ |
278 | console_redraw(console, CONSOLE_FORCE_REDRAW); |
279 | break; |
280 | } |
281 | } else { |
282 | switch (k) { |
283 | case 0x1C: /* left */ |
284 | console->session->ibuf[console->session->ibuflen++] = '\33'; |
285 | console->session->ibuf[console->session->ibuflen++] = '['; |
286 | console->session->ibuf[console->session->ibuflen++] = 'D'; |
287 | break; |
288 | case 0x1D: /* right */ |
289 | console->session->ibuf[console->session->ibuflen++] = '\33'; |
290 | console->session->ibuf[console->session->ibuflen++] = '['; |
291 | console->session->ibuf[console->session->ibuflen++] = 'C'; |
292 | break; |
293 | case 0x1E: /* up */ |
294 | console->session->ibuf[console->session->ibuflen++] = '\33'; |
295 | console->session->ibuf[console->session->ibuflen++] = '['; |
296 | console->session->ibuf[console->session->ibuflen++] = 'A'; |
297 | break; |
298 | case 0x1F: /* down */ |
299 | console->session->ibuf[console->session->ibuflen++] = '\33'; |
300 | console->session->ibuf[console->session->ibuflen++] = '['; |
301 | console->session->ibuf[console->session->ibuflen++] = 'B'; |
302 | break; |
303 | default: |
304 | console->session->ibuf[console->session->ibuflen++] = k; |
305 | if (k == '\r') |
306 | console->session->ibuf[console->session->ibuflen++] = '\n'; |
307 | } |
308 | } |
309 | } |
310 | |
311 | void |
312 | console_close(struct focusable *focusable, EventRecord *event) |
313 | { |
314 | struct console *console = (struct console *)focusable->cookie; |
315 | console->session->ending = 1; |
316 | } |
317 | |
318 | /* session API */ |
319 | |
320 | void |
321 | console_setup(struct session *session) |
322 | { |
323 | struct console *console = (struct console *)session->cookie; |
324 | session->terminal_columns = console->ncolumns; |
325 | session->terminal_lines = console->nlines; |
326 | snprintf(session->terminal_type, sizeof(session->terminal_type), |
327 | "vt100"); |
328 | } |
329 | |
330 | short |
331 | console_output(struct session *session) |
332 | { |
333 | struct console *console = (struct console *)session->cookie; |
334 | console_idle(console->focusable, NULL); |
335 | return 0; |
336 | } |
337 | |
338 | short |
339 | console_input(struct session *session) |
340 | { |
341 | /* nothing to do here, input is fed from main loop */ |
342 | return 0; |
343 | } |
344 | |
345 | void |
346 | console_close_from_session(struct session *session) |
347 | { |
348 | struct console *console = (struct console *)session->cookie; |
349 | session_logf(session, "Closing console session"); |
350 | destroy_focusable(console->focusable); |
351 | xfree(&console); |
352 | } |
353 | |
354 | short |
355 | console_bound(struct console *console) |
356 | { |
357 | unsigned short shift_lines, shift_cols, chars_left, pxout, |
358 | pxout_scroll; |
359 | RgnHandle rgn; |
360 | Rect r, r2; |
361 | GrafPtr old_port; |
362 | short ret = 0, n; |
363 | |
364 | while (console->cursor_column > console->ncolumns) { |
365 | console->cursor_line++; |
366 | console->cursor_column -= console->ncolumns; |
367 | } |
368 | |
369 | if (console->cursor_line >= console->nlines) { |
370 | shift_lines = console->cursor_line - console->nlines + 1; |
371 | shift_cols = shift_lines * console->ncolumns; |
372 | chars_left = (console->nlines * console->ncolumns) - shift_cols; |
373 | pxout = shift_lines * FONT_HEIGHT; |
374 | |
375 | GetPort(&old_port); |
376 | SetPort(console->win); |
377 | rgn = NewRgn(); |
378 | r.left = console->win->portRect.left + CONSOLE_PADDING; |
379 | r.top = console->win->portRect.top + CONSOLE_PADDING; |
380 | r.right = r.left + (console->ncolumns * FONT_WIDTH); |
381 | r.bottom = r.top + ((console->nlines) * FONT_HEIGHT); |
382 | |
383 | n = CONSOLE_SMOOTH_SCROLLING ? CONSOLE_SMOOTH_SCROLLING : pxout; |
384 | pxout_scroll = pxout; |
385 | while (pxout_scroll > 0) { |
386 | if (pxout_scroll < n) |
387 | n = pxout_scroll; |
388 | ScrollRect(&r, 0, -n, rgn); |
389 | r2 = r; |
390 | r2.top = r.bottom - 1; |
391 | r2.bottom = r.top + n; |
392 | FillRect(&r2, white); |
393 | pxout_scroll -= n; |
394 | } |
395 | DisposeRgn(rgn); |
396 | |
397 | BlockMove(console->chars + shift_cols, console->chars, chars_left); |
398 | BlockMove(console->attrs + shift_cols, console->attrs, chars_left); |
399 | memset(console->chars + chars_left, ' ', shift_cols); |
400 | memset(console->attrs + chars_left, console->cur_attr, shift_cols); |
401 | |
402 | SetPort(old_port); |
403 | |
404 | console->cursor_line = console->nlines - 1; |
405 | ret = 1; |
406 | } |
407 | |
408 | return ret; |
409 | } |
410 | |
411 | #define CONSOLE_ATTRS_DRAWABLE (ATTR_REVERSE | ATTR_BOLD | ATTR_DIRTY) |
412 | |
413 | void |
414 | console_redraw(struct console *console, short force) |
415 | { |
416 | Rect chunk; |
417 | GrafPtr old_port; |
418 | short curbold = -1, line, off, c, firstdirty; |
419 | unsigned char curattr = 0, a; |
420 | |
421 | GetPort(&old_port); |
422 | SetPort(console->win); |
423 | |
424 | TextFont(TEXT_FONT); |
425 | TextSize(TEXT_SIZE); |
426 | |
427 | for (line = 0; line < console->nlines; line++) { |
428 | off = line * console->ncolumns; |
429 | chunk.top = console->win->portRect.top + CONSOLE_PADDING + |
430 | (line * FONT_HEIGHT); |
431 | for (firstdirty = -1, c = 0; c < console->ncolumns; c++) { |
432 | a = console->attrs[off + c] & CONSOLE_ATTRS_DRAWABLE; |
433 | if (!force && !(a & ATTR_DIRTY) && firstdirty == -1) |
434 | continue; |
435 | console->attrs[off + c] &= ~ATTR_DIRTY; |
436 | |
437 | if (c == console->ncolumns - 1) { |
438 | if (firstdirty == -1) |
439 | firstdirty = c; |
440 | } else if (firstdirty == -1) { |
441 | firstdirty = MAX(c - 1, 0); |
442 | curattr = a; |
443 | continue; |
444 | } else if (a == curattr) { |
445 | continue; |
446 | } |
447 | |
448 | /* draw current chunk of dirty chars */ |
449 | chunk.left = console->win->portRect.left + CONSOLE_PADDING + |
450 | (firstdirty * FONT_WIDTH); |
451 | chunk.right = chunk.left + |
452 | ((c - firstdirty) * FONT_WIDTH); |
453 | chunk.bottom = chunk.top + FONT_HEIGHT; |
454 | FillRect(&chunk, white); |
455 | |
456 | if (curattr & ATTR_BOLD) { |
457 | if (curbold != 1) { |
458 | TextFace(thePort->txFace | bold | condense); |
459 | curbold = 1; |
460 | } |
461 | } else if (curbold != 0) { |
462 | TextFace(thePort->txFace & ~(bold | condense)); |
463 | curbold = 0; |
464 | } |
465 | MoveTo(chunk.left, chunk.top + FONT_HEIGHT - 2); |
466 | DrawText(console->chars, off + firstdirty, c - firstdirty); |
467 | |
468 | if (curattr & ATTR_REVERSE) |
469 | InvertRect(&chunk); |
470 | |
471 | if (c != console->ncolumns - 1) { |
472 | if (a & ATTR_DIRTY) |
473 | firstdirty = c; |
474 | else |
475 | firstdirty = -1; |
476 | curattr = a; |
477 | } |
478 | } |
479 | } |
480 | |
481 | /* redraw cursor */ |
482 | chunk.top = console->win->portRect.top + CONSOLE_PADDING + |
483 | (console->cursor_line * FONT_HEIGHT); |
484 | chunk.left = console->win->portRect.left + CONSOLE_PADDING + |
485 | (console->cursor_column * FONT_WIDTH); |
486 | chunk.bottom = chunk.top + FONT_HEIGHT; |
487 | chunk.right = chunk.left + FONT_WIDTH; |
488 | InvertRect(&chunk); |
489 | |
490 | ValidRect(&console->win->portRect); |
491 | SetPort(old_port); |
492 | } |
493 | |
494 | void |
495 | console_erase_chars(struct console *console, short start, short count) |
496 | { |
497 | Rect eraser; |
498 | short start_line, end_line, start_col, end_col, line; |
499 | |
500 | memset(console->chars + start, ' ', count); |
501 | memset(console->attrs + start, console->cur_attr, count); |
502 | |
503 | start_line = start / console->ncolumns; |
504 | start_col = start - (start_line * console->ncolumns); |
505 | end_line = (start + count) / console->ncolumns; |
506 | end_col = start + count - (end_line * console->ncolumns); |
507 | |
508 | for (line = start_line; line <= end_line; line++) { |
509 | eraser.top = console->win->portRect.top + CONSOLE_PADDING + |
510 | (line * FONT_HEIGHT); |
511 | eraser.left = console->win->portRect.left + CONSOLE_PADDING; |
512 | if (line == start_line) |
513 | eraser.left += start_col * FONT_WIDTH; |
514 | eraser.bottom = eraser.top + FONT_HEIGHT; |
515 | if (line == end_line) |
516 | eraser.right = eraser.left + (end_col * FONT_WIDTH); |
517 | else |
518 | eraser.right = eraser.left + (console->ncolumns * FONT_WIDTH); |
519 | FillRect(&eraser, |
520 | (console->cur_attr & ATTR_REVERSE) ? black : white); |
521 | } |
522 | } |
523 | |
524 | void |
525 | console_shift_chars(struct console *console, short start, short offset) |
526 | { |
527 | Rect mover; |
528 | RgnHandle rgn; |
529 | short count; |
530 | |
531 | /* XXX: this currently only works on whole lines */ |
532 | |
533 | /* move all chars at start by offset, overwriting what's there */ |
534 | count = (console->ncolumns * console->nlines) - start; |
535 | if (start + offset + count > sizeof(console->chars)) |
536 | /* inserting lines, losing trailing lines */ |
537 | count = sizeof(console->chars) - start - offset; |
538 | memmove(console->chars + start + offset, console->chars + start, |
539 | count); |
540 | memmove(console->attrs + start + offset, console->attrs + start, |
541 | count); |
542 | |
543 | if (start % console->ncolumns != 0) |
544 | warn("TODO partial console_shift_chars"); |
545 | if (offset % console->ncolumns != 0) |
546 | warn("TODO partial console_shift_chars"); |
547 | |
548 | rgn = NewRgn(); |
549 | mover.top = console->win->portRect.top + CONSOLE_PADDING + |
550 | ((start / console->ncolumns) * FONT_HEIGHT); |
551 | if (offset < 0) |
552 | mover.top -= FONT_HEIGHT; |
553 | mover.left = console->win->portRect.left + CONSOLE_PADDING; |
554 | mover.bottom = mover.top + (FONT_HEIGHT * ((count / console->ncolumns) + 1)); |
555 | mover.right = mover.left + (FONT_WIDTH * console->ncolumns); |
556 | ScrollRect(&mover, 0, (offset / console->ncolumns) * FONT_HEIGHT, rgn); |
557 | DisposeRgn(rgn); |
558 | } |
559 | |
560 | void |
561 | console_parse_csi(struct console *console) |
562 | { |
563 | short x, y; |
564 | long cursor; |
565 | short serviced = 0; |
566 | short param1 = -1, param2 = -1; |
567 | short parambuflen; |
568 | short start, count, offset; |
569 | struct session *session; |
570 | char parambuf[5]; |
571 | unsigned char c; |
572 | |
573 | if (console->csilen == 0) |
574 | return; |
575 | |
576 | c = console->csi[console->csilen - 1]; |
577 | |
578 | if (c < 'A' || (c > 'Z' && c < 'a') || c > 'z') |
579 | return; |
580 | |
581 | switch (c) { |
582 | case 'A': |
583 | case 'B': |
584 | case 'C': |
585 | case 'D': |
586 | case 'E': |
587 | case 'F': |
588 | case 'G': |
589 | case 'I': |
590 | case 'J': |
591 | case 'K': |
592 | case 'L': |
593 | case 'M': |
594 | case 'S': |
595 | case 'T': |
596 | case 'd': |
597 | case 'g': |
598 | case 'n': |
599 | case 's': |
600 | case 'u': |
601 | case '7': |
602 | case '8': |
603 | /* optional multiplier */ |
604 | if (c == 'J' || c == 'K') |
605 | param1 = 0; |
606 | else |
607 | param1 = 1; |
608 | |
609 | if (console->csilen > 1) { |
610 | for (x = 0; x < console->csilen - 1 && |
611 | x < nitems(parambuf) - 1; x++) { |
612 | parambuf[x] = console->csi[x]; |
613 | parambuf[x + 1] = '\0'; |
614 | } |
615 | param1 = atoi(parambuf); |
616 | } |
617 | break; |
618 | case 'H': |
619 | case 'f': |
620 | /* two optional parameters separated by ; each defaulting to 1 */ |
621 | param1 = 1; |
622 | param2 = 1; |
623 | |
624 | y = -1; |
625 | for (x = 0; x < console->csilen; x++) { |
626 | if (console->csi[x] == ';') { |
627 | y = x; |
628 | break; |
629 | } |
630 | } |
631 | if (y == -1) |
632 | /* CSI 17H -> CSI 17; */ |
633 | y = console->csilen - 1; |
634 | |
635 | if (y > 0) { |
636 | for (x = 0; x < y && x < nitems(parambuf) - 1; x++) { |
637 | parambuf[x] = console->csi[x]; |
638 | parambuf[x + 1] = '\0'; |
639 | } |
640 | param1 = atoi(parambuf); |
641 | |
642 | if (y < console->csilen - 2) { |
643 | parambuf[0] = '\0'; |
644 | for (x = 0; x < (console->csilen - 1 - y) && |
645 | x < nitems(parambuf) - 1; x++) { |
646 | parambuf[x] = console->csi[y + 1 + x]; |
647 | parambuf[x + 1] = '\0'; |
648 | } |
649 | param2 = atoi(parambuf); |
650 | } |
651 | } |
652 | break; |
653 | } |
654 | |
655 | serviced = 1; |
656 | |
657 | /* uncursor */ |
658 | cursor = (console->cursor_line * console->ncolumns) + |
659 | console->cursor_column; |
660 | console->attrs[cursor] &= ~ATTR_CURSOR; |
661 | console->attrs[cursor] |= ATTR_DIRTY; |
662 | |
663 | switch (c) { |
664 | case 'A': /* CUU - cursor up, stop at top of screen */ |
665 | console->cursor_line = MAX(0, console->cursor_line - param1); |
666 | break; |
667 | case 'B': /* CUD - cursor down, stop at bottom of screen */ |
668 | console->cursor_line = MIN(console->nlines - 1, |
669 | console->cursor_line + param1); |
670 | break; |
671 | case 'C': /* CUF - cursor forward, stop at screen edge */ |
672 | console->cursor_column = MIN(console->ncolumns - 1, |
673 | console->cursor_column + param1); |
674 | break; |
675 | case 'D': /* CUB - cursor back, stop at left edge of screen */ |
676 | console->cursor_column = MAX(0, console->cursor_column - param1); |
677 | break; |
678 | case 'E': /* CNL - cursor next line */ |
679 | console->cursor_column = 0; |
680 | console->cursor_line = MAX(console->nlines - 1, |
681 | console->cursor_line + param1); |
682 | break; |
683 | case 'F': /* CPL - cursor previous line */ |
684 | console->cursor_column = 0; |
685 | console->cursor_line = MAX(0, console->cursor_line - param1); |
686 | break; |
687 | case 'G': /* CHA - cursor horizontal absolute */ |
688 | console->cursor_column = BOUND(param1, 0, console->ncolumns - 1); |
689 | break; |
690 | case 'H': /* CUP - cursor absolute position */ |
691 | case 'f': /* HVP - horizontal vertical position */ |
692 | if (param1 - 1 < 0) |
693 | console->cursor_line = 0; |
694 | else if (param1 > console->nlines) |
695 | console->cursor_line = console->nlines - 1; |
696 | else |
697 | console->cursor_line = param1 - 1; |
698 | |
699 | if (param2 - 1 < 0) |
700 | console->cursor_column = 0; |
701 | else if (param2 > console->ncolumns) |
702 | console->cursor_column = console->ncolumns - 1; |
703 | else |
704 | console->cursor_column = param2 - 1; |
705 | break; |
706 | case 'J': /* ED - erase in display */ |
707 | switch (param1) { |
708 | case 0: |
709 | /* clear from cursor (inclusive) to end of screen */ |
710 | start = (console->cursor_line * console->ncolumns) + |
711 | console->cursor_column; |
712 | count = (console->ncolumns * console->nlines) - start; |
713 | console_erase_chars(console, start, count); |
714 | break; |
715 | case 1: |
716 | /* clear from cursor to beginning of the screen */ |
717 | start = 0; |
718 | count = (console->cursor_line * console->ncolumns) + |
719 | console->cursor_column; |
720 | console_erase_chars(console, start, count); |
721 | break; |
722 | case 2: |
723 | /* clear entire screen, mark everything clean */ |
724 | start = 0; |
725 | count = console->ncolumns * console->nlines; |
726 | console_erase_chars(console, start, count); |
727 | break; |
728 | } |
729 | break; |
730 | case 'K': /* EL - erase in line */ |
731 | switch (param1) { |
732 | case 0: |
733 | /* clear from cursor to end of line */ |
734 | start = (console->cursor_line * console->ncolumns) + |
735 | console->cursor_column; |
736 | count = console->ncolumns - console->cursor_column; |
737 | console_erase_chars(console, start, count); |
738 | break; |
739 | case 1: |
740 | /* clear from cursor to beginning of line */ |
741 | start = (console->cursor_line * console->ncolumns); |
742 | count = console->ncolumns - console->cursor_column; |
743 | console_erase_chars(console, start, count); |
744 | break; |
745 | case 2: |
746 | /* clear entire line */ |
747 | start = (console->cursor_line * console->ncolumns); |
748 | count = console->ncolumns; |
749 | console_erase_chars(console, start, count); |
750 | break; |
751 | } |
752 | break; |
753 | case 'L': /* IL - insert line */ |
754 | param1 = BOUND(param1, 0, console->nlines - console->cursor_line); |
755 | if (param1 == 0) |
756 | break; |
757 | if (console->cursor_line != console->nlines - 1) { |
758 | /* shift lines down to insert new ones */ |
759 | start = console->ncolumns * console->cursor_line; |
760 | offset = console->ncolumns * param1; |
761 | console_shift_chars(console, start, offset); |
762 | } |
763 | /* clear new lines at cursor line */ |
764 | start = console->ncolumns * console->cursor_line; |
765 | count = console->ncolumns * param1; |
766 | console_erase_chars(console, start, count); |
767 | break; |
768 | case 'M': /* DL - delete line */ |
769 | param1 = BOUND(param1, 0, console->nlines - console->cursor_line); |
770 | if (param1 == 0) |
771 | break; |
772 | if (console->cursor_line != console->nlines - 1) { |
773 | start = console->ncolumns * (console->cursor_line + param1); |
774 | offset = console->ncolumns * -param1; |
775 | console_shift_chars(console, start, offset); |
776 | } |
777 | /* fill in new blank lines after lines that moved up */ |
778 | start = console->ncolumns * (console->nlines - param1); |
779 | count = console->ncolumns * param1; |
780 | console_erase_chars(console, start, count); |
781 | break; |
782 | case 'S': /* SU - scroll up */ |
783 | /* TODO */ |
784 | break; |
785 | case 'T': /* SD - scroll down */ |
786 | /* TODO */ |
787 | break; |
788 | case 'd': /* absolute line number */ |
789 | if (param1 < 1) |
790 | console->cursor_line = 0; |
791 | else if (param1 > console->nlines) |
792 | console->cursor_line = console->nlines; |
793 | else |
794 | console->cursor_line = param1 - 1; |
795 | break; |
796 | case 'g': /* clear tabs, ignore */ |
797 | break; |
798 | case 'h': /* reset, ignore */ |
799 | break; |
800 | case 'm': /* graphic changes */ |
801 | parambuf[0] = '\0'; |
802 | parambuflen = 0; |
803 | param2 = console->cur_attr; |
804 | |
805 | for (x = 0; x < console->csilen; x++) { |
806 | /* all the way to console->csilen to catch 'm' */ |
807 | if (console->csi[x] == ';' || console->csi[x] == 'm') { |
808 | param1 = atoi(parambuf); |
809 | |
810 | switch (param1) { |
811 | case 0: /* reset */ |
812 | #ifdef COLOR |
813 | cur_color = FG_WHITE | BG_BLACK; |
814 | #endif |
815 | param2 = 0; |
816 | break; |
817 | case 1: /* bold */ |
818 | param2 |= ATTR_BOLD; |
819 | break; |
820 | case 4: /* underline */ |
821 | param2 |= ATTR_UNDERLINE; |
822 | break; |
823 | case 7: /* reverse */ |
824 | param2 |= ATTR_REVERSE; |
825 | break; |
826 | case 22: /* normal color */ |
827 | param2 = 0; |
828 | break; |
829 | case 21: /* bold off */ |
830 | param2 &= ~ATTR_BOLD; |
831 | break; |
832 | case 24: /* underline off */ |
833 | param2 &= ~ATTR_UNDERLINE; |
834 | break; |
835 | case 27: /* inverse off */ |
836 | param2 &= ~ATTR_REVERSE; |
837 | break; |
838 | case 30: /* fg black */ |
839 | case 31: /* fg red */ |
840 | case 32: /* fg green */ |
841 | case 33: /* fg yellow */ |
842 | case 34: /* fg blue */ |
843 | case 35: /* fg magenta */ |
844 | case 36: /* fg cyan */ |
845 | case 37: /* fg white */ |
846 | #ifdef COLOR |
847 | cur_color &= ~0xff; |
848 | cur_color |= (1 << (param1 - 30)); |
849 | #endif |
850 | break; |
851 | case 40: /* bg black */ |
852 | case 41: /* bg red */ |
853 | case 42: /* bg green */ |
854 | case 43: /* bg yellow */ |
855 | case 44: /* bg blue */ |
856 | case 45: /* bg magenta */ |
857 | case 46: /* bg cyan */ |
858 | case 47: /* bg white */ |
859 | #ifdef COLOR |
860 | cur_color = (1 << (8 + param1 - 40)) | |
861 | (cur_color & 0xff); |
862 | #endif |
863 | break; |
864 | } |
865 | |
866 | parambuf[0] = '\0'; |
867 | parambuflen = 0; |
868 | } else if (parambuflen < nitems(parambuf) - 1) { |
869 | parambuf[parambuflen] = console->csi[x]; |
870 | parambuflen++; |
871 | parambuf[parambuflen] = '\0'; |
872 | } |
873 | } |
874 | |
875 | console->cur_attr = param2; |
876 | break; |
877 | case 'n': /* DSR - device status report */ |
878 | switch (param1) { |
879 | case 5: /* terminal is ready */ |
880 | session = console->session; |
881 | if (session->ibuflen >= sizeof(session->ibuf) - 4) |
882 | break; |
883 | session->ibuf[session->ibuflen++] = ESC; |
884 | session->ibuf[session->ibuflen++] = '['; |
885 | session->ibuf[session->ibuflen++] = '0'; |
886 | session->ibuf[session->ibuflen++] = 'n'; |
887 | break; |
888 | case 6: /* CPR - report cursor position */ |
889 | session = console->session; |
890 | if (session->ibuflen >= sizeof(session->ibuf) - 10) |
891 | break; |
892 | session->ibuf[session->ibuflen++] = ESC; |
893 | session->ibuf[session->ibuflen++] = '['; |
894 | |
895 | if (console->cursor_line >= 99) |
896 | session->ibuf[session->ibuflen++] = '0' + |
897 | ((console->cursor_line + 1) / 100); |
898 | if (console->cursor_line >= 9) |
899 | session->ibuf[session->ibuflen++] = '0' + |
900 | (((console->cursor_line + 1) % 100) / 10); |
901 | session->ibuf[session->ibuflen++] = '0' + |
902 | ((console->cursor_line + 1) % 10); |
903 | |
904 | session->ibuf[session->ibuflen++] = ';'; |
905 | |
906 | if (console->cursor_column >= 99) |
907 | session->ibuf[session->ibuflen++] = '0' + |
908 | ((console->cursor_column + 1) / 100); |
909 | if (console->cursor_column >= 9) |
910 | session->ibuf[session->ibuflen++] = '0' + |
911 | (((console->cursor_column + 1) % 100) / 10); |
912 | session->ibuf[session->ibuflen++] = '0' + |
913 | ((console->cursor_column + 1) % 10); |
914 | |
915 | session->ibuf[session->ibuflen++] = 'R'; |
916 | break; |
917 | } |
918 | break; |
919 | case 's': /* SCO - save cursor position */ |
920 | console->saved_cursor_column = console->cursor_column; |
921 | console->saved_cursor_line = console->cursor_line; |
922 | break; |
923 | case 'u': /* SCO - restore saved cursor position */ |
924 | console->cursor_column = console->saved_cursor_column; |
925 | console->cursor_line = console->saved_cursor_line; |
926 | break; |
927 | default: |
928 | /* |
929 | * if the last character is a letter and we haven't serviced |
930 | * it, assume it's a sequence we don't support and should just |
931 | * suppress |
932 | */ |
933 | if (c < 65 || (c > 90 && c < 97) || c > 122) |
934 | serviced = 0; |
935 | } |
936 | |
937 | if (serviced) { |
938 | console->csilen = 0; |
939 | console->csi[0] = '\0'; |
940 | console->in_csi = 0; |
941 | console_bound(console); |
942 | } |
943 | } |