mudgangster

Log | Files | Refs

commit 7ce09b3b89a5c22e132fbeb19457f3cb3e71dc54
parent 49cd18b9bf49dee6c2c9d13bba52fe50f7998505
Author: Michael Savage <mikejsavage@gmail.com>
Date:   Mon,  3 Sep 2018 15:12:15 +0300

Switch to C++, better text representation

Diffstat:
Makefile | 16+++++++++-------
src/common.h | 50+++++++++++++++++++++++++-------------------------
src/input.c | 210-------------------------------------------------------------------------------
src/input.cc | 210+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/main.c | 29-----------------------------
src/main.cc | 29+++++++++++++++++++++++++++++
src/script.c | 235-------------------------------------------------------------------------------
src/script.cc | 200+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/script.h | 4++--
src/textbox.c | 209-------------------------------------------------------------------------------
src/textbox.cc | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/textbox.h | 73+++++++++++++++++++++++++++++++++----------------------------------------
src/ui.c | 461-------------------------------------------------------------------------------
src/ui.cc | 401+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
14 files changed, 1034 insertions(+), 1218 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,13 +1,15 @@ -CC = gcc SRCDIR = src -CFLAGS = -O2 -std=gnu99 $(shell pkg-config --cflags lua5.1) -LIBS = -lX11 -L/usr/X11R6/lib $(shell pkg-config --libs lua5.1) -WARNINGS = -Wall -Wextra -Werror +CXXFLAGS = -std=c++11 $(shell pkg-config --cflags lua51) -O0 -ggdb +LIBS = -lm -lX11 -L/usr/X11R6/lib $(shell pkg-config --libs lua51) +WARNINGS = -Wall -Wextra -Wno-unused-parameter -Wno-unused-function -Wshadow -Wcast-align -Wstrict-overflow -Wvla OBJS = main.o textbox.o input.o ui.o script.o .PHONY: all all: ${OBJS} - ${CC} -o mudGangster ${OBJS} ${CFLAGS} ${LIBS} ${WARNINGS} + ${CXX} -o mudGangster ${OBJS} ${CXXFLAGS} ${LIBS} ${WARNINGS} -%.o: ${SRCDIR}/%.c - ${CC} -c ${SRCDIR}/$*.c ${CFLAGS} ${LIBS} ${WARNINGS} +%.o: ${SRCDIR}/%.cc + ${CXX} -c ${SRCDIR}/$*.cc ${CXXFLAGS} ${LIBS} ${WARNINGS} + +clean: + rm -f mudGangster ${OBJS} diff --git a/src/common.h b/src/common.h @@ -1,16 +1,12 @@ -#ifndef _COMMON_H_ -#define _COMMON_H_ - -typedef enum { false, true } bool; +#pragma once #include <X11/Xlib.h> #include "config.h" #include "textbox.h" -struct -{ - Display* display; +struct UIDefs { + Display * display; int screen; GC gc; @@ -18,18 +14,19 @@ struct Window window; - TextBox* textMain; - TextBox* textChat; + TextBox textMain; + TextBox textChat; int width; int height; int depth; int hasFocus; -} UI; +}; + +extern UIDefs UI; -typedef struct -{ +struct MudFont { int ascent; int descent; @@ -39,11 +36,10 @@ typedef struct int height; int width; - XFontStruct* font; -} MudFont; + XFontStruct * font; +}; -struct -{ +struct StyleDefs { ulong bg; ulong fg; @@ -60,10 +56,8 @@ struct MudFont font; MudFont fontBold; - union - { - struct - { + union { + struct { ulong black; ulong red; ulong green; @@ -87,12 +81,18 @@ struct ulong colours[ 2 ][ 8 ]; }; -} Style; +}; -#endif // _COMMON_H_ +extern StyleDefs Style; -#define PRETEND_TO_USE( x ) ( void ) ( x ) #define STRL( x ) ( x ), sizeof( x ) - 1 -#define MIN( a, b ) ( ( a ) < ( b ) ? ( a ) : ( b ) ) -#define MAX( a, b ) ( ( a ) > ( b ) ? ( a ) : ( b ) ) +template< typename T > +constexpr T min( T a, T b ) { + return a < b ? a : b; +} + +template< typename T > +constexpr T max( T a, T b ) { + return a > b ? a : b; +} diff --git a/src/input.c b/src/input.c @@ -1,210 +0,0 @@ -#include <stdlib.h> -#include <string.h> - -#include "common.h" -#include "input.h" -#include "script.h" - -typedef struct -{ - char* text; - int len; -} InputHistory; - -static InputHistory inputHistory[ MAX_INPUT_HISTORY ]; -static int inputHistoryHead = 0; -static int inputHistoryCount = 0; -static int inputHistoryDelta = 0; - -static char* inputBuffer = NULL; -static char* starsBuffer = NULL; - -static int inputBufferSize = 256; - -static int inputLen = 0; -static int inputPos = 0; - -void input_send() -{ - if( inputLen > 0 ) - { - InputHistory* lastCmd = &inputHistory[ ( inputHistoryHead + inputHistoryCount - 1 ) % MAX_INPUT_HISTORY ]; - - if( inputLen != lastCmd->len || strncmp( inputBuffer, lastCmd->text, inputLen ) != 0 ) - { - int pos = ( inputHistoryHead + inputHistoryCount ) % MAX_INPUT_HISTORY; - - if( inputHistoryCount == MAX_INPUT_HISTORY ) - { - free( inputHistory[ pos ].text ); - - inputHistoryHead = ( inputHistoryHead + 1 ) % MAX_INPUT_HISTORY; - } - else - { - inputHistoryCount++; - } - - inputHistory[ pos ].text = malloc( inputLen ); - - memcpy( inputHistory[ pos ].text, inputBuffer, inputLen ); - inputHistory[ pos ].len = inputLen; - } - } - - script_handleInput( inputBuffer, inputLen ); - - inputHistoryDelta = 0; - - inputLen = 0; - inputPos = 0; - - input_draw(); -} - -void input_backspace() -{ - if( inputPos > 0 ) - { - memmove( inputBuffer + inputPos - 1, inputBuffer + inputPos, inputLen - inputPos ); - - inputLen--; - inputPos--; - } - - input_draw(); -} - -void input_delete() -{ - if( inputPos < inputLen ) - { - memmove( inputBuffer + inputPos, inputBuffer + inputPos + 1, inputLen - inputPos ); - - inputLen--; - } - - input_draw(); -} - -void input_up() -{ - if( inputHistoryDelta >= inputHistoryCount ) - { - return; - } - - inputHistoryDelta++; - int pos = ( inputHistoryHead + inputHistoryCount - inputHistoryDelta ) % MAX_INPUT_HISTORY; - - InputHistory cmd = inputHistory[ pos ]; - - memcpy( inputBuffer, cmd.text, cmd.len ); - - inputLen = cmd.len; - inputPos = cmd.len; - - input_draw(); -} - -void input_down() -{ - if( inputHistoryDelta == 0 ) - { - return; - } - - inputHistoryDelta--; - - if( inputHistoryDelta != 0 ) - { - int pos = ( inputHistoryHead + inputHistoryCount - inputHistoryDelta ) % MAX_INPUT_HISTORY; - - InputHistory cmd = inputHistory[ pos ]; - - memcpy( inputBuffer, cmd.text, cmd.len ); - - inputLen = cmd.len; - inputPos = cmd.len; - } - else - { - inputLen = 0; - inputPos = 0; - } - - input_draw(); -} - -void input_left() -{ - inputPos = MAX( inputPos - 1, 0 ); - - input_draw(); -} - -void input_right() -{ - inputPos = MIN( inputPos + 1, inputLen ); - - input_draw(); -} - -void input_add( char* buffer, int len ) -{ - if( inputLen + len >= inputBufferSize ) - { - inputBufferSize *= 2; - - inputBuffer = realloc( inputBuffer, inputBufferSize ); - starsBuffer = realloc( starsBuffer, inputBufferSize ); - - memset( starsBuffer + inputBufferSize / 2, '*', inputBufferSize / 2 ); - } - - if( inputPos < inputLen ) - { - memmove( inputBuffer + inputPos + len, inputBuffer + inputPos, inputLen - inputPos ); - } - - memcpy( inputBuffer + inputPos, buffer, len ); - - inputLen += len; - inputPos += len; - - input_draw(); -} - -void input_draw() -{ - XSetFont( UI.display, UI.gc, Style.font.font->fid ); - - XSetForeground( UI.display, UI.gc, Style.bg ); - XFillRectangle( UI.display, UI.window, UI.gc, PADDING, UI.height - ( PADDING + Style.font.height ), UI.width - 6, Style.font.height ); - - XSetForeground( UI.display, UI.gc, Style.fg ); - XDrawString( UI.display, UI.window, UI.gc, PADDING, UI.height - ( PADDING + Style.font.descent ), inputBuffer, inputLen ); - - XSetForeground( UI.display, UI.gc, Style.cursor ); - XFillRectangle( UI.display, UI.window, UI.gc, PADDING + Style.font.width * inputPos, UI.height - ( PADDING + Style.font.height ), Style.font.width, Style.font.height ); - - if( inputPos < inputLen ) - { - XSetForeground( UI.display, UI.gc, Style.bg ); - XDrawString( UI.display, UI.window, UI.gc, PADDING + Style.font.width * inputPos, UI.height - ( PADDING + Style.font.descent ), inputBuffer + inputPos, 1 ); - } -} - -void input_init() -{ - inputBuffer = malloc( inputBufferSize ); - starsBuffer = malloc( inputBufferSize ); - - memset( starsBuffer, '*', inputBufferSize ); -} - -void input_end() -{ - free( inputBuffer ); - free( starsBuffer ); -} diff --git a/src/input.cc b/src/input.cc @@ -0,0 +1,210 @@ +#include <stdlib.h> +#include <string.h> + +#include "common.h" +#include "input.h" +#include "script.h" + +typedef struct +{ + char* text; + int len; +} InputHistory; + +static InputHistory inputHistory[ MAX_INPUT_HISTORY ]; +static int inputHistoryHead = 0; +static int inputHistoryCount = 0; +static int inputHistoryDelta = 0; + +static char* inputBuffer = NULL; +static char* starsBuffer = NULL; + +static int inputBufferSize = 256; + +static int inputLen = 0; +static int inputPos = 0; + +void input_send() +{ + if( inputLen > 0 ) + { + InputHistory* lastCmd = &inputHistory[ ( inputHistoryHead + inputHistoryCount - 1 ) % MAX_INPUT_HISTORY ]; + + if( inputLen != lastCmd->len || strncmp( inputBuffer, lastCmd->text, inputLen ) != 0 ) + { + int pos = ( inputHistoryHead + inputHistoryCount ) % MAX_INPUT_HISTORY; + + if( inputHistoryCount == MAX_INPUT_HISTORY ) + { + free( inputHistory[ pos ].text ); + + inputHistoryHead = ( inputHistoryHead + 1 ) % MAX_INPUT_HISTORY; + } + else + { + inputHistoryCount++; + } + + inputHistory[ pos ].text = ( char * ) malloc( inputLen ); + + memcpy( inputHistory[ pos ].text, inputBuffer, inputLen ); + inputHistory[ pos ].len = inputLen; + } + } + + script_handleInput( inputBuffer, inputLen ); + + inputHistoryDelta = 0; + + inputLen = 0; + inputPos = 0; + + input_draw(); +} + +void input_backspace() +{ + if( inputPos > 0 ) + { + memmove( inputBuffer + inputPos - 1, inputBuffer + inputPos, inputLen - inputPos ); + + inputLen--; + inputPos--; + } + + input_draw(); +} + +void input_delete() +{ + if( inputPos < inputLen ) + { + memmove( inputBuffer + inputPos, inputBuffer + inputPos + 1, inputLen - inputPos ); + + inputLen--; + } + + input_draw(); +} + +void input_up() +{ + if( inputHistoryDelta >= inputHistoryCount ) + { + return; + } + + inputHistoryDelta++; + int pos = ( inputHistoryHead + inputHistoryCount - inputHistoryDelta ) % MAX_INPUT_HISTORY; + + InputHistory cmd = inputHistory[ pos ]; + + memcpy( inputBuffer, cmd.text, cmd.len ); + + inputLen = cmd.len; + inputPos = cmd.len; + + input_draw(); +} + +void input_down() +{ + if( inputHistoryDelta == 0 ) + { + return; + } + + inputHistoryDelta--; + + if( inputHistoryDelta != 0 ) + { + int pos = ( inputHistoryHead + inputHistoryCount - inputHistoryDelta ) % MAX_INPUT_HISTORY; + + InputHistory cmd = inputHistory[ pos ]; + + memcpy( inputBuffer, cmd.text, cmd.len ); + + inputLen = cmd.len; + inputPos = cmd.len; + } + else + { + inputLen = 0; + inputPos = 0; + } + + input_draw(); +} + +void input_left() +{ + inputPos = max( inputPos - 1, 0 ); + + input_draw(); +} + +void input_right() +{ + inputPos = min( inputPos + 1, inputLen ); + + input_draw(); +} + +void input_add( char* buffer, int len ) +{ + if( inputLen + len >= inputBufferSize ) + { + inputBufferSize *= 2; + + inputBuffer = ( char * ) realloc( inputBuffer, inputBufferSize ); + starsBuffer = ( char * ) realloc( starsBuffer, inputBufferSize ); + + memset( starsBuffer + inputBufferSize / 2, '*', inputBufferSize / 2 ); + } + + if( inputPos < inputLen ) + { + memmove( inputBuffer + inputPos + len, inputBuffer + inputPos, inputLen - inputPos ); + } + + memcpy( inputBuffer + inputPos, buffer, len ); + + inputLen += len; + inputPos += len; + + input_draw(); +} + +void input_draw() +{ + XSetFont( UI.display, UI.gc, Style.font.font->fid ); + + XSetForeground( UI.display, UI.gc, Style.bg ); + XFillRectangle( UI.display, UI.window, UI.gc, PADDING, UI.height - ( PADDING + Style.font.height ), UI.width - 6, Style.font.height ); + + XSetForeground( UI.display, UI.gc, Style.fg ); + XDrawString( UI.display, UI.window, UI.gc, PADDING, UI.height - ( PADDING + Style.font.descent ), inputBuffer, inputLen ); + + XSetForeground( UI.display, UI.gc, Style.cursor ); + XFillRectangle( UI.display, UI.window, UI.gc, PADDING + Style.font.width * inputPos, UI.height - ( PADDING + Style.font.height ), Style.font.width, Style.font.height ); + + if( inputPos < inputLen ) + { + XSetForeground( UI.display, UI.gc, Style.bg ); + XDrawString( UI.display, UI.window, UI.gc, PADDING + Style.font.width * inputPos, UI.height - ( PADDING + Style.font.descent ), inputBuffer + inputPos, 1 ); + } +} + +void input_init() +{ + inputBuffer = ( char * ) malloc( inputBufferSize ); + starsBuffer = ( char * ) malloc( inputBufferSize ); + + memset( starsBuffer, '*', inputBufferSize ); +} + +void input_end() +{ + free( inputBuffer ); + free( starsBuffer ); +} diff --git a/src/main.c b/src/main.c @@ -1,29 +0,0 @@ -#include <stdio.h> -#include <stdlib.h> -#include <unistd.h> -#include <string.h> - -#include <X11/Xutil.h> -#include <X11/XKBlib.h> -#include <X11/cursorfont.h> -#include <X11/keysym.h> - -#include "common.h" -#include "script.h" -#include "input.h" -#include "ui.h" - -int main() -{ - ui_init(); - input_init(); - script_init(); - - // main loop is done in lua - - script_end(); - input_end(); - ui_end(); - - return EXIT_SUCCESS; -} diff --git a/src/main.cc b/src/main.cc @@ -0,0 +1,29 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +#include <X11/Xutil.h> +#include <X11/XKBlib.h> +#include <X11/cursorfont.h> +#include <X11/keysym.h> + +#include "common.h" +#include "script.h" +#include "input.h" +#include "ui.h" + +int main() +{ + ui_init(); + input_init(); + script_init(); + + // main loop is done in lua + + script_end(); + input_end(); + ui_end(); + + return EXIT_SUCCESS; +} diff --git a/src/script.c b/src/script.c @@ -1,235 +0,0 @@ -#include <stdlib.h> -#include <assert.h> - -#include <lua.h> -#include <lualib.h> -#include <lauxlib.h> - -#include <X11/Xutil.h> - -#include "common.h" -#include "ui.h" - -static lua_State* L; - -static int inputHandlerIdx = LUA_NOREF; -static int macroHandlerIdx = LUA_NOREF; -static int closeHandlerIdx = LUA_NOREF; - -void script_handleInput( char* buffer, int len ) -{ - assert( inputHandlerIdx != LUA_NOREF ); - - lua_rawgeti( L, LUA_REGISTRYINDEX, inputHandlerIdx ); - lua_pushlstring( L, buffer, len ); - - lua_call( L, 1, 0 ); -} - -void script_doMacro( char* key, int len, bool shift, bool ctrl, bool alt ) -{ - lua_rawgeti( L, LUA_REGISTRYINDEX, macroHandlerIdx ); - - lua_pushlstring( L, key, len ); - - lua_pushboolean( L, shift ); - lua_pushboolean( L, ctrl ); - lua_pushboolean( L, alt ); - - lua_call( L, 4, 0 ); -} - -void script_handleClose() -{ - assert( closeHandlerIdx != LUA_NOREF ); - - lua_rawgeti( L, LUA_REGISTRYINDEX, closeHandlerIdx ); - - lua_call( L, 0, 0 ); -} - -static int mud_handleXEvents( lua_State* L ) -{ - PRETEND_TO_USE( L ); - - ui_handleXEvents(); - - return 0; -} - -static int mud_printMain( lua_State* L ) -{ - const char* str = luaL_checkstring( L, 1 ); - size_t len = lua_objlen( L, 1 ); - - Colour fg = luaL_checkint( L, 2 ); - Colour bg = luaL_checkint( L, 3 ); - bool bold = lua_toboolean( L, 4 ); - - textbox_add( UI.textMain, str, len, fg, bg, bold ); - - return 0; -} - -static int mud_newlineMain( lua_State* L ) -{ - PRETEND_TO_USE( L ); - - textbox_newline( UI.textMain ); - - return 0; -} - -static int mud_drawMain( lua_State* L ) -{ - PRETEND_TO_USE( L ); - - textbox_draw( UI.textMain ); - - return 0; -} - -static int mud_printChat( lua_State* L ) -{ - const char* str = luaL_checkstring( L, 1 ); - size_t len = lua_objlen( L, 1 ); - - Colour fg = luaL_checkint( L, 2 ); - Colour bg = luaL_checkint( L, 3 ); - bool bold = lua_toboolean( L, 4 ); - - textbox_add( UI.textChat, str, len, fg, bg, bold ); - - return 0; -} - -static int mud_newlineChat( lua_State* L ) -{ - PRETEND_TO_USE( L ); - - textbox_newline( UI.textChat ); - - return 0; -} - -static int mud_drawChat( lua_State* L ) -{ - PRETEND_TO_USE( L ); - - textbox_draw( UI.textChat ); - - return 0; -} - -static int mud_setStatus( lua_State* L ) -{ - luaL_argcheck( L, lua_type( L, 1 ) == LUA_TTABLE, 1, "expected function" ); - - size_t len = lua_objlen( L, 1 ); - - ui_statusClear(); - - for( size_t i = 0; i < len; i++ ) - { - lua_pushnumber( L, i + 1 ); - lua_gettable( L, 1 ); - - lua_pushliteral( L, "text" ); - lua_gettable( L, 2 ); - size_t seglen; - const char* str = lua_tolstring( L, -1, &seglen ); - - lua_pushliteral( L, "fg" ); - lua_gettable( L, 2 ); - const Colour fg = lua_tointeger( L, -1 ); - - lua_pushliteral( L, "fg" ); - lua_gettable( L, 2 ); - const bool bold = lua_toboolean( L, -1 ); - - for( size_t j = 0; j < seglen; j++ ) { - ui_statusAdd( str[ j ], fg, bold ); - } - - lua_pop( L, 4 ); - } - - ui_statusDraw(); - - return 0; -} - -static int mud_setHandlers( lua_State* L ) -{ - luaL_argcheck( L, lua_type( L, 1 ) == LUA_TFUNCTION, 1, "expected function" ); - luaL_argcheck( L, lua_type( L, 2 ) == LUA_TFUNCTION, 2, "expected function" ); - luaL_argcheck( L, lua_type( L, 3 ) == LUA_TFUNCTION, 3, "expected function" ); - - closeHandlerIdx = luaL_ref( L, LUA_REGISTRYINDEX ); - macroHandlerIdx = luaL_ref( L, LUA_REGISTRYINDEX ); - inputHandlerIdx = luaL_ref( L, LUA_REGISTRYINDEX ); - - return 0; -} - -static int mud_urgent( lua_State* L ) -{ - PRETEND_TO_USE( L ); - - if( !UI.hasFocus ) { - XWMHints* hints = XGetWMHints( UI.display, UI.window ); - hints->flags |= XUrgencyHint; - XSetWMHints( UI.display, UI.window, hints ); - XFree( hints ); - } - - return 0; -} - -void script_init() -{ - mud_handleXEvents( NULL ); - - L = lua_open(); - luaL_openlibs( L ); - - lua_getglobal( L, "debug" ); - lua_getfield( L, -1, "traceback" ); - lua_remove( L, -2 ); - - if( luaL_loadfile( L, "main.lua" ) ) - { - printf( "Error reading main.lua: %s\n", lua_tostring( L, -1 ) ); - - exit( 1 ); - } - - lua_pushinteger( L, ConnectionNumber( UI.display ) ); - lua_pushcfunction( L, mud_handleXEvents ); - - lua_pushcfunction( L, mud_printMain ); - lua_pushcfunction( L, mud_newlineMain ); - lua_pushcfunction( L, mud_drawMain ); - - lua_pushcfunction( L, mud_printChat ); - lua_pushcfunction( L, mud_newlineChat ); - lua_pushcfunction( L, mud_drawChat ); - - lua_pushcfunction( L, mud_setHandlers ); - - lua_pushcfunction( L, mud_urgent ); - - lua_pushcfunction( L, mud_setStatus ); - - if( lua_pcall( L, 11, 0, -13 ) ) - { - printf( "Error running main.lua: %s\n", lua_tostring( L, -1 ) ); - - exit( 1 ); - } -} - -void script_end() -{ - lua_close( L ); -} diff --git a/src/script.cc b/src/script.cc @@ -0,0 +1,200 @@ +#include <stdlib.h> +#include <assert.h> + +#include <lua.hpp> + +#include <X11/Xutil.h> + +#include "common.h" +#include "ui.h" + +static lua_State * lua; + +static int inputHandlerIdx = LUA_NOREF; +static int macroHandlerIdx = LUA_NOREF; +static int closeHandlerIdx = LUA_NOREF; + +void script_handleInput( const char * buffer, int len ) { + assert( inputHandlerIdx != LUA_NOREF ); + + lua_rawgeti( lua, LUA_REGISTRYINDEX, inputHandlerIdx ); + lua_pushlstring( lua, buffer, len ); + + lua_call( lua, 1, 0 ); +} + +void script_doMacro( const char * key, int len, bool shift, bool ctrl, bool alt ) { + lua_rawgeti( lua, LUA_REGISTRYINDEX, macroHandlerIdx ); + + lua_pushlstring( lua, key, len ); + + lua_pushboolean( lua, shift ); + lua_pushboolean( lua, ctrl ); + lua_pushboolean( lua, alt ); + + lua_call( lua, 4, 0 ); +} + +void script_handleClose() { + assert( closeHandlerIdx != LUA_NOREF ); + + lua_rawgeti( lua, LUA_REGISTRYINDEX, closeHandlerIdx ); + + lua_call( lua, 0, 0 ); +} + +// namespace { + +extern "C" int mud_handleXEvents( lua_State * ) { + ui_handleXEvents(); + return 0; +} + +static void generic_print( TextBox * tb, lua_State * L ) { + const char* str = luaL_checkstring( L, 1 ); + size_t len = lua_objlen( L, 1 ); + + Colour fg = Colour( luaL_checkinteger( L, 2 ) ); + Colour bg = Colour( luaL_checkinteger( L, 3 ) ); + bool bold = lua_toboolean( L, 4 ); + + textbox_add( tb, str, len, fg, bg, bold ); +} + +extern "C" int mud_printMain( lua_State * L ) { + generic_print( &UI.textMain, L ); + return 0; +} + +extern "C" int mud_newlineMain( lua_State * L ) { + textbox_newline( &UI.textMain ); + return 0; +} + +extern "C" int mud_drawMain( lua_State * L ) { + textbox_draw( &UI.textMain ); + return 0; +} + +extern "C" int mud_printChat( lua_State * L ) { + generic_print( &UI.textChat, L ); + return 0; +} + +extern "C" int mud_newlineChat( lua_State * L ) { + textbox_newline( &UI.textChat ); + return 0; +} + +extern "C" int mud_drawChat( lua_State * L ) { + textbox_draw( &UI.textChat ); + return 0; +} + +extern "C" int mud_setStatus( lua_State * L ) { + luaL_argcheck( L, lua_type( L, 1 ) == LUA_TTABLE, 1, "expected function" ); + + size_t len = lua_objlen( L, 1 ); + + ui_statusClear(); + + for( size_t i = 0; i < len; i++ ) + { + lua_pushnumber( L, i + 1 ); + lua_gettable( L, 1 ); + + lua_pushliteral( L, "text" ); + lua_gettable( L, 2 ); + size_t seglen; + const char* str = lua_tolstring( L, -1, &seglen ); + + lua_pushliteral( L, "fg" ); + lua_gettable( L, 2 ); + const Colour fg = Colour( lua_tointeger( L, -1 ) ); + + lua_pushliteral( L, "fg" ); + lua_gettable( L, 2 ); + const bool bold = lua_toboolean( L, -1 ); + + for( size_t j = 0; j < seglen; j++ ) { + ui_statusAdd( str[ j ], fg, bold ); + } + + lua_pop( L, 4 ); + } + + ui_statusDraw(); + + return 0; +} + +extern "C" int mud_setHandlers( lua_State * L ) { + luaL_argcheck( L, lua_type( L, 1 ) == LUA_TFUNCTION, 1, "expected function" ); + luaL_argcheck( L, lua_type( L, 2 ) == LUA_TFUNCTION, 2, "expected function" ); + luaL_argcheck( L, lua_type( L, 3 ) == LUA_TFUNCTION, 3, "expected function" ); + + closeHandlerIdx = luaL_ref( L, LUA_REGISTRYINDEX ); + macroHandlerIdx = luaL_ref( L, LUA_REGISTRYINDEX ); + inputHandlerIdx = luaL_ref( L, LUA_REGISTRYINDEX ); + + return 0; +} + +extern "C" int mud_urgent( lua_State * L ) { + if( !UI.hasFocus ) { + XWMHints* hints = XGetWMHints( UI.display, UI.window ); + hints->flags |= XUrgencyHint; + XSetWMHints( UI.display, UI.window, hints ); + XFree( hints ); + } + + return 0; +} + +// } // anon namespace + +void script_init() { + mud_handleXEvents( NULL ); // TODO: why is this here? + + lua = lua_open(); + luaL_openlibs( lua ); + + lua_getglobal( lua, "debug" ); + lua_getfield( lua, -1, "traceback" ); + lua_remove( lua, -2 ); + + if( luaL_loadfile( lua, "main.lua" ) ) + { + printf( "Error reading main.lua: %s\n", lua_tostring( lua, -1 ) ); + + exit( 1 ); + } + + lua_pushinteger( lua, ConnectionNumber( UI.display ) ); + lua_pushcfunction( lua, mud_handleXEvents ); + + lua_pushcfunction( lua, mud_printMain ); + lua_pushcfunction( lua, mud_newlineMain ); + lua_pushcfunction( lua, mud_drawMain ); + + lua_pushcfunction( lua, mud_printChat ); + lua_pushcfunction( lua, mud_newlineChat ); + lua_pushcfunction( lua, mud_drawChat ); + + lua_pushcfunction( lua, mud_setHandlers ); + + lua_pushcfunction( lua, mud_urgent ); + + lua_pushcfunction( lua, mud_setStatus ); + + if( lua_pcall( lua, 11, 0, -13 ) ) + { + printf( "Error running main.lua: %s\n", lua_tostring( lua, -1 ) ); + + exit( 1 ); + } +} + +void script_end() { + lua_close( lua ); +} diff --git a/src/script.h b/src/script.h @@ -1,8 +1,8 @@ #ifndef _SCRIPT_H_ #define _SCRIPT_H_ -void script_handleInput( char* buffer, int len ); -void script_doMacro( char* key, int len, bool shift, bool ctrl, bool alt ); +void script_handleInput( const char * buffer, int len ); +void script_doMacro( const char * key, int len, bool shift, bool ctrl, bool alt ); void script_handleClose(); void script_init(); diff --git a/src/textbox.c b/src/textbox.c @@ -1,209 +0,0 @@ -#include <stdlib.h> -#include <string.h> -#include <assert.h> - -#include <X11/Xlib.h> - -#include "common.h" -#include <stdio.h> - -TextBox* textbox_new( unsigned int maxLines ) -{ - TextBox* textbox = calloc( sizeof( TextBox ), 1 ); - - assert( textbox != NULL ); - - textbox->lines = malloc( maxLines * sizeof( Line ) ); - textbox->numLines = -1; - textbox->maxLines = maxLines; - textbox->scrollDelta = 0; - - Line* line = &textbox->lines[ 0 ]; - - line->head = line->tail = calloc( sizeof( Text ), 1 ); - - return textbox; -} - -void textbox_freeline( TextBox* self, int idx ) -{ - Text* node = self->lines[ idx ].head; - - while( node != NULL ) - { - Text* next = node->next; - - free( node->buffer ); - free( node ); - - node = next; - } -} - -void textbox_free( TextBox* self ) -{ - for( int i = 0; i < self->numLines; i++ ) - { - textbox_freeline( self, i ); - } - - free( self ); -} - -void textbox_setpos( TextBox* self, int x, int y ) -{ - self->x = x; - self->y = y; -} - -void textbox_setsize( TextBox* self, int width, int height ) -{ - self->width = width; - self->height = height; - - self->rows = height / ( Style.font.height + SPACING ); - self->cols = width / Style.font.width; -} - -void textbox_add( TextBox* self, const char* str, unsigned int len, Colour fg, Colour bg, bool bold ) -{ - if( self->numLines == -1 ) - { - self->numLines = 0; - } - - Line* line = &self->lines[ ( self->head + self->numLines ) % self->maxLines ]; - - Text* node = malloc( sizeof( Text ) ); - node->buffer = malloc( len ); - - memcpy( node->buffer, str, len ); - node->len = len; - - node->fg = fg; - node->bg = bg; - node->bold = bold; - - node->next = NULL; - - line->tail->next = node; - line->tail = node; - - line->len += len; -} - -void textbox_newline( TextBox* self ) -{ - if( self->numLines < self->maxLines ) - { - self->numLines++; - - Line* line = &self->lines[ ( self->head + self->numLines ) % self->maxLines ]; - - line->head = line->tail = calloc( sizeof( Text ), 1 ); - - if( self->scrollDelta != 0 ) - { - self->scrollDelta++; - } - } - else - { - textbox_freeline( self, self->head ); - - Line* line = &self->lines[ self->head ]; - - line->head = line->tail = calloc( sizeof( Text ), 1 ); - - self->head = ( self->head + 1 ) % self->maxLines; - } -} - -void textbox_draw( TextBox* self ) -{ - if( self->width == 0 || self->height == 0 ) { - return; - } - - Pixmap doublebuf = XCreatePixmap( UI.display, UI.window, self->width, self->height, UI.depth ); - - XSetForeground( UI.display, UI.gc, Style.bg ); - XFillRectangle( UI.display, doublebuf, UI.gc, 0, 0, self->width, self->height ); - - int rowsRemaining = self->rows; - int linesRemaining = self->numLines - self->scrollDelta + 1; - - int lineTop = self->height; - int linesPrinted = 0; - - int firstLine = self->head + self->numLines - self->scrollDelta; - - while( rowsRemaining > 0 && linesRemaining > 0 ) - { - Line* line = &self->lines[ ( firstLine - linesPrinted ) % self->maxLines ]; - - int lineHeight = ( line->len / self->cols ); - - if( line->len % self->cols != 0 || line->len == 0 ) - { - lineHeight++; - } - - lineTop -= lineHeight * ( Style.font.height + SPACING ); - - Text* node = line->head->next; - int row = 0; - int col = 0; - - while( node != NULL ) - { - if( node->len != 0 ) - { - XSetFont( UI.display, UI.gc, ( node->bold ? Style.fontBold : Style.font ).font->fid ); - XSetForeground( UI.display, UI.gc, - node->fg == SYSTEM ? Style.Colours.system : Style.colours[ node->bold ][ node->fg ] ); - - // TODO: draw bg - - int remainingChars = node->len; - int pos = 0; - - while( remainingChars > 0 ) - { - int charsToPrint = MIN( remainingChars, self->cols - col ); - - int x = ( Style.font.width * col ); - int y = lineTop + ( ( Style.font.height + SPACING ) * row ); - - if( y >= 0 ) - { - XDrawString( UI.display, doublebuf, UI.gc, x, y + Style.font.ascent + SPACING, node->buffer + pos, charsToPrint ); - } - - pos += charsToPrint; - remainingChars -= charsToPrint; - - if( remainingChars != 0 ) - { - col = 0; - row++; - } - else - { - col += charsToPrint; - } - } - } - - node = node->next; - } - - rowsRemaining -= lineHeight; - linesRemaining--; - - linesPrinted++; - } - - XCopyArea( UI.display, doublebuf, UI.window, UI.gc, 0, 0, self->width, self->height, self->x, self->y ); - XFreePixmap( UI.display, doublebuf ); -} diff --git a/src/textbox.cc b/src/textbox.cc @@ -0,0 +1,125 @@ +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include <X11/Xlib.h> + +#include "common.h" +#include <stdio.h> + +void textbox_init( TextBox * tb ) { + *tb = { }; + // TODO: this is kinda crap + tb->text.lines = ( Line * ) calloc( sizeof( Line ), SCROLLBACK_SIZE ); +} + +void textbox_term( TextBox * tb ) { + free( tb->text.lines ); +} + +void textbox_setpos( TextBox * tb, int x, int y ) { + tb->x = x; + tb->y = y; +} + +void textbox_setsize( TextBox * tb, int width, int height ) { + tb->width = width; + tb->height = height; +} + +void textbox_add( TextBox * tb, const char * str, unsigned int len, Colour fg, Colour bg, bool bold ) { + Line * line = &tb->text.lines[ ( tb->text.head + tb->text.num_lines ) % SCROLLBACK_SIZE ]; + size_t remaining = MAX_LINE_LENGTH - line->len; + size_t n = min( strlen( str ), remaining ); + + for( size_t i = 0; i < n; i++ ) { + Glyph & glyph = line->glyphs[ line->len + i ]; + glyph.ch = str[ i ]; + glyph.fg = fg; + glyph.bg = bg; + glyph.bold = bold; + }; + + line->len += n; +} + +void textbox_newline( TextBox * tb ) { + if( tb->text.num_lines < SCROLLBACK_SIZE ) { + tb->text.num_lines++; + return; + } + + tb->text.head++; + Line * line = &tb->text.lines[ ( tb->text.head + tb->text.num_lines ) % SCROLLBACK_SIZE ]; + line->len = 0; +} + +void textbox_draw( const TextBox * tb ) { + if( tb->width == 0 || tb->height == 0 ) { + return; + } + + Pixmap doublebuf = XCreatePixmap( UI.display, UI.window, tb->width, tb->height, UI.depth ); + + XSetForeground( UI.display, UI.gc, Style.bg ); + XFillRectangle( UI.display, doublebuf, UI.gc, 0, 0, tb->width, tb->height ); + + size_t rows_drawn = 0; + size_t tb_rows = tb->height / ( Style.font.height + SPACING ); + size_t tb_cols = tb->width / Style.font.width; + + for( size_t l = 0; l < tb->text.num_lines; l++ ) { + const Line & line = tb->text.lines[ ( tb->text.head + tb->text.num_lines - tb->scroll_offset - l ) % SCROLLBACK_SIZE ]; + + size_t rows = 1 + line.len / tb_cols; + if( line.len > 0 && line.len % tb_cols == 0 ) + rows--; + + for( size_t i = 0; i < line.len; i++ ) { + const Glyph & glyph = line.glyphs[ i ]; + + size_t row = i / tb_cols; + if( i > 0 && i % tb_cols == 0 ) + row--; + + int left = ( i % tb_cols ) * Style.font.width; + // TODO: wrapping doesn't update top correctly + int top = tb->height - ( 1 + rows_drawn + row ) * ( Style.font.height + SPACING ); + if( top < 0 ) + continue; + + XSetFont( UI.display, UI.gc, ( glyph.bold ? Style.fontBold : Style.font ).font->fid ); + XSetForeground( UI.display, UI.gc, + glyph.fg == SYSTEM ? Style.Colours.system : Style.colours[ glyph.bold ][ glyph.fg ] ); + XDrawString( UI.display, doublebuf, UI.gc, left, top + Style.font.ascent + SPACING, &glyph.ch, 1 ); + } + + rows_drawn += rows; + if( rows_drawn >= tb_rows ) + break; + } + + XCopyArea( UI.display, doublebuf, UI.window, UI.gc, 0, 0, tb->width, tb->height, tb->x, tb->y ); + XFreePixmap( UI.display, doublebuf ); +} + +void TextBox::scroll( int offset ) { + if( offset < 0 ) { + scroll_offset -= min( size_t( -offset ), scroll_offset ); + } + else { + scroll_offset = min( scroll_offset + offset, text.num_lines ); + } + + textbox_draw( this ); +} + +void TextBox::page_down() { + size_t rows = height / ( Style.font.height + SPACING ); + scroll( -int( rows ) - 2 ); +} + +void TextBox::page_up() { + size_t rows = height / ( Style.font.height + SPACING ); + scroll( rows - 2 ); // TODO +} diff --git a/src/textbox.h b/src/textbox.h @@ -1,9 +1,7 @@ -#ifndef _TEXTBOX_H_ -#define _TEXTBOX_H_ +#pragma once -typedef enum -{ - BLACK = 0, +enum Colour { + BLACK, RED, GREEN, YELLOW, @@ -13,54 +11,49 @@ typedef enum WHITE, SYSTEM, NONE, -} Colour; +}; -typedef struct Text Text; -struct Text -{ - char* buffer; - unsigned int len; +constexpr size_t MAX_LINE_LENGTH = 2048; +constexpr size_t SCROLLBACK_SIZE = 1 << 16; // TODO - Colour fg; - Colour bg; +// TODO: pack these better +struct Glyph { + char ch; + Colour fg, bg; bool bold; - - Text* next; }; -typedef struct -{ - Text* head; - Text* tail; - - unsigned int len; -} Line; +struct Line { + Glyph glyphs[ MAX_LINE_LENGTH ]; + size_t len = 0; +}; -typedef struct -{ - Line* lines; +struct Text { + Line * lines; + size_t head; + size_t num_lines; +}; - int maxLines; - int numLines; - int head; +struct TextBox { + Text text; int x; int y; int width; int height; - int rows; - int cols; + size_t scroll_offset; - int scrollDelta; -} TextBox; + void scroll( int offset ); + void page_up(); + void page_down(); +}; -TextBox* textbox_new( unsigned int maxLines ); -void textbox_free( TextBox* self ); -void textbox_setpos( TextBox* self, int x, int y ); -void textbox_setsize( TextBox* self, int width, int height ); -void textbox_add( TextBox* self, const char* str, unsigned int len, Colour fg, Colour bg, bool bold ); -void textbox_newline( TextBox* self ); -void textbox_draw( TextBox* self ); +void textbox_init( TextBox * tb ); +void textbox_term( TextBox * tb ); -#endif // _TEXTBOX_H_ +void textbox_setpos( TextBox * tb, int x, int y ); +void textbox_setsize( TextBox * tb, int width, int height ); +void textbox_add( TextBox * tb, const char * str, unsigned int len, Colour fg, Colour bg, bool bold ); +void textbox_newline( TextBox * tb ); +void textbox_draw( const TextBox * tb ); diff --git a/src/ui.c b/src/ui.c @@ -1,461 +0,0 @@ -#include <stdio.h> -#include <stdlib.h> -#include <err.h> - -#include <X11/Xutil.h> -#include <X11/XKBlib.h> -#include <X11/cursorfont.h> -#include <X11/keysym.h> - -#include "common.h" -#include "input.h" -#include "script.h" - -Atom wmDeleteWindow; - -typedef struct { - char c; - - Colour fg; - bool bold; -} StatusChar; - -static StatusChar* statusContents = NULL; -static size_t statusCapacity = 256; -static size_t statusLen = 0; - -void ui_statusDraw() -{ - XSetForeground( UI.display, UI.gc, Style.statusBG ); - XFillRectangle( UI.display, UI.window, UI.gc, 0, UI.height - ( PADDING * 4 ) - ( Style.font.height * 2 ), UI.width, Style.font.height + ( PADDING * 2 ) ); - - for( size_t i = 0; i < statusLen; i++ ) - { - StatusChar sc = statusContents[ i ]; - - XSetFont( UI.display, UI.gc, ( sc.bold ? Style.fontBold : Style.font ).font->fid ); - XSetForeground( UI.display, UI.gc, Style.colours[ sc.bold ][ sc.fg ] ); - - int x = PADDING + i * Style.font.width; - int y = UI.height - ( PADDING * 3 ) - Style.font.height - Style.font.descent; - XDrawString( UI.display, UI.window, UI.gc, x, y, &sc.c, 1 ); - } -} - -void ui_statusClear() -{ - statusLen = 0; -} - -void ui_statusAdd( const char c, const Colour fg, const bool bold ) -{ - if( ( statusLen + 1 ) * sizeof( StatusChar ) > statusCapacity ) - { - size_t newcapacity = statusCapacity * 2; - StatusChar* newcontents = realloc( statusContents, newcapacity ); - - if( !newcontents ) - { - return err( 1, "oom" ); - } - - statusContents = newcontents; - statusCapacity = newcapacity; - } - - statusContents[ statusLen ] = ( StatusChar ) { c, fg, bold }; - statusLen++; -} - -void ui_draw() -{ - XClearWindow( UI.display, UI.window ); - - input_draw(); - ui_statusDraw(); - - textbox_draw( UI.textChat ); - textbox_draw( UI.textMain ); - - int spacerY = ( 2 * PADDING ) + ( Style.font.height + SPACING ) * CHAT_ROWS; - XSetForeground( UI.display, UI.gc, Style.statusBG ); - XFillRectangle( UI.display, UI.window, UI.gc, 0, spacerY, UI.width, 1 ); -} - -void eventButtonPress( XEvent* event ) -{ - PRETEND_TO_USE( event ); -} - -void eventButtonRelease( XEvent* event ) -{ - PRETEND_TO_USE( event ); -} - -void eventMessage( XEvent* event ) -{ - if( ( Atom ) event->xclient.data.l[ 0 ] == wmDeleteWindow ) - { - script_handleClose(); - } -} - -void eventResize( XEvent* event ) -{ - int newWidth = event->xconfigure.width; - int newHeight = event->xconfigure.height; - - if( newWidth == UI.width && newHeight == UI.height ) - { - return; - } - - UI.width = newWidth; - UI.height = newHeight; - - XSetForeground( UI.display, UI.gc, Style.bg ); - XFillRectangle( UI.display, UI.window, UI.gc, 0, 0, UI.width, UI.height ); - - textbox_setpos( UI.textChat, PADDING, PADDING ); - textbox_setsize( UI.textChat, UI.width - ( 2 * PADDING ), ( Style.font.height + SPACING ) * CHAT_ROWS ); - - textbox_setpos( UI.textMain, PADDING, ( PADDING * 2 ) + CHAT_ROWS * ( Style.font.height + SPACING ) + 1 ); - textbox_setsize( UI.textMain, UI.width - ( 2 * PADDING ), UI.height - - ( ( ( Style.font.height + SPACING ) * CHAT_ROWS ) + ( PADDING * 2 ) ) - - ( ( Style.font.height * 2 ) + ( PADDING * 5 ) ) - 1 - ); -} - -void eventExpose( XEvent* event ) -{ - PRETEND_TO_USE( event ); - - ui_draw(); -} - -void eventKeyPress( XEvent* event ) -{ - #define ADD_MACRO( key, name ) \ - case key: \ - script_doMacro( name, sizeof( name ) - 1, shift, ctrl, alt ); \ - break - - XKeyEvent* keyEvent = &event->xkey; - - char keyBuffer[ 32 ]; - KeySym key; - - bool shift = keyEvent->state & ShiftMask; - bool ctrl = keyEvent->state & ControlMask; - bool alt = keyEvent->state & Mod1Mask; - - int len = XLookupString( keyEvent, keyBuffer, sizeof( keyBuffer ), &key, NULL ); - - switch( key ) - { - case XK_Return: - { - input_send(); - - break; - } - - case XK_BackSpace: - input_backspace(); - - break; - - case XK_Delete: - input_delete(); - - break; - - case XK_Page_Up: - if( UI.textMain->scrollDelta < UI.textMain->numLines ) - { - int toScroll = shift ? 1 : ( UI.textMain->rows - 2 ); - - UI.textMain->scrollDelta = MIN( UI.textMain->scrollDelta + toScroll, UI.textMain->numLines ); - - textbox_draw( UI.textMain ); - } - - break; - - case XK_Page_Down: - if( UI.textMain->scrollDelta > 0 ) - { - int toScroll = shift ? 1 : ( UI.textMain->rows - 2 ); - - UI.textMain->scrollDelta = MAX( UI.textMain->scrollDelta - toScroll, 0 ); - - textbox_draw( UI.textMain ); - } - - break; - - case XK_Up: - input_up(); - - break; - case XK_Down: - input_down(); - - break; - - case XK_Left: - input_left(); - - break; - - case XK_Right: - input_right(); - - break; - - ADD_MACRO( XK_KP_1, "kp1" ); - ADD_MACRO( XK_KP_End, "kp1" ); - - ADD_MACRO( XK_KP_2, "kp2" ); - ADD_MACRO( XK_KP_Down, "kp2" ); - - ADD_MACRO( XK_KP_3, "kp3" ); - ADD_MACRO( XK_KP_Page_Down, "kp3" ); - - ADD_MACRO( XK_KP_4, "kp4" ); - ADD_MACRO( XK_KP_Left, "kp4" ); - - ADD_MACRO( XK_KP_5, "kp5" ); - ADD_MACRO( XK_KP_Begin, "kp5" ); - - ADD_MACRO( XK_KP_6, "kp6" ); - ADD_MACRO( XK_KP_Right, "kp6" ); - - ADD_MACRO( XK_KP_7, "kp7" ); - ADD_MACRO( XK_KP_Home, "kp7" ); - - ADD_MACRO( XK_KP_8, "kp8" ); - ADD_MACRO( XK_KP_Up, "kp8" ); - - ADD_MACRO( XK_KP_9, "kp9" ); - ADD_MACRO( XK_KP_Page_Up, "kp9" ); - - ADD_MACRO( XK_KP_0, "kp0" ); - ADD_MACRO( XK_KP_Insert, "kp0" ); - - ADD_MACRO( XK_KP_Multiply, "kp*" ); - ADD_MACRO( XK_KP_Divide, "kp/" ); - ADD_MACRO( XK_KP_Subtract, "kp-" ); - ADD_MACRO( XK_KP_Add, "kp+" ); - - ADD_MACRO( XK_KP_Delete, "kp." ); - ADD_MACRO( XK_KP_Decimal, "kp." ); - - ADD_MACRO( XK_F1, "f1" ); - ADD_MACRO( XK_F2, "f2" ); - ADD_MACRO( XK_F3, "f3" ); - ADD_MACRO( XK_F4, "f4" ); - ADD_MACRO( XK_F5, "f5" ); - ADD_MACRO( XK_F6, "f6" ); - ADD_MACRO( XK_F7, "f7" ); - ADD_MACRO( XK_F8, "f8" ); - ADD_MACRO( XK_F9, "f9" ); - ADD_MACRO( XK_F10, "f10" ); - ADD_MACRO( XK_F11, "f11" ); - ADD_MACRO( XK_F12, "f12" ); - - default: - if( ctrl || alt ) - { - script_doMacro( keyBuffer, len, shift, ctrl, alt ); - } - else - { - input_add( keyBuffer, len ); - } - - break; - } - - #undef ADD_MACRO -} - -void eventFocusOut( XEvent* event ) { - PRETEND_TO_USE( event ); - - UI.hasFocus = 0; -} - -void eventFocusIn( XEvent* event ) -{ - PRETEND_TO_USE( event ); - - UI.hasFocus = 1; - - XWMHints* hints = XGetWMHints( UI.display, UI.window ); - hints->flags &= ~XUrgencyHint; - XSetWMHints( UI.display, UI.window, hints ); - XFree( hints ); -} - -void ( *EventHandler[ LASTEvent ] ) ( XEvent* ) = -{ - [ ButtonPress ] = eventButtonPress, - [ ButtonRelease ] = eventButtonRelease, - [ ClientMessage ] = eventMessage, - [ ConfigureNotify ] = eventResize, - [ Expose ] = eventExpose, - [ KeyPress ] = eventKeyPress, - [ FocusOut ] = eventFocusOut, - [ FocusIn ] = eventFocusIn, -}; - -void ui_handleXEvents() -{ - while( XPending( UI.display ) ) - { - XEvent event; - XNextEvent( UI.display, &event ); - - if( EventHandler[ event.type ] ) - { - EventHandler[ event.type ]( &event ); - } - } -} - -MudFont loadFont( char* fontStr ) -{ - MudFont font; - - font.font = XLoadQueryFont( UI.display, fontStr ); - - if( !font.font ) - { - printf( "could not load font %s\n", fontStr ); - - exit( 1 ); - } - - font.ascent = font.font->ascent; - font.descent = font.font->descent; - font.lbearing = font.font->min_bounds.lbearing; - font.rbearing = font.font->max_bounds.rbearing; - - font.width = font.rbearing - font.lbearing; - font.height = font.ascent + font.descent; - - return font; -} - -void initStyle() -{ - #define SETCOLOR( x, c ) \ - do { \ - XColor color; \ - XAllocNamedColor( UI.display, UI.colorMap, c, &color, &color ); \ - x = color.pixel; \ - } while( false ) - #define SETXCOLOR( x, y, c ) \ - do { \ - XColor color; \ - XAllocNamedColor( UI.display, UI.colorMap, c, &color, &color ); \ - x = color.pixel; \ - y = color; \ - } while( false ) - - SETXCOLOR( Style.bg, Style.xBG, "#1a1a1a" ); - SETXCOLOR( Style.fg, Style.xFG, "#b6c2c4" ); - - SETCOLOR( Style.cursor, "#00ff00" ); - - SETCOLOR( Style.statusBG, "#333333" ); - SETCOLOR( Style.statusFG, "#ffffff" ); - - SETCOLOR( Style.Colours.black, "#1a1a1a" ); - SETCOLOR( Style.Colours.red, "#ca4433" ); - SETCOLOR( Style.Colours.green, "#178a3a" ); - SETCOLOR( Style.Colours.yellow, "#dc7c2a" ); - SETCOLOR( Style.Colours.blue, "#415e87" ); - SETCOLOR( Style.Colours.magenta, "#5e468c" ); - SETCOLOR( Style.Colours.cyan, "#35789b" ); - SETCOLOR( Style.Colours.white, "#b6c2c4" ); - - SETCOLOR( Style.Colours.lblack, "#666666" ); - SETCOLOR( Style.Colours.lred, "#ff2954" ); - SETCOLOR( Style.Colours.lgreen, "#5dd030" ); - SETCOLOR( Style.Colours.lyellow, "#fafc4f" ); - SETCOLOR( Style.Colours.lblue, "#3581e1" ); - SETCOLOR( Style.Colours.lmagenta, "#875fff" ); - SETCOLOR( Style.Colours.lcyan, "#29fbff" ); - SETCOLOR( Style.Colours.lwhite, "#cedbde" ); - - SETCOLOR( Style.Colours.system, "#ffffff" ); - - #undef SETCOLOR - #undef SETXCOLOR - - Style.font = loadFont( "-windows-dina-medium-r-normal--12-120-75-75-c-0-microsoft-cp1252" ); - Style.fontBold = loadFont( "-windows-dina-bold-r-normal--12-120-75-75-c-0-microsoft-cp1252" ); -} - -void ui_init() -{ - UI.display = XOpenDisplay( NULL ); - UI.screen = XDefaultScreen( UI.display ); - UI.width = -1; - UI.height = -1; - - Window root = XRootWindow( UI.display, UI.screen ); - UI.depth = XDefaultDepth( UI.display, UI.screen ); - Visual* visual = XDefaultVisual( UI.display, UI.screen ); - UI.colorMap = XDefaultColormap( UI.display, UI.screen ); - - UI.textMain = textbox_new( OUTPUT_MAX_LINES ); - UI.textChat = textbox_new( OUTPUT_MAX_LINES ); - statusContents = malloc( statusCapacity * sizeof( StatusChar ) ); - - if( statusContents == NULL ) { - err( 1, "oom" ); - } - - initStyle(); - - XSetWindowAttributes attr = - { - .background_pixel = Style.bg, - .event_mask = ExposureMask | StructureNotifyMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask | FocusChangeMask, - .colormap = UI.colorMap, - }; - - UI.window = XCreateWindow( UI.display, root, 0, 0, 800, 600, 0, UI.depth, InputOutput, visual, CWBackPixel | CWEventMask | CWColormap, &attr ); - UI.gc = XCreateGC( UI.display, UI.window, 0, NULL ); - - XWMHints* hints = XAllocWMHints(); - XSetWMHints( UI.display, UI.window, hints ); - XFree( hints ); - - Cursor cursor = XCreateFontCursor( UI.display, XC_xterm ); - XDefineCursor( UI.display, UI.window, cursor ); - XRecolorCursor( UI.display, cursor, &Style.xFG, &Style.xBG ); - - XStoreName( UI.display, UI.window, "Mud Gangster" ); - XMapWindow( UI.display, UI.window ); - - wmDeleteWindow = XInternAtom( UI.display, "WM_DELETE_WINDOW", false ); - XSetWMProtocols( UI.display, UI.window, &wmDeleteWindow, 1 ); -} - -void ui_end() -{ - textbox_free( UI.textMain ); - textbox_free( UI.textChat ); - free( statusContents ); - - XFreeFont( UI.display, Style.font.font ); - XFreeFont( UI.display, Style.fontBold.font ); - - XFreeGC( UI.display, UI.gc ); - XDestroyWindow( UI.display, UI.window ); - XCloseDisplay( UI.display ); -} diff --git a/src/ui.cc b/src/ui.cc @@ -0,0 +1,401 @@ +#include <stdio.h> +#include <stdlib.h> +#include <err.h> + +#include <X11/Xutil.h> +#include <X11/XKBlib.h> +#include <X11/cursorfont.h> +#include <X11/keysym.h> + +#include "common.h" +#include "input.h" +#include "script.h" + +UIDefs UI; +StyleDefs Style; + +static Atom wmDeleteWindow; + +typedef struct { + char c; + + Colour fg; + bool bold; +} StatusChar; + +static StatusChar* statusContents = NULL; +static size_t statusCapacity = 256; +static size_t statusLen = 0; + +void ui_statusDraw() { + XSetForeground( UI.display, UI.gc, Style.statusBG ); + XFillRectangle( UI.display, UI.window, UI.gc, 0, UI.height - ( PADDING * 4 ) - ( Style.font.height * 2 ), UI.width, Style.font.height + ( PADDING * 2 ) ); + + for( size_t i = 0; i < statusLen; i++ ) + { + StatusChar sc = statusContents[ i ]; + + XSetFont( UI.display, UI.gc, ( sc.bold ? Style.fontBold : Style.font ).font->fid ); + XSetForeground( UI.display, UI.gc, Style.colours[ sc.bold ][ sc.fg ] ); + + int x = PADDING + i * Style.font.width; + int y = UI.height - ( PADDING * 3 ) - Style.font.height - Style.font.descent; + XDrawString( UI.display, UI.window, UI.gc, x, y, &sc.c, 1 ); + } +} + +void ui_statusClear() { + statusLen = 0; +} + +void ui_statusAdd( const char c, const Colour fg, const bool bold ) { + if( ( statusLen + 1 ) * sizeof( StatusChar ) > statusCapacity ) { + size_t newcapacity = statusCapacity * 2; + StatusChar * newcontents = ( StatusChar * ) realloc( statusContents, newcapacity ); + + if( !newcontents ) + return err( 1, "oom" ); + + statusContents = newcontents; + statusCapacity = newcapacity; + } + + statusContents[ statusLen ] = ( StatusChar ) { c, fg, bold }; + statusLen++; +} + +void ui_draw() { + XClearWindow( UI.display, UI.window ); + + input_draw(); + ui_statusDraw(); + + textbox_draw( &UI.textChat ); + textbox_draw( &UI.textMain ); + + int spacerY = ( 2 * PADDING ) + ( Style.font.height + SPACING ) * CHAT_ROWS; + XSetForeground( UI.display, UI.gc, Style.statusBG ); + XFillRectangle( UI.display, UI.window, UI.gc, 0, spacerY, UI.width, 1 ); +} + +void eventButtonPress( XEvent* event ) { } + +void eventButtonRelease( XEvent* event ) { } + +void eventMessage( XEvent* event ) { + if( ( Atom ) event->xclient.data.l[ 0 ] == wmDeleteWindow ) { + script_handleClose(); + } +} + +void eventResize( XEvent* event ) { + int newWidth = event->xconfigure.width; + int newHeight = event->xconfigure.height; + + if( newWidth == UI.width && newHeight == UI.height ) + return; + + UI.width = newWidth; + UI.height = newHeight; + + XSetForeground( UI.display, UI.gc, Style.bg ); + XFillRectangle( UI.display, UI.window, UI.gc, 0, 0, UI.width, UI.height ); + + textbox_setpos( &UI.textChat, PADDING, PADDING ); + textbox_setsize( &UI.textChat, UI.width - ( 2 * PADDING ), ( Style.font.height + SPACING ) * CHAT_ROWS ); + + textbox_setpos( &UI.textMain, PADDING, ( PADDING * 2 ) + CHAT_ROWS * ( Style.font.height + SPACING ) + 1 ); + textbox_setsize( &UI.textMain, UI.width - ( 2 * PADDING ), UI.height + - ( ( ( Style.font.height + SPACING ) * CHAT_ROWS ) + ( PADDING * 2 ) ) + - ( ( Style.font.height * 2 ) + ( PADDING * 5 ) ) - 1 + ); +} + +void eventExpose( XEvent* event ) { + ui_draw(); +} + +void eventKeyPress( XEvent* event ) { + #define ADD_MACRO( key, name ) \ + case key: \ + script_doMacro( name, sizeof( name ) - 1, shift, ctrl, alt ); \ + break + + XKeyEvent* keyEvent = &event->xkey; + + char keyBuffer[ 32 ]; + KeySym key; + + bool shift = keyEvent->state & ShiftMask; + bool ctrl = keyEvent->state & ControlMask; + bool alt = keyEvent->state & Mod1Mask; + + int len = XLookupString( keyEvent, keyBuffer, sizeof( keyBuffer ), &key, NULL ); + + switch( key ) { + case XK_Return: + input_send(); + break; + + case XK_BackSpace: + input_backspace(); + break; + + case XK_Delete: + input_delete(); + break; + + case XK_Page_Up: + if( shift ) + UI.textMain.scroll( 1 ); + else + UI.textMain.page_up(); + break; + + case XK_Page_Down: + if( shift ) + UI.textMain.scroll( -1 ); + else + UI.textMain.page_down(); + break; + + case XK_Up: + input_up(); + break; + + case XK_Down: + input_down(); + break; + + case XK_Left: + input_left(); + break; + + case XK_Right: + input_right(); + break; + + ADD_MACRO( XK_KP_1, "kp1" ); + ADD_MACRO( XK_KP_End, "kp1" ); + + ADD_MACRO( XK_KP_2, "kp2" ); + ADD_MACRO( XK_KP_Down, "kp2" ); + + ADD_MACRO( XK_KP_3, "kp3" ); + ADD_MACRO( XK_KP_Page_Down, "kp3" ); + + ADD_MACRO( XK_KP_4, "kp4" ); + ADD_MACRO( XK_KP_Left, "kp4" ); + + ADD_MACRO( XK_KP_5, "kp5" ); + ADD_MACRO( XK_KP_Begin, "kp5" ); + + ADD_MACRO( XK_KP_6, "kp6" ); + ADD_MACRO( XK_KP_Right, "kp6" ); + + ADD_MACRO( XK_KP_7, "kp7" ); + ADD_MACRO( XK_KP_Home, "kp7" ); + + ADD_MACRO( XK_KP_8, "kp8" ); + ADD_MACRO( XK_KP_Up, "kp8" ); + + ADD_MACRO( XK_KP_9, "kp9" ); + ADD_MACRO( XK_KP_Page_Up, "kp9" ); + + ADD_MACRO( XK_KP_0, "kp0" ); + ADD_MACRO( XK_KP_Insert, "kp0" ); + + ADD_MACRO( XK_KP_Multiply, "kp*" ); + ADD_MACRO( XK_KP_Divide, "kp/" ); + ADD_MACRO( XK_KP_Subtract, "kp-" ); + ADD_MACRO( XK_KP_Add, "kp+" ); + + ADD_MACRO( XK_KP_Delete, "kp." ); + ADD_MACRO( XK_KP_Decimal, "kp." ); + + ADD_MACRO( XK_F1, "f1" ); + ADD_MACRO( XK_F2, "f2" ); + ADD_MACRO( XK_F3, "f3" ); + ADD_MACRO( XK_F4, "f4" ); + ADD_MACRO( XK_F5, "f5" ); + ADD_MACRO( XK_F6, "f6" ); + ADD_MACRO( XK_F7, "f7" ); + ADD_MACRO( XK_F8, "f8" ); + ADD_MACRO( XK_F9, "f9" ); + ADD_MACRO( XK_F10, "f10" ); + ADD_MACRO( XK_F11, "f11" ); + ADD_MACRO( XK_F12, "f12" ); + + default: + if( ctrl || alt ) + script_doMacro( keyBuffer, len, shift, ctrl, alt ); + else + input_add( keyBuffer, len ); + + break; + } + + #undef ADD_MACRO +} + +void eventFocusOut( XEvent* event ) { + UI.hasFocus = 0; +} + +void eventFocusIn( XEvent* event ) { + UI.hasFocus = 1; + + XWMHints* hints = XGetWMHints( UI.display, UI.window ); + hints->flags &= ~XUrgencyHint; + XSetWMHints( UI.display, UI.window, hints ); + XFree( hints ); +} + +void ui_handleXEvents() { + void ( *EventHandler[ LASTEvent ] )( XEvent* ) = { }; + EventHandler[ ButtonPress ] = eventButtonPress; + EventHandler[ ButtonRelease ] = eventButtonRelease; + EventHandler[ ClientMessage ] = eventMessage; + EventHandler[ ConfigureNotify ] = eventResize; + EventHandler[ Expose ] = eventExpose; + EventHandler[ KeyPress ] = eventKeyPress; + EventHandler[ FocusOut ] = eventFocusOut; + EventHandler[ FocusIn ] = eventFocusIn; + + while( XPending( UI.display ) ) { + XEvent event; + XNextEvent( UI.display, &event ); + + if( EventHandler[ event.type ] != NULL ) + EventHandler[ event.type ]( &event ); + } +} + +MudFont loadFont( const char * fontStr ) { + MudFont font; + + font.font = XLoadQueryFont( UI.display, fontStr ); + + if( !font.font ) { + printf( "could not load font %s\n", fontStr ); + + exit( 1 ); + } + + font.ascent = font.font->ascent; + font.descent = font.font->descent; + font.lbearing = font.font->min_bounds.lbearing; + font.rbearing = font.font->max_bounds.rbearing; + + font.width = font.rbearing - font.lbearing; + font.height = font.ascent + font.descent; + + return font; +} + +void initStyle() { + #define SETCOLOR( x, c ) \ + do { \ + XColor color; \ + XAllocNamedColor( UI.display, UI.colorMap, c, &color, &color ); \ + x = color.pixel; \ + } while( false ) + #define SETXCOLOR( x, y, c ) \ + do { \ + XColor color; \ + XAllocNamedColor( UI.display, UI.colorMap, c, &color, &color ); \ + x = color.pixel; \ + y = color; \ + } while( false ) + + SETXCOLOR( Style.bg, Style.xBG, "#1a1a1a" ); + SETXCOLOR( Style.fg, Style.xFG, "#b6c2c4" ); + + SETCOLOR( Style.cursor, "#00ff00" ); + + SETCOLOR( Style.statusBG, "#333333" ); + SETCOLOR( Style.statusFG, "#ffffff" ); + + SETCOLOR( Style.Colours.black, "#1a1a1a" ); + SETCOLOR( Style.Colours.red, "#ca4433" ); + SETCOLOR( Style.Colours.green, "#178a3a" ); + SETCOLOR( Style.Colours.yellow, "#dc7c2a" ); + SETCOLOR( Style.Colours.blue, "#415e87" ); + SETCOLOR( Style.Colours.magenta, "#5e468c" ); + SETCOLOR( Style.Colours.cyan, "#35789b" ); + SETCOLOR( Style.Colours.white, "#b6c2c4" ); + + SETCOLOR( Style.Colours.lblack, "#666666" ); + SETCOLOR( Style.Colours.lred, "#ff2954" ); + SETCOLOR( Style.Colours.lgreen, "#5dd030" ); + SETCOLOR( Style.Colours.lyellow, "#fafc4f" ); + SETCOLOR( Style.Colours.lblue, "#3581e1" ); + SETCOLOR( Style.Colours.lmagenta, "#875fff" ); + SETCOLOR( Style.Colours.lcyan, "#29fbff" ); + SETCOLOR( Style.Colours.lwhite, "#cedbde" ); + + SETCOLOR( Style.Colours.system, "#ffffff" ); + + #undef SETCOLOR + #undef SETXCOLOR + + Style.font = loadFont( "-windows-dina-medium-r-normal--10-*-*-*-c-0-*-*" ); + Style.fontBold = loadFont( "-windows-dina-bold-r-normal--10-*-*-*-c-0-*-*" ); +} + +void ui_init() { + textbox_init( &UI.textMain ); + textbox_init( &UI.textChat ); + UI.display = XOpenDisplay( NULL ); + UI.screen = XDefaultScreen( UI.display ); + UI.width = -1; + UI.height = -1; + + Window root = XRootWindow( UI.display, UI.screen ); + UI.depth = XDefaultDepth( UI.display, UI.screen ); + Visual * visual = XDefaultVisual( UI.display, UI.screen ); + UI.colorMap = XDefaultColormap( UI.display, UI.screen ); + + statusContents = ( StatusChar * ) malloc( statusCapacity * sizeof( StatusChar ) ); + + if( statusContents == NULL ) { + err( 1, "oom" ); + } + + initStyle(); + + XSetWindowAttributes attr = { }; + attr.background_pixel = Style.bg, + attr.event_mask = ExposureMask | StructureNotifyMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask | FocusChangeMask, + attr.colormap = UI.colorMap, + + UI.window = XCreateWindow( UI.display, root, 0, 0, 800, 600, 0, UI.depth, InputOutput, visual, CWBackPixel | CWEventMask | CWColormap, &attr ); + UI.gc = XCreateGC( UI.display, UI.window, 0, NULL ); + + XWMHints* hints = XAllocWMHints(); + XSetWMHints( UI.display, UI.window, hints ); + XFree( hints ); + + Cursor cursor = XCreateFontCursor( UI.display, XC_xterm ); + XDefineCursor( UI.display, UI.window, cursor ); + XRecolorCursor( UI.display, cursor, &Style.xFG, &Style.xBG ); + + XStoreName( UI.display, UI.window, "Mud Gangster" ); + XMapWindow( UI.display, UI.window ); + + wmDeleteWindow = XInternAtom( UI.display, "WM_DELETE_WINDOW", false ); + XSetWMProtocols( UI.display, UI.window, &wmDeleteWindow, 1 ); +} + +void ui_end() { + textbox_term( &UI.textMain ); + textbox_term( &UI.textChat ); + free( statusContents ); + + XFreeFont( UI.display, Style.font.font ); + XFreeFont( UI.display, Style.fontBold.font ); + + XFreeGC( UI.display, UI.gc ); + XDestroyWindow( UI.display, UI.window ); + XCloseDisplay( UI.display ); +}