2008-08-22 Robert Jordan <robertj@gmx.net>
[mono.git] / mono / metadata / console-io.c
1
2 /*
3  * console-io.c: ConsoleDriver internal calls
4  *
5  * Author:
6  *      Gonzalo Paniagua Javier (gonzalo@ximian.com)
7  *
8  * Copyright (C) 2005 Novell, Inc. (http://www.novell.com)
9  */
10
11 #include <config.h>
12 #include <glib.h>
13 #include <stdio.h>
14 #include <string.h>
15 #include <errno.h>
16 #include <signal.h>
17 #ifdef HAVE_SYS_TIME_H
18 #include <sys/time.h>
19 #endif
20 #include <sys/types.h>
21 #ifdef HAVE_UNISTD_H
22 #include <unistd.h>
23 #endif
24 #include <mono/metadata/appdomain.h>
25 #include <mono/metadata/object-internals.h>
26 #include <mono/metadata/class-internals.h>
27 #include <mono/metadata/domain-internals.h>
28 #include <mono/metadata/metadata.h>
29 #include <mono/metadata/threadpool.h>
30 /* On solaris, curses.h must come before both termios.h and term.h */
31 #ifdef HAVE_CURSES_H
32 #include <curses.h>
33 #endif
34 #ifdef HAVE_TERMIOS_H
35 #include <termios.h>
36 #endif
37 #ifdef HAVE_TERM_H
38 #include <term.h>
39 #endif
40 /* Needed for FIONREAD under solaris */
41 #ifdef HAVE_SYS_FILIO_H
42 #include <sys/filio.h>
43 #endif
44 #ifndef PLATFORM_WIN32
45 #ifndef TIOCGWINSZ
46 #include <sys/ioctl.h>
47 #endif
48 #endif
49
50 #include <mono/metadata/console-io.h>
51 #include <mono/metadata/exception.h>
52
53 static gboolean setup_finished;
54 static gboolean atexit_called;
55
56 /* The string used to return the terminal to its previous state */
57 static gchar *teardown_str;
58
59 /* The string used to set the terminal into keypad xmit mode after SIGCONT is received */
60 static gchar *keypad_xmit_str;
61
62 #ifdef HAVE_TERMIOS_H
63 /* This is the last state used by Mono, used after a CONT signal is received */
64 static struct termios mono_attr;
65 #endif
66
67 #ifdef PLATFORM_WIN32
68 MonoBoolean
69 ves_icall_System_ConsoleDriver_Isatty (HANDLE handle)
70 {
71         MONO_ARCH_SAVE_REGS;
72
73         return (GetFileType (handle) == FILE_TYPE_CHAR);
74 }
75
76 MonoBoolean
77 ves_icall_System_ConsoleDriver_SetEcho (MonoBoolean want_echo)
78 {
79         return FALSE;
80 }
81
82 MonoBoolean
83 ves_icall_System_ConsoleDriver_SetBreak (MonoBoolean want_break)
84 {
85         return FALSE;
86 }
87
88 gint32
89 ves_icall_System_ConsoleDriver_InternalKeyAvailable (gint32 timeout)
90 {
91         return FALSE;
92 }
93
94 MonoBoolean
95 ves_icall_System_ConsoleDriver_TtySetup (MonoString *keypad, MonoString *teardown, char *verase, char *vsusp, char *intr)
96 {
97         return FALSE;
98 }
99
100 MonoBoolean
101 ves_icall_System_ConsoleDriver_GetTtySize (HANDLE handle, gint32 *width, gint32 *height)
102 {
103         return FALSE;
104 }
105
106 #else
107 static struct termios initial_attr;
108
109 MonoBoolean
110 ves_icall_System_ConsoleDriver_Isatty (HANDLE handle)
111 {
112         MONO_ARCH_SAVE_REGS;
113
114         return isatty (GPOINTER_TO_INT (handle));
115 }
116
117 static MonoBoolean
118 set_property (gint property, gboolean value)
119 {
120         struct termios attr;
121         gboolean callset = FALSE;
122         gboolean check;
123         
124         MONO_ARCH_SAVE_REGS;
125
126         if (tcgetattr (STDIN_FILENO, &attr) == -1)
127                 return FALSE;
128
129         check = (attr.c_lflag & property) != 0;
130         if ((value || check) && !(value && check)) {
131                 callset = TRUE;
132                 if (value)
133                         attr.c_lflag |= property;
134                 else
135                         attr.c_lflag &= ~property;
136         }
137
138         if (!callset)
139                 return TRUE;
140
141         if (tcsetattr (STDIN_FILENO, TCSANOW, &attr) == -1)
142                 return FALSE;
143
144         mono_attr = attr;
145         return TRUE;
146 }
147
148 MonoBoolean
149 ves_icall_System_ConsoleDriver_SetEcho (MonoBoolean want_echo)
150 {
151         
152         return set_property (ECHO, want_echo);
153 }
154
155 MonoBoolean
156 ves_icall_System_ConsoleDriver_SetBreak (MonoBoolean want_break)
157 {
158         return set_property (IGNBRK, !want_break);
159 }
160
161 gint32
162 ves_icall_System_ConsoleDriver_InternalKeyAvailable (gint32 timeout)
163 {
164         fd_set rfds;
165         struct timeval tv;
166         struct timeval *tvptr;
167         div_t divvy;
168         int ret, nbytes;
169
170         MONO_ARCH_SAVE_REGS;
171
172         do {
173                 FD_ZERO (&rfds);
174                 FD_SET (STDIN_FILENO, &rfds);
175                 if (timeout >= 0) {
176                         divvy = div (timeout, 1000);
177                         tv.tv_sec = divvy.quot;
178                         tv.tv_usec = divvy.rem;
179                         tvptr = &tv;
180                 } else {
181                         tvptr = NULL;
182                 }
183                 ret = select (STDIN_FILENO + 1, &rfds, NULL, NULL, tvptr);
184         } while (ret == -1 && errno == EINTR);
185
186         if (ret > 0) {
187                 nbytes = 0;
188                 ret = ioctl (STDIN_FILENO, FIONREAD, &nbytes);
189                 if (ret >= 0)
190                         ret = nbytes;
191         }
192
193         return (ret > 0) ? ret : 0;
194 }
195
196 static void
197 tty_teardown (void)
198 {
199         MONO_ARCH_SAVE_REGS;
200
201         if (!setup_finished)
202                 return;
203
204         if (teardown_str != NULL) {
205                 write (STDOUT_FILENO, teardown_str, strlen (teardown_str));
206                 g_free (teardown_str);
207                 teardown_str = NULL;
208         }
209
210         tcflush (STDIN_FILENO, TCIFLUSH);
211         tcsetattr (STDIN_FILENO, TCSANOW, &initial_attr);
212         set_property (ECHO, TRUE);
213         setup_finished = FALSE;
214 }
215
216 static void
217 do_console_cancel_event (void)
218 {
219         static MonoClassField *cancel_handler_field;
220         MonoDomain *domain = mono_domain_get ();
221         MonoClass *klass;
222         MonoDelegate *load_value;
223         MonoMethod *method;
224         MonoMethodMessage *msg;
225         MonoMethod *im;
226
227         if (!domain->domain)
228                 return;
229
230         klass = mono_class_from_name (mono_defaults.corlib, "System", "Console");
231         if (klass == NULL)
232                 return;
233
234         if (cancel_handler_field == NULL) {
235                 cancel_handler_field = mono_class_get_field_from_name (klass, "cancel_handler");
236                 g_assert (cancel_handler_field);
237         }
238
239         mono_field_static_get_value (mono_class_vtable (domain, klass), cancel_handler_field, &load_value);
240         if (load_value == NULL)
241                 return;
242
243         klass = load_value->object.vtable->klass;
244         method = mono_class_get_method_from_name (klass, "BeginInvoke", -1);
245         g_assert (method != NULL);
246         im = mono_get_delegate_invoke (method->klass);
247         msg = mono_method_call_message_new (method, NULL, im, NULL, NULL);
248         mono_thread_pool_add ((MonoObject *) load_value, msg, NULL, NULL);
249 }
250
251 static gboolean in_sigint;
252 static void
253 sigint_handler (int signo)
254 {
255         MONO_ARCH_SAVE_REGS;
256
257         if (in_sigint)
258                 return;
259
260         in_sigint = TRUE;
261         do_console_cancel_event ();
262         in_sigint = FALSE;
263 }
264
265 static void
266 sigcont_handler (int signo)
267 {
268         // Ignore error, there is not much we can do in the sigcont handler.
269         tcsetattr (STDIN_FILENO, TCSANOW, &mono_attr);
270
271         if (keypad_xmit_str != NULL)
272                 write (STDOUT_FILENO, keypad_xmit_str, strlen (keypad_xmit_str));
273 }
274
275 static struct sigaction save_sigcont, save_sigint;
276
277 void
278 console_set_signal_handlers ()
279 {
280         struct sigaction sigcont, sigint;
281
282         // Continuing
283         sigcont.sa_handler = sigcont_handler;
284         sigcont.sa_flags = 0;
285         sigemptyset (&sigcont.sa_mask);
286         sigaction (SIGCONT, &sigcont, &save_sigcont);
287         
288         // Interrupt handler
289         sigint.sa_handler = sigint_handler;
290         sigint.sa_flags = 0;
291         sigemptyset (&sigint.sa_mask);
292         sigaction (SIGINT, &sigint, &save_sigint);
293 }
294
295 //
296 // Currently unused, should we ever call the restore handler?
297 // Perhaps before calling into Process.Start?
298 //
299 void
300 console_restore_signal_handlers ()
301 {
302         sigaction (SIGCONT, &save_sigcont, NULL);
303         sigaction (SIGINT, &save_sigint, NULL);
304 }
305
306 MonoBoolean
307 ves_icall_System_ConsoleDriver_TtySetup (MonoString *keypad, MonoString *teardown, char *verase, char *vsusp, char*intr)
308 {
309         MONO_ARCH_SAVE_REGS;
310
311         *verase = '\0';
312         *vsusp = '\0';
313         *intr = '\0';
314         if (tcgetattr (STDIN_FILENO, &initial_attr) == -1)
315                 return FALSE;
316
317         mono_attr = initial_attr;
318         mono_attr.c_lflag &= ~ICANON;
319         mono_attr.c_cc [VMIN] = 1;
320         mono_attr.c_cc [VTIME] = 0;
321         if (tcsetattr (STDIN_FILENO, TCSANOW, &mono_attr) == -1)
322                 return FALSE;
323
324         *verase = initial_attr.c_cc [VERASE];
325         *vsusp = initial_attr.c_cc [VSUSP];
326         *intr = initial_attr.c_cc [VINTR];
327         /* If initialized from another appdomain... */
328         if (setup_finished)
329                 return TRUE;
330
331         keypad_xmit_str = keypad != NULL ? mono_string_to_utf8 (keypad) : NULL;
332         
333         console_set_signal_handlers ();
334         setup_finished = TRUE;
335         if (!atexit_called) {
336                 if (teardown != NULL)
337                         teardown_str = mono_string_to_utf8 (teardown);
338
339                 atexit (tty_teardown);
340         }
341
342         return TRUE;
343 }
344
345 MonoBoolean
346 ves_icall_System_ConsoleDriver_GetTtySize (HANDLE handle, gint32 *width, gint32 *height)
347 {
348 #ifdef TIOCGWINSZ
349         struct winsize ws;
350         int res;
351
352         MONO_ARCH_SAVE_REGS;
353
354         res = ioctl (GPOINTER_TO_INT (handle), TIOCGWINSZ, &ws);
355
356         if (!res) {
357                 *width = ws.ws_col;
358                 *height = ws.ws_row;
359                 return TRUE;
360         }
361         else
362                 return FALSE;
363 #else
364         return FALSE;
365 #endif
366 }
367
368 #endif /* !PLATFORM_WIN32 */