function [ch, when] = GetChar(getExtendedData, getRawCode)
% [ch, when] = GetChar([getExtendedData], [getRawCode])
% 
% Wait for a typed character and return it.  If a character was typed
% before calling GetChar then GetChar will return that character immediately.
% Characters flushed by FlushEvents are all ignored by GetChar. Characters
% are returned in the first return argument "ch".
%
% Please read the 'help ListenChar' carefully to understand various
% limitations and caveats of this function, and to learn about - often
% better - alternatives. Also read the "international keyboard" section in
% 'help KbEventGet' for limitations, caveats, or possibly needed extra
% setup in some cases when using a keyboard with a non-US layout.
% 
% CAUTION: Do not rely on the keypress timestamps returned by GetChar
% without fully reading and understanding this help text. Run your own
% timing tests on GetChar and KbCheck to verify that the timing is good
% enough and avoid GetChar for timed keypresses if possible at all. Use
% KbWait and KbCheck instead.
%
% The main purpose of GetChar is to reliably collect keyboard input in the
% background while your experiment script is occupied with performing other
% operations, e.g., Matlab computations, sound output or visual stimulus
% drawing. After an initial call to ListenChar, the operating system will
% record all keyboard input into an internal queue. GetChar removes
% characters from that queue, one character per invocation of GetChar. You
% can empty that queue any time by calling FlushEvents('keyDown').
%
% If you want to check the current state of the keyboard, e.g., for
% triggering immediate actions in response to a key press, waiting for a
% subjects response, synchronizing to keytriggers (e.g., fMRI machines) or
% if you require high timing precision then use KbCheck instead of GetChar.
%
% GetChar should work on all platforms, but its specific functionality,
% beyond simply returning simple typed characters, will vary depending on
% OS type and version, the type of desktop GUI in use, or if you use Matlab
% or Octave, and if you use Matlab with or without Java based GUI active.
% For portability it is therefore best to ignore all info returned beyond
% the character code. Composed characters, e.g., putting accents on top of
% letters, do not always work on all setups. In general, only Matlab
% versions older than R2025a, which have a Java based desktop GUI, on Linux
% and macOS, will get optimal international keyboard and composed character
% handling, at reduced timing precision, whereas anything on MS-Windows,
% and on Linux with KDE desktop, and on Octave, and Matlab in -nodesktop
% mode, and Matlab R2025a or later, will use an implementation based on
% KbQueues, which has better timing and flexibility, but worse handling of
% international keyboards and composed characters at the moment.
%
% "when" is a struct. It (used to) return the time of the keypress, the "adb"
% address of the input device, and the state of all the modifier keys
% (shift, control, command, option, alphaLock) and the mouse button.
% "when.secs" is an estimate, of what GetSecs would have been. Since it's
% derived sometime from a timebase different from the timebase of GetSecs,
% times returned by GetSecs are not directly comparable to when.secs.
%
% By setting getExtendedData to 0, all extended timing/modifier information
% will not be collected and "when" will be returned empty.  This speeds up
% calls to this function. If omitted or set to 1, the "when" data
% structure is filled.  getRawCode set to 1 will set "ch" to be the integer
% ASCII code of the available character.  If omitted or set to 0, "ch" will
% be in char format. When running under Linux in "matlab -nojvm" mode or on
% Octave, "when" will be returned empty. When running on any other
% operating system under Octave or in "matlab -nojvm" mode, or on Windows
% Vista and later versions of the Windows OS, when will only contain a
% valid timestamp, but all other fields will be meaningless.
%
% GetChar and CharAvail are character-oriented (and slow), whereas KbCheck
% and KbWait are keypress-oriented (and fast). If only a meta key (like
% <option> or <shift>) was hit, KbCheck will return true, because a key was
% pressed, but CharAvail will return false, because no character was
% generated. See KbCheck.
% 
% CharAvail and GetChar try to use the system event queue to retrieve the
% character generated if possible, not the raw key press(es) per se. If the
% user presses "a", GetChar returns 'a', but if the user presses option-e
% followed by "a", this selects an accented a, "?", which is treated by
% GetChar as a single character, even though it took the user three
% keypresses (counting the option key) to produce it.
% 
% There can be some delay between when the key is pressed and when CharAvail
% or GetChar detects it, due to internal processing overhead in Matlabs Java
% implementation. GetChar internally collects timestamps in the timebase
% used by Matlabs Java implementation, whereas other Psychtoolbox timing functions
% (GetSecs, Screen('Flip'), KbCheck, KbWait, ...) use time reported by some
% high precision system timer. The "when.secs" time reported by GetChar is
% converted from Java timebase to Psychtoolboxs timebase. Due to conversion
% errors mostly out of our control, the reported values can be off by
% multiple dozen or even hundreds of milliseconds from what KbWait, KbCheck
% or GetSecs would report. Example: A high-end Pentium-4 3.2 Ghz system
% running Windows-XP has been measured to be off by 40 to 70 milliseconds.
%
% Some Java implementations are also known to have problems/bugs in
% timestamping keyboard presses properly and each Matlab version on each
% operating system is bundled with a different Java version, so some Matlab
% versions may be reliable with respect to GetChars timing, whereas others
% are not.
%
% ---> If precise timing of the keypress is important, use KbCheck or
% KbWait or KbQueueXXX functions or KbEventGet for consistent results!
%
% macOS or Linux (with other than KDE desktop GUI), with Matlab older than
% R2025a, and Java GUI enabled:
%
% JAVA PATH: The GetChar implementation for Matlab on these systems is
% based on Java. Therefore, the Psychtoolbox subfolder PsychJava must be
% added to Matlabs static classpath. Normally this is done by the
% Psychtoolbox installer by editing the Matlab file "classpath.txt" (enter
% which('classpath.txt') to find the location of that file). If the
% installer fails to edit the file properly, you'll need to perform that
% step manually by following the instructions of the installer. See 'help
% PsychJavaTrouble' for more infos on this.
%
% KEYSTROKES IN THE BACKGROUND: To detect keypresses made before the
% GetChar call, you must have called "ListenChar" earlier.  ListenChar
% redirects keystrokes to the GetChar queue. Calling ListenChar at the
% beginning of your script should cause GetChar to behave identically
% to OS 9 GetChar with respect to background keystroke collection.
%
% KEYSTROKES IN MATLAB WINDOW: By default, all keystrokes are also sent to
% Matlabs window, generating some ugly clutter. You can suppress this by
% calling ListenChar(2), so your Matlab console stays nice and clean. Don't
% forget to call ListenChar(1) or ListenChar(0) though before the end of
% your script. If Matlab returns to its command prompt without reenabling
% keyboard input via ListenChar(0) or ListenChar(1), Matlab will be left
% with a dead keyboard until you press the CTRL+C key combo. This silencing
% of clutter does currently not work in matlab -nojvm mode, or if you use
% GNU/Octave instead of Matlab. It also does not work on Matlab R2025a in
% GUI mode anymore, as Mathworks switched to a JavaScript based GUI, with
% no known good and reliable way of suppressing keystrokes on that GUI.
%
% OTHER "when" RETURN ARGUMENT FIELDS: Owing to differences in what
% accessory information the underlying operating systems provides about
% keystrokes, "when' return argument fields differs between operating systems.
% GetChar sets fields for which it returns no value to the value 'Nan'.  
%
% See also: ListenChar, CharAvail, FlushEvents, GetCharTest, KbCheck,
% KbWait

% 5/7/96  dgp Wrote this help file.
% 1/22/97 dhb Added comment and pointer to TIMER routines.
% 3/6/97  dhb References to KbWait, KbCheck.
% 7/23/97 dgp It's a character not a keypress.
% 8/2/97  dgp Explain difference between key and character. See KbCheck.
% 2/7/98  dgp Streamlined. Eliminated call to GetKey, since it's now GetChar.mex.
% 3/24/98 dgp Explain backgrounding and meta keys. Don't mention obsolete GetKey and KbHit.
% 3/15/99 xmz Put in some comment for Windows version.
% 3/19/99 dgp Update explanation of backgrounding. 
% 3/28/99 dgp Show how to turn off backgrounding. 
% 8/19/00 dgp Cosmetic. 
% 4/23/02 dgp Fixed GetChar.mex to always quit on command-period.
% 4/27/02 dgp Added optional second return argument.
% 6/1/02  dgp Mention Tick0Secs.
% 9/21/02 dgp Added address field to "when", using C code donated by Tom Busey.
% 7/12/04 awi ****** OS X-specific fork from the OS 9 version *******
%                   Expanded on explanation in the first paragraph about when
%                   GetChar waits, when it returns immediately, what
%                   it ignores.  Retains OS 9-specific comments.    
% 1/27/04 awi Issue an error when calling GetChar and suggest KbWait. 
% 1/19/05 awi Implemented GetChar on OS X.  Added AssertMex for OS 9 and OS X conditional block.
% 7/20/05 awi Wrote OS X documentation section.
% 2/2/06  awi Tested to see if this works when the MATLAB text editing
%             window is minimized. It does not.
% 2/22/06 awi Commented out Cocoa wrapper and wrote Java wrapper.
% 3/28/06 awi Detect buffer overflow.
%             Handle new double value from .getChar(), was char type.
%             Changed "char" return value to "ch" to avoid name conflict with
%             built-in MATLAB function "char" 
% 6/15/06 awi Added a second return argument.
%             Updated built-in help for the Java implementation.
% 6/15/06 awi Added break on CTRL-C
%             Added TO DO section and item to detect genuine KeyDown
%             events.
% 6/20/06 awi Use AddPsychJavaPath instead of AssertGetCharJava.
% 8/16/06 cgb Now using the new GetChar system which taps straight into
%             the java keypress dispatcher.
% 9/18/06  mk GetChar now works on all Matlabs (OS-X, Windows) in JVM
%             mode. In -nojvm mode on Windows, it falls back to the old
%             Windows DLL ...
%
% 05/31/09 mk Add support for Octave and Matlab in noJVM mode.
% 10/22/12 mk Remove support for legacy Matlab R11 GetCharNoJVM.dll.
% 10/22/12 mk Add support for KbQueue-Based implementation.
% 06/20/19 mk Try to protect against KDE focus stealing nastiness via kbqueues.
% 06/22/25 mk Make baseline compatible with Matlab R2025a+ non-Java GUI.

% NOTES:
%
% The second return argument from OS 9 PTB looks like this:
%     ticks: 5760808
%     secs: 1.4681e+05
%     address: 2
%     mouseButton: 0
%     alphaLock: 0
%     commandKey: 0
%     controlKey: 0
%     optionKey: 0
%     shiftKey: 0
% 

global OSX_JAVA_GETCHAR; %#ok<GVMIS>

% If no command line argument was passed we'll assume that the user only
% wants to get character data and timing/modifier data.
if nargin == 0
    getExtendedData = 1;
    getRawCode = 0;
elseif nargin == 1
    getRawCode = 0;
end

persistent isjavadesktop;

% Is this old Matlab with Java based GUI? Isn't this Windows? Isn't the Linux KDE GUI active?
% Only check this once because psychusejava is a slow command.
if isempty(isjavadesktop)
    isjavadesktop = psychusejava('desktop') && ~IsWin && isempty(getenv('KDE_FULL_SESSION'));
end

if isjavadesktop
    % Java virtual machine and AWT and Desktop are running. Use our Java based
    % GetChar.

    % Make sure that the GetCharJava class is loaded and registered with
    % the java focus manager.
    if isempty(OSX_JAVA_GETCHAR)
        try
            OSX_JAVA_GETCHAR = AssignGetCharJava;
        catch %#ok<*CTCH>
            error('Could not load Java class GetCharJava! Read ''help PsychJavaTrouble'' for help.');
        end
        OSX_JAVA_GETCHAR.register;
    end

    % Make sure the Matlab window has keyboard focus:
    if exist('commandwindow') %#ok<EXIST>
        % Call builtin implementation:
        commandwindow;
    end

    % Loop until we receive character input.
    keepChecking = 1;
    while keepChecking
        % Check to see if a character is available, and stop looking if
        % we've found one.
        charValue = OSX_JAVA_GETCHAR.getChar;
        keepChecking = charValue == 0;
        if keepChecking
            drawnow;
        end
    end

    % Throw an error if we've exceeded the buffer size.
    if charValue == -1
        % Re-enable keystroke dispatching to Matlab to leave us with a
        % functional Matlab console.
        OSX_JAVA_GETCHAR.setRedispatchFlag(0);
        error('GetChar buffer overflow. Use "FlushEvents" to clear error');
    end

    % Get the typed character.
    if getRawCode
        ch = charValue;
    else
        ch = char(charValue);
    end

    % Only fill up the 'when' data structure if extended data was requested.
    if getExtendedData
        when.address=nan;
        when.mouseButton=nan;
        when.alphaLock=nan;
        modifiers = OSX_JAVA_GETCHAR.getModifiers;
        when.commandKey = modifiers(1);
        when.controlKey = modifiers(2);
        when.optionKey = modifiers(3);
        when.shiftKey = modifiers(4);
        rawEventTimeMs = OSX_JAVA_GETCHAR.getEventTime;  % result is in units of ms.
        when.ticks = nan;
        when.secs = JavaTimeToGetSecs(rawEventTimeMs, -1);
    else
        when = [];
    end

    return;
end

% Running either on Octave, or on Matlab command line, or on Matlab R2025a+, or on MS-Windows:

% If we are on Linux and the keyboard queue is already in use by usercode,
% we can fall back to 'GetMouseHelper' low-level terminal tty magic. The
% only downside is that typed characters will spill into the console, ie.,
% ListenChar(2) suppression is unsupported:
if IsLinux && KbQueueReserve(3, 2, [])
    % Loop until we receive character input.
    keepChecking = 1;
    while keepChecking
        % Check to see if a character is available, and stop looking if
        % we've found one.
        
        % KeyboardHelper with command code 15 delivers
        % id of currently pending characters on stdin:
        charValue = PsychHID('KeyboardHelper', -15);
        keepChecking = charValue == 0;
        if keepChecking
            drawnow;
            if exist('fflush') %#ok<EXIST>
                builtin('fflush', 1);
            end
        end
    end
    
    % Throw an error if we've exceeded the buffer size.
    if charValue == -1
        % Re-enable keystroke display to leave us with a
        % functional console.
        PsychHID('KeyboardHelper', -11);
        error('GetChar buffer overflow. Use "FlushEvents" to clear error');
    end

    % No extended data in this mode:
    when = [];
else
    % Use keyboard queue:
    
    % Only need to reserve/create/start queue if we don't have it
    % already:
    if ~KbQueueReserve(3, 1, [])
        % LoadPsychHID is needed on MS-Windows. It no-ops if called redundantly:
        LoadPsychHID;
        
        % Try to reserve default keyboard queue for our exclusive use:
        if ~KbQueueReserve(1, 1, [])
            % Ok, we have to abort here. While the same issue is only worth
            % a warning for CharAvail and FlushEvents, it is game over for
            % GetChar, as we must not touch a user managed kbqueue, and we
            % can't provide any sensible behaviour if we can't do that:
            error('Keyboard queue for default keyboard device already in use by KbQueue/KbEvent functions et al. Use of ListenChar/GetChar/CharAvail/FlushEvents etc. and keyboard queues is mutually exclusive!');
        end
        
        % Got it. Allocate and start it:
        PsychHID('KbQueueCreate');
        PsychHID('KbQueueStart');

        if IsOctave && IsGUI
            % Enable keystroke redirection via kbyqueue and pty to bypass
            % blockade of onscreen windows:
            PsychHID('KeyboardHelper', -14);
        end        
    end
    
    % Queue is running: Poll it for new events we're interested in:
    % Loop until we receive character input.
    keepChecking = 1;
    while keepChecking
        % Check to see if a character is available, and stop looking if
        % we've found one.        
        event = PsychHID('KbQueueGetEvent', [], 0.1);
        if ~isempty(event) && event.Pressed && (event.CookedKey > 0)
            charValue = event.CookedKey;
            keepChecking = 0;
        else
            charValue = 0;
            keepChecking = 1;
        end
        
        if keepChecking
            drawnow;
            if exist('fflush') %#ok<EXIST>
                builtin('fflush', 1);
            end
        end
    end
    
    % Only fill up the 'when' data structure if extended data was requested.
    if getExtendedData
        when.address=nan;
        when.mouseButton=nan;
        when.alphaLock=nan;
        modifiers = [nan, nan, nan, nan];
        when.commandKey = modifiers(1);
        when.controlKey = modifiers(2);
        when.optionKey = modifiers(3);
        when.shiftKey = modifiers(4);
        when.ticks = nan;
        when.secs = event.Time;
    else
        when = [];
    end
end

% Get the typed character.
if getRawCode
    ch = charValue;
else
    ch = char(charValue);
end

return;
