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