FreeBASIC  0.91.0
hinit.c
Go to the documentation of this file.
1 /* libfb initialization for Unix */
2 
3 /* for getpgid() */
4 #define _GNU_SOURCE 1
5 
6 #include "../fb.h"
7 #include "fb_private_console.h"
8 #include "../fb_private_thread.h"
9 #include <signal.h>
10 #include <termcap.h>
11 #ifdef HOST_LINUX
12 #include <sys/io.h>
13 #endif
14 #include <sys/ioctl.h>
15 #include <fcntl.h>
16 
18 
19 typedef void (*SIGHANDLER)(int);
21 static volatile sig_atomic_t __fb_console_resized;
22 static const char *seq[] = { "cm", "ho", "cs", "cl", "ce", "WS", "bl", "AF", "AB",
23  "me", "md", "SF", "ve", "vi", "dc", "ks", "ke" };
24 
25 static pthread_t __fb_bg_thread;
26 static int bgthread_inited = FALSE;
27 static pthread_mutex_t __fb_bg_mutex;
28 FBCALL void fb_BgLock ( void ) { pthread_mutex_lock ( &__fb_bg_mutex ); }
29 FBCALL void fb_BgUnlock ( void ) { pthread_mutex_unlock( &__fb_bg_mutex ); }
30 
31 #ifdef ENABLE_MT
32 extern int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int kind);
33 
34 static pthread_mutex_t __fb_global_mutex;
35 static pthread_mutex_t __fb_string_mutex;
36 FBCALL void fb_Lock ( void ) { pthread_mutex_lock ( &__fb_global_mutex ); }
37 FBCALL void fb_Unlock ( void ) { pthread_mutex_unlock( &__fb_global_mutex ); }
38 FBCALL void fb_StrLock ( void ) { pthread_mutex_lock ( &__fb_string_mutex ); }
39 FBCALL void fb_StrUnlock( void ) { pthread_mutex_unlock( &__fb_string_mutex ); }
40 #endif
41 
42 static void *bg_thread(void *arg)
43 {
44  while (__fb_con.inited) {
45 
46  BG_LOCK();
47  if (__fb_con.keyboard_handler)
48  __fb_con.keyboard_handler();
49  if (__fb_con.mouse_handler)
50  __fb_con.mouse_handler();
51  BG_UNLOCK();
52 
53  usleep(30000);
54  }
55  return NULL;
56 }
57 
58 void fb_hStartBgThread( void )
59 {
60  if( bgthread_inited == FALSE ) {
61  pthread_create( &__fb_bg_thread, NULL, bg_thread, NULL );
63  }
64 }
65 
66 static int default_getch(void)
67 {
68  return fgetc(__fb_con.f_in);
69 }
70 
71 static void signal_handler(int sig)
72 {
73  signal(sig, old_sighandler[sig]);
74  fb_hEnd(1);
75  raise(sig);
76 }
77 
78 #ifdef HOST_LINUX
79 int fb_hTermQuery( int code, int *val1, int *val2 )
80 {
81  if( fb_hTermOut( code, 0, 0 ) == FALSE )
82  return FALSE;
83 
84  int filled;
85  do {
86  /* The terminal should have sent its reply through stdin. However, it's
87  possible that there's other data pending in stdin, e.g. if the user
88  happened to press a key at the right time. */
89 
90  /* Read until an '\e[' (ESC char followed by '[') is reached,
91  it should be the begin of the terminal's answer string) */
92  int c;
93  do {
94  do {
95  c = getchar( );
96  if( c == EOF ) return FALSE;
97  if( c == '\e' ) break;
98 
99  /* Add skipped char to Inkey() buffer so it's not lost */
100  fb_hAddCh( c );
101  } while (1);
102 
103  c = getchar( );
104  if( c == '[' ) break;
105 
106  /* ditto */
107  fb_hAddCh( c );
108  } while (1);
109 
110  const char *format;
111  if( code == SEQ_QUERY_WINDOW )
112  format = "8;%d;%dt";
113  else /* SEQ_QUERY_CURSOR */
114  format = "%d;%dR";
115 
116  filled = scanf( format, val1, val2 );
117  } while (filled != 2);
118 
119  return TRUE;
120 }
121 #endif
122 
123 /* If the SIGWINCH handler was called, re-query terminal width/height
124  - Assuming BG_LOCK() is acquired, because this can be called from
125  linux/io_mouse.c:mouse_handler() from the background thread
126  - Assuming __fb_con.inited */
128 {
129  unsigned char *char_buffer, *attr_buffer;
130  struct winsize win;
131  int r, c, w, h;
132 
133  if( __fb_console_resized == FALSE )
134  return;
135 
137 
138  /* __fb_console_resized may be set to TRUE again here if the signal
139  handler is called right now, but it doesn't matter since we're about
140  to update anyways */
141 
142  win.ws_row = 0xFFFF;
143  ioctl( STDOUT_FILENO, TIOCGWINSZ, &win );
144  if (win.ws_row == 0xFFFF) {
145 #ifdef HOST_LINUX
146  if( fb_hTermQuery( SEQ_QUERY_WINDOW, &r, &c ) ) {
147  win.ws_row = r;
148  win.ws_col = c;
149  }
150  else
151 #endif
152  {
153  win.ws_row = 25;
154  win.ws_col = 80;
155  }
156  }
157 
158  char_buffer = calloc(1, win.ws_row * win.ws_col * 2);
159  attr_buffer = char_buffer + (win.ws_row * win.ws_col);
160  if (__fb_con.char_buffer) {
161  h = (__fb_con.h < win.ws_row) ? __fb_con.h : win.ws_row;
162  w = (__fb_con.w < win.ws_col) ? __fb_con.w : win.ws_col;
163  for (r = 0; r < h; r++) {
164  memcpy(char_buffer + (r * win.ws_col), __fb_con.char_buffer + (r * __fb_con.w), w);
165  memcpy(attr_buffer + (r * win.ws_col), __fb_con.attr_buffer + (r * __fb_con.w), w);
166  }
167  free(__fb_con.char_buffer);
168  }
169  __fb_con.char_buffer = char_buffer;
170  __fb_con.attr_buffer = attr_buffer;
171  __fb_con.h = win.ws_row;
172  __fb_con.w = win.ws_col;
173 #ifdef HOST_LINUX
174  if( fb_hTermQuery( SEQ_QUERY_CURSOR, &__fb_con.cur_y, &__fb_con.cur_x ) == FALSE )
175 #endif
176  {
177  __fb_con.cur_y = __fb_con.cur_x = 1;
178  }
179 
180  /* If __fb_console_resized is set to TRUE only now (after the above
181  check) then we will miss it for now, but it's ok because the next
182  fb_hRecheckConsoleSize() will handle it. */
183 }
184 
185 static void sigwinch_handler(int sig)
186 {
188  signal(SIGWINCH, sigwinch_handler);
189 }
190 
191 int fb_hTermOut( int code, int param1, int param2 )
192 {
193  const char *extra_seq[] = { "\e(U", "\e(B", "\e[6n", "\e[18t",
194  "\e[?1000h\e[?1003h", "\e[?1003l\e[?1000l", "\e[H\e[J\e[0m" };
195  char *str;
196 
197  if (!__fb_con.inited)
198  return FALSE;
199 
200  if (code > SEQ_MAX) {
201  switch (code) {
202  case SEQ_SET_COLOR_EX:
203  if( fprintf( stdout, "\e[%dm", param1 ) < 4 )
204  return FALSE;
205  break;
206  default:
207  if( fputs( extra_seq[code - SEQ_EXTRA], stdout ) == EOF )
208  return FALSE;
209  break;
210  }
211  } else {
212  if (!__fb_con.seq[code])
213  return FALSE;
214  str = tgoto(__fb_con.seq[code], param1, param2);
215  if (!str)
216  return FALSE;
217  tputs(str, 1, putchar);
218  }
219 
220  fflush( stdout );
221 
222  return TRUE;
223 }
224 
226 {
227  struct termios term_out, term_in;
228 
229  if (!__fb_con.inited)
230  return -1;
231 
232  /* Init terminal I/O */
233  if( !isatty( STDOUT_FILENO ) || !isatty( STDIN_FILENO ) )
234  return -1;
235  __fb_con.f_in = fopen("/dev/tty", "r+b");
236  if (!__fb_con.f_in)
237  return -1;
238  __fb_con.h_in = fileno(__fb_con.f_in);
239 
240  /* Cannot control console if process was started in background */
241  if( tcgetpgrp( STDOUT_FILENO ) != getpgid( 0 ) )
242  return -1;
243 
244  /* Output setup */
245  if( tcgetattr( STDOUT_FILENO, &__fb_con.old_term_out ) )
246  return -1;
247  memcpy(&term_out, &__fb_con.old_term_out, sizeof(term_out));
248  term_out.c_oflag |= OPOST;
249  if( tcsetattr( STDOUT_FILENO, TCSANOW, &term_out ) )
250  return -1;
251 
252  /* Input setup */
253  if (tcgetattr(__fb_con.h_in, &__fb_con.old_term_in))
254  return -1;
255  memcpy(&term_in, &__fb_con.old_term_in, sizeof(term_in));
256  /* Send SIGINT on control-C */
257  term_in.c_iflag |= BRKINT;
258  /* Disable Xon/off and input BREAK condition ignoring */
259  term_in.c_iflag &= ~(IXOFF | IXON | IGNBRK);
260  /* Character oriented, no echo */
261  term_in.c_lflag &= ~(ICANON | ECHO);
262  /* No timeout, just don't block */
263  term_in.c_cc[VMIN] = 1;
264  term_in.c_cc[VTIME] = 0;
265  if (tcsetattr(__fb_con.h_in, TCSANOW, &term_in))
266  return -1;
267 
268  /* Don't block */
269  __fb_con.old_in_flags = fcntl(__fb_con.h_in, F_GETFL, 0);
270  fcntl(__fb_con.h_in, F_SETFL, __fb_con.old_in_flags | O_NONBLOCK);
271 
272 #ifdef HOST_LINUX
273  if (__fb_con.inited == INIT_CONSOLE)
274  fb_hTermOut(SEQ_INIT_CHARSET, 0, 0);
275 #endif
277 
278  /* Initialize keyboard and mouse handlers if set */
279  BG_LOCK();
280  if (__fb_con.keyboard_init)
281  __fb_con.keyboard_init();
282  if (__fb_con.mouse_init)
283  __fb_con.mouse_init();
284  BG_UNLOCK();
285 
286  return 0;
287 }
288 
289 void fb_hExitConsole( void )
290 {
291  int bottom;
292 
293  if (__fb_con.inited) {
294 
295  if (__fb_con.gfx_exit)
296  __fb_con.gfx_exit();
297 
298  BG_LOCK();
299  if (__fb_con.keyboard_exit)
300  __fb_con.keyboard_exit();
301  if (__fb_con.mouse_exit)
302  __fb_con.mouse_exit();
303  BG_UNLOCK();
304 
305  bottom = fb_ConsoleGetMaxRow();
306  if ((fb_ConsoleGetTopRow() != 0) || (fb_ConsoleGetBotRow() != bottom - 1)) {
307  /* Restore scrolling region to whole screen and clear */
308  fb_hTermOut(SEQ_SCROLL_REGION, bottom - 1, 0);
309  fb_hTermOut(SEQ_CLS, 0, 0);
310  fb_hTermOut(SEQ_HOME, 0, 0);
311  }
312 
313  /* Cleanup terminal */
314 #ifdef HOST_LINUX
315  if (__fb_con.inited == INIT_CONSOLE)
316  fb_hTermOut(SEQ_EXIT_CHARSET, 0, 0);
317 #endif
321  tcsetattr( STDOUT_FILENO, TCSANOW, &__fb_con.old_term_out );
322 
323  /* Restore old console keyboard state */
324  fcntl(__fb_con.h_in, F_SETFL, __fb_con.old_in_flags);
325  tcsetattr(__fb_con.h_in, TCSANOW, &__fb_con.old_term_in);
326 
327  if (__fb_con.f_in) {
328  fclose(__fb_con.f_in);
329  __fb_con.f_in = NULL;
330  }
331  }
332 }
333 
334 static void hInit( void )
335 {
336  const int sigs[] = { SIGABRT, SIGFPE, SIGILL, SIGSEGV, SIGTERM, SIGINT, SIGQUIT, -1 };
337  char buffer[2048], *p, *term;
338  struct termios tty;
339  int i;
340 
341 #ifdef ENABLE_MT
342  pthread_mutexattr_t attr;
343 #endif
344 
345 #if defined(__GNUC__) && defined(__i386__)
346  unsigned int control_word;
347 
348  /* Get FPU control word */
349  __asm__ __volatile__( "fstcw %0" : "=m" (control_word) : );
350  /* Set 64-bit and round to nearest */
351  control_word = (control_word & 0xF0FF) | 0x300;
352  /* Write back FPU control word */
353  __asm__ __volatile__( "fldcw %0" : : "m" (control_word) );
354 #endif
355 
356 #ifdef ENABLE_MT
357  /* make mutex recursive to behave the same on Win32 and Linux (if possible) */
358  pthread_mutexattr_init(&attr);
359 
360  /* TODO: Figure out which Unixy systems have/don't have PTHREAD_MUTEX_RECURSIVE[_NP] */
361  /* Currently it seems that PTHREAD_MUTEX_RECURSIVE is the standard POSIX name,
362  while PTHREAD_MUTEX_RECURSIVE_NP is something non-posixy used on Linux.
363  Some Linux distros (or glibc/pthread versions) only seemed to make the *_NP
364  version available (at least this was observed in the past). */
365 #ifdef HOST_LINUX
366  pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP);
367 #else
368  pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
369 #endif
370 
371  /* Init multithreading support */
372  pthread_mutex_init(&__fb_global_mutex, &attr);
373  pthread_mutex_init(&__fb_string_mutex, &attr);
374 #endif
375 
376  pthread_mutex_init( &__fb_bg_mutex, NULL );
377 
378  memset(&__fb_con, 0, sizeof(__fb_con));
379 
380  /* Init termcap */
381  term = getenv("TERM");
382  if ((!term) || (tgetent(buffer, term) <= 0))
383  return;
384  BC = UP = 0;
385  p = tgetstr("pc", NULL);
386  PC = p ? *p : 0;
387  if (tcgetattr(1, &tty))
388  return;
389  ospeed = cfgetospeed(&tty);
390  if (!tgetflag("am"))
391  return;
392  for (i = 0; i < SEQ_MAX; i++)
393  __fb_con.seq[i] = tgetstr(seq[i], NULL);
394 
395  /* !!!TODO!!! detect other OS consoles? (freebsd: 'cons25', etc?) */
396  if ((!strcmp(term, "console")) || (!strncmp(term, "linux", 5)))
397  __fb_con.inited = INIT_CONSOLE;
398  else
399  __fb_con.inited = INIT_X11;
400 
401  if (!strncasecmp(term, "eterm", 5))
402  __fb_con.term_type = TERM_ETERM;
403  else if (!strncmp(term, "xterm", 5))
404  __fb_con.term_type = TERM_XTERM;
405  else
406  __fb_con.term_type = TERM_GENERIC;
407 
408  if (fb_hInitConsole()) {
409  __fb_con.inited = FALSE;
410  return;
411  }
412  __fb_con.keyboard_getch = default_getch;
413 
414  /* Install signal handlers to quietly shut down */
415  for (i = 0; sigs[i] >= 0; i++)
416  old_sighandler[sigs[i]] = signal(sigs[i], signal_handler);
417 
418  __fb_con.char_buffer = NULL;
419  __fb_con.fg_color = 7;
420  __fb_con.bg_color = 0;
421 
424  signal(SIGWINCH, sigwinch_handler);
425 }
426 
427 void fb_hInit( void )
428 {
429  hInit( );
430 
431 #if defined HOST_LINUX && (defined HOST_X86 || defined HOST_X86_64)
432  /* Permissions for port I/O */
433  __fb_con.has_perm = ioperm(0, 0x400, 1) ? FALSE : TRUE;
434 #endif
435 }
436 
437 void fb_hEnd( int unused )
438 {
439  fb_hExitConsole();
440  __fb_con.inited = FALSE;
441  if( bgthread_inited ) {
442  pthread_join(__fb_bg_thread, NULL);
444  }
445  pthread_mutex_destroy(&__fb_bg_mutex);
446 
447 #ifdef ENABLE_MT
448  /* Release multithreading support resources */
449  pthread_mutex_destroy(&__fb_global_mutex);
450  pthread_mutex_destroy(&__fb_string_mutex);
451 #endif
452 }