Newer
Older
alicedbg / debugger / term.d
/// In-house console/terminal library
///
/// Authors: dd86k <dd@dax.moe>
/// Copyright: © dd86k <dd@dax.moe>
/// License: BSD-3-Clause-Clear
module term;

import core.stdc.stdlib;
import core.stdc.stdio;

//TODO: Consider using PDCurses instead

// NOTE: Functions prefixed with "con" to avoid clashing with the "tc" POSIX stuff

extern (C):

private int putchar(int);
private int getchar();

version (Windows) {
	private import core.sys.windows.windows;
	private enum ALT_PRESSED =  RIGHT_ALT_PRESSED  | LEFT_ALT_PRESSED;
	private enum CTRL_PRESSED = RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED;
	private __gshared HANDLE handleIn, handleOut, handleOld;
} else version (Posix) {
	private import core.sys.posix.sys.ioctl;
	private import core.sys.posix.unistd;
	private import core.sys.posix.termios;
	private import core.sys.posix.signal;
	private import core.sys.posix.ucontext;
	
	version (CRuntime_Musl) {
		alias uint tcflag_t;
		alias uint speed_t;
		alias char cc_t;
		private enum TCSANOW	= 0;
		private enum NCCS	= 32;
		private enum ICANON	= 2;
		private enum ECHO	= 10;
		private enum TIOCGWINSZ	= 0x5413;
		private struct termios {
			tcflag_t c_iflag;
			tcflag_t c_oflag;
			tcflag_t c_cflag;
			tcflag_t c_lflag;
			cc_t c_line;
			cc_t[NCCS] c_cc;
			speed_t __c_ispeed;
			speed_t __c_ospeed;
		}
		private struct winsize {
			ushort ws_row;
			ushort ws_col;
			ushort ws_xpixel;
			ushort ws_ypixel;
		}
		private int tcgetattr(int fd, termios *termios_p);
		private int tcsetattr(int fd, int a, termios *termios_p);
		private int ioctl(int fd, ulong request, ...);
	}
	version (CRuntime_Bionic) {
		private int tcgetattr(int __fd, termios* __t);
		private int tcsetattr(int __fd, int __optional_actions, termios* __t);
	}
	
	private enum TERM_ATTR = ~(ICANON | ECHO);
	private enum SIGWINCH = 28;
	private __gshared termios old_tio = void, new_tio = void;
}

// Flags: CONFxyz

private __gshared {
	/// User defined function for resize events
	void function(ushort,ushort) term_resize_handler;
	int term_opts; // default to 0
}

//
// ANCHOR Initiation
//

/// Initiates terminal basics
/// Returns: Error keyCode, non-zero on error
int coninit(int flags = 0) {
version (Posix) {
	tcgetattr(STDIN_FILENO, &old_tio);
	new_tio = old_tio;
	new_tio.c_lflag &= TERM_ATTR;

	//TODO: See flags we can put
	// tty_ioctl TIOCSETD
} else {
	handleOut = GetStdHandle(STD_OUTPUT_HANDLE);
	handleIn  = GetStdHandle(STD_INPUT_HANDLE);
	
	// Disable annoying mouse mode.
	DWORD mode = void;
	GetConsoleMode(handleIn, &mode);
	mode &= ~ENABLE_MOUSE_INPUT;
	SetConsoleMode(handleIn, mode);
}
	return 0;
}

// Invert console color with defaultColor
/*void thcolin() {
	version (Windows)
		SetConsoleTextAttribute(hOut, COMMON_LVB_REVERSE_VIDEO | defaultColor);
	version (Posix)
		fputs("\033[7m", stdout);
}

// Reset console color to defaultColor
void thcolrst() {
	version (Windows)
		SetConsoleTextAttribute(hOut, defaultColor);
	version (Posix)
		fputs("\033[0m", stdout);
}*/

/// Clear screen
void conclear() {
version (Windows) {
	CONSOLE_SCREEN_BUFFER_INFO csbi = void;
	COORD c; // 0, 0
	GetConsoleScreenBufferInfo(handleOut, &csbi);
	//const int buflen = csbi.dwSize.X * csbi.dwSize.Y; buf buflen
	const int buflen = // window buflen
		(csbi.srWindow.Right - csbi.srWindow.Left + 1)* // width
		(csbi.srWindow.Bottom - csbi.srWindow.Top + 1); // height
	DWORD num = void; // kind of ala .NET
	FillConsoleOutputCharacterA(handleOut, ' ', buflen, c, &num);
	FillConsoleOutputAttribute(handleOut, csbi.wAttributes, buflen, c, &num);
	conmvcur(0, 0);
} else version (Posix) {
	// "ESC [ 2 J" acts like clear(1)
	// "ESC c" is a full reset ala cls (Windows)
	printf("\033c");
}
else static assert(false, "Not implemented");
}

/// Get host console screen size.
/// Params:
///   w = Width (columns) pointer.
///   h = Height (rows) pointer.
void consize(int *w, int *h) {
	/// NOTE: A COORD uses SHORT (short) and Linux uses unsigned shorts.
version (Windows) {
	CONSOLE_SCREEN_BUFFER_INFO c = void;
	GetConsoleScreenBufferInfo(handleOut, &c);
	*w = c.srWindow.Right - c.srWindow.Left + 1;
	*h = c.srWindow.Bottom - c.srWindow.Top + 1;
} else version (Posix) {
	winsize win = void;
	ioctl(STDOUT_FILENO, TIOCGWINSZ, &win);
	*w = win.ws_col;
	*h = win.ws_row;
}
}

/// Set cursor position.
///
/// Coordonates start at zero.
/// Params:
///   x = X position (horizontal, columns)
///   y = Y position (vertical, rows)
void conmvcur(int x, int y) {
version (Windows) { // 0-based
	COORD c = { cast(SHORT)x, cast(SHORT)y };
	SetConsoleCursorPosition(handleOut, c);
} else version (Posix) { // 1-based
	printf("\033[%d;%dH", y + 1, x + 1);
}
}

/// Get cursor position.
///
/// Coordonates start at zero.
/// Params:
///   x = X position (horizontal, columns)
///   y = Y position (vertical, rows)
void congetxy(int *x, int *y) {
version (Windows) { // 0-based
	CONSOLE_SCREEN_BUFFER_INFO csbi = void;
	GetConsoleScreenBufferInfo(handleOut, &csbi);
	*x = csbi.dwCursorPosition.X;
	*y = csbi.dwCursorPosition.Y;
} else version (Posix) { // 1-based
	tcsetattr(STDIN_FILENO, TCSANOW, &new_tio);
	printf("\033[6n");
	scanf("\033[%d;%dR", y, x); // row, col
	tcsetattr(STDIN_FILENO, TCSANOW, &old_tio);
	--*x;
	--*y;
}
}

//
// ANCHOR Terminal input
//

/// Read a single immediate terminal/console event such as a keyboard or mouse.
///
/// Window resize events are handled externally.
/// Windows: User handler function called if EventType is WINDOW_BUFFER_SIZE_EVENT.
/// Posix: Handled externally via the SIGWINCH signal.
/// Params: ii = InputInfo structure
void conrdkey(InputInfo *ii) {
	ii.type = InputType.None;
version (Windows) {
	INPUT_RECORD ir = void;
	DWORD d = void;
L_READ_AGAIN:
	if (ReadConsoleInput(handleIn, &ir, 1, &d) == FALSE)
		return;

	switch (ir.EventType) {
	case KEY_EVENT:
		if (ir.KeyEvent.bKeyDown == FALSE)
			goto L_READ_AGAIN;
		
		with (ii) {
		type = InputType.Key;
		const DWORD state = ir.KeyEvent.dwControlKeyState;
		key.alt   = (state & ALT_PRESSED)   != 0;
		key.ctrl  = (state & CTRL_PRESSED)  != 0;
		key.shift = (state & SHIFT_PRESSED) != 0;
		key.keyChar = ir.KeyEvent.AsciiChar;
		key.keyCode = key.ctrl ?
			cast(Key)ir.KeyEvent.AsciiChar :
			cast(Key)ir.KeyEvent.wVirtualKeyCode;
		}
		break;
	case MOUSE_EVENT: //TODO: MOUSE_EVENT
		ii.type = InputType.Mouse;
		break;
	case WINDOW_BUFFER_SIZE_EVENT:
		with (ir)
		if (term_resize_handler)
			term_resize_handler(
				WindowBufferSizeEvent.dwSize.X,
				WindowBufferSizeEvent.dwSize.Y);
		FlushConsoleInputBuffer(handleIn);
		break;
	default: // Menu and Focus events
		goto L_READ_AGAIN;
	}
} else version (Posix) {
	//TODO: Get modifier keys states
	//TODO: See console_ioctl for KDGETKEYCODE
	//      https://linux.die.net/man/4/console_ioctl

	ii.type = InputType.Key;

	tcsetattr(STDIN_FILENO, TCSANOW, &new_tio);
	scope(exit) tcsetattr(STDIN_FILENO, TCSANOW, &old_tio);

	uint c = getchar;

	with (ii.key)
	switch (c) {
	case 0: keyCode = Key.Null; return;
	case 1: keyCode = Key.HeadingStart; return;
	case 2: keyCode = Key.TextStart; return;
	case 3: /* ^C */ keyCode = Key.TextEnd; return;
	case 4: /* ^D */ keyCode = Key.TransmissionEnd; return;
	case 5: keyCode = Key.Enquiry; return;
	case 6: keyCode = Key.Acknowledge; return;
	case 7: keyCode = Key.Bell; return;
	case '\n', '\r': // \n (RETURN) or \r (ENTER)
		keyCode = Key.Enter;
		return;
	case 27: // ESC
		switch (c = getchar) {
		case '[':
			switch (c = getchar) {
			case 'A': keyCode = Key.UpArrow; return;
			case 'B': keyCode = Key.DownArrow; return;
			case 'C': keyCode = Key.RightArrow; return;
			case 'D': keyCode = Key.LeftArrow; return;
			case 'F': keyCode = Key.End; return;
			case 'H': keyCode = Key.Home; return;
			// There is an additional getchar due to the pending '~'
			case '2': keyCode = Key.Insert; getchar; return;
			case '3': keyCode = Key.Delete; getchar; return;
			case '5': keyCode = Key.PageUp; getchar; return;
			case '6': keyCode = Key.PageDown; getchar; return;
			default: goto L_DEFAULT;
			} // [
		default: goto L_DEFAULT;
		} // ESC
	case 0x08, 0x7F: // backspace
		keyCode = Key.Backspace;
		return;
	case 23: // #
		keyCode = Key.NumSign;
		keyChar = '#';
		return;
	default:
		if (c >= 'a' && c <= 'z') {
			keyCode = cast(Key)(c - 32);
			keyChar = cast(char)c;
			return;
		} else if (c >= 20 && c <= 126) {
			keyCode = cast(Key)c;
			keyChar = cast(char)c;
			return;
		}
	}

L_DEFAULT:
	ii.key.keyCode = cast(Key)c;
} // version (Posix)
}

/// Read a line from stdin.
/// Returns: Character slice; Or null on error.
char[] conrdln() {
	import core.stdc.ctype : isprint;
	
	// GNU readline has this set to 512
	enum BUFFERSIZE = 1024;
	
	__gshared char* buffer;
	
	if (buffer == null) {
		buffer = cast(char*)malloc(BUFFERSIZE);
		if (buffer == null)
			return null;
	}
	
	// NOTE: stdin is line-buffered by the host console in their own buffer.
	//       Hitting return or enter makes the console write its buffer to stdin.
	//       Reading stdin, we copy until we see a newline.
	size_t len;
	while (len < BUFFERSIZE) {
		int c = getchar();
		if (c == '\n' || c == EOF)
			break;
		buffer[len++] = cast(char)c;
	}
	buffer[len] = 0;
	return buffer[0..len];
}

/// Key information structure
struct KeyInfo {
	Key  keyCode;	/// Key keyCode.
	char keyChar;	/// Character.
	bool ctrl;	/// If either CTRL was held down.
	bool alt;	/// If either ALT was held down.
	bool shift;	/// If SHIFT was held down.
}
/// Mouse input event structure
struct MouseInfo {
	ushort x, y;
}
/// Global input event structure
struct InputInfo {
	InputType type;	/// Input event type, can only be mouse or key
	union {
		KeyInfo key;	/// Keyboard event structure
		MouseInfo mouse;	/// Mouse event structure
	}
}

/// Input type for InputInfo structure
enum InputType : ushort {
	None, Key, Mouse
}
/*
enum MouseButton : ushort { // Windows compilant
	Left = 1, Right = 2, Middle = 4, Mouse4 = 8, Mouse5 = 16
}

enum MouseState : ushort { // Windows compilant
	RightAlt = 1, LeftAlt = 2, RightCtrl = 4,
	LeftCtrl = 8, Shift = 0x10, NumLock = 0x20,
	ScrollLock = 0x40, CapsLock = 0x80, EnhancedKey = 0x100
}

enum MouseEventType { // Windows compilant
	Moved = 1, DoubleClick = 2, Wheel = 4, HorizontalWheel = 8
}
*/
/// Key codes mapping.
//TODO: Redo keycodes
//      < 128: ascii map
//      >=128: special codes (e.g., arrow keys)
enum Key : short {
	Null = 0,	/// ^@, NUL
	HeadingStart = 1,	// ^A, SOH
	TextStart = 2,	/// ^B, STX
	TextEnd = 3,	/// ^C, ETX
	TransmissionEnd = 4,	/// ^D, EOT
	Enquiry = 5, 	/// ^E, ENQ
	Acknowledge = 6,	/// ^F, ACK
	Bell = 7,	/// ^G, BEL
	Backspace = 8,	/// ^H, BS
	Tab = 9,	/// ^I, HT
	LineFeed = 10,	/// ^J, LF
	VerticalTab = 11,	/// ^K, VT
	FormFeed = 12,	/// ^L, FF
	Enter = 13,	/// ^M, CR (return key)
	Pause = 19,
	Escape = 27,
	Spacebar = 32,
	PageUp = 33,
	PageDown = 34,
	End = 35,
	Home = 36,
	LeftArrow = 37,
	UpArrow = 38,
	RightArrow = 39,
	DownArrow = 40,
	Select = 41,
	Print = 42,
	Execute = 43,
	PrintScreen = 44,
	Insert = 45,
	Delete = 46,
	Help = 47,
	D0 = 48,
	D1 = 49,
	D2 = 50,
	D3 = 51,
	D4 = 52,
	D5 = 53,
	D6 = 54,
	D7 = 55,
	D8 = 56,
	D9 = 57,
	A = 65,
	B = 66,
	C = 67,
	D = 68,
	E = 69,
	F = 70,
	G = 71,
	H = 72,
	I = 73,
	J = 74,
	K = 75,
	L = 76,
	M = 77,
	N = 78,
	O = 79,
	P = 80,
	Q = 81,
	R = 82,
	S = 83,
	T = 84,
	U = 85,
	V = 86,
	W = 87,
	X = 88,
	Y = 89,
	Z = 90,
	LeftMeta = 91,
	RightMeta = 92,
	Applications = 93,
	Sleep = 95,
	NumPad0 = 96,
	NumPad1 = 97,
	NumPad2 = 98,
	NumPad3 = 99,
	NumPad4 = 100,
	NumPad5 = 101,
	NumPad6 = 102,
	NumPad7 = 103,
	NumPad8 = 104,
	NumPad9 = 105,
	Multiply = 106,
	Add = 107,
	Separator = 108,
	Subtract = 109,
	Decimal = 110,
	Divide = 111,
	F1 = 112,
	F2 = 113,
	F3 = 114,
	F4 = 115,
	F5 = 116,
	F6 = 117,
	F7 = 118,
	F8 = 119,
	F9 = 120,
	F10 = 121,
	F11 = 122,
	F12 = 123,
	F13 = 124,
	F14 = 125,
	F15 = 126,
	F16 = 127,
	F17 = 128,
	F18 = 129,
	F19 = 130,
	F20 = 131,
	F21 = 132,
	F22 = 133,
	F23 = 134,
	F24 = 135,
	BrowserBack = 166,
	BrowserForward = 167,
	BrowserRefresh = 168,
	BrowserStop = 169,
	BrowserSearch = 170,
	BrowserFavorites = 171,
	BrowserHome = 172,
	VolumeMute = 173,
	VolumeDown = 174,
	VolumeUp = 175,
	MediaNext = 176,
	MediaPrevious = 177,
	MediaStop = 178,
	MediaPlay = 179,
	LaunchMail = 180,
	LaunchMediaSelect = 181,
	LaunchApp1 = 182,
	LaunchApp2 = 183,
	Oem1 = 186,
	OemPlus = 187,
	OemComma = 188,
	OemMinus = 189,
	OemPeriod = 190,
	Oem2 = 191,
	Oem3 = 192,
	Oem4 = 219,
	Oem5 = 220,
	Oem6 = 221,
	Oem7 = 222,
	Oem8 = 223,
	Oem102 = 226,
	Process = 229,
	Packet = 231,
	Attention = 246,
	CrSel = 247,
	ExSel = 248,
	EraseEndOfFile = 249,
	Play = 250,
	Zoom = 251,
	NumSign = 252,	/// #
	Pa1 = 253,
	OemClear = 254
}