1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
|
/*
PsychToolbox3/Source/Common/PsychHID/PsychHIDKeyboardHelper.c
PROJECTS: PsychHID only.
PLATFORMS: All
AUTHORS:
mario.kleiner@tuebingen.mpg.de mk
HISTORY:
TO DO:
*/
#include "PsychHID.h"
// Following code is transplanted from SCREENGetMouseHelper.c. It used
// to live inside Screen() for many happy years, and a copy still exists
// there for backwards compatibility and some legacy support.
//
// But mostly this implementation is now used, esp. by the new
// ListenChar()/FlushEvents()/CharAvail()/GetChar() implementation
// for GNU/Octave and Matlab in matlab -nojvm mode. That implementation
// uses our PsychHID keyboard queues to emulate good ol' GetChar()
// functionality when the Java-Based GetChar implementation can't be
// used. For handling of CTRL+C key presses by users by our kbqueue
// thread, we need easy direct access to ConsoleHelper(). For this reason
// we transplant ConsoleHelper() et al. to PsychHID. All other approaches
// proved to fragile.
// Current ListenChar state:
static int listenchar_enabled = 0;
static int stdinpipe[2] = {-1, -1};
static FILE* stdininject = NULL;
#if PSYCH_SYSTEM == PSYCH_LINUX
#include <errno.h>
#endif
#if PSYCH_SYSTEM != PSYCH_WINDOWS
/**
POSIX implementation of _kbhit() for Linux and OS/X:
Adapted from example code by Morgan McGuire, morgan@cs.brown.edu
*/
#include <stdio.h>
#include <sys/select.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <signal.h>
// Pseudo-TTY includes for OSX and Linux (openpty() et al.):
#if PSYCH_SYSTEM == PSYCH_OSX
#include <util.h>
#else
#include <pty.h>
#endif
// This implementation also does at/detaching of the stdin stream
// from the controlling tty, control of character echo'ing, buffering,
// and canonical input processing, depending on the requested state
// transitions between different listenchar states.
//
// These are Unix only features.
int _kbhit(void) {
struct termios term;
int bytesWaiting;
static int current_mode = 0;
static struct termios oldterm;
static int fd = -1;
// Change of mode requested?
if (current_mode != listenchar_enabled) {
// Enable of character suppression requested on Linux?
// Or enable of any character listening on OSX?
//
// Why the difference? On Linux, our controlling tty
// receives keystroke input even if an onscreen window
// obscures the terminal window, so we can use regular
// ctty for ListenChar(1), only need this code for
// character suppression in ListenChar(2) mode. On OSX
// a fullscreen window completely blocks this ctty path,
// so even in ListenChar(1) mode we need to route key input
// around this blockade by use of the keyboard queue thread
// and our pty for injecting keystrokes/characters into the
// runtime. This rerouting works, but loses the special features
// of a real pty/terminal: No keyboard auto-repeat, no cursor
// keys, etc. The Linux way is preferrable, that's why we retain
// it here by special-casing OSX.
#if PSYCH_SYSTEM == PSYCH_LINUX
if (!(current_mode & 2) && (listenchar_enabled & 2)) {
#else
if ((current_mode == 0) && (listenchar_enabled > 0)) {
#endif
// Switching from unsuppressed to suppressed.
// Or more specifically: From ctty->stdin to
// pipe->stdin.
// Get backup of filedescriptor fd of real stdin:
fd = dup(fileno(stdin));
// Get current termios state of real stdin:
tcgetattr(fileno(stdin), &term);
// Back it up:
oldterm = term;
// Disable echo on real stdin:
term.c_lflag &= ~ECHO;
tcsetattr(fileno(stdin), TCSANOW, &term);
#if (PSYCH_SYSTEM == PSYCH_LINUX) && defined(PTBOCTAVE3MEX) && (PSYCH_LANGUAGE == PSYCH_MATLAB)
// Linux with Octave: We can't use a pty or unix pipe(), as
// Octave would terminate if we tried to detach from a pty or
// pipe while it is in interactive mode, waiting for input.
//
// This is because Octave uses the readline library for i/o,
// and readline doesn't like this while it is watching. Apparently
// it does accept detaching files, but not pipes or ptys.
//
// Anyway, what does work perfectly with no known limitations
// is to attach a simple file "/dev/zero" instead, so we do that.
//
// Detach stdin from controlling tty, redirect to
// /dev/zero, so it doesn't get any input from now on,
// regardless what characters go to the terminal:
if (NULL == freopen("/dev/zero", "r", stdin))
printf("PsychHID-WARNING: Could not detach stdin from controlling tty [%s]. Character to terminal suppression may not work.\n", strerror(errno));
// We are detached: No characters received from terminal,
// no characters echo'ed by terminal itself.
#else
// OSX, or Linux with Matlab or Python instead of Octave:
//
// On OSX with Octave, we must use a pipe(), again because of
// the readline library: When going to interactive mode, readline()
// tries to tcsetattr() its favorite settings on its tty. This implies
// a tcsetattr(TCSADRAIN) and the drain blocks infinitely if the tty
// contains pending data at that time, e.g., because the user has
// done some keystrokes during non-interactive compute ops, so characters
// are pending in our pty. Bad bad. How to avoid? On OSX we can't use
// a /dev/zero file as on Linux, because we always must do kbqueue based
// redirection. However, if we use a pipe() instead of a pty, then readline
// detects that a tcsetattr() on a pipe() will not work (ENOTTY), so the
// blocking tcsetattr() turns into a no-op and all is good. Except that a
// CTRL+C press while in interactive mode (SIGINT) can screw occassionally
// with readline and cause an app termination. Luckily this is nothing joe
// average user would normally do, as it is meaningless while in interactive
// input mode, so having this potential (but low probability) hazard is the
// less of two evils.
//
// On OSX with Matlab, we use a pty() instead, as Matlab doesn't like connecting
// to anything but a full-fledged pty. We only fallback to a pipe() in the unlikely
// (impossible?) case that pty() creation fails, as that is somewhat hazardous on
// Matlab.
//
// We try to use a pty - a pseudo-terminal, with the same settings
// "oldterm" as our real controlling tty. This allows good compatibility
// with Matlab.
//
// So basically we have 1 code path (with pty()) for use with Matlab, as Matlab
// only works reliably with ptys, and 2 code pathes for Octave to account for
// the peculiarities of lib readline x 2 cases for the peculiarities of the
// windowing systems of Linux vs. OSX.
#if (PSYCH_SYSTEM == PSYCH_OSX) && defined(PTBOCTAVE3MEX)
if (0 != pipe(stdinpipe)) {
printf("PsychHID-WARNING: Unix pipe() creation failed [%s]. This may end badly!\n", strerror(errno));
fflush(stdout);
}
#else
if (0 != openpty(&stdinpipe[1], &stdinpipe[0], NULL, &oldterm, NULL)) {
printf("PsychHID-WARNING: openpty() for pseudo-tty failed! [%s]. Falling back to Unix pipe().\n", strerror(errno));
if (0 != pipe(stdinpipe)) printf("PsychHID-WARNING: Unix pipe() creation failed [%s]. This may end badly!\n", strerror(errno));
fflush(stdout);
}
#endif
// Attach the read descriptor [0] to stdin of the runtime.
// This way, everything written into stdinpipe[1] will appear
// as input to stdin -> gets fed into our host application:
dup2(stdinpipe[0], fileno(stdin));
// Clear potential error conditions:
clearerr(stdin);
// Attach write descriptor to standard FILE* stdinject for
// simple use with fwrite() et al.:
stdininject = fdopen(stdinpipe[1], "a");
if (NULL == stdininject) printf("PsychHID-WARNING: Creation of stdinject failed! [%s]\n", strerror(errno));
#endif
}
// Disable of character suppression requested Linux?
// Or disable of any character processing requested on OSX?
// See above for explanation of this OS difference.
#if PSYCH_SYSTEM == PSYCH_LINUX
if ((current_mode & 2) && !(listenchar_enabled & 2)) {
#else
if ((current_mode > 0) && (listenchar_enabled == 0)) {
#endif
// Switching from suppressed to unsuppressed:
// Reassign filedescriptor fd of real stdin to stdin from
// our previous backup, thereby reattaching stdin to the
// controlling tty:
dup2(fd, fileno(stdin));
// Close and invalidate our backup fildescriptor:
close(fd);
fd = -1;
// Clear potential error conditions:
clearerr(stdin);
// Restore termios settings from backup as well. This
// reenables auto-echo'ing of tty if it was enabled
// beforehand (different between Octave and matlab -nojvm),
// and flushes all buffers, so we don't get spillover that
// was cached in some low-level kernel line-discipline buffer:
tcsetattr(fileno(stdin), TCSAFLUSH, &oldterm);
// We are reattached.
// Injector stream used? If so, close it:
if (stdininject) {
// Close our, now unused, stdinpipe by closing both ends:
close(stdinpipe[1]);
close(stdinpipe[0]);
stdinpipe[1] = -1;
stdinpipe[0] = -1;
fclose(stdininject);
stdininject = NULL;
}
}
// Transition to active character listening?
if ((current_mode == 0) && (listenchar_enabled > 0) && (fd == -1)) {
// Yes, and stdin attached to real controlling tty.
// Get current settings of stream:
tcgetattr(fileno(stdin), &term);
// Disable canonic input processing so we don't need to wait
// for newline before we get input:
term.c_lflag &= ~ICANON;
// Apply:
tcsetattr(fileno(stdin), TCSANOW, &term);
// Disable buffering of characters:
setbuf(stdin, NULL);
}
// New opmode established:
current_mode = listenchar_enabled;
}
// Query number of pending characters in stdin stream:
ioctl(fileno(stdin), FIONREAD, &bytesWaiting);
return(bytesWaiting);
}
#else
// _kbhit() is part of MS-Windows CRT standard runtime library. We just
// need to include the conio header file. Character suppression does not
// work with it though:
#include <conio.h>
#endif
// Special console handler: Performs functions of ListenChar, FlushEvents,
// CharAvail and GetChar when run in terminal mode without GUI and JavaVM:
void ConsoleInputHelper(int ccode)
{
int ret;
// Keystroke character from KbQueue thread received? This is on the kbqueue thread,
// not the main interpreter thread!
if (ccode >= 0) {
// Yes. If our console-based ListenChar(1) mode is active, but
// not ListenChar(2), then we need to forward the character to
// the runtime via our pipe, if there is a pipe:
if ((PSYCH_SYSTEM != PSYCH_LINUX) && (stdininject) && (listenchar_enabled == 1) && (ccode != 3)) {
// Inject character into runtime:
fputc(ccode, stdininject);
fflush(stdininject);
}
// Done.
return;
}
// Negatice ccode -- A command code:
switch (ccode) {
case -1: // KeyboardQueue-Thread reports detection of CTRL+C interrupt keys:
// We are on the kbqueue thread, not the main interpreter thread, so printf
// would be dangerous, at least on recent Matlab versions.
// If console based ListenChar() is enabled at all, ie., ListenChar(1)
// or ListenChar(2) are active by use of the KeyboardQueue thread,
// then we need dispatch a SIGINT interrupt signal to our host process,
// so that it interrupts running processing on the console or inside M-Files,
// ie., execution of M-Scripts or M-Functions, and returns to the interactive
// command prompt, waiting for user input and commands via the stdin terminal
// input stream:
if (listenchar_enabled > 0) {
// We only need to always send an explicit signal on OSX.
// On Linux with Matlab, or with Octave in console mode, the signal
// is delivered inline in the stdin character stream as ASCII code 3
// (CTRL+C). On Linux with Octave's builtin QT based GUI enabled, we
// need to send an explicit signal, as the implementation of Octave's
// GUI does not send any CTRL+C signal to the interpreter inline or
// in any other way. Presence of the Octave QT-GUI is signalled by the
// GNUTERM environment variable being set to "qt".
// Update April 2014: As of Octave 3.8.1 on Linux, this is no longer
// needed for the Qt-GUI, and also not executed, because GNUTERM is
// no longer set to 'qt', so all is good :)
//
// On MS-Windows, this may be the same, maybe not, but
// there ain't nothing we could do there if it isn't:
#if PSYCH_SYSTEM != PSYCH_WINDOWS
if ((PSYCH_SYSTEM == PSYCH_OSX) ||
(getenv("GNUTERM") && strstr(getenv("GNUTERM"), "qt"))) {
kill(getpid(), SIGINT);
}
#endif
}
// If we are in ListenChar(2) mode, drop us down to ListenChar(1) mode:
// This disables character suppression, so command input by the user
// into the - now interactive - command line session works as expected:
if (listenchar_enabled > 1) {
// Enable char listening:
listenchar_enabled = 1;
_kbhit();
}
break;
case -10: // ListenChar(0);
// Disable char listening:
listenchar_enabled = 0;
_kbhit();
break;
case -11: // ListenChar(1);
// Enable char listening:
listenchar_enabled = 1;
_kbhit();
break;
case -12: // ListenChar(2);
// Enable char listening and suppress output to console:
listenchar_enabled = 1 + 2;
_kbhit();
break;
case -13: // FlushEvents:
// Enable char listening:
listenchar_enabled |= 1;
// Drain queue from all pending characters:
while (_kbhit()) getchar();
break;
case -14: // CharAvail():
// Enable char listening:
listenchar_enabled |= 1;
// Return number of pending chars in queue:
PsychCopyOutDoubleArg(1, kPsychArgOptional, (double)_kbhit());
break;
case -15: // GetChar():
// Enable char listening:
listenchar_enabled |= 1;
// Retrieve one character: Return if none available.
// We call _kbhit() once to turn the terminal into non-blocking mode,
// so it doesn't wait for newlines:
if (0 == _kbhit()) {
// Nothing available: Return zero result code:
ret = 0;
}
else {
// At least one available: Fetch one...
ret = getchar();
// Valid?
if (ret == EOF) {
// Failed - End of stream or error! Clear error condition:
clearerr(stdin);
errno = 0;
// Return error code -1:
ret = -1;
}
}
// Return whatever we've got:
PsychCopyOutDoubleArg(1, kPsychArgOptional, (double)ret);
break;
default:
PsychErrorExitMsg(PsychError_internal, "Invalid command code encountered in ConsoleInputHelper()! This is an implementation BUG!");
}
return;
}
static char useString[] = "rc = PsychHID('KeyboardHelper', commandCode)";
static char synopsisString[] = "Low-Level helper function for controlling keyboard input.\n"
"This function accepts a couple esoteric commandCode's to control "
"low-level aspects of how keyboards are handled. The commands are "
"intentionally undocumented, because only PTB developers should "
"deal with this interface.\n";
static char seeAlsoString[] = "";
PsychError PSYCHHIDKeyboardHelper(void)
{
int commandCode;
PsychPushHelp(useString, synopsisString, seeAlsoString);
if (PsychIsGiveHelp()) {PsychGiveHelp(); return(PsychError_none);};
PsychErrorExit(PsychCapNumOutputArgs(1));
PsychErrorExit(PsychCapNumInputArgs(1));
// Get and validate command code:
PsychCopyInIntegerArg(1, kPsychArgRequired, &commandCode);
// Validate against our current weird range, inherited from Screen('GetMouseHelper'):
if ((commandCode < -15) || (commandCode > -10)) PsychErrorExitMsg(PsychError_user, "Invalid commandCode provided! Outside range [-15 ; -10].");
// Ok. Call the ConsoleInputHelper for that range:
if ((commandCode >= -15) && (commandCode <= -10)) ConsoleInputHelper(commandCode);
// We're done:
return(PsychError_none);
}
|