1 /*
2  *
3  * CLEX File Manager
4  *
5  * Copyright (C) 2001-2018 Vlado Potisk <vlado_potisk@clex.sk>
6  *
7  * CLEX is free software without warranty of any kind; see the
8  * GNU General Public License as set out in the "COPYING" document
9  * which accompanies the CLEX File Manager package.
10  *
11  * CLEX can be downloaded from http://www.clex.sk
12  *
13  */
14 
15 #include "clexheaders.h"
16 
17 #include <ctype.h>		/* tolower() */
18 #include <errno.h>		/* errno */
19 #include <fcntl.h>		/* fcntl */
20 #include <stdio.h>		/* fputs() */
21 #include <signal.h>		/* SIGTTIN */
22 #include <termios.h>	/* struct termios */
23 #include <unistd.h>		/* STDIN_FILENO */
24 
25 #include "tty.h"
26 
27 #include "control.h"	/* err_exit() */
28 
29 static struct termios *p_raw, *p_text = 0, *p_save = 0;
30 
31 #ifdef _POSIX_JOB_CONTROL
32 static pid_t save_pgid = 0;
33 #endif
34 
35 void
jc_initialize(void)36 jc_initialize(void)
37 {
38 #ifdef _POSIX_JOB_CONTROL
39 	struct sigaction act;
40 
41 	/* Wait until we are in the foreground */
42 	while (tcgetpgrp(STDIN_FILENO) != (save_pgid = getpgrp()))
43 		kill(-save_pgid,SIGTTIN);
44 
45 	/* ignore job control signals */
46 	act.sa_handler = SIG_IGN;
47 	act.sa_flags = 0;
48 	sigemptyset(&act.sa_mask);
49 	sigaction(SIGTSTP,&act,0);
50 	sigaction(SIGTTIN,&act,0);
51 	sigaction(SIGTTOU,&act,0);
52 
53 	/* put CLEX into its own process group */
54 	setpgid(clex_data.pid,clex_data.pid);
55 	/* make it the foreground process group */
56 	tcsetpgrp(STDIN_FILENO,clex_data.pid);
57 #endif
58 }
59 
60 /* this is a cleanup function (see err_exit() in control.c) */
61 void
jc_reset(void)62 jc_reset(void)
63 {
64 #ifdef _POSIX_JOB_CONTROL
65 	if (save_pgid)
66 		tcsetpgrp(STDIN_FILENO,save_pgid);
67 #endif
68 }
69 
70 void
tty_initialize(void)71 tty_initialize(void)
72 {
73 	static struct termios text, raw;
74 
75 	if (!isatty(STDIN_FILENO))
76 		err_exit("This is an interactive program, but the standard input is not a terminal");
77 
78 	if (tcgetattr(STDIN_FILENO,&text) < 0)
79 		err_exit("Cannot read the terminal parameters");
80 
81 	raw = text;		/* struct copy */
82 	raw.c_lflag &= ~(ECHO | ICANON | ISIG | IEXTEN);
83 	raw.c_cc[VMIN] = 1;
84 	raw.c_cc[VTIME] = 0;
85 	p_text = &text;
86 	p_raw  = &raw;
87 }
88 
89 void
tty_save()90 tty_save()
91 {
92 	static struct termios save;
93 
94 	/* errors are silently ignored */
95 	p_save = tcgetattr(STDIN_FILENO,&save) == 0 ? &save : 0;
96 }
97 
98 void
tty_restore(void)99 tty_restore(void)
100 {
101 	if (p_save)
102 		tcsetattr(STDIN_FILENO,TCSAFLUSH,p_save);
103 }
104 
105 /*
106  * make sure interrupt key is ctrl-C
107  * usage: tty_save(); tty_ctrlc();
108  *          install-SIGINT-handler
109  *            do-stuff
110  *          disable-SIGINT
111  *        tty_restore();
112  */
113 void
tty_ctrlc()114 tty_ctrlc()
115 {
116 	struct termios ctrlc;
117 
118 	if (p_save) {
119 		ctrlc = *p_save;
120 		ctrlc.c_cc[VINTR] = CH_CTRL('C');
121 		tcsetattr(STDIN_FILENO,TCSAFLUSH,&ctrlc);
122 	}
123 }
124 
125 /* noncanonical, no echo */
126 void
tty_setraw(void)127 tty_setraw(void)
128 {
129 	if (p_raw)
130 		tcsetattr(STDIN_FILENO,TCSAFLUSH,p_raw);
131 }
132 
133 /* this is a cleanup function (see err_exit() in control.c) */
134 void
tty_reset(void)135 tty_reset(void)
136 {
137 	if (p_text)
138 		tcsetattr(STDIN_FILENO,TCSAFLUSH,p_text);
139 }
140 
141 static int
tty_getchar(void)142 tty_getchar(void)
143 {
144 	int in, flags, loops = 0;
145 
146 	while ((in = getchar()) == EOF) {
147 		if (errno == EINTR)
148 			continue;
149 		if (errno == EAGAIN) {
150 			flags = fcntl(STDIN_FILENO,F_GETFL);
151 			if ((flags & O_NONBLOCK) == O_NONBLOCK) {
152 				/* clear the non-blocking flag */
153 				fcntl(STDIN_FILENO,F_SETFL,flags & ~O_NONBLOCK);
154 				continue;
155 			}
156 		}
157 		if (++loops >= 3)
158 			err_exit("Cannot read from standard input");
159 	}
160 	return in;
161 }
162 
163 void
tty_press_enter(void)164 tty_press_enter(void)
165 {
166 	int in;
167 
168 	if (disp_data.noenter) {
169 		fputs("Returning to CLEX.",stdout);
170 		disp_data.noenter = 0;
171 	}
172 	else {
173 		fputs("Press <enter> to continue. ",stdout);
174 		fflush(stdout);
175 		tty_setraw();
176 		while ((in = tty_getchar()) != '\n' && in != '\r')
177 			;
178 		tty_reset();
179 	}
180 
181 	puts("\n----------------------------------------------");
182 	fflush(stdout);
183 	disp_data.wait = 0;
184 }
185 
186 /*
187  * - if 'yeschar' is set, msg is a yes/no question where 'yeschar' (in lower or upper case)
188  *   means confirmation ('yeschar' parameter itself should be entered in lower case)
189  */
190 int
tty_dialog(int yeschar,const char * msg)191 tty_dialog(int yeschar, const char *msg)
192 {
193 	int code;
194 
195 	putchar('\n');
196 	fputs(msg,stdout);
197 	if (yeschar)
198 		fprintf(stdout," (%c = %s) ",yeschar,"yes");
199 	fflush(stdout);
200 	tty_setraw();
201 	code = tolower(tty_getchar());
202 	tty_reset();
203 	if (yeschar) {
204 		code = code == yeschar;
205 		puts(code ? "yes" : "no");
206 	}
207 	putchar('\n');
208 	fflush(stdout);
209 	return code;
210 }
211