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