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 #include <limits.h>
534 #ifdef DISABLE_CURSES
535 #include "nocurses.h"
536 #elif defined(SOLARIS)
537 #define NCURSES_CONST
538 #define CHTYPE int
539 #include </usr/include/curses.h>
540 #include </usr/include/term.h>
541 #elif defined(NETBSD)
542 #include <ncurses.h>
543 #include <ncurses/term.h>
544 #else /* NETBSD */
545 #include <curses.h>
546 #include <term.h>
547 #endif /* SOLARIS */
548 #include <termios.h>
549 #include <signal.h>
550 #include <time.h>
551 #include <fcntl.h>
552 #include <sys/ioctl.h>
553 #include <wchar.h>
554 #include <locale.h>
555 #if !defined(__linux__) && !defined(__OpenBSD__) && !defined(__NetBSD__)
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 static locale_t term_locale;
592 static mbstate_t term_in_mbs;
593 static mbstate_t term_out_mbs;
594
s_ee_init_term(void)595 static IBOOL s_ee_init_term(void) {
596 int errret;
597
598 if (init_status != -1) return init_status;
599
600 if (isatty(STDIN_FD)
601 && isatty(STDOUT_FD)
602 && setupterm(NULL, STDOUT_FD, &errret) != ERR
603 /* assuming here and in our optproc definitions later that the names of
604 missing capabilities are set to NULL, although this does not appear
605 to be documented */
606 && cursor_up
607 && cursor_down
608 && cursor_left
609 && cursor_right
610 && clr_eol
611 && clr_eos
612 && clear_screen
613 && scroll_reverse
614 && carriage_return) {
615 if (auto_right_margin) {
616 /* terminal automatically wraps. safest to disable if possible */
617 if (exit_am_mode && enter_am_mode) {
618 disable_auto_margin = 1;
619 avoid_last_column = 0;
620 /* can't disable automatic margins, but eat_newline_glitch is set.
621 may still be okay, since we never write past the last column,
622 and the automatic newline should be emitted only if we do. but
623 see hack in s_ee_enter_am_mode */
624 } else if (eat_newline_glitch) {
625 disable_auto_margin = 0;
626 avoid_last_column = 0;
627 } else {
628 disable_auto_margin = 0;
629 avoid_last_column = 1;
630 }
631 } else {
632 disable_auto_margin = 0;
633 avoid_last_column = 0;
634 }
635
636 #ifdef HANDLE_SIGWINCH
637 struct sigaction act;
638
639 sigemptyset(&act.sa_mask);
640
641 act.sa_flags = 0;
642 act.sa_handler = handle_sigwinch;
643 sigaction(SIGWINCH, &act, (struct sigaction *)0);
644 #endif
645
646 term_locale = newlocale(LC_ALL_MASK, "", NULL);
647 memset(&term_out_mbs, 0, sizeof(term_out_mbs));
648 memset(&term_in_mbs, 0, sizeof(term_in_mbs));
649
650 init_status = 1;
651 } else {
652 init_status = 0;
653 }
654
655 return init_status;
656 }
657
658 /* returns char, eof, #t (winched), or #f (nothing ready), the latter
659 only if blockp is false */
s_ee_read_char(IBOOL blockp)660 static ptr s_ee_read_char(IBOOL blockp) {
661 ptr msg; int fd = STDIN_FD; int n; char buf[1]; wchar_t wch; size_t sz;
662 locale_t old_locale;
663 #ifdef PTHREADS
664 ptr tc = get_thread_context();
665 #endif
666
667 do {
668 #ifdef HANDLE_SIGWINCH
669 if (winched) { winched = 0; return Strue; }
670 #endif
671 #ifdef PTHREADS
672 if (!blockp || DISABLECOUNT(tc) == FIX(0)) {
673 fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | NOBLOCK);
674 n = READ(fd, buf, 1);
675 fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~NOBLOCK);
676 if (n < 0 && errno == EWOULDBLOCK) {
677 if (!blockp) return Sfalse;
678 deactivate_thread(tc);
679 n = READ(fd, buf, 1);
680 reactivate_thread(tc);
681 }
682 } else {
683 n = READ(fd, buf, 1);
684 }
685 #else /* PTHREADS */
686 if (!blockp) {
687 fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | NOBLOCK);
688 n = READ(fd, buf, 1);
689 fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~NOBLOCK);
690 if (n < 0 && errno == EWOULDBLOCK) return Sfalse;
691 } else {
692 n = READ(fd, buf, 1);
693 }
694 #endif /* PTHREADS */
695
696 if (n == 1) {
697 if (buf[0] == '\0') {
698 return Schar('\0');
699 } else {
700 old_locale = uselocale(term_locale);
701 sz = mbrtowc(&wch, buf, 1, &term_out_mbs);
702 uselocale(old_locale);
703 if (sz == 1) {
704 return Schar(wch);
705 }
706 }
707 }
708
709 } while ((n < 0 && errno == EINTR) || (n == 1 && sz == (size_t)-2));
710
711 if (n == 0) return Seof_object;
712
713 msg = S_strerror(errno);
714 S_error1("expeditor", "error reading from console: ~a", msg);
715
716 memset(&term_out_mbs, 0, sizeof(term_out_mbs));
717 return Svoid;
718 }
719
720 /* returns a pair of positive integers */
s_ee_get_screen_size(void)721 static ptr s_ee_get_screen_size(void) {
722 static INT ee_rows = 0;
723 static INT ee_cols = 0;
724
725 #ifdef TIOCGWINSZ
726 struct winsize ws;
727 if (ioctl(STDOUT_FD, TIOCGWINSZ, &ws) == 0) {
728 if (ws.ws_row > 0) ee_rows = ws.ws_row;
729 if (ws.ws_col > 0) ee_cols = ws.ws_col;
730 }
731 #ifdef MACOSX
732 static IBOOL tried_resize = 0;
733 /* attempt to work around 10.6 tty driver / xterm bug */
734 if (ee_rows == 0 && ee_cols == 0 && !tried_resize) {
735 system("exec /usr/X11/bin/resize >& /dev/null");
736 tried_resize = 1;
737 return s_ee_get_screen_size();
738 }
739 #endif /* MACOSX */
740 #endif /* TIOCGWINSZ */
741
742 if (ee_rows == 0) {
743 char *s, *endp;
744 if ((s = getenv("LINES")) != NULL) {
745 INT n = (int)strtol(s, &endp, 10);
746 if (n > 0 && *endp == '\0') ee_rows = n;
747 }
748 if (ee_rows == 0) ee_rows = lines > 0 ? lines : 24;
749 }
750
751 if (ee_cols == 0) {
752 char *s, *endp;
753 if ((s = getenv("COLUMNS")) != NULL) {
754 INT n = (int)strtol(s, &endp, 10);
755 if (n > 0 && *endp == '\0') ee_cols = n;
756 }
757 if (ee_cols == 0) ee_cols = columns > 0 ? columns : 80;
758 }
759
760 return Scons(Sinteger(ee_rows), Sinteger(ee_cols > 1 && avoid_last_column ? ee_cols - 1 : ee_cols));
761 }
762
eeputc(tputsputcchar c)763 static int eeputc(tputsputcchar c) {
764 return putchar(c);
765 }
766
767 static struct termios orig_termios;
768
s_ee_raw(void)769 static void s_ee_raw(void) {
770 struct termios new_termios;
771 while (tcgetattr(STDIN_FD, &orig_termios) != 0) {
772 if (errno != EINTR) {
773 ptr msg = S_strerror(errno);
774 if (msg != Sfalse)
775 S_error1("expeditor", "error entering raw mode: ~a", msg);
776 else
777 S_error("expeditor", "error entering raw mode");
778 }
779 }
780 new_termios = orig_termios;
781
782 /* essentially want "stty raw -echo". the appropriate flags to accomplish
783 this were determined by studying the gnu/linux stty and termios man
784 pages, with particular attention to the cfmakeraw function. */
785 new_termios.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|INPCK|ISTRIP
786 |INLCR|IGNCR|ICRNL|IXON);
787 new_termios.c_oflag &= ~(OPOST);
788 new_termios.c_lflag &= ~(ISIG|ICANON|ECHO|IEXTEN);
789 new_termios.c_cflag &= ~(CSIZE|PARENB);
790 new_termios.c_cflag |= CS8;
791 new_termios.c_cc[VMIN] = 1;
792 new_termios.c_cc[VTIME] = 0;
793
794 while (tcsetattr(STDIN_FD, TCSADRAIN, &new_termios) != 0) {
795 if (errno != EINTR) {
796 ptr msg = S_strerror(errno);
797 if (msg != Sfalse)
798 S_error1("expeditor", "error entering raw mode: ~a", msg);
799 else
800 S_error("expeditor", "error entering raw mode");
801 }
802 }
803 }
804
s_ee_noraw(void)805 static void s_ee_noraw(void) {
806 while (tcsetattr(STDIN_FD, TCSADRAIN, &orig_termios) != 0) {
807 if (errno != EINTR) {
808 ptr msg = S_strerror(errno);
809 if (msg != Sfalse)
810 S_error1("expeditor", "error leaving raw mode: ~a", msg);
811 else
812 S_error("expeditor", "error leaving raw mode");
813 }
814 }
815 }
816
s_ee_enter_am_mode(void)817 static void s_ee_enter_am_mode(void) {
818 if (disable_auto_margin) {
819 tputs(enter_am_mode, 1, eeputc);
820 /* flush to minimize time span when automatic margins are disabled */
821 fflush(stdout);
822 } else if (eat_newline_glitch) {
823 /* hack: try to prevent terminal from eating subsequent cr or lf.
824 assumes we've just written to last column. probably works only
825 for vt100 interpretation of eat_newline_glitch/xn/xenl flag. */
826 tputs(cursor_left, 1, eeputc);
827 tputs(cursor_right, 1, eeputc);
828 }
829 }
830
s_ee_exit_am_mode(void)831 static void s_ee_exit_am_mode(void) {
832 if (disable_auto_margin) {
833 tputs(exit_am_mode, 1, eeputc);
834 }
835 }
836
s_ee_pause(void)837 static void s_ee_pause(void) { /* used to handle ^Z */
838 fflush(stdout);
839 kill(0, SIGTSTP);
840 }
841
s_ee_nanosleep(U32 secs,U32 nanosecs)842 static void s_ee_nanosleep(U32 secs, U32 nanosecs) {
843 struct timespec ts;
844 ts.tv_sec = secs;
845 ts.tv_nsec = nanosecs;
846 nanosleep(&ts, (struct timespec *)0);
847 }
848
s_ee_up(I32 n)849 static void s_ee_up(I32 n) {
850 while (n--) tputs(cursor_up, 1, eeputc);
851 }
852
s_ee_down(I32 n)853 static void s_ee_down(I32 n) {
854 while (n--) tputs(cursor_down, 1, eeputc);
855 }
856
s_ee_left(I32 n)857 static void s_ee_left(I32 n) {
858 while (n--) tputs(cursor_left, 1, eeputc);
859 }
860
s_ee_right(I32 n)861 static void s_ee_right(I32 n) {
862 while (n--) tputs(cursor_right, 1, eeputc);
863 }
864
s_ee_clear_eol(void)865 static void s_ee_clear_eol(void) {
866 tputs(clr_eol, 1, eeputc);
867 }
868
s_ee_clear_eos(void)869 static void s_ee_clear_eos(void) {
870 tputs(clr_eos, 1, eeputc);
871 }
872
s_ee_clear_screen(void)873 static void s_ee_clear_screen(void) {
874 tputs(clear_screen, 1, eeputc);
875 }
876
s_ee_scroll_reverse(I32 n)877 static void s_ee_scroll_reverse(I32 n) {
878 /* moving up from an entry that was only partially displayed,
879 scroll-reverse may be called when cursor isn't at the top line of
880 the screen, in which case we hope it will move up by one line.
881 in this case, which we have no way of distinguishing from the normal
882 case, scroll-reverse needs to clear the line explicitly */
883 while (n--) {
884 tputs(scroll_reverse, 1, eeputc);
885 tputs(clr_eol, 1, eeputc);
886 }
887 }
888
s_ee_bell(void)889 static void s_ee_bell(void) {
890 tputs(bell, 1, eeputc);
891 }
892
s_ee_carriage_return(void)893 static void s_ee_carriage_return(void) {
894 tputs(carriage_return, 1, eeputc);
895 }
896
897 /* move-line-down doesn't scroll the screen when performed on the last
898 line on the freebsd and openbsd consoles. the official way to scroll
899 the screen is to use scroll-forward (ind), but ind is defined only
900 at the bottom left corner of the screen, and we don't always know
901 where the bottom of the screen actually is. so we write a line-feed
902 (newline) character and hope that will do the job. */
s_ee_line_feed(void)903 static void s_ee_line_feed(void) {
904 putchar(0x0a);
905 }
906
907 #ifdef LIBX11
908 #include <dlfcn.h>
909 #include <X11/Xlib.h>
910 #include <X11/Xatom.h>
911 #include <sys/select.h>
912 #endif /* LIBX11 */
913
s_ee_get_clipboard(void)914 static ptr s_ee_get_clipboard(void) {
915 #ifdef LIBX11
916 static enum {UNINITIALIZED, INITIALIZED, FAILED} status = UNINITIALIZED;
917 static int (*pXConvertSelection)(Display *, Atom, Atom, Atom, Window, Time);
918 static int (*pXPending)(Display *display);
919 static int (*pXNextEvent)(Display *, XEvent *);
920 static int (*pXGetWindowProperty)(Display *, Window, Atom, long, long, Bool, Atom, Atom *, int *, unsigned long *, unsigned long *, unsigned char **);
921 static int (*pXFree)(void *);
922
923 static Display *D;
924 static Window R, W;
925 #endif /* LIBX11 */
926
927 ptr p = S_G.null_string;
928
929 #ifdef LIBX11
930 if (status == UNINITIALIZED) {
931 char *display_name;
932 void *handle;
933 Display *(*pXOpenDisplay)(char *);
934 Window (*pXDefaultRootWindow)(Display *);
935 Window (*pXCreateSimpleWindow)(Display *, Window, int, int, unsigned int, unsigned int, unsigned int, unsigned long, unsigned long);
936
937 status = (display_name = getenv("DISPLAY"))
938 && (handle = dlopen(LIBX11, RTLD_NOW))
939 && (pXOpenDisplay = (Display *(*)(char *display_name))dlsym(handle, "XOpenDisplay"))
940 && (pXDefaultRootWindow = (Window (*)(Display *))dlsym(handle, "XDefaultRootWindow"))
941 && (pXCreateSimpleWindow = (Window (*)(Display *, Window, int, int, unsigned int, unsigned int, unsigned int, unsigned long, unsigned long))dlsym(handle, "XCreateSimpleWindow"))
942 && (pXConvertSelection = (int (*)(Display *, Atom, Atom, Atom, Window, Time))dlsym(handle, "XConvertSelection"))
943 && (pXPending = (int (*)(Display *display))dlsym(handle, "XPending"))
944 && (pXNextEvent = (int (*)(Display *, XEvent *))dlsym(handle, "XNextEvent"))
945 && (pXGetWindowProperty = (int (*)(Display *, Window, Atom, long, long, Bool, Atom, Atom *, int *, unsigned long *, unsigned long *, unsigned char **))dlsym(handle, "XGetWindowProperty"))
946 && (pXFree = (int (*)(void *))dlsym(handle, "XFree"))
947 && (D = pXOpenDisplay(display_name))
948 && (R = pXDefaultRootWindow(D))
949 && (W = pXCreateSimpleWindow(D, R, 0, 0, 1, 1, 0, 0, 0))
950 ? INITIALIZED : FAILED;
951 }
952
953 if (status == INITIALIZED) {
954 XEvent XE;
955 Window W2; Atom P;
956 Atom type;
957 int format;
958 unsigned long items, bytes, ignore_bytes;
959 unsigned char *buf;
960 int timeout;
961
962 /* flush late arrivals from previous requests, if any */
963 while (pXPending(D)) pXNextEvent(D, &XE);
964
965 pXConvertSelection(D, XA_PRIMARY, XA_STRING, XA_STRING, W, CurrentTime);
966
967 /* mini event loop to catch response, if any */
968 timeout = 20; /* wait two seconds, 100ms at a time */
969 for (;;) {
970 if (pXPending(D)) {
971 pXNextEvent(D, &XE);
972 if (XE.type == SelectionNotify) {
973 if (XE.xselection.property == None) {
974 W2 = R;
975 P = XA_CUT_BUFFER0;
976 } else {
977 W2 = XE.xselection.requestor;
978 P = XE.xselection.property;
979 }
980
981 if (pXGetWindowProperty(D, W2, P, 0, 0, 0, AnyPropertyType,
982 &type, &format, &items, &bytes, &buf) == Success
983 && type == XA_STRING
984 && format == 8) {
985 pXFree(buf);
986 if (pXGetWindowProperty(D, W2, P, 0, bytes, 0, AnyPropertyType,
987 &type, &format, &items, &ignore_bytes, &buf) == Success
988 && type == XA_STRING
989 && format == 8) {
990 p = S_string((char *)buf, (iptr)bytes);
991 }
992 }
993
994 pXFree(buf);
995
996 break;
997 }
998 } else {
999 int xfd;
1000 fd_set rfds;
1001 struct timeval tv;
1002
1003 if (timeout == 0) break;
1004 xfd = ConnectionNumber(D);
1005 FD_ZERO(&rfds);
1006 FD_SET(xfd, &rfds);
1007 tv.tv_sec = 0;
1008 tv.tv_usec = 100*1000;
1009 select(xfd+1, &rfds, NULL, NULL, &tv);
1010 timeout -= 1;
1011 }
1012 }
1013 }
1014 #endif /* LIBX11 */
1015
1016 #ifdef MACOSX
1017 #define PBPASTEBUFSIZE 1024
1018 if (p == S_G.null_string) {
1019 char buf[PBPASTEBUFSIZE];
1020 FILE *f = popen("/usr/bin/pbpaste", "r");
1021 iptr i, n, m;
1022 char *s;
1023
1024 for (;;) {
1025 ptr newp;
1026 n = fread(buf, 1, PBPASTEBUFSIZE, f);
1027 if (n == 0) break;
1028 n += (m = Sstring_length(p));
1029 newp = S_string(NULL, n);
1030 for (i = 0; i != m; i += 1) Sstring_set(newp, i, Sstring_ref(p, i));
1031 for (s = buf; i != n; i += 1, s += 1)
1032 Sstring_set(newp, i, *s);
1033 p = newp;
1034 }
1035
1036 fclose(f);
1037 }
1038 #endif /* MACOSX */
1039
1040 return p;
1041 }
1042
s_ee_write_char(wchar_t wch)1043 static void s_ee_write_char(wchar_t wch) {
1044 locale_t old; char buf[MB_LEN_MAX]; size_t n;
1045
1046 old = uselocale(term_locale);
1047 n = wcrtomb(buf, wch, &term_in_mbs);
1048 if (n == (size_t)-1) {
1049 putchar('?');
1050 } else {
1051 fwrite(buf, 1, n, stdout);
1052 }
1053 uselocale(old);
1054 }
1055
1056 #endif /* WIN32 */
1057
s_ee_flush(void)1058 static void s_ee_flush(void) {
1059 fflush(stdout);
1060 }
1061
S_expeditor_init(void)1062 void S_expeditor_init(void) {
1063 Sforeign_symbol("(cs)ee_init_term", (void *)s_ee_init_term);
1064 Sforeign_symbol("(cs)ee_read_char", (void *)s_ee_read_char);
1065 Sforeign_symbol("(cs)ee_write_char", (void *)s_ee_write_char);
1066 Sforeign_symbol("(cs)ee_flush", (void *)s_ee_flush);
1067 Sforeign_symbol("(cs)ee_get_screen_size", (void *)s_ee_get_screen_size);
1068 Sforeign_symbol("(cs)ee_raw", (void *)s_ee_raw);
1069 Sforeign_symbol("(cs)ee_noraw", (void *)s_ee_noraw);
1070 Sforeign_symbol("(cs)ee_enter_am_mode", (void *)s_ee_enter_am_mode);
1071 Sforeign_symbol("(cs)ee_exit_am_mode", (void *)s_ee_exit_am_mode);
1072 Sforeign_symbol("(cs)ee_pause", (void *)s_ee_pause);
1073 Sforeign_symbol("(cs)ee_nanosleep", (void *)s_ee_nanosleep);
1074 Sforeign_symbol("(cs)ee_get_clipboard", (void *)s_ee_get_clipboard);
1075 Sforeign_symbol("(cs)ee_up", (void *)s_ee_up);
1076 Sforeign_symbol("(cs)ee_down", (void *)s_ee_down);
1077 Sforeign_symbol("(cs)ee_left", (void *)s_ee_left);
1078 Sforeign_symbol("(cs)ee_right", (void *)s_ee_right);
1079 Sforeign_symbol("(cs)ee_clr_eol", (void *)s_ee_clear_eol);
1080 Sforeign_symbol("(cs)ee_clr_eos", (void *)s_ee_clear_eos);
1081 Sforeign_symbol("(cs)ee_clear_screen", (void *)s_ee_clear_screen);
1082 Sforeign_symbol("(cs)ee_scroll_reverse", (void *)s_ee_scroll_reverse);
1083 Sforeign_symbol("(cs)ee_bell", (void *)s_ee_bell);
1084 Sforeign_symbol("(cs)ee_carriage_return", (void *)s_ee_carriage_return);
1085 Sforeign_symbol("(cs)ee_line_feed", (void *)s_ee_line_feed);
1086 }
1087
1088 #endif /* FEATURE_EXPEDITOR */
1089