Newer
Older
ddhx / src / ddcon.d
/*
 * ddcon.d : In-house console library
 */

module ddcon;

///
extern (C) int putchar(int);
///
private extern (C) int getchar();

private import core.stdc.stdio : printf;
private alias sys = core.stdc.stdlib.system;

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 enum DEFAULT_COLOR =
		FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED;
	private __gshared HANDLE hIn, hOut;
	private __gshared USHORT defaultColor = DEFAULT_COLOR;
}
version (Posix) {
	private import core.sys.posix.sys.ioctl;
	private import core.sys.posix.unistd;
	private import core.sys.posix.termios;
	private enum TERM_ATTR = ~(ICANON | ECHO);
	private __gshared termios old_tio, new_tio;
}

extern (C):

/// Initiate ddcon
void coninit() {
	version (Windows) {
		hOut = GetStdHandle(STD_OUTPUT_HANDLE);
		hIn  = GetStdHandle(STD_INPUT_HANDLE);
		SetConsoleMode(hIn, ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT);
	}
	version (Posix) {
		tcgetattr(STDIN_FILENO, &old_tio);
		new_tio = old_tio;
		new_tio.c_lflag &= TERM_ATTR;
	}
}

/// Clear screen
void conclear() {
	version (Windows) {
		CONSOLE_SCREEN_BUFFER_INFO csbi = void;
		COORD c;
		GetConsoleScreenBufferInfo(hOut, &csbi);
		const int size = csbi.dwSize.X * csbi.dwSize.Y;
		DWORD num;
		if (FillConsoleOutputCharacterA(hOut, ' ', size, c, &num) == 0
			/*|| // .NET uses this but no idea why yet.
			FillConsoleOutputAttribute(hOut, csbi.wAttributes, size, c, &num) == 0*/) {
			conpos(0, 0);
		}
		else // If that fails, run cls.
			sys ("cls");
	} else version (Posix) {
		printf("\033c");
	}
	else static assert(0, "Clear: Not implemented");
}

/// Window width
/// Returns: Window width in characters
@property ushort conwidth() {
	// Note: A COORD uses SHORT (short) and Linux uses unsigned shorts.
	version (Windows) {
		CONSOLE_SCREEN_BUFFER_INFO c = void;
		GetConsoleScreenBufferInfo(hOut, &c);
		return cast(ushort)(c.srWindow.Right - c.srWindow.Left + 1);
	} else version (Posix) {
		winsize ws = void;
		ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
		return ws.ws_col;
	} else {
		static assert(0, "WindowWidth : Not implemented");
	}
}

/// Window height
/// Returns: Window height in characters
@property ushort conheight() {
	version (Windows) {
		CONSOLE_SCREEN_BUFFER_INFO c = void;
		GetConsoleScreenBufferInfo(hOut, &c);
		return cast(ushort)(c.srWindow.Bottom - c.srWindow.Top + 1);
	} else version (Posix) {
		winsize ws = void;
		ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
		return ws.ws_row;
	} else {
		static assert(0, "WindowHeight : Not implemented");
	}
}

/**
 * Set cursor position x and y position respectively from the top left corner,
 * 0-based.
 * Params:
 *   x = X position (horizontal)
 *   y = Y position (vertical)
 */
void conpos(int x, int y) {
	version (Windows) { // 0-based
		__gshared COORD c = void;
		c.X = cast(short)x;
		c.Y = cast(short)y;
		SetConsoleCursorPosition(hOut, c);
	} else version (Posix) { // 1-based
		printf("\033[%d;%dH", ++y, ++x);
	}
}

/**
 * Read an input event. This function is blocking.
 * Params:
 *   k = InputInfo struct
 */
void coninput(ref InputInfo k) {
	version (Windows) {
		INPUT_RECORD ir = void;
		DWORD num = void;
L_READ:
		if (ReadConsoleInput(hIn, &ir, 1, &num) == 0)
			goto L_READ;
		// Despite being bit fields, a switch is recommended
		switch (ir.EventType) {
		case KEY_EVENT:
			if (ir.KeyEvent.bKeyDown == FALSE)
				goto L_READ;
			const DWORD state = ir.KeyEvent.dwControlKeyState;
			k.key.alt   = (state & ALT_PRESSED)   != 0;
			k.key.ctrl  = (state & CTRL_PRESSED)  != 0;
			k.key.shift = (state & SHIFT_PRESSED) != 0;
			k.value = ir.KeyEvent.wVirtualKeyCode;
			return;
		case MOUSE_EVENT:
			switch (ir.MouseEvent.dwEventFlags) {
			case MOUSE_WHEELED:
				// Up=0x00780000 Down=0xFF880000
				k.value = ir.MouseEvent.dwButtonState > 0xFF_0000 ?
					Mouse.ScrollDown : Mouse.ScrollUp;
				return;
			default: goto L_READ;
			}
		default: goto L_READ;
		}
	} else version (Posix) {
		//TODO: Get modifier keys states

		// Commenting this section will echo the character and make
		// getchar unusable
		tcsetattr(STDIN_FILENO, TCSANOW, &new_tio);

		int c = getchar;

		with (k) switch (c) {
		case '\n': // \n (ENTER)
			value = Key.Enter;
			goto _READKEY_END;
		case 27: // ESC
			switch (c = getchar) {
			case '[':
				switch (c = getchar) {
				case 'A': value = Key.UpArrow; goto _READKEY_END;
				case 'B': value = Key.DownArrow; goto _READKEY_END;
				case 'C': value = Key.RightArrow; goto _READKEY_END;
				case 'D': value = Key.LeftArrow; goto _READKEY_END;
				case 'F': value = Key.End; goto _READKEY_END;
				case 'H': value = Key.Home; goto _READKEY_END;
				// There is an additional getchar due to the pending '~'
				case '2': value = Key.Insert; getchar; goto _READKEY_END;
				case '3': value = Key.Delete; getchar; goto _READKEY_END;
				case '5': value = Key.PageUp; getchar; goto _READKEY_END;
				case '6': value = Key.PageDown; getchar; goto _READKEY_END;
				default: goto _READKEY_DEFAULT;
				} // [
			default: goto _READKEY_DEFAULT;
			} // ESC
		default:
			if (c >= 'a' && c <= 'z') {
				k.value = cast(Key)(c - 32);
				goto _READKEY_END;
			}
		}

_READKEY_DEFAULT:
		k.value = cast(ushort)c;

_READKEY_END:
		tcsetattr(STDIN_FILENO, TCSANOW, &old_tio);
	} // version posix
}

/// Key codes mapping.
enum Key : ushort {
	Undefined = 0,
	Backspace = 8,
	Tab = 9,
	Clear = 12,
	Enter = 13,
	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,
	Colon = 58,
	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,
	NoName = 252,
	Pa1 = 253,
	OemClear = 254
}
enum Mouse : ushort {
	Click	= 0x100,
	ScrollUp	= 0x200,
	ScrollDown	= 0x300,
}

/*******************************************************************
 * Structs
 *******************************************************************/

/// Key information structure
struct InputInfo {
	ushort value;	/// Character or mouse event
	union {
		struct key_t {
			ubyte ctrl;	/// If either CTRL was held down.
			ubyte alt;	/// If either ALT was held down.
			ubyte shift;	/// If SHIFT was held down.
		}
		struct mouse_t {
			ushort x, y;
		}
		key_t key;
		mouse_t mouse;
	}
}

/// 
struct WindowSize {
	/// 
	ushort Width, Height;
}