1 /* expeditor.c
2 * Copyright 1984-2017 Cisco Systems, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "system.h"
18
19 #ifdef FEATURE_EXPEDITOR
20
21 /* locally defined functions */
22 static IBOOL s_ee_init_term(void);
23 static ptr s_ee_read_char(IBOOL blockp);
24 static void s_ee_write_char(wchar_t c);
25 static void s_ee_flush(void);
26 static ptr s_ee_get_screen_size(void);
27 static void s_ee_raw(void);
28 static void s_ee_noraw(void);
29 static void s_ee_enter_am_mode(void);
30 static void s_ee_exit_am_mode(void);
31 static void s_ee_pause(void);
32 static void s_ee_nanosleep(U32 secs, U32 nanosecs);
33 static ptr s_ee_get_clipboard(void);
34 static void s_ee_up(I32);
35 static void s_ee_down(I32);
36 static void s_ee_left(I32);
37 static void s_ee_right(I32);
38 static void s_ee_clear_eol(void);
39 static void s_ee_clear_eos(void);
40 static void s_ee_clear_screen(void);
41 static void s_ee_scroll_reverse(I32);
42 static void s_ee_bell(void);
43 static void s_ee_carriage_return(void);
44 static void s_ee_line_feed(void);
45
46 static INT init_status = -1;
47
48 #ifdef WIN32
49
50 #include <objbase.h>
51 #include <io.h>
52
53 static HANDLE hStdout, hStdin;
54 static DWORD InMode, OutMode;
55
s_ee_init_term(void)56 static IBOOL s_ee_init_term(void) {
57 CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
58
59 if (init_status != -1) return init_status;
60
61 init_status =
62 (hStdin = GetStdHandle(STD_INPUT_HANDLE)) != INVALID_HANDLE_VALUE
63 && (hStdout = GetStdHandle(STD_OUTPUT_HANDLE)) != INVALID_HANDLE_VALUE
64 && GetConsoleScreenBufferInfo(hStdout, &csbiInfo)
65 && GetConsoleMode(hStdin, &InMode)
66 && GetConsoleMode(hStdout, &OutMode);
67
68 return init_status;
69 }
70
71 /* returns char, eof, #t (winched), or #f (nothing ready), the latter
72 only if blockp is false */
s_ee_read_char(IBOOL blockp)73 static ptr s_ee_read_char(IBOOL blockp) {
74 DWORD cNumRead;
75 INPUT_RECORD irInBuf[1];
76 #ifdef PTHREADS
77 ptr tc;
78 #endif /* PTHREADS */
79 BOOL succ;
80 static wchar_t buf[10];
81 static int bufidx = 0;
82 static int buflen = 0;
83 static int rptcnt = 0;
84
85 for (;;) {
86 if (buflen != 0) {
87 int i = bufidx++;
88 if (bufidx == buflen) {
89 bufidx = 0;
90 if (--rptcnt == 0) buflen = 0;
91 }
92 return Schar(buf[i]);
93 }
94
95 if (!blockp) {
96 DWORD NumberOfEvents;
97 if (!GetNumberOfConsoleInputEvents(hStdin, &NumberOfEvents))
98 S_error1("expeditor", "error getting console input: ~a",
99 S_LastErrorString());
100 if (NumberOfEvents == 0) return Sfalse;
101 }
102
103 #ifdef PTHREADS
104 tc = get_thread_context();
105 if (DISABLECOUNT(tc) == FIX(0)) {
106 deactivate_thread(tc);
107 succ = ReadConsoleInputW(hStdin, irInBuf, 1, &cNumRead);
108 reactivate_thread(tc);
109 } else {
110 succ = ReadConsoleInputW(hStdin, irInBuf, 1, &cNumRead);
111 }
112 #else /* PTHREADS */
113 succ = ReadConsoleInputW(hStdin, irInBuf, 1, &cNumRead);
114 #endif /* PTHREADS */
115
116
117 if (!succ)
118 S_error1("expeditor", "error getting console info: ~a",
119 S_LastErrorString());
120
121 if (cNumRead == 0) return Seof_object;
122
123 switch(irInBuf[0].EventType) {
124 case KEY_EVENT: {
125 KEY_EVENT_RECORD ker = irInBuf[0].Event.KeyEvent;
126 rptcnt = ker.wRepeatCount;
127 if (ker.bKeyDown) {
128 wchar_t c;
129
130 if ((c = ker.uChar.UnicodeChar)) {
131 /* translate ^<space> to nul */
132 if (c == 0x20 && (ker.dwControlKeyState & (LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED)))
133 buf[0] = 0;
134 else
135 buf[0] = c;
136 buflen = 1;
137 } else {
138 switch (ker.wVirtualKeyCode) {
139 case VK_DELETE:
140 buf[0] = '\033';
141 buf[1] = '[';
142 buf[2] = '3';
143 buf[3] = '~';
144 buflen = 4;
145 break;
146 case VK_NEXT: /* page-down */
147 buf[0] = '\033';
148 buf[1] = '[';
149 buf[2] = '6';
150 buf[3] = '~';
151 buflen = 4;
152 break;
153 case VK_PRIOR: /* page-up */
154 buf[0] = '\033';
155 buf[1] = '[';
156 buf[2] = '5';
157 buf[3] = '~';
158 buflen = 4;
159 break;
160 case VK_END:
161 buf[0] = '\033';
162 buf[1] = '[';
163 buf[2] = 'F';
164 buflen = 3;
165 break;
166 case VK_HOME:
167 buf[0] = '\033';
168 buf[1] = '[';
169 buf[2] = 'H';
170 buflen = 3;
171 break;
172 case VK_LEFT:
173 buf[0] = '\033';
174 buf[1] = '[';
175 buf[2] = 'D';
176 buflen = 3;
177 break;
178 case VK_UP:
179 buf[0] = '\033';
180 buf[1] = '[';
181 buf[2] = 'A';
182 buflen = 3;
183 break;
184 case VK_RIGHT:
185 buf[0] = '\033';
186 buf[1] = '[';
187 buf[2] = 'C';
188 buflen = 3;
189 break;
190 case VK_DOWN:
191 buf[0] = '\033';
192 buf[1] = '[';
193 buf[2] = 'B';
194 buflen = 3;
195 break;
196 /* translate ^@ to nul */
197 case 0x32:
198 if (ker.dwControlKeyState & (LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED)) {
199 buf[0] = 0;
200 buflen = 1;
201 }
202 break;
203 default:
204 break;
205 }
206 }
207 }
208 break;
209 }
210
211 /* this tells us when the buffer size changes, but nothing comes through
212 when the window size changes. */
213 case WINDOW_BUFFER_SIZE_EVENT: // scrn buf. resizing
214 return Strue;
215
216 default:
217 break;
218 }
219 }
220 }
221
222 /* probably need write-char too */
223
s_ee_get_screen_size(void)224 static ptr s_ee_get_screen_size(void) {
225 CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
226
227 if (!GetConsoleScreenBufferInfo(hStdout, &csbiInfo))
228 S_error1("expeditor", "error getting console info: ~a",
229 S_LastErrorString());
230
231 return Scons(Sinteger(csbiInfo.srWindow.Bottom - csbiInfo.srWindow.Top + 1),
232 Sinteger(csbiInfo.srWindow.Right - csbiInfo.srWindow.Left + 1));
233 }
234
s_ee_raw(void)235 static void s_ee_raw(void) {
236 /* see http://msdn2.microsoft.com/en-us/library/ms686033.aspx */
237 if (!SetConsoleMode(hStdin, ENABLE_WINDOW_INPUT)
238 || !SetConsoleMode(hStdout, 0))
239 S_error1("expeditor", "error setting console mode: ~a",
240 S_LastErrorString());
241 }
242
s_ee_noraw(void)243 static void s_ee_noraw(void) {
244 if (!SetConsoleMode(hStdin, InMode) || !SetConsoleMode(hStdout, OutMode))
245 S_error1("expeditor", "error setting console mode: ~a",
246 S_LastErrorString());
247 }
248
s_ee_enter_am_mode(void)249 static void s_ee_enter_am_mode(void) { return; }
250
s_ee_exit_am_mode(void)251 static void s_ee_exit_am_mode(void) { return; }
252
s_ee_pause(void)253 static void s_ee_pause(void) { return; }
254
s_ee_nanosleep(U32 secs,U32 nanosecs)255 static void s_ee_nanosleep(U32 secs, U32 nanosecs) {
256 Sleep((secs * 1000) + (nanosecs / 1000000));
257 }
258
s_ee_up(I32 n)259 static void s_ee_up(I32 n) {
260 CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
261 COORD cursor_pos;
262
263 fflush(stdout);
264
265 if (!GetConsoleScreenBufferInfo(hStdout, &csbiInfo))
266 S_error1("expeditor", "error getting console info: ~a",
267 S_LastErrorString());
268
269 cursor_pos.X = csbiInfo.dwCursorPosition.X;
270 cursor_pos.Y = csbiInfo.dwCursorPosition.Y - n;
271
272 /* ignore error, which can occur only if someone else screwed with screen */
273 SetConsoleCursorPosition(hStdout, cursor_pos);
274 }
275
s_ee_down(I32 n)276 static void s_ee_down(I32 n) {
277 CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
278 COORD cursor_pos;
279
280 fflush(stdout);
281
282 if (!GetConsoleScreenBufferInfo(hStdout, &csbiInfo))
283 S_error1("expeditor", "error getting console info: ~a",
284 S_LastErrorString());
285
286 cursor_pos.X = csbiInfo.dwCursorPosition.X;
287 cursor_pos.Y = csbiInfo.dwCursorPosition.Y + n;
288
289 /* ignore error, which can occur only if someone else screwed with screen */
290 SetConsoleCursorPosition(hStdout, cursor_pos);
291 }
292
s_ee_left(I32 n)293 static void s_ee_left(I32 n) {
294 CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
295 COORD cursor_pos;
296
297 fflush(stdout);
298
299 if (!GetConsoleScreenBufferInfo(hStdout, &csbiInfo))
300 S_error1("expeditor", "error getting console info: ~a",
301 S_LastErrorString());
302
303 cursor_pos.X = csbiInfo.dwCursorPosition.X - n;
304 cursor_pos.Y = csbiInfo.dwCursorPosition.Y;
305
306 /* ignore error, which can occur only if someone else screwed with screen */
307 SetConsoleCursorPosition(hStdout, cursor_pos);
308 }
309
s_ee_right(I32 n)310 static void s_ee_right(I32 n) {
311 CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
312 COORD cursor_pos;
313
314 fflush(stdout);
315
316 if (!GetConsoleScreenBufferInfo(hStdout, &csbiInfo))
317 S_error1("expeditor", "error getting console info: ~a",
318 S_LastErrorString());
319
320 cursor_pos.X = csbiInfo.dwCursorPosition.X + n;
321 cursor_pos.Y = csbiInfo.dwCursorPosition.Y;
322
323 /* ignore error, which can occur only if someone else screwed with screen */
324 SetConsoleCursorPosition(hStdout, cursor_pos);
325 }
326
s_ee_clear_eol(void)327 static void s_ee_clear_eol(void) {
328 CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
329 DWORD ntowrite, numwritten;
330
331 fflush(stdout);
332
333 if (!GetConsoleScreenBufferInfo(hStdout, &csbiInfo))
334 S_error1("expeditor", "error getting console info: ~a",
335 S_LastErrorString());
336
337 ntowrite = csbiInfo.dwSize.X - csbiInfo.dwCursorPosition.X;
338
339 if (!FillConsoleOutputCharacter(hStdout, (TCHAR)' ',
340 ntowrite, csbiInfo.dwCursorPosition, &numwritten))
341 S_error1("expeditor", "error clearing section of screen buffer: ~a",
342 S_LastErrorString());
343
344 if (!FillConsoleOutputAttribute(hStdout, csbiInfo.wAttributes,
345 ntowrite, csbiInfo.dwCursorPosition, &numwritten))
346 S_error1("expeditor", "error setting attributes in section of screen buffer: ~a",
347 S_LastErrorString());
348 }
349
s_ee_clear_eos(void)350 static void s_ee_clear_eos(void) {
351 CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
352 DWORD ntowrite, numwritten;
353
354 fflush(stdout);
355
356 if (!GetConsoleScreenBufferInfo(hStdout, &csbiInfo))
357 S_error1("expeditor", "error getting console info: ~a",
358 S_LastErrorString());
359
360 ntowrite = (csbiInfo.dwSize.X - csbiInfo.dwCursorPosition.X) +
361 (csbiInfo.dwSize.X *
362 (csbiInfo.dwSize.Y - csbiInfo.dwCursorPosition.Y - 1));
363
364 if (!FillConsoleOutputCharacter(hStdout, (TCHAR)' ',
365 ntowrite, csbiInfo.dwCursorPosition, &numwritten))
366 S_error1("expeditor", "error clearing section of screen buffer: ~a",
367 S_LastErrorString());
368
369 if (!FillConsoleOutputAttribute(hStdout, csbiInfo.wAttributes,
370 ntowrite, csbiInfo.dwCursorPosition, &numwritten))
371 S_error1("expeditor", "error setting attributes in section of screen buffer: ~a",
372 S_LastErrorString());
373 }
374
s_ee_clear_screen(void)375 static void s_ee_clear_screen(void) {
376 CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
377 COORD cursor_pos;
378 DWORD ntowrite, numwritten;
379
380 fflush(stdout);
381
382 if (!GetConsoleScreenBufferInfo(hStdout, &csbiInfo))
383 S_error1("expeditor", "error getting console info: ~a",
384 S_LastErrorString());
385
386 cursor_pos.X = 0;
387 cursor_pos.Y = csbiInfo.srWindow.Top;
388 SetConsoleCursorPosition(hStdout, cursor_pos);
389
390 ntowrite = csbiInfo.dwSize.X * (csbiInfo.dwSize.Y - cursor_pos.Y);
391
392 if (!FillConsoleOutputCharacter(hStdout, (TCHAR)' ',
393 ntowrite, cursor_pos, &numwritten))
394 S_error1("expeditor", "error clearing section of screen buffer: ~a",
395 S_LastErrorString());
396
397 if (!FillConsoleOutputAttribute(hStdout, csbiInfo.wAttributes,
398 ntowrite, cursor_pos, &numwritten))
399 S_error1("expeditor", "error setting attributes in section of screen buffer: ~a",
400 S_LastErrorString());
401 }
402
s_ee_scroll_reverse(I32 n)403 static void s_ee_scroll_reverse(I32 n) {
404 CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
405
406 fflush(stdout);
407
408 if (!GetConsoleScreenBufferInfo(hStdout, &csbiInfo))
409 S_error1("expeditor", "error getting console info: ~a",
410 S_LastErrorString());
411
412 if (csbiInfo.dwCursorPosition.Y - n < 0) {
413 SMALL_RECT rect;
414 COORD dest;
415 CHAR_INFO fill;
416
417 /* set fill to blank so new top lines will be cleared */
418 fill.Attributes = csbiInfo.wAttributes;
419 fill.Char.AsciiChar = (char)' ';
420
421 /* move lines 0 through N-n-1 down to lines n through N-1 */
422 rect.Top = 0;
423 rect.Bottom = csbiInfo.dwSize.Y - n - 1;
424 rect.Left = 0;
425 rect.Right = csbiInfo.dwSize.X - 1;
426 dest.X = 0;
427 dest.Y = n;
428 if (!ScrollConsoleScreenBuffer(hStdout, &rect, (SMALL_RECT *)0, dest, &fill))
429 S_error1("expeditor", "error scrolling screen buffer: ~a",
430 S_LastErrorString());
431 } else {
432 COORD cursor_pos; DWORD numwritten;
433
434 cursor_pos.X = csbiInfo.dwCursorPosition.X;
435 cursor_pos.Y = csbiInfo.dwCursorPosition.Y - n;
436 SetConsoleCursorPosition(hStdout, cursor_pos);
437
438 if (!FillConsoleOutputCharacter(hStdout, (TCHAR)' ',
439 csbiInfo.dwSize.X * n, cursor_pos, &numwritten))
440 S_error1("expeditor", "error clearing section of screen buffer: ~a",
441 S_LastErrorString());
442
443 if (!FillConsoleOutputAttribute(hStdout, csbiInfo.wAttributes,
444 csbiInfo.dwSize.X * n, cursor_pos, &numwritten))
445 S_error1("expeditor", "error setting attributes in section of screen buffer: ~a",
446 S_LastErrorString());
447 }
448 }
449
s_ee_bell(void)450 static void s_ee_bell(void) {
451 MessageBeep(MB_OK);
452 }
453
s_ee_carriage_return(void)454 static void s_ee_carriage_return(void) {
455 CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
456 COORD cursor_pos;
457
458 fflush(stdout);
459
460 if (!GetConsoleScreenBufferInfo(hStdout, &csbiInfo))
461 S_error1("expeditor", "error getting console info: ~a",
462 S_LastErrorString());
463
464 cursor_pos.X = 0;
465 cursor_pos.Y = csbiInfo.dwCursorPosition.Y;
466
467 SetConsoleCursorPosition(hStdout, cursor_pos);
468 }
469
s_ee_line_feed(void)470 static void s_ee_line_feed(void) {
471 CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
472
473 fflush(stdout);
474
475 if (!GetConsoleScreenBufferInfo(hStdout, &csbiInfo))
476 S_error1("expeditor", "error getting console info: ~a",
477 S_LastErrorString());
478
479 if (csbiInfo.dwCursorPosition.Y == (csbiInfo.dwSize.Y - 1)) {
480 SMALL_RECT rect;
481 COORD dest;
482 CHAR_INFO fill;
483
484 /* set fill to blank so new bottom line will be cleared */
485 fill.Attributes = csbiInfo.wAttributes;
486 fill.Char.AsciiChar = (char)' ';
487
488 /* move lines 1 through N-1 up to lines 0 through N-2 */
489 rect.Top = 1;
490 rect.Bottom = csbiInfo.dwSize.Y - 1;
491 rect.Left = 0;
492 rect.Right = csbiInfo.dwSize.X - 1;
493 dest.X = 0;
494 dest.Y = 0;
495 if (!ScrollConsoleScreenBuffer(hStdout, &rect, (SMALL_RECT *)0, dest, &fill))
496 S_error1("expeditor", "error scrolling screen buffer: ~a",
497 S_LastErrorString());
498 } else {
499 COORD cursor_pos;
500
501 cursor_pos.X = csbiInfo.dwCursorPosition.X;
502 cursor_pos.Y = csbiInfo.dwCursorPosition.Y + 1;
503 SetConsoleCursorPosition(hStdout, cursor_pos);
504 }
505 }
506
s_ee_get_clipboard(void)507 static ptr s_ee_get_clipboard(void) {
508 ptr x = S_G.null_string;
509
510 if (OpenClipboard((HWND)0)) {
511 HANDLE h = GetClipboardData(CF_UNICODETEXT);
512 if (h != NULL) {
513 wchar_t *w = (wchar_t*)GlobalLock(h);
514 if (w != NULL) {
515 char *s = Swide_to_utf8(w);
516 x = Sstring_utf8(s, -1);
517 free(s);
518 GlobalUnlock(h);
519 }
520 }
521 CloseClipboard();
522 }
523
524 return x;
525 }
526
s_ee_write_char(wchar_t c)527 static void s_ee_write_char(wchar_t c) {
528 DWORD n;
529 WriteConsoleW(hStdout, &c, 1, &n, NULL);
530 }
531
532 #else /* WIN32 */
533
534 #include <limits.h>
535 #ifdef DISABLE_CURSES
536 # include "nocurses.h"
537 #elif defined(SOLARIS)
538 # define NCURSES_CONST
539 # define CHTYPE int
540 # include </usr/include/curses.h>
541 # include </usr/include/term.h>
542 #elif defined(NETBSD)
543 # include <ncurses.h>
544 # include <ncurses/term.h>
545 #else
546 # include <curses.h>
547 # include <term.h>
548 #endif
549 #include <termios.h>
550 #include <signal.h>
551 #include <fcntl.h>
552 #include <sys/ioctl.h>
553 #include <wchar.h>
554 #include <locale.h>
555 #if !defined(__GLIBC__) && !defined(__OpenBSD__) && !defined(__NetBSD__) && !defined(__linux__) && !defined(NO_USELOCALE)
556 # include <xlocale.h>
557 #endif
558
559 #if defined(TIOCGWINSZ) && defined(SIGWINCH) && defined(EINTR)
560 # define HANDLE_SIGWINCH
561 #endif
562
563 #ifdef USE_MBRTOWC_L
564 static locale_t the_locale;
uselocale_alt(locale_t l)565 static locale_t uselocale_alt(locale_t l) {
566 locale_t old = the_locale;
567 the_locale = l;
568 return old;
569 }
570 # define uselocale(v) uselocale_alt(v)
571 # define mbrtowc(pwc, s, n, ps) mbrtowc_l(pwc, s, n, ps, the_locale)
572 #endif
573
574 /* locally defined functions */
575 static int eeputc(tputsputcchar c);
576 #ifdef HANDLE_SIGWINCH
577 static void handle_sigwinch(int sig);
578 #endif
579
580 #ifdef HANDLE_SIGWINCH
581 static IBOOL winched = 0;
handle_sigwinch(UNUSED int sig)582 static void handle_sigwinch(UNUSED int sig) {
583 winched = 1;
584 }
585 #endif
586
587 #define STDIN_FD 0
588 #define STDOUT_FD 1
589
590 static IBOOL disable_auto_margin = 0, avoid_last_column = 0;
591 #ifndef NO_USELOCALE
592 static locale_t term_locale;
593 #endif
594 static mbstate_t term_in_mbs;
595 static mbstate_t term_out_mbs;
596
s_ee_init_term(void)597 static IBOOL s_ee_init_term(void) {
598 int errret;
599
600 if (init_status != -1) return init_status;
601
602 if (isatty(STDIN_FD)
603 && isatty(STDOUT_FD)
604 && setupterm(NULL, STDOUT_FD, &errret) != ERR
605 /* assuming here and in our optproc definitions later that the names of
606 missing capabilities are set to NULL, although this does not appear
607 to be documented */
608 && cursor_up
609 && cursor_down
610 && cursor_left
611 && cursor_right
612 && clr_eol
613 && clr_eos
614 && clear_screen
615 && scroll_reverse
616 && carriage_return) {
617 if (auto_right_margin) {
618 /* terminal automatically wraps. safest to disable if possible */
619 if (exit_am_mode && enter_am_mode) {
620 disable_auto_margin = 1;
621 avoid_last_column = 0;
622 /* can't disable automatic margins, but eat_newline_glitch is set.
623 may still be okay, since we never write past the last column,
624 and the automatic newline should be emitted only if we do. but
625 see hack in s_ee_enter_am_mode */
626 } else if (eat_newline_glitch) {
627 disable_auto_margin = 0;
628 avoid_last_column = 0;
629 } else {
630 disable_auto_margin = 0;
631 avoid_last_column = 1;
632 }
633 } else {
634 disable_auto_margin = 0;
635 avoid_last_column = 0;
636 }
637
638 #ifdef HANDLE_SIGWINCH
639 struct sigaction act;
640
641 sigemptyset(&act.sa_mask);
642
643 act.sa_flags = 0;
644 act.sa_handler = handle_sigwinch;
645 sigaction(SIGWINCH, &act, (struct sigaction *)0);
646 #endif
647
648 #ifndef NO_USELOCALE
649 term_locale = newlocale(LC_ALL_MASK, "", NULL);
650 #endif
651 memset(&term_out_mbs, 0, sizeof(term_out_mbs));
652 memset(&term_in_mbs, 0, sizeof(term_in_mbs));
653
654 init_status = 1;
655 } else {
656 init_status = 0;
657 }
658
659 return init_status;
660 }
661
662 /* returns char, eof, #t (winched), or #f (nothing ready), the latter
663 only if blockp is false */
s_ee_read_char(IBOOL blockp)664 static ptr s_ee_read_char(IBOOL blockp) {
665 ptr msg; int fd = STDIN_FD; int n; char buf[1]; wchar_t wch; size_t sz;
666 #ifdef PTHREADS
667 ptr tc = get_thread_context();
668 #endif
669
670 do {
671 #ifdef HANDLE_SIGWINCH
672 if (winched) { winched = 0; return Strue; }
673 #endif
674 #ifdef PTHREADS
675 if (!blockp || DISABLECOUNT(tc) == FIX(0)) {
676 fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | NOBLOCK);
677 n = READ(fd, buf, 1);
678 fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~NOBLOCK);
679 if (n < 0 && errno == EWOULDBLOCK) {
680 if (!blockp) return Sfalse;
681 deactivate_thread(tc);
682 n = READ(fd, buf, 1);
683 reactivate_thread(tc);
684 }
685 } else {
686 n = READ(fd, buf, 1);
687 }
688 #else /* PTHREADS */
689 if (!blockp) {
690 fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | NOBLOCK);
691 n = READ(fd, buf, 1);
692 fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~NOBLOCK);
693 if (n < 0 && errno == EWOULDBLOCK) return Sfalse;
694 } else {
695 n = READ(fd, buf, 1);
696 }
697 #endif /* PTHREADS */
698
699 if (n == 1) {
700 if (buf[0] == '\0') {
701 return Schar('\0');
702 } else {
703 #ifndef NO_USELOCALE
704 locale_t old_locale = uselocale(term_locale);
705 #endif
706 sz = mbrtowc(&wch, buf, 1, &term_out_mbs);
707 #ifndef NO_USELOCALE
708 uselocale(old_locale);
709 #endif
710 if (sz == 1) {
711 return Schar(wch);
712 }
713 }
714 } else
715 sz = 0;
716
717 } while ((n < 0 && errno == EINTR) || (n == 1 && sz == (size_t)-2));
718
719 if (n == 0) return Seof_object;
720
721 msg = S_strerror(errno);
722 S_error1("expeditor", "error reading from console: ~a", msg);
723
724 memset(&term_out_mbs, 0, sizeof(term_out_mbs));
725 return Svoid;
726 }
727
728 /* returns a pair of positive integers */
s_ee_get_screen_size(void)729 static ptr s_ee_get_screen_size(void) {
730 static INT ee_rows = 0;
731 static INT ee_cols = 0;
732
733 #ifdef TIOCGWINSZ
734 struct winsize ws;
735 if (ioctl(STDOUT_FD, TIOCGWINSZ, &ws) == 0) {
736 if (ws.ws_row > 0) ee_rows = ws.ws_row;
737 if (ws.ws_col > 0) ee_cols = ws.ws_col;
738 }
739 #ifdef MACOSX
740 static IBOOL tried_resize = 0;
741 /* attempt to work around 10.6 tty driver / xterm bug */
742 if (ee_rows == 0 && ee_cols == 0 && !tried_resize) {
743 SYSTEM("exec /usr/X11/bin/resize >& /dev/null");
744 tried_resize = 1;
745 return s_ee_get_screen_size();
746 }
747 #endif /* MACOSX */
748 #endif /* TIOCGWINSZ */
749
750 if (ee_rows == 0) {
751 char *s, *endp;
752 if ((s = getenv("LINES")) != NULL) {
753 INT n = (int)strtol(s, &endp, 10);
754 if (n > 0 && *endp == '\0') ee_rows = n;
755 }
756 if (ee_rows == 0) ee_rows = lines > 0 ? lines : 24;
757 }
758
759 if (ee_cols == 0) {
760 char *s, *endp;
761 if ((s = getenv("COLUMNS")) != NULL) {
762 INT n = (int)strtol(s, &endp, 10);
763 if (n > 0 && *endp == '\0') ee_cols = n;
764 }
765 if (ee_cols == 0) ee_cols = columns > 0 ? columns : 80;
766 }
767
768 return Scons(Sinteger(ee_rows), Sinteger(ee_cols > 1 && avoid_last_column ? ee_cols - 1 : ee_cols));
769 }
770
eeputc(tputsputcchar c)771 static int eeputc(tputsputcchar c) {
772 return putchar(c);
773 }
774
775 static struct termios orig_termios;
776
s_ee_raw(void)777 static void s_ee_raw(void) {
778 struct termios new_termios;
779 while (tcgetattr(STDIN_FD, &orig_termios) != 0) {
780 if (errno != EINTR) {
781 ptr msg = S_strerror(errno);
782 if (msg != Sfalse)
783 S_error1("expeditor", "error entering raw mode: ~a", msg);
784 else
785 S_error("expeditor", "error entering raw mode");
786 }
787 }
788 new_termios = orig_termios;
789
790 /* essentially want "stty raw -echo". the appropriate flags to accomplish
791 this were determined by studying the gnu/linux stty and termios man
792 pages, with particular attention to the cfmakeraw function. */
793 new_termios.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|INPCK|ISTRIP
794 |INLCR|IGNCR|ICRNL|IXON);
795 new_termios.c_oflag &= ~(OPOST);
796 new_termios.c_lflag &= ~(ISIG|ICANON|ECHO|IEXTEN);
797 new_termios.c_cflag &= ~(CSIZE|PARENB);
798 new_termios.c_cflag |= CS8;
799 new_termios.c_cc[VMIN] = 1;
800 new_termios.c_cc[VTIME] = 0;
801
802 while (tcsetattr(STDIN_FD, TCSADRAIN, &new_termios) != 0) {
803 if (errno != EINTR) {
804 ptr msg = S_strerror(errno);
805 if (msg != Sfalse)
806 S_error1("expeditor", "error entering raw mode: ~a", msg);
807 else
808 S_error("expeditor", "error entering raw mode");
809 }
810 }
811 }
812
s_ee_noraw(void)813 static void s_ee_noraw(void) {
814 while (tcsetattr(STDIN_FD, TCSADRAIN, &orig_termios) != 0) {
815 if (errno != EINTR) {
816 ptr msg = S_strerror(errno);
817 if (msg != Sfalse)
818 S_error1("expeditor", "error leaving raw mode: ~a", msg);
819 else
820 S_error("expeditor", "error leaving raw mode");
821 }
822 }
823 }
824
s_ee_enter_am_mode(void)825 static void s_ee_enter_am_mode(void) {
826 if (disable_auto_margin) {
827 tputs(enter_am_mode, 1, eeputc);
828 /* flush to minimize time span when automatic margins are disabled */
829 fflush(stdout);
830 } else if (eat_newline_glitch) {
831 /* hack: try to prevent terminal from eating subsequent cr or lf.
832 assumes we've just written to last column. probably works only
833 for vt100 interpretation of eat_newline_glitch/xn/xenl flag. */
834 tputs(cursor_left, 1, eeputc);
835 tputs(cursor_right, 1, eeputc);
836 }
837 }
838
s_ee_exit_am_mode(void)839 static void s_ee_exit_am_mode(void) {
840 if (disable_auto_margin) {
841 tputs(exit_am_mode, 1, eeputc);
842 }
843 }
844
s_ee_pause(void)845 static void s_ee_pause(void) { /* used to handle ^Z */
846 fflush(stdout);
847 kill(0, SIGTSTP);
848 }
849
s_ee_nanosleep(U32 secs,U32 nanosecs)850 static void s_ee_nanosleep(U32 secs, U32 nanosecs) {
851 struct timespec ts;
852 ts.tv_sec = secs;
853 ts.tv_nsec = nanosecs;
854 nanosleep(&ts, (struct timespec *)0);
855 }
856
s_ee_up(I32 n)857 static void s_ee_up(I32 n) {
858 while (n--) tputs(cursor_up, 1, eeputc);
859 }
860
s_ee_down(I32 n)861 static void s_ee_down(I32 n) {
862 while (n--) tputs(cursor_down, 1, eeputc);
863 }
864
s_ee_left(I32 n)865 static void s_ee_left(I32 n) {
866 while (n--) tputs(cursor_left, 1, eeputc);
867 }
868
s_ee_right(I32 n)869 static void s_ee_right(I32 n) {
870 while (n--) tputs(cursor_right, 1, eeputc);
871 }
872
s_ee_clear_eol(void)873 static void s_ee_clear_eol(void) {
874 tputs(clr_eol, 1, eeputc);
875 }
876
s_ee_clear_eos(void)877 static void s_ee_clear_eos(void) {
878 tputs(clr_eos, 1, eeputc);
879 }
880
s_ee_clear_screen(void)881 static void s_ee_clear_screen(void) {
882 tputs(clear_screen, 1, eeputc);
883 }
884
s_ee_scroll_reverse(I32 n)885 static void s_ee_scroll_reverse(I32 n) {
886 /* moving up from an entry that was only partially displayed,
887 scroll-reverse may be called when cursor isn't at the top line of
888 the screen, in which case we hope it will move up by one line.
889 in this case, which we have no way of distinguishing from the normal
890 case, scroll-reverse needs to clear the line explicitly */
891 while (n--) {
892 tputs(scroll_reverse, 1, eeputc);
893 tputs(clr_eol, 1, eeputc);
894 }
895 }
896
s_ee_bell(void)897 static void s_ee_bell(void) {
898 tputs(bell, 1, eeputc);
899 }
900
s_ee_carriage_return(void)901 static void s_ee_carriage_return(void) {
902 tputs(carriage_return, 1, eeputc);
903 }
904
905 /* move-line-down doesn't scroll the screen when performed on the last
906 line on the freebsd and openbsd consoles. the official way to scroll
907 the screen is to use scroll-forward (ind), but ind is defined only
908 at the bottom left corner of the screen, and we don't always know
909 where the bottom of the screen actually is. so we write a line-feed
910 (newline) character and hope that will do the job. */
s_ee_line_feed(void)911 static void s_ee_line_feed(void) {
912 putchar(0x0a);
913 }
914
915 #ifdef LIBX11
916 #include <dlfcn.h>
917 #include <X11/Xlib.h>
918 #include <X11/Xatom.h>
919 #include <sys/select.h>
920 #endif /* LIBX11 */
921
s_ee_get_clipboard(void)922 static ptr s_ee_get_clipboard(void) {
923 #ifdef LIBX11
924 static enum {UNINITIALIZED, INITIALIZED, FAILED} status = UNINITIALIZED;
925 static int (*pXConvertSelection)(Display *, Atom, Atom, Atom, Window, Time);
926 static int (*pXPending)(Display *display);
927 static int (*pXNextEvent)(Display *, XEvent *);
928 static int (*pXGetWindowProperty)(Display *, Window, Atom, long, long, Bool, Atom, Atom *, int *, unsigned long *, unsigned long *, unsigned char **);
929 static int (*pXFree)(void *);
930
931 static Display *D;
932 static Window R, W;
933 #endif /* LIBX11 */
934
935 ptr p = S_G.null_string;
936
937 #ifdef LIBX11
938 if (status == UNINITIALIZED) {
939 char *display_name;
940 void *handle;
941 Display *(*pXOpenDisplay)(char *);
942 Window (*pXDefaultRootWindow)(Display *);
943 Window (*pXCreateSimpleWindow)(Display *, Window, int, int, unsigned int, unsigned int, unsigned int, unsigned long, unsigned long);
944
945 status = (display_name = getenv("DISPLAY"))
946 && (handle = dlopen(LIBX11, RTLD_NOW))
947 && (pXOpenDisplay = (Display *(*)(char *display_name))dlsym(handle, "XOpenDisplay"))
948 && (pXDefaultRootWindow = (Window (*)(Display *))dlsym(handle, "XDefaultRootWindow"))
949 && (pXCreateSimpleWindow = (Window (*)(Display *, Window, int, int, unsigned int, unsigned int, unsigned int, unsigned long, unsigned long))dlsym(handle, "XCreateSimpleWindow"))
950 && (pXConvertSelection = (int (*)(Display *, Atom, Atom, Atom, Window, Time))dlsym(handle, "XConvertSelection"))
951 && (pXPending = (int (*)(Display *display))dlsym(handle, "XPending"))
952 && (pXNextEvent = (int (*)(Display *, XEvent *))dlsym(handle, "XNextEvent"))
953 && (pXGetWindowProperty = (int (*)(Display *, Window, Atom, long, long, Bool, Atom, Atom *, int *, unsigned long *, unsigned long *, unsigned char **))dlsym(handle, "XGetWindowProperty"))
954 && (pXFree = (int (*)(void *))dlsym(handle, "XFree"))
955 && (D = pXOpenDisplay(display_name))
956 && (R = pXDefaultRootWindow(D))
957 && (W = pXCreateSimpleWindow(D, R, 0, 0, 1, 1, 0, 0, 0))
958 ? INITIALIZED : FAILED;
959 }
960
961 if (status == INITIALIZED) {
962 XEvent XE;
963 Window W2; Atom P;
964 Atom type;
965 int format;
966 unsigned long items, bytes, ignore_bytes;
967 unsigned char *buf;
968 int timeout;
969
970 /* flush late arrivals from previous requests, if any */
971 while (pXPending(D)) pXNextEvent(D, &XE);
972
973 pXConvertSelection(D, XA_PRIMARY, XA_STRING, XA_STRING, W, CurrentTime);
974
975 /* mini event loop to catch response, if any */
976 timeout = 20; /* wait two seconds, 100ms at a time */
977 for (;;) {
978 if (pXPending(D)) {
979 pXNextEvent(D, &XE);
980 if (XE.type == SelectionNotify) {
981 if (XE.xselection.property == None) {
982 W2 = R;
983 P = XA_CUT_BUFFER0;
984 } else {
985 W2 = XE.xselection.requestor;
986 P = XE.xselection.property;
987 }
988
989 if (pXGetWindowProperty(D, W2, P, 0, 0, 0, AnyPropertyType,
990 &type, &format, &items, &bytes, &buf) == Success
991 && type == XA_STRING
992 && format == 8) {
993 pXFree(buf);
994 if (pXGetWindowProperty(D, W2, P, 0, bytes, 0, AnyPropertyType,
995 &type, &format, &items, &ignore_bytes, &buf) == Success
996 && type == XA_STRING
997 && format == 8) {
998 p = S_string((char *)buf, (iptr)bytes);
999 }
1000 }
1001
1002 pXFree(buf);
1003
1004 break;
1005 }
1006 } else {
1007 int xfd;
1008 fd_set rfds;
1009 struct timeval tv;
1010
1011 if (timeout == 0) break;
1012 xfd = ConnectionNumber(D);
1013 FD_ZERO(&rfds);
1014 FD_SET(xfd, &rfds);
1015 tv.tv_sec = 0;
1016 tv.tv_usec = 100*1000;
1017 select(xfd+1, &rfds, NULL, NULL, &tv);
1018 timeout -= 1;
1019 }
1020 }
1021 }
1022 #endif /* LIBX11 */
1023
1024 #ifdef MACOSX
1025 #define PBPASTEBUFSIZE 1024
1026 if (p == S_G.null_string) {
1027 char buf[PBPASTEBUFSIZE];
1028 FILE *f = popen("/usr/bin/pbpaste", "r");
1029 iptr i, n, m;
1030 char *s;
1031
1032 for (;;) {
1033 ptr newp;
1034 n = fread(buf, 1, PBPASTEBUFSIZE, f);
1035 if (n == 0) break;
1036 n += (m = Sstring_length(p));
1037 newp = S_string(NULL, n);
1038 for (i = 0; i != m; i += 1) Sstring_set(newp, i, Sstring_ref(p, i));
1039 for (s = buf; i != n; i += 1, s += 1)
1040 Sstring_set(newp, i, *s);
1041 p = newp;
1042 }
1043
1044 fclose(f);
1045 }
1046 #endif /* MACOSX */
1047
1048 return p;
1049 }
1050
s_ee_write_char(wchar_t wch)1051 static void s_ee_write_char(wchar_t wch) {
1052 char buf[MB_LEN_MAX]; size_t n;
1053 #ifndef NO_USELOCALE
1054 locale_t old = uselocale(term_locale);
1055 #endif
1056
1057 n = wcrtomb(buf, wch, &term_in_mbs);
1058 if (n == (size_t)-1) {
1059 putchar('?');
1060 } else {
1061 fwrite(buf, 1, n, stdout);
1062 }
1063
1064 #ifndef NO_USELOCALE
1065 uselocale(old);
1066 #endif
1067 }
1068
1069 #endif /* WIN32 */
1070
s_ee_flush(void)1071 static void s_ee_flush(void) {
1072 fflush(stdout);
1073 }
1074
S_expeditor_init(void)1075 void S_expeditor_init(void) {
1076 Sforeign_symbol("(cs)ee_init_term", (void *)s_ee_init_term);
1077 Sforeign_symbol("(cs)ee_read_char", (void *)s_ee_read_char);
1078 Sforeign_symbol("(cs)ee_write_char", (void *)s_ee_write_char);
1079 Sforeign_symbol("(cs)ee_flush", (void *)s_ee_flush);
1080 Sforeign_symbol("(cs)ee_get_screen_size", (void *)s_ee_get_screen_size);
1081 Sforeign_symbol("(cs)ee_raw", (void *)s_ee_raw);
1082 Sforeign_symbol("(cs)ee_noraw", (void *)s_ee_noraw);
1083 Sforeign_symbol("(cs)ee_enter_am_mode", (void *)s_ee_enter_am_mode);
1084 Sforeign_symbol("(cs)ee_exit_am_mode", (void *)s_ee_exit_am_mode);
1085 Sforeign_symbol("(cs)ee_pause", (void *)s_ee_pause);
1086 Sforeign_symbol("(cs)ee_nanosleep", (void *)s_ee_nanosleep);
1087 Sforeign_symbol("(cs)ee_get_clipboard", (void *)s_ee_get_clipboard);
1088 Sforeign_symbol("(cs)ee_up", (void *)s_ee_up);
1089 Sforeign_symbol("(cs)ee_down", (void *)s_ee_down);
1090 Sforeign_symbol("(cs)ee_left", (void *)s_ee_left);
1091 Sforeign_symbol("(cs)ee_right", (void *)s_ee_right);
1092 Sforeign_symbol("(cs)ee_clr_eol", (void *)s_ee_clear_eol);
1093 Sforeign_symbol("(cs)ee_clr_eos", (void *)s_ee_clear_eos);
1094 Sforeign_symbol("(cs)ee_clear_screen", (void *)s_ee_clear_screen);
1095 Sforeign_symbol("(cs)ee_scroll_reverse", (void *)s_ee_scroll_reverse);
1096 Sforeign_symbol("(cs)ee_bell", (void *)s_ee_bell);
1097 Sforeign_symbol("(cs)ee_carriage_return", (void *)s_ee_carriage_return);
1098 Sforeign_symbol("(cs)ee_line_feed", (void *)s_ee_line_feed);
1099 }
1100
1101 #endif /* FEATURE_EXPEDITOR */
1102