C · 15369 bytes Raw Blame History
1 // C wrapper for terminal functions to enable raw mode in Fortran
2 // Platform-independent implementation for Unix and Windows
3
4 #ifdef _WIN32
5 // Windows implementation
6 #include <windows.h>
7 #include <conio.h>
8 #include <stdio.h>
9 #include <string.h>
10
11 static HANDLE hStdin = INVALID_HANDLE_VALUE;
12 static HANDLE hStdout = INVALID_HANDLE_VALUE;
13 static DWORD orig_stdin_mode = 0;
14 static DWORD orig_stdout_mode = 0;
15 static int raw_mode_enabled = 0;
16
17 // Input buffer for batching reads
18 #define INPUT_BUFFER_SIZE 256
19 static unsigned char input_buffer[INPUT_BUFFER_SIZE];
20 static int buffer_start = 0;
21 static int buffer_end = 0;
22
23 // Flush any pending input from stdin
24 static void flush_input(void) {
25 FlushConsoleInputBuffer(hStdin);
26 buffer_start = buffer_end = 0;
27 }
28
29 // Enable raw mode - returns 0 on success, -1 on failure
30 int enable_raw_mode(void) {
31 if (raw_mode_enabled) return 0;
32
33 hStdin = GetStdHandle(STD_INPUT_HANDLE);
34 hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
35
36 if (hStdin == INVALID_HANDLE_VALUE || hStdout == INVALID_HANDLE_VALUE) {
37 return -1;
38 }
39
40 // Save original console modes
41 if (!GetConsoleMode(hStdin, &orig_stdin_mode)) {
42 return -1;
43 }
44 if (!GetConsoleMode(hStdout, &orig_stdout_mode)) {
45 return -1;
46 }
47
48 // Set input mode: disable line input, echo, and processed input
49 DWORD new_stdin_mode = orig_stdin_mode;
50 new_stdin_mode &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
51 new_stdin_mode |= ENABLE_VIRTUAL_TERMINAL_INPUT; // Enable VT input sequences
52
53 if (!SetConsoleMode(hStdin, new_stdin_mode)) {
54 // Try without VT input (older Windows)
55 new_stdin_mode &= ~ENABLE_VIRTUAL_TERMINAL_INPUT;
56 if (!SetConsoleMode(hStdin, new_stdin_mode)) {
57 return -1;
58 }
59 }
60
61 // Enable VT processing for output (ANSI escape codes)
62 DWORD new_stdout_mode = orig_stdout_mode;
63 new_stdout_mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
64
65 if (!SetConsoleMode(hStdout, new_stdout_mode)) {
66 // Try with just VT processing
67 new_stdout_mode = orig_stdout_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
68 if (!SetConsoleMode(hStdout, new_stdout_mode)) {
69 // Restore stdin and fail
70 SetConsoleMode(hStdin, orig_stdin_mode);
71 return -1;
72 }
73 }
74
75 raw_mode_enabled = 1;
76 buffer_start = buffer_end = 0;
77 flush_input();
78
79 return 0;
80 }
81
82 // Disable raw mode - returns 0 on success, -1 on failure
83 int disable_raw_mode(void) {
84 if (!raw_mode_enabled) return 0;
85
86 int result = 0;
87 if (!SetConsoleMode(hStdin, orig_stdin_mode)) {
88 result = -1;
89 }
90 if (!SetConsoleMode(hStdout, orig_stdout_mode)) {
91 result = -1;
92 }
93
94 raw_mode_enabled = 0;
95 buffer_start = buffer_end = 0;
96 return result;
97 }
98
99 // Check if input is available (non-blocking)
100 int input_available(void) {
101 if (buffer_start < buffer_end) {
102 return 1;
103 }
104
105 DWORD num_events = 0;
106 if (!GetNumberOfConsoleInputEvents(hStdin, &num_events)) {
107 return 0;
108 }
109
110 if (num_events == 0) return 0;
111
112 // Peek to see if there's actually a key event
113 INPUT_RECORD ir[16];
114 DWORD events_read = 0;
115 if (!PeekConsoleInput(hStdin, ir, 16, &events_read)) {
116 return 0;
117 }
118
119 for (DWORD i = 0; i < events_read; i++) {
120 if (ir[i].EventType == KEY_EVENT && ir[i].Event.KeyEvent.bKeyDown) {
121 return 1;
122 }
123 }
124
125 return 0;
126 }
127
128 // Get count of available input bytes
129 int input_available_count(void) {
130 int buffered = buffer_end - buffer_start;
131 DWORD num_events = 0;
132 GetNumberOfConsoleInputEvents(hStdin, &num_events);
133 return buffered + (int)num_events;
134 }
135
136 // Read a single character from console
137 static int read_console_char(int timeout_ms) {
138 DWORD wait_result;
139
140 if (timeout_ms < 0) {
141 wait_result = WaitForSingleObject(hStdin, INFINITE);
142 } else {
143 wait_result = WaitForSingleObject(hStdin, (DWORD)timeout_ms);
144 }
145
146 if (wait_result != WAIT_OBJECT_0) {
147 return -1; // Timeout or error
148 }
149
150 INPUT_RECORD ir;
151 DWORD events_read;
152
153 while (ReadConsoleInput(hStdin, &ir, 1, &events_read) && events_read > 0) {
154 if (ir.EventType == KEY_EVENT && ir.Event.KeyEvent.bKeyDown) {
155 KEY_EVENT_RECORD *key = &ir.Event.KeyEvent;
156
157 // Handle special keys by generating escape sequences
158 if (key->wVirtualKeyCode == VK_UP) {
159 input_buffer[buffer_end++] = 27; // ESC
160 input_buffer[buffer_end++] = '[';
161 input_buffer[buffer_end++] = 'A';
162 return input_buffer[buffer_start++];
163 } else if (key->wVirtualKeyCode == VK_DOWN) {
164 input_buffer[buffer_end++] = 27;
165 input_buffer[buffer_end++] = '[';
166 input_buffer[buffer_end++] = 'B';
167 return input_buffer[buffer_start++];
168 } else if (key->wVirtualKeyCode == VK_RIGHT) {
169 input_buffer[buffer_end++] = 27;
170 input_buffer[buffer_end++] = '[';
171 input_buffer[buffer_end++] = 'C';
172 return input_buffer[buffer_start++];
173 } else if (key->wVirtualKeyCode == VK_LEFT) {
174 input_buffer[buffer_end++] = 27;
175 input_buffer[buffer_end++] = '[';
176 input_buffer[buffer_end++] = 'D';
177 return input_buffer[buffer_start++];
178 } else if (key->wVirtualKeyCode == VK_HOME) {
179 input_buffer[buffer_end++] = 27;
180 input_buffer[buffer_end++] = '[';
181 input_buffer[buffer_end++] = 'H';
182 return input_buffer[buffer_start++];
183 } else if (key->wVirtualKeyCode == VK_END) {
184 input_buffer[buffer_end++] = 27;
185 input_buffer[buffer_end++] = '[';
186 input_buffer[buffer_end++] = 'F';
187 return input_buffer[buffer_start++];
188 } else if (key->wVirtualKeyCode == VK_DELETE) {
189 input_buffer[buffer_end++] = 27;
190 input_buffer[buffer_end++] = '[';
191 input_buffer[buffer_end++] = '3';
192 input_buffer[buffer_end++] = '~';
193 return input_buffer[buffer_start++];
194 } else if (key->wVirtualKeyCode == VK_PRIOR) { // Page Up
195 input_buffer[buffer_end++] = 27;
196 input_buffer[buffer_end++] = '[';
197 input_buffer[buffer_end++] = '5';
198 input_buffer[buffer_end++] = '~';
199 return input_buffer[buffer_start++];
200 } else if (key->wVirtualKeyCode == VK_NEXT) { // Page Down
201 input_buffer[buffer_end++] = 27;
202 input_buffer[buffer_end++] = '[';
203 input_buffer[buffer_end++] = '6';
204 input_buffer[buffer_end++] = '~';
205 return input_buffer[buffer_start++];
206 } else if (key->wVirtualKeyCode == VK_INSERT) {
207 input_buffer[buffer_end++] = 27;
208 input_buffer[buffer_end++] = '[';
209 input_buffer[buffer_end++] = '2';
210 input_buffer[buffer_end++] = '~';
211 return input_buffer[buffer_start++];
212 } else if (key->wVirtualKeyCode >= VK_F1 && key->wVirtualKeyCode <= VK_F12) {
213 // F1-F12 keys
214 int fnum = key->wVirtualKeyCode - VK_F1 + 1;
215 input_buffer[buffer_end++] = 27;
216 input_buffer[buffer_end++] = 'O';
217 input_buffer[buffer_end++] = 'P' + (fnum - 1); // Simplified
218 return input_buffer[buffer_start++];
219 } else if (key->uChar.AsciiChar != 0) {
220 // Regular ASCII character
221 return (unsigned char)key->uChar.AsciiChar;
222 }
223 }
224 }
225
226 return -1;
227 }
228
229 // Fill the input buffer
230 static void fill_input_buffer(void) {
231 if (buffer_start > 0 && buffer_start < buffer_end) {
232 memmove(input_buffer, input_buffer + buffer_start, buffer_end - buffer_start);
233 buffer_end -= buffer_start;
234 buffer_start = 0;
235 } else if (buffer_start >= buffer_end) {
236 buffer_start = buffer_end = 0;
237 }
238 }
239
240 // Read a single character with smart timeout
241 int read_char_timeout(void) {
242 if (buffer_start < buffer_end) {
243 return input_buffer[buffer_start++];
244 }
245
246 fill_input_buffer();
247 if (buffer_start < buffer_end) {
248 return input_buffer[buffer_start++];
249 }
250
251 return read_console_char(50); // 50ms timeout
252 }
253
254 // Read a character with very short timeout (for escape sequences)
255 int read_char_escape(void) {
256 if (buffer_start < buffer_end) {
257 return input_buffer[buffer_start++];
258 }
259
260 fill_input_buffer();
261 if (buffer_start < buffer_end) {
262 return input_buffer[buffer_start++];
263 }
264
265 return read_console_char(5); // 5ms timeout
266 }
267
268 // Public function to flush input buffer
269 void flush_input_buffer(void) {
270 flush_input();
271 }
272
273 // Get terminal size
274 void get_terminal_size(int *rows, int *cols) {
275 CONSOLE_SCREEN_BUFFER_INFO csbi;
276 if (GetConsoleScreenBufferInfo(hStdout, &csbi)) {
277 *cols = csbi.srWindow.Right - csbi.srWindow.Left + 1;
278 *rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
279 } else {
280 *rows = 24;
281 *cols = 80;
282 }
283 }
284
285 #else
286 // Unix implementation (original code)
287 #include <termios.h>
288 #include <unistd.h>
289 #include <stdlib.h>
290 #include <stdio.h>
291 #include <errno.h>
292 #include <sys/ioctl.h>
293 #include <sys/select.h>
294 #include <string.h>
295
296 static struct termios orig_termios;
297 static int raw_mode_enabled = 0;
298
299 // Input buffer for batching reads
300 #define INPUT_BUFFER_SIZE 256
301 static unsigned char input_buffer[INPUT_BUFFER_SIZE];
302 static int buffer_start = 0;
303 static int buffer_end = 0;
304
305 // Flush any pending input from stdin
306 static void flush_input(void) {
307 // Discard any pending input data
308 tcflush(STDIN_FILENO, TCIFLUSH);
309
310 // Also drain our internal buffer
311 buffer_start = buffer_end = 0;
312
313 // Small delay to let any in-flight data arrive and be discarded
314 struct timeval tv = {0, 10000}; // 10ms
315 fd_set readfds;
316 FD_ZERO(&readfds);
317 FD_SET(STDIN_FILENO, &readfds);
318 while (select(STDIN_FILENO + 1, &readfds, NULL, NULL, &tv) > 0) {
319 char discard[256];
320 read(STDIN_FILENO, discard, sizeof(discard));
321 tv.tv_sec = 0;
322 tv.tv_usec = 5000; // Keep draining with shorter timeout
323 FD_ZERO(&readfds);
324 FD_SET(STDIN_FILENO, &readfds);
325 }
326 }
327
328 // Enable raw mode - returns 0 on success, -1 on failure
329 int enable_raw_mode(void) {
330 if (raw_mode_enabled) return 0;
331
332 if (tcgetattr(STDIN_FILENO, &orig_termios) == -1) {
333 return -1;
334 }
335
336 struct termios raw = orig_termios;
337
338 // Input flags: disable break, CR to NL, parity check, strip char, start/stop output control
339 raw.c_iflag &= ~(tcflag_t)(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
340
341 // Output flags: disable post processing
342 raw.c_oflag &= ~(tcflag_t)(OPOST);
343
344 // Control flags: set 8 bit chars
345 raw.c_cflag |= (CS8);
346
347 // Local flags: disable canonical mode, echo, signals, extended input processing
348 raw.c_lflag &= ~(tcflag_t)(ECHO | ICANON | ISIG | IEXTEN);
349
350 // Control characters: non-blocking reads
351 // We'll use select() for timeout management instead of VTIME
352 raw.c_cc[VMIN] = 0; // Don't block
353 raw.c_cc[VTIME] = 0; // No timeout - we use select() instead
354
355 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {
356 return -1;
357 }
358
359 raw_mode_enabled = 1;
360 buffer_start = buffer_end = 0;
361
362 // Flush any stale input that might be waiting
363 flush_input();
364
365 return 0;
366 }
367
368 // Disable raw mode - returns 0 on success, -1 on failure
369 int disable_raw_mode(void) {
370 if (!raw_mode_enabled) return 0;
371
372 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1) {
373 return -1;
374 }
375
376 raw_mode_enabled = 0;
377 buffer_start = buffer_end = 0;
378 return 0;
379 }
380
381 // Check if input is available (non-blocking)
382 int input_available(void) {
383 // First check our buffer
384 if (buffer_start < buffer_end) {
385 return 1;
386 }
387
388 // Then check stdin
389 int nread;
390 if (ioctl(STDIN_FILENO, FIONREAD, &nread) == -1) {
391 return 0;
392 }
393 return nread > 0;
394 }
395
396 // Get count of available input bytes
397 int input_available_count(void) {
398 int buffered = buffer_end - buffer_start;
399 int pending = 0;
400 if (ioctl(STDIN_FILENO, FIONREAD, &pending) == -1) {
401 pending = 0;
402 }
403 return buffered + pending;
404 }
405
406 // Fill the input buffer with all available data
407 static void fill_input_buffer(void) {
408 // Shift remaining data to start of buffer
409 if (buffer_start > 0 && buffer_start < buffer_end) {
410 memmove(input_buffer, input_buffer + buffer_start, buffer_end - buffer_start);
411 buffer_end -= buffer_start;
412 buffer_start = 0;
413 } else if (buffer_start >= buffer_end) {
414 buffer_start = buffer_end = 0;
415 }
416
417 // Read all available data into buffer
418 int space = INPUT_BUFFER_SIZE - buffer_end;
419 if (space > 0) {
420 ssize_t nread = read(STDIN_FILENO, input_buffer + buffer_end, space);
421 if (nread > 0) {
422 buffer_end += nread;
423 }
424 }
425 }
426
427 // Read a single character with smart timeout
428 int read_char_timeout(void) {
429 // Return from buffer if available
430 if (buffer_start < buffer_end) {
431 return input_buffer[buffer_start++];
432 }
433
434 // Try to fill buffer
435 fill_input_buffer();
436 if (buffer_start < buffer_end) {
437 return input_buffer[buffer_start++];
438 }
439
440 // No data available - wait with select()
441 fd_set readfds;
442 struct timeval tv;
443
444 FD_ZERO(&readfds);
445 FD_SET(STDIN_FILENO, &readfds);
446 tv.tv_sec = 0;
447 tv.tv_usec = 50000; // 50ms
448
449 int ret = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &tv);
450 if (ret > 0) {
451 fill_input_buffer();
452 if (buffer_start < buffer_end) {
453 return input_buffer[buffer_start++];
454 }
455 }
456
457 return -1; // No input
458 }
459
460 // Read a character with very short timeout (for escape sequences)
461 int read_char_escape(void) {
462 // Return from buffer if available
463 if (buffer_start < buffer_end) {
464 return input_buffer[buffer_start++];
465 }
466
467 // Try immediate read first
468 fill_input_buffer();
469 if (buffer_start < buffer_end) {
470 return input_buffer[buffer_start++];
471 }
472
473 // Short wait for escape sequence continuation (5ms)
474 fd_set readfds;
475 struct timeval tv;
476
477 FD_ZERO(&readfds);
478 FD_SET(STDIN_FILENO, &readfds);
479 tv.tv_sec = 0;
480 tv.tv_usec = 5000; // 5ms
481
482 int ret = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &tv);
483 if (ret > 0) {
484 fill_input_buffer();
485 if (buffer_start < buffer_end) {
486 return input_buffer[buffer_start++];
487 }
488 }
489
490 return -1;
491 }
492
493 // Public function to flush input buffer (callable from Fortran)
494 void flush_input_buffer(void) {
495 flush_input();
496 }
497
498 // Get terminal size using ioctl (no escape sequences needed)
499 void get_terminal_size(int *rows, int *cols) {
500 struct winsize ws;
501 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) {
502 *rows = ws.ws_row;
503 *cols = ws.ws_col;
504 } else {
505 // Fallback to defaults
506 *rows = 24;
507 *cols = 80;
508 }
509 }
510
511 #endif
512