mudgangster

Log | Files | Refs

commit fb71710ac31d7efd90ca285e98d9bf472e27ba7b
parent b09af99d67088a1036836ad25ecfa5cab79f3fc8
Author: Michael Savage <mikejsavage@gmail.com>
Date:   Mon,  3 Sep 2018 20:30:20 +0300

Move all xlib stuff into x11.cc

Diffstat:
make.lua | 2+-
src/common.h | 101++++++++++++++-----------------------------------------------------------------
src/input.cc | 68+++++++++++++++++++++-----------------------------------------------
src/input.h | 23++++++++++++++---------
src/main.cc | 6+++---
src/script.cc | 46+++++++++++++++++-----------------------------
src/script.h | 8+++-----
src/textbox.cc | 126++++++++++---------------------------------------------------------------------
src/textbox.h | 61+++++++++++++++----------------------------------------------
src/ui.cc | 400-------------------------------------------------------------------------------
src/ui.h | 27++++++++++++++++++---------
src/x11.cc | 639+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
12 files changed, 765 insertions(+), 742 deletions(-)

diff --git a/make.lua b/make.lua @@ -5,5 +5,5 @@ require( "scripts.gen_makefile" ) -- require( "libs.lua" ) -- require( "libs.lpeg" ) -bin( "mudGangster", { "src/main", "src/script", "src/textbox", "src/input", "src/ui" } ) +bin( "mudGangster", { "src/main", "src/script", "src/textbox", "src/input", "src/x11" } ) gcc_bin_ldflags( "mudGangster", "-lm -lX11 -llua" ) -- -Wl,-E" ) need to export symbols when vendoring diff --git a/src/common.h b/src/common.h @@ -1,92 +1,12 @@ #pragma once +#include <stdio.h> #include <stdint.h> +#include <stddef.h> +#include <stdlib.h> #include <assert.h> -#include <X11/Xlib.h> - #include "config.h" -#include "textbox.h" - -struct UIDefs { - Display * display; - int screen; - - GC gc; - Colormap colorMap; - - Window window; - - TextBox textMain; - TextBox textChat; - - int width; - int height; - int depth; - - int hasFocus; -}; - -extern UIDefs UI; - -struct MudFont { - int ascent; - int descent; - - short lbearing; - short rbearing; - - int height; - int width; - - XFontStruct * font; -}; - -struct StyleDefs { - ulong bg; - ulong fg; - - XColor xBG; - XColor xFG; - - ulong statusBG; - ulong statusFG; - - ulong inputBG; - ulong inputFG; - ulong cursor; - - MudFont font; - MudFont fontBold; - - union { - struct { - ulong black; - ulong red; - ulong green; - ulong yellow; - ulong blue; - ulong magenta; - ulong cyan; - ulong white; - - ulong lblack; - ulong lred; - ulong lgreen; - ulong lyellow; - ulong lblue; - ulong lmagenta; - ulong lcyan; - ulong lwhite; - - ulong system; - } Colours; - - ulong colours[ 2 ][ 8 ]; - }; -}; - -extern StyleDefs Style; #define STRL( x ) ( x ), sizeof( x ) - 1 @@ -108,3 +28,18 @@ inline To checked_cast( const From & from ) { assert( From( result ) == from ); return result; } + +// TODO: probably don't need most of these +enum Colour { + BLACK, + RED, + GREEN, + YELLOW, + BLUE, + MAGENTA, + CYAN, + WHITE, + SYSTEM, + + NUM_COLOURS, +}; diff --git a/src/input.cc b/src/input.cc @@ -23,7 +23,27 @@ static int inputBufferSize = 256; static int inputLen = 0; static int inputPos = 0; -void input_send() { +void input_init() { + inputBuffer = ( char * ) malloc( inputBufferSize ); + starsBuffer = ( char * ) malloc( inputBufferSize ); + + memset( starsBuffer, '*', inputBufferSize ); +} + +void input_term() { + free( inputBuffer ); + free( starsBuffer ); +} + +InputBuffer input_get_buffer() { + InputBuffer buf; + buf.buf = inputBuffer; + buf.len = inputLen; + buf.cursor_pos = inputPos; + return buf; +} + +void input_return() { if( inputLen > 0 ) { InputHistory * lastCmd = &inputHistory[ ( inputHistoryHead + inputHistoryCount - 1 ) % MAX_INPUT_HISTORY ]; @@ -52,8 +72,6 @@ void input_send() { inputLen = 0; inputPos = 0; - - input_draw(); } void input_backspace() { @@ -63,8 +81,6 @@ void input_backspace() { inputLen--; inputPos--; } - - input_draw(); } void input_delete() { @@ -73,8 +89,6 @@ void input_delete() { inputLen--; } - - input_draw(); } void input_up() { @@ -90,8 +104,6 @@ void input_up() { inputLen = cmd.len; inputPos = cmd.len; - - input_draw(); } void input_down() { @@ -114,20 +126,14 @@ void input_down() { 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( const char * buffer, int len ) { @@ -148,36 +154,4 @@ void input_add( const char * buffer, int 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/input.h b/src/input.h @@ -1,8 +1,18 @@ -#ifndef _INPUT_H_ -#define _INPUT_H_ +#pragma once -void input_send(); +#include <stddef.h> +struct InputBuffer { + char * buf; + + size_t len; + size_t cursor_pos; +}; + +void input_init(); +void input_term(); + +void input_return(); void input_backspace(); void input_delete(); @@ -13,9 +23,4 @@ void input_right(); void input_add( const char * buffer, int len ); -void input_draw(); - -void input_init(); -void input_end(); - -#endif // _INPUT_H_ +InputBuffer input_get_buffer(); diff --git a/src/main.cc b/src/main.cc @@ -9,9 +9,9 @@ int main() { // main loop is done in lua - script_end(); - input_end(); - ui_end(); + script_term(); + input_term(); + ui_term(); return 0; } diff --git a/src/script.cc b/src/script.cc @@ -1,13 +1,8 @@ -#include <stdlib.h> -#include <assert.h> - -#include <lua.hpp> - -#include <X11/Xutil.h> - #include "common.h" -#include "ui.h" #include "platform.h" +#include "ui.h" + +#include <lua.hpp> #if LUA_VERSION_NUM < 502 #define luaL_len lua_objlen @@ -59,7 +54,8 @@ extern "C" int mud_handleXEvents( lua_State * ) { return 0; } -static void generic_print( TextBox * tb, lua_State * L ) { +template< typename F > +static void generic_print( F * f, lua_State * L ) { const char * str = luaL_checkstring( L, 1 ); size_t len = luaL_len( L, 1 ); @@ -67,36 +63,36 @@ static void generic_print( TextBox * tb, lua_State * L ) { Colour bg = Colour( luaL_checkinteger( L, 3 ) ); bool bold = lua_toboolean( L, 4 ); - textbox_add( tb, str, len, fg, bg, bold ); + f( str, len, fg, bg, bold ); } extern "C" int mud_printMain( lua_State * L ) { - generic_print( &UI.textMain, L ); + generic_print( ui_main_print, L ); return 0; } extern "C" int mud_newlineMain( lua_State * L ) { - textbox_newline( &UI.textMain ); + ui_main_newline(); return 0; } extern "C" int mud_drawMain( lua_State * L ) { - textbox_draw( &UI.textMain ); + ui_main_draw(); return 0; } extern "C" int mud_printChat( lua_State * L ) { - generic_print( &UI.textChat, L ); + generic_print( ui_chat_print, L ); return 0; } extern "C" int mud_newlineChat( lua_State * L ) { - textbox_newline( &UI.textChat ); + ui_chat_newline(); return 0; } extern "C" int mud_drawChat( lua_State * L ) { - textbox_draw( &UI.textChat ); + ui_chat_draw(); return 0; } @@ -105,7 +101,7 @@ extern "C" int mud_setStatus( lua_State * L ) { size_t len = luaL_len( L, 1 ); - ui_statusClear(); + ui_clear_status(); for( size_t i = 0; i < len; i++ ) { lua_pushnumber( L, i + 1 ); @@ -131,7 +127,7 @@ extern "C" int mud_setStatus( lua_State * L ) { lua_pop( L, 4 ); } - ui_statusDraw(); + ui_draw_status(); return 0; } @@ -149,13 +145,7 @@ extern "C" int mud_setHandlers( lua_State * L ) { } 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 ); - } - + ui_urgent(); return 0; } @@ -182,11 +172,10 @@ void script_init() { if( luaL_loadbufferx( lua, ( const char * ) lua_bytecode, sizeof( lua_bytecode ), "main", "b" ) != LUA_OK ) { printf( "Error reading main.lua: %s\n", lua_tostring( lua, -1 ) ); - exit( 1 ); } - lua_pushinteger( lua, ConnectionNumber( UI.display ) ); + lua_pushinteger( lua, ui_display_fd() ); lua_pushcfunction( lua, mud_handleXEvents ); lua_pushcfunction( lua, mud_printMain ); @@ -205,11 +194,10 @@ void script_init() { if( lua_pcall( lua, 11, 0, -13 ) ) { printf( "Error running main.lua: %s\n", lua_tostring( lua, -1 ) ); - exit( 1 ); } } -void script_end() { +void script_term() { lua_close( lua ); } diff --git a/src/script.h b/src/script.h @@ -1,11 +1,9 @@ -#ifndef _SCRIPT_H_ -#define _SCRIPT_H_ +#pragma once +// TODO: should be size_t here? 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(); -void script_end(); - -#endif // _SCRIPT_H_ +void script_term(); diff --git a/src/textbox.cc b/src/textbox.cc @@ -1,10 +1,7 @@ -#include <stdlib.h> #include <string.h> -#include <assert.h> - -#include <X11/Xlib.h> #include "common.h" +#include "textbox.h" static uint8_t pack_style( Colour fg, Colour bg, bool bold ) { STATIC_ASSERT( NUM_COLOURS * NUM_COLOURS * 2 < UINT8_MAX ); @@ -18,7 +15,7 @@ static uint8_t pack_style( Colour fg, Colour bg, bool bold ) { return checked_cast< uint8_t >( style ); } -static void unpack_style( uint8_t style, int * fg, int * bg, int * bold ) { +void unpack_style( uint8_t style, int * fg, int * bg, int * bold ) { *bold = style % 2; style /= 2; @@ -28,35 +25,24 @@ static void unpack_style( uint8_t style, int * fg, int * bg, int * bold ) { *fg = style; } -void textbox_init( TextBox * tb, size_t scrollback ) { - *tb = { }; +void text_init( TextBuffer * tb, size_t scrollback ) { // TODO: this is kinda crap - tb->text.lines = ( Line * ) calloc( sizeof( Line ), scrollback ); - tb->text.num_lines = 1; - tb->text.max_lines = scrollback; -} - -void textbox_term( TextBox * tb ) { - free( tb->text.lines ); -} - -void textbox_setpos( TextBox * tb, int x, int y ) { - tb->x = x; - tb->y = y; + tb->lines = ( TextBuffer::Line * ) calloc( sizeof( TextBuffer::Line ), scrollback ); + tb->num_lines = 1; + tb->max_lines = scrollback; } -void textbox_setsize( TextBox * tb, int width, int height ) { - tb->width = width; - tb->height = height; +void text_destroy( TextBuffer * tb ) { + free( tb->lines ); } -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 ) % tb->text.max_lines ]; +void text_add( TextBuffer * tb, const char * str, size_t len, Colour fg, Colour bg, bool bold ) { + TextBuffer::Line * line = &tb->lines[ ( tb->head + tb->num_lines ) % tb->max_lines ]; 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 ]; + TextBuffer::Glyph & glyph = line->glyphs[ line->len + i ]; glyph.ch = str[ i ]; glyph.style = pack_style( fg, bg, bold ); }; @@ -64,93 +50,13 @@ void textbox_add( TextBox * tb, const char * str, unsigned int len, Colour fg, C line->len += n; } -void textbox_newline( TextBox * tb ) { - if( tb->text.num_lines < tb->text.max_lines ) { - tb->text.num_lines++; +void text_newline( TextBuffer * tb ) { + if( tb->num_lines < tb->max_lines ) { + tb->num_lines++; return; } - tb->text.head++; - Line * line = &tb->text.lines[ ( tb->text.head + tb->text.num_lines ) % tb->text.max_lines ]; + tb->head++; + TextBuffer::Line * line = &tb->lines[ ( tb->head + tb->num_lines ) % tb->max_lines ]; 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 ); - - /* - * lines refers to lines of text sent from the game - * rows refers to visual rows of text in the client, so when lines get - * wrapped they have more than one row - */ - size_t lines_drawn = 0; - size_t rows_drawn = 0; - size_t tb_rows = tb->height / ( Style.font.height + SPACING ); - size_t tb_cols = tb->width / Style.font.width; - - while( rows_drawn < tb_rows && lines_drawn < tb->text.num_lines ) { - const Line & line = tb->text.lines[ ( tb->text.head + tb->text.num_lines - tb->scroll_offset - lines_drawn ) % tb->text.max_lines ]; - - size_t line_rows = 1 + line.len / tb_cols; - if( line.len > 0 && line.len % tb_cols == 0 ) - line_rows--; - - for( size_t i = 0; i < line.len; i++ ) { - const Glyph & glyph = line.glyphs[ i ]; - - size_t row = i / tb_cols; - - int left = ( i % tb_cols ) * Style.font.width; - int top = tb->height - ( rows_drawn + line_rows - row ) * ( Style.font.height + SPACING ); - if( top < 0 ) - continue; - - int fg, bg, bold; - unpack_style( glyph.style, &fg, &bg, &bold ); - - // bg - int top_spacing = SPACING / 2; - int bot_spacing = SPACING - top_spacing; - XSetForeground( UI.display, UI.gc, bg == SYSTEM ? Style.Colours.system : Style.colours[ 0 ][ bg ] ); - XFillRectangle( UI.display, doublebuf, UI.gc, left, top - top_spacing, Style.font.width, Style.font.height + bot_spacing ); - - // fg - XSetFont( UI.display, UI.gc, ( bold ? Style.fontBold : Style.font ).font->fid ); - XSetForeground( UI.display, UI.gc, fg == SYSTEM ? Style.Colours.system : Style.colours[ bold ][ fg ] ); - XDrawString( UI.display, doublebuf, UI.gc, left, top + Style.font.ascent + SPACING, &glyph.ch, 1 ); - } - - lines_drawn++; - rows_drawn += line_rows; - } - - 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( TextBox * tb, int offset ) { - if( offset < 0 ) { - tb->scroll_offset -= min( size_t( -offset ), tb->scroll_offset ); - } - else { - tb->scroll_offset = min( tb->scroll_offset + offset, tb->text.num_lines - 1 ); - } - - textbox_draw( tb ); -} - -void textbox_page_down( TextBox * tb ) { - size_t rows = tb->height / ( Style.font.height + SPACING ); - textbox_scroll( tb, -int( rows ) + 1 ); -} - -void textbox_page_up( TextBox * tb ) { - size_t rows = tb->height / ( Style.font.height + SPACING ); - textbox_scroll( tb, rows - 1 ); -} diff --git a/src/textbox.h b/src/textbox.h @@ -1,62 +1,31 @@ #pragma once -#include <stdint.h> - -// TODO: probably don't need most of these -enum Colour { - BLACK, - RED, - GREEN, - YELLOW, - BLUE, - MAGENTA, - CYAN, - WHITE, - SYSTEM, - - NUM_COLOURS, -}; +#include "common.h" constexpr size_t MAX_LINE_LENGTH = 2048; constexpr size_t SCROLLBACK_SIZE = 1 << 16; -struct Glyph { - char ch; - uint8_t style; -}; +struct TextBuffer { + struct Glyph { + char ch; + uint8_t style; + }; -struct Line { - Glyph glyphs[ MAX_LINE_LENGTH ]; - size_t len = 0; -}; + struct Line { + Glyph glyphs[ MAX_LINE_LENGTH ]; + size_t len = 0; + }; -struct Text { Line * lines; size_t head; size_t num_lines; size_t max_lines; }; -struct TextBox { - Text text; - - int x; - int y; - int width; - int height; - - size_t scroll_offset; -}; - -void textbox_init( TextBox * tb, size_t scrollback ); -void textbox_term( TextBox * tb ); +void text_init( TextBuffer * tb, size_t scrollback ); -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 ); +void text_add( TextBuffer * tb, const char * str, size_t len, Colour fg, Colour bg, bool bold ); +void text_newline( TextBuffer * tb ); +void unpack_style( uint8_t style, int * fg, int * bg, int * bold ); -void textbox_scroll( TextBox * tb, int offset ); -void textbox_page_up( TextBox * tb ); -void textbox_page_down( TextBox * tb ); +void text_destroy( TextBuffer * tb ); diff --git a/src/ui.cc b/src/ui.cc @@ -1,400 +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" - -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 ); -} - -static void eventButtonPress( XEvent * event ) { } - -static void eventButtonRelease( XEvent * event ) { } - -static void eventMessage( XEvent * event ) { - if( ( Atom ) event->xclient.data.l[ 0 ] == wmDeleteWindow ) { - script_handleClose(); - } -} - -static 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 - ); -} - -static void eventExpose( XEvent * event ) { - ui_draw(); -} - -static 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 ) - textbox_scroll( &UI.textMain, 1 ); - else - textbox_page_up( &UI.textMain ); - break; - - case XK_Page_Down: - if( shift ) - textbox_scroll( &UI.textMain, -1 ); - else - textbox_page_down( &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 -} - -static void eventFocusOut( XEvent * event ) { - UI.hasFocus = 0; -} - -static 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 ); - } -} - -static 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; -} - -static 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, SCROLLBACK_SIZE ); - textbox_init( &UI.textChat, CHAT_ROWS ); - 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 ); -} diff --git a/src/ui.h b/src/ui.h @@ -1,17 +1,26 @@ -#ifndef _UI_H_ -#define _UI_H_ +#pragma once #include "common.h" -void ui_handleXEvents(); +void ui_handleXEvents(); // TODO: very x11 specific! -void ui_statusDraw(); -void ui_statusClear(); -void ui_statusAdd( const char c, const Colour fg, const bool bold ); +void ui_draw_status(); +void ui_clear_status(); +void ui_statusAdd( char c, Colour fg, bool bold ); void ui_draw(); -void ui_init(); -void ui_end(); +void ui_main_draw(); +void ui_main_newline(); +void ui_main_print( const char * str, size_t len, Colour fg, Colour bg, bool bold ); + +void ui_chat_draw(); +void ui_chat_newline(); +void ui_chat_print( const char * str, size_t len, Colour fg, Colour bg, bool bold ); + +bool ui_urgent(); -#endif // _UI_H_ +int ui_display_fd(); // TODO: very x11 specific! + +void ui_init(); +void ui_term(); diff --git a/src/x11.cc b/src/x11.cc @@ -0,0 +1,639 @@ +#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" +#include "textbox.h" + +struct TextBufferView { + TextBuffer text; + + int x; + int y; + int width; + int height; + + size_t scroll_offset; +}; + +struct { + Display * display; + int screen; + + GC gc; + Colormap colorMap; + + Window window; + + TextBufferView main_text_view; + TextBufferView chat_text_view; + + int width; + int height; + int depth; + + bool has_focus; +} UI; + +struct MudFont { + int ascent; + int descent; + + int height; + int width; + + XFontStruct * font; +}; + +struct { + ulong bg; + ulong fg; + + XColor xBG; + XColor xFG; + + ulong statusBG; + ulong statusFG; + + ulong inputBG; + ulong inputFG; + ulong cursor; + + MudFont font; + MudFont fontBold; + + union { + struct { + ulong black; + ulong red; + ulong green; + ulong yellow; + ulong blue; + ulong magenta; + ulong cyan; + ulong white; + + ulong lblack; + ulong lred; + ulong lgreen; + ulong lyellow; + ulong lblue; + ulong lmagenta; + ulong lcyan; + ulong lwhite; + + ulong system; + } Colours; + + ulong colours[ 2 ][ 8 ]; + }; +} Style; + +static void textview_draw( const TextBufferView * tv ) { + if( tv->width == 0 || tv->height == 0 ) + return; + + Pixmap doublebuf = XCreatePixmap( UI.display, UI.window, tv->width, tv->height, UI.depth ); + + XSetForeground( UI.display, UI.gc, Style.bg ); + XFillRectangle( UI.display, doublebuf, UI.gc, 0, 0, tv->width, tv->height ); + + /* + * lines refers to lines of text sent from the game + * rows refers to visual rows of text in the client, so when lines get + * wrapped they have more than one row + */ + size_t lines_drawn = 0; + size_t rows_drawn = 0; + size_t tv_rows = tv->height / ( Style.font.height + SPACING ); + size_t tv_cols = tv->width / Style.font.width; + + while( rows_drawn < tv_rows && lines_drawn < tv->text.num_lines ) { + const TextBuffer::Line & line = tv->text.lines[ ( tv->text.head + tv->text.num_lines - tv->scroll_offset - lines_drawn ) % tv->text.max_lines ]; + + size_t line_rows = 1 + line.len / tv_cols; + if( line.len > 0 && line.len % tv_cols == 0 ) + line_rows--; + + for( size_t i = 0; i < line.len; i++ ) { + const TextBuffer::Glyph & glyph = line.glyphs[ i ]; + + size_t row = i / tv_cols; + + int left = ( i % tv_cols ) * Style.font.width; + int top = tv->height - ( rows_drawn + line_rows - row ) * ( Style.font.height + SPACING ); + if( top < 0 ) + continue; + + int fg, bg, bold; + unpack_style( glyph.style, &fg, &bg, &bold ); + + // bg + int top_spacing = SPACING / 2; + int bot_spacing = SPACING - top_spacing; + XSetForeground( UI.display, UI.gc, bg == SYSTEM ? Style.Colours.system : Style.colours[ 0 ][ bg ] ); + XFillRectangle( UI.display, doublebuf, UI.gc, left, top - top_spacing, Style.font.width, Style.font.height + bot_spacing ); + + // fg + XSetFont( UI.display, UI.gc, ( bold ? Style.fontBold : Style.font ).font->fid ); + XSetForeground( UI.display, UI.gc, fg == SYSTEM ? Style.Colours.system : Style.colours[ bold ][ fg ] ); + XDrawString( UI.display, doublebuf, UI.gc, left, top + Style.font.ascent + SPACING, &glyph.ch, 1 ); + } + + lines_drawn++; + rows_drawn += line_rows; + } + + XCopyArea( UI.display, doublebuf, UI.window, UI.gc, 0, 0, tv->width, tv->height, tv->x, tv->y ); + XFreePixmap( UI.display, doublebuf ); +} + +static void textview_scroll( TextBufferView * tv, int offset ) { + if( offset < 0 ) { + tv->scroll_offset -= min( size_t( -offset ), tv->scroll_offset ); + } + else { + tv->scroll_offset = min( tv->scroll_offset + offset, tv->text.num_lines - 1 ); + } + + textview_draw( tv ); +} + +static void textview_page_down( TextBufferView * tv ) { + size_t rows = tv->height / ( Style.font.height + SPACING ); + textview_scroll( tv, -int( rows ) + 1 ); +} + +static void textview_page_up( TextBufferView * tv ) { + size_t rows = tv->height / ( Style.font.height + SPACING ); + textview_scroll( tv, rows - 1 ); +} + +static void textview_set_pos( TextBufferView * tv, int x, int y ) { + tv->x = x; + tv->y = y; +} + +static void textview_set_size( TextBufferView * tv, int w, int h ) { + tv->width = w; + tv->height = h; +} + +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_clear_status() { + 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_status() { + 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 draw_input() { + InputBuffer input = input_get_buffer(); + + 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 ), input.buf, input.len ); + + XSetForeground( UI.display, UI.gc, Style.cursor ); + XFillRectangle( UI.display, UI.window, UI.gc, PADDING + Style.font.width * input.cursor_pos, UI.height - ( PADDING + Style.font.height ), Style.font.width, Style.font.height ); + + if( input.cursor_pos < input.len ) { + XSetForeground( UI.display, UI.gc, Style.bg ); + XDrawString( UI.display, UI.window, UI.gc, PADDING + Style.font.width * input.cursor_pos, UI.height - ( PADDING + Style.font.descent ), input.buf + input.cursor_pos, 1 ); + } +} + +void ui_draw() { + XClearWindow( UI.display, UI.window ); + + draw_input(); + ui_draw_status(); + + textview_draw( &UI.chat_text_view ); + textview_draw( &UI.main_text_view ); + + 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 ); +} + +static void eventButtonPress( XEvent * event ) { } + +static void eventButtonRelease( XEvent * event ) { } + +static void eventMessage( XEvent * event ) { + if( ( Atom ) event->xclient.data.l[ 0 ] == wmDeleteWindow ) { + script_handleClose(); + } +} + +static 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 ); + + textview_set_pos( &UI.chat_text_view, PADDING, PADDING ); + textview_set_size( &UI.chat_text_view, UI.width - ( 2 * PADDING ), ( Style.font.height + SPACING ) * CHAT_ROWS ); + + textview_set_pos( &UI.main_text_view, PADDING, ( PADDING * 2 ) + CHAT_ROWS * ( Style.font.height + SPACING ) + 1 ); + textview_set_size( &UI.main_text_view, UI.width - ( 2 * PADDING ), UI.height + - ( ( ( Style.font.height + SPACING ) * CHAT_ROWS ) + ( PADDING * 2 ) ) + - ( ( Style.font.height * 2 ) + ( PADDING * 5 ) ) - 1 + ); +} + +static void eventExpose( XEvent * event ) { + ui_draw(); +} + +static 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_return(); + draw_input(); + break; + + case XK_BackSpace: + input_backspace(); + draw_input(); + break; + + case XK_Delete: + input_delete(); + draw_input(); + break; + + case XK_Page_Up: + if( shift ) + textview_scroll( &UI.main_text_view, 1 ); + else + textview_page_up( &UI.main_text_view ); + break; + + case XK_Page_Down: + if( shift ) + textview_scroll( &UI.main_text_view, -1 ); + else + textview_page_down( &UI.main_text_view ); + break; + + case XK_Up: + input_up(); + draw_input(); + break; + + case XK_Down: + input_down(); + draw_input(); + break; + + case XK_Left: + input_left(); + draw_input(); + break; + + case XK_Right: + input_right(); + draw_input(); + 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 ); + draw_input(); + } + + break; + } + + #undef ADD_MACRO +} + +static void eventFocusOut( XEvent * event ) { + UI.has_focus = false; +} + +static void eventFocusIn( XEvent * event ) { + UI.has_focus = true; + + 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 ); + } +} + +static 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.width = font.font->max_bounds.rbearing - font.font->min_bounds.lbearing; + font.height = font.ascent + font.descent; + + return font; +} + +static 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() { + UI = { }; + + text_init( &UI.main_text_view.text, SCROLLBACK_SIZE ); + text_init( &UI.chat_text_view.text, CHAT_ROWS ); + 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_main_draw() { + textview_draw( &UI.main_text_view ); +} + +void ui_main_newline() { + text_newline( &UI.main_text_view.text ); +} + +void ui_main_print( const char * str, size_t len, Colour fg, Colour bg, bool bold ) { + text_add( &UI.main_text_view.text, str, len, fg, bg, bold ); +} + +void ui_chat_draw() { + textview_draw( &UI.chat_text_view ); +} + +void ui_chat_newline() { + text_newline( &UI.chat_text_view.text ); +} + +void ui_chat_print( const char * str, size_t len, Colour fg, Colour bg, bool bold ) { + text_add( &UI.chat_text_view.text, str, len, fg, bg, bold ); +} + +void ui_urgent() { + if( !UI.has_focus ) { + XWMHints * hints = XGetWMHints( UI.display, UI.window ); + hints->flags |= XUrgencyHint; + XSetWMHints( UI.display, UI.window, hints ); + XFree( hints ); + } +} + +int ui_display_fd() { + return ConnectionNumber( UI.display ); +} + +void ui_term() { + text_destroy( &UI.main_text_view.text ); + text_destroy( &UI.chat_text_view.text ); + 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 ); +}