/* See LICENSE file for copyright and license details. */
#ifndef LIBTERMINPUT_H
#define LIBTERMINPUT_H
#include <stddef.h>
/**
* Flags for supporting incompatible input; the user must
* set or clear his flag after setting or clearing it on
* the terminal, and the user must make sure that the
* terminal support this flag if set
*/
enum libterminput_flags {
/**
* The sequence CSI M shall be parsed be parse as a DECSET 1005
* sequence which is incompatible with legacy mouse tracking
*
* This flag shall only be set if DECSET 1005 has sent to the
* terminal and the user is sure it is supported by the terminal
*/
LIBTERMINPUT_DECSET_1005 = 0x0001,
/**
* Parse CSI M as Macro key presses rather rather than
* mouse tracking events
*
* This is incompatible with all mouse tracking modes except
* DECSET 1006
*/
LIBTERMINPUT_MACRO_ON_CSI_M = 0x0002,
/**
* Parse CSI P as Pause key presses rather than F1 key presses
*/
LIBTERMINPUT_PAUSE_ON_CSI_P = 0x0004,
/**
* Parse CSI @ as Insert key presses rather than a number of
* possible special keys combined with the control and shift
* modifiers
*/
LIBTERMINPUT_INS_ON_CSI_AT = 0x0008,
/**
* Backtab shall be treated as a separate key, and not be
* reported as tab with the shift modifier. This flag is just
* a usability issue. Keyboards put backtab on shift+tab,
* which is why the tab keycap has both a backward arrow
* (backtab) and a forward arrow (tab); but most users are
* unfamiliar with backtab, and just see it as shift+tab.
*/
LIBTERMINPUT_SEPARATE_BACKTAB = 0x0010,
/**
* If an ESC is received without anything after it,
* return ESC keypress. This is not always desirable
* behaviour as the user may manually press ESC to
* simulate a keypress that terminal does not support
* (yes, this is a real world issue).
*/
LIBTERMINPUT_ESC_ON_BLOCK = 0x0020,
/**
* This flag should be set, by the application, once
* the application sends an escape sequence requesting
* the terminal to report the cursor's position, and
* cleared once the position has been sent by the
* terminal and retreived by application
*
* This is required for distinguishing cursor position
* reports from F3 key presses
*/
LIBTERMINPUT_AWAITING_CURSOR_POSITION = 0x0040
};
/**
* Modifier keys
*
* These are the commonly reported modifiers,
* but additional modifiers are possible
*/
enum libterminput_mod {
/**
* Shift modifier
*/
LIBTERMINPUT_SHIFT = 0x01,
/**
* Meta/Alternative modifier
*/
LIBTERMINPUT_META = 0x02,
/**
* Control modifier
*/
LIBTERMINPUT_CTRL = 0x04
};
/**
* Keyboard buttons
*
* Only listed values can be reported, however the value
* must be listed for the version the application is linked
* against, not compiled against, so other values can be
* reported the application is linked against a newer version
* of the library than it is compiled against
*/
enum libterminput_key {
/**
* Non-special key
*
* Each code point that is generated as a keypress
* is reported separately, meaning that keypresses
* that generate multiple code points appear as
* multiple keypresses
*/
LIBTERMINPUT_SYMBOL,
LIBTERMINPUT_UP,
LIBTERMINPUT_DOWN,
LIBTERMINPUT_RIGHT,
LIBTERMINPUT_LEFT,
LIBTERMINPUT_BEGIN, /* keypad 5 without numlock */
LIBTERMINPUT_TAB, /* backtab is interpreted as shift+tab by default */
LIBTERMINPUT_BACKTAB, /* requires LIBTERMINPUT_SEPARATE_BACKTAB */
LIBTERMINPUT_F1,
LIBTERMINPUT_F2,
LIBTERMINPUT_F3,
LIBTERMINPUT_F4,
LIBTERMINPUT_F5,
LIBTERMINPUT_F6,
LIBTERMINPUT_F7,
LIBTERMINPUT_F8,
LIBTERMINPUT_F9,
LIBTERMINPUT_F10,
LIBTERMINPUT_F11,
LIBTERMINPUT_F12,
LIBTERMINPUT_HOME, /* = find */
LIBTERMINPUT_INS,
LIBTERMINPUT_DEL, /* = remove */
LIBTERMINPUT_END, /* = select */
LIBTERMINPUT_PRIOR, /* page up */
LIBTERMINPUT_NEXT, /* page down */
LIBTERMINPUT_ERASE, /* backspace */
LIBTERMINPUT_ENTER, /* return */
LIBTERMINPUT_ESC,
LIBTERMINPUT_MACRO,
LIBTERMINPUT_PAUSE,
LIBTERMINPUT_KEYPAD_0,
LIBTERMINPUT_KEYPAD_1,
LIBTERMINPUT_KEYPAD_2,
LIBTERMINPUT_KEYPAD_3,
LIBTERMINPUT_KEYPAD_4,
LIBTERMINPUT_KEYPAD_5,
LIBTERMINPUT_KEYPAD_6,
LIBTERMINPUT_KEYPAD_7,
LIBTERMINPUT_KEYPAD_8,
LIBTERMINPUT_KEYPAD_9,
LIBTERMINPUT_KEYPAD_PLUS,
LIBTERMINPUT_KEYPAD_MINUS,
LIBTERMINPUT_KEYPAD_TIMES,
LIBTERMINPUT_KEYPAD_DIVISION,
LIBTERMINPUT_KEYPAD_DECIMAL,
LIBTERMINPUT_KEYPAD_COMMA,
LIBTERMINPUT_KEYPAD_POINT,
LIBTERMINPUT_KEYPAD_ENTER
};
/**
* Mouse buttons
*
* It is possible that non-listed buttons are
* reported in events
*/
enum libterminput_button {
/**
* No mouse button is held down
*/
LIBTERMINPUT_NO_BUTTON,
/**
* Primary button
*
* Left button if right-handed,
* right button if left-handed
*/
LIBTERMINPUT_BUTTON1,
/**
* Middle button
*/
LIBTERMINPUT_BUTTON2,
/**
* Secondary button
*
* Right button if right-handed,
* left button if left-handed
*/
LIBTERMINPUT_BUTTON3,
/**
* Wheel scrolled up
*
* No corresponding release event shall be generated
*/
LIBTERMINPUT_SCROLL_UP,
/**
* Wheel scrolled down
*
* No corresponding release event shall be generated
*/
LIBTERMINPUT_SCROLL_DOWN,
/**
* Left-scroll button or wheel scrolled left
*
* May or may not have a corresponding release event
*/
LIBTERMINPUT_SCROLL_LEFT,
/**
* Right-scroll button or wheel scrolled right
*
* May or may not have a corresponding release event
*/
LIBTERMINPUT_SCROLL_RIGHT,
/**
* Extended button 1, also known as backward
*/
LIBTERMINPUT_XBUTTON1,
/**
* Extended button 2, also known as farward
*/
LIBTERMINPUT_XBUTTON2,
/**
* Extended button 3
*
* You probably don't have this button
*/
LIBTERMINPUT_XBUTTON3,
/**
* Extended button 4
*
* You probably don't have this button
*/
LIBTERMINPUT_XBUTTON4
};
/**
* Input event type
*/
enum libterminput_type {
/**
* A special value to mark that the input was either
* discard or not yet completed
*/
LIBTERMINPUT_NONE,
/**
* Normal key press
*/
LIBTERMINPUT_KEYPRESS,
/**
* Pseudo-event that marks that beginning of a bracketed paste
*/
LIBTERMINPUT_BRACKETED_PASTE_START,
/**
* Pseudo-event that marks that end of a bracketed paste
*/
LIBTERMINPUT_BRACKETED_PASTE_END,
/**
* Bracketed paste
*/
LIBTERMINPUT_TEXT,
/**
* Mouse event
*/
LIBTERMINPUT_MOUSEEVENT,
/**
* OK response for a device status query
*/
LIBTERMINPUT_TERMINAL_IS_OK, /* response to CSI 5 n */
/**
* Not-OK response for a device status query
*/
LIBTERMINPUT_TERMINAL_IS_NOT_OK, /* response to CSI 5 n */
/**
* Cursor position report event as a response
* to a cursor position query
*/
LIBTERMINPUT_CURSOR_POSITION /* response to CSI 6 n */
};
/**
* Mouse event subtype
*/
enum libterminput_event {
/**
* Mouse button pressed
*/
LIBTERMINPUT_PRESS,
/**
* Mouse button released
*/
LIBTERMINPUT_RELEASE,
/**
* Mouse moved, possibly with dragging
*/
LIBTERMINPUT_MOTION,
/**
* Highlight ended inside of selected region
*/
LIBTERMINPUT_HIGHLIGHT_INSIDE,
/**
* Highlight ended outside of selected region
*/
LIBTERMINPUT_HIGHLIGHT_OUTSIDE
};
/**
* Keypress event
*
* Some key presses may actually be mouse events,
* particularly when mouse tracking is disabled.
* In particular, mouse scrolling may appear as
* repeated Up key or Down key pesses
*/
struct libterminput_keypress {
/**
* Should be `LIBTERMINPUT_KEYPRESS`
*/
enum libterminput_type type;
/**
* Which key was pressed
*/
enum libterminput_key key;
/**
* This number of types the key was
* pressed
*
* Normally this would be 1, some mouse
* events generate arrow key presses that
* are reported, not multiple times, but
* as clicked multiple times
*
* For the next `.times - 1` reads (if
* this value is not modified by the user)
* will reported as the same event except
* that this value will be decreased by 1
* each time
*/
unsigned long long int times;
/**
* OR of active modifier keys
*/
enum libterminput_mod mods;
/**
* The symbol generated by the pressed key
*
* Only set if `.key == LIBTERMINPUT_SYMBOL`
*/
char symbol[7];
};
/**
* Text from a bracketed paste
*/
struct libterminput_text {
/**
* Should be `LIBTERMINPUT_TEXT`
*/
enum libterminput_type type;
/**
* The number of bytes available in `.bytes`
*/
size_t nbytes;
/**
* The section of the paste included in this
* event report
*
* If the text is longer than this buffer, it
* is split into multiple events, however they
* will all be between the same
* `LIBTERMINPUT_BRACKETED_PASTE_START` and
* `LIBTERMINPUT_BRACKETED_PASTE_END`, so it is
* possible to determine which events are actually
* the same paste event
*/
char bytes[512];
};
/**
* Mouse event
*/
struct libterminput_mouseevent {
/**
* Should be `LIBTERMINPUT_MOUSEEVENT`
*/
enum libterminput_type type;
/**
* Active modifier keys
*
* Set to 0 for `LIBTERMINPUT_HIGHLIGHT_INSIDE`
* and `LIBTERMINPUT_HIGHLIGHT_OUTSIDE`
*/
enum libterminput_mod mods;
/**
* The mouse button used in the event
*
* Set to 1 (LIBTERMINPUT_BUTTON1) for
* `LIBTERMINPUT_HIGHLIGHT_INSIDE` and
* `LIBTERMINPUT_HIGHLIGHT_OUTSIDE`
*/
enum libterminput_button button;
/**
* Mouse event sub type
*/
enum libterminput_event event;
/**
* Horizontal pointer position
*
* The number of cells offset right from the left edge, plus 1
*/
size_t x;
/**
* Vertical pointer position
*
* The number of cells offset down from the top edge, plus 1
*/
size_t y;
/**
* Horizontal beginning of the selection region
*
* Only set for `LIBTERMINPUT_HIGHLIGHT_OUTSIDE`
*/
size_t start_x;
/**
* Vertical beginning of the selection region
*
* Only set for `LIBTERMINPUT_HIGHLIGHT_OUTSIDE`
*/
size_t start_y;
/**
* Horizontal end of the selection region
*
* Only set for `LIBTERMINPUT_HIGHLIGHT_OUTSIDE`
*/
size_t end_x;
/**
* Vertical end of the selection region
*
* Only set for `LIBTERMINPUT_HIGHLIGHT_OUTSIDE`
*/
size_t end_y;
};
/**
* Cursor position response
*/
struct libterminput_position {
/**
* Should be `LIBTERMINPUT_CURSOR_POSITION`
*/
enum libterminput_type type;
/**
* Horizontal cursor position
*
* The number of cells offset right from the left edge, plus 1
*/
size_t x;
/**
* Vertical cursor position
*
* The number of cells offset down from the top edge, plus 1
*/
size_t y;
};
/**
* Input event
*/
union libterminput_input {
/**
* Input event type, used to determine which
* other member to read
*
* The following values have no corresponding
* member to read data from:
* `LIBTERMINPUT_NONE`,
* `LIBTERMINPUT_BRACKETED_PASTE_START`,
* `LIBTERMINPUT_BRACKETED_PASTE_END`,
* `LIBTERMINPUT_TERMINAL_IS_OK`,
* `LIBTERMINPUT_TERMINAL_IS_NOT_OK`
*/
enum libterminput_type type;
/**
* Use if `.type == LIBTERMINPUT_KEYPRESS`
*/
struct libterminput_keypress keypress;
/**
* Use if `.type == LIBTERMINPUT_TEXT`
*/
struct libterminput_text text;
/**
* Use if `.type == LIBTERMINPUT_MOUSEEVENT`
*/
struct libterminput_mouseevent mouseevent;
/**
* Use if `.type == LIBTERMINPUT_CURSOR_POSITION`
*/
struct libterminput_position position;
};
/**
* The current input state and configurations
*
* This struct should be considered opaque
*
* Initialised with by setting all bytes to 0
*/
struct libterminput_state {
int inited; /* whether the input in initialised, not this struct */
enum libterminput_mod mods;
enum libterminput_flags flags;
char bracketed_paste;
char mouse_tracking;
char meta;
char n;
size_t stored_head;
size_t stored_tail;
char paused;
char npartial;
char partial[7];
char key[44];
char stored[512];
};
/**
* Get input from the terminal
*
* @param fd The file descriptor to the terminal
* @param input Output parameter for input
* @param ctx State for the terminal, parts of the state may be stored in `input`
* @return 1 normally, 0 on end of input, -1 on error
*
* @throws Any reason specified for read(3)
*/
int libterminput_read(int fd, union libterminput_input *input, struct libterminput_state *ctx);
/**
* Check if more input available that can be processed
* by `libterminput_read` without performing any additional
* read(3) operation
*
* @param input Input gathered by `libterminput_read`
* @param ctx State for the terminal
* @return 1 if there there is more input available, 0 otherwise
*/
inline int
libterminput_is_ready(const union libterminput_input *input, const struct libterminput_state *ctx)
{
if (!ctx->inited || ctx->paused || ctx->mouse_tracking)
return 0;
if (input->type == LIBTERMINPUT_KEYPRESS && input->keypress.times > 1)
return 1;
return ctx->stored_head > ctx->stored_tail;
}
/**
* Set a behavioural flags
*
* @param ctx Argument pasted as the last parameter to `libterminput_read`
* @param flags The OR of the flags to set
* @return 0 on success, -1 on failure
*
* The current version of this function cannot fail
*/
int libterminput_set_flags(struct libterminput_state *ctx, enum libterminput_flags flags);
/**
* Clear a behavioural flags
*
* @param ctx Argument pasted as the last parameter to `libterminput_read`
* @param flags The OR of the flags to clear
* @return 0 on success, -1 on failure
*
* The current version of this function cannot fail
*/
int libterminput_clear_flags(struct libterminput_state *ctx, enum libterminput_flags flags);
#endif