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