mudgangster

Tiny, scriptable MUD client
Log | Files | Refs | README

win32.cc (13068B)


      1 #include <windows.h>
      2 #include <windowsx.h>
      3 #include <Winsock2.h>
      4 #include <stdio.h>
      5 
      6 #include "common.h"
      7 #include "input.h"
      8 #include "script.h"
      9 #include "ui.h"
     10 
     11 #include "platform_network.h"
     12 
     13 #define WINDOW_CLASSNAME "MudGangsterClass"
     14 
     15 struct {
     16 	HWND hwnd;
     17 	HDC hdc;
     18 
     19 	HDC back_buffer;
     20 	HBITMAP back_buffer_bitmap;
     21 
     22 	int max_width, max_height;
     23 } UI;
     24 
     25 struct Socket {
     26 	TCPSocket sock;
     27 	bool in_use;
     28 };
     29 
     30 static Socket sockets[ 128 ];
     31 
     32 void * platform_connect( const char ** err, const char * host, int port ) {
     33 	size_t idx;
     34 	{
     35 		bool ok = false;
     36 		for( size_t i = 0; i < ARRAY_COUNT( sockets ); i++ ) {
     37 			if( !sockets[ i ].in_use ) {
     38 				idx = i;
     39 				ok = true;
     40 				break;
     41 			}
     42 		}
     43 
     44 		if( !ok ) {
     45 			*err = "too many connections";
     46 			return NULL;
     47 		}
     48 	}
     49 
     50 	NetAddress addr;
     51 	{
     52 		bool ok = dns_first( host, &addr );
     53 		if( !ok ) {
     54 			*err = "couldn't resolve hostname"; // TODO: error from dns_first
     55 			return NULL;
     56 		}
     57 	}
     58 	addr.port = checked_cast< u16 >( port );
     59 
     60 	TCPSocket sock;
     61 	bool ok = net_new_tcp( &sock, addr, err );
     62 	if( !ok )
     63 		return NULL;
     64 
     65 	sockets[ idx ].sock = sock;
     66 	sockets[ idx ].in_use = true;
     67 
     68 	WSAAsyncSelect( sock.fd, UI.hwnd, 12345, FD_READ | FD_CLOSE );
     69 
     70 	return &sockets[ idx ];
     71 }
     72 
     73 void platform_send( void * vsock, const char * data, size_t len ) {
     74 	Socket * sock = ( Socket * ) vsock;
     75 	net_send( sock->sock, data, len );
     76 }
     77 
     78 void platform_close( void * vsock ) {
     79 	Socket * sock = ( Socket * ) vsock;
     80 	net_destroy( &sock->sock );
     81 	sock->in_use = false;
     82 }
     83 
     84 static Socket * socket_from_fd( int fd ) {
     85 	for( Socket & sock : sockets ) {
     86 		if( sock.in_use && sock.sock.fd == fd ) {
     87 			return &sock;
     88 		}
     89 	}
     90 
     91 	return NULL;
     92 }
     93 
     94 struct MudFont {
     95 	int ascent;
     96 	int width, height;
     97 	HFONT regular;
     98 	HFONT bold;
     99 };
    100 
    101 struct {
    102 	COLORREF bg;
    103 	COLORREF status_bg;
    104 	COLORREF cursor;
    105 
    106 	MudFont font;
    107 	HFONT dc_font;
    108 
    109 	union {
    110 		struct {
    111 			COLORREF black;
    112 			COLORREF red;
    113 			COLORREF green;
    114 			COLORREF yellow;
    115 			COLORREF blue;
    116 			COLORREF magenta;
    117 			COLORREF cyan;
    118 			COLORREF white;
    119 
    120 			COLORREF lblack;
    121 			COLORREF lred;
    122 			COLORREF lgreen;
    123 			COLORREF lyellow;
    124 			COLORREF lblue;
    125 			COLORREF lmagenta;
    126 			COLORREF lcyan;
    127 			COLORREF lwhite;
    128 
    129 			COLORREF system;
    130 		} Colours;
    131 
    132 		COLORREF colours[ 2 ][ 8 ];
    133 	};
    134 } Style;
    135 
    136 static COLORREF get_colour( Colour colour, bool bold ) {
    137 	switch( colour ) {
    138 		case SYSTEM:
    139 			return Style.Colours.system;
    140 		case COLOUR_BG:
    141 			return Style.bg;
    142 		case COLOUR_STATUSBG:
    143 			return Style.status_bg;
    144 		case COLOUR_CURSOR:
    145 			return Style.cursor;
    146 	}
    147 
    148 	return Style.colours[ bold ][ colour ];
    149 }
    150 
    151 void platform_fill_rect( int left, int top, int width, int height, Colour colour, bool bold ) {
    152 	ZoneScoped;
    153 
    154 	// TODO: preallocate these
    155 	HBRUSH brush = CreateSolidBrush( get_colour( colour, bold ) );
    156 	RECT r = { left, top, left + width, top + height };
    157 	FillRect( UI.back_buffer, &r, brush );
    158 	DeleteObject( brush );
    159 }
    160 
    161 void platform_draw_char( int left, int top, char c, Colour colour, bool bold, bool force_bold_font ) {
    162 	ZoneScoped;
    163 
    164 	SelectObject( UI.back_buffer, ( bold || force_bold_font ? Style.font.bold : Style.font.regular ) );
    165 	SetTextColor( UI.back_buffer, get_colour( colour, bold ) );
    166 	TextOutA( UI.back_buffer, left, top + SPACING, &c, 1 );
    167 }
    168 
    169 void platform_make_dirty( int left, int top, int width, int height ) {
    170 	ZoneScoped;
    171 
    172 	RECT r = { left, top, left + width, top + height };
    173 	InvalidateRect( UI.hwnd, &r, FALSE );
    174 }
    175 
    176 void ui_get_font_size( int * fw, int * fh ) {
    177 	*fw = Style.font.width;
    178 	*fh = Style.font.height;
    179 }
    180 
    181 void ui_urgent() {
    182 	FlashWindow( UI.hwnd, FALSE );
    183 }
    184 
    185 bool ui_set_font( const char * name, int size ) {
    186 	HFONT regular = CreateFontA( size, 0, 0, 0, FW_REGULAR,
    187 		FALSE, FALSE, FALSE, DEFAULT_CHARSET,
    188 		OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, FIXED_PITCH,
    189 		name );
    190 
    191 	if( regular == NULL )
    192 		return false;
    193 
    194 	HFONT bold = CreateFontA( size, 0, 0, 0, FW_BOLD,
    195 		FALSE, FALSE, FALSE, DEFAULT_CHARSET,
    196 		OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, FIXED_PITCH,
    197 		name );
    198 
    199 	if( bold == NULL ) {
    200 		DeleteObject( regular );
    201 		return false;
    202 	}
    203 
    204 	if( Style.font.regular != NULL ) {
    205 		SelectObject( UI.hdc, Style.dc_font );
    206 		DeleteObject( Style.font.regular );
    207 		DeleteObject( Style.font.bold );
    208 	}
    209 
    210 	Style.font.regular = regular;
    211 	Style.font.bold = bold;
    212 	Style.dc_font = ( HFONT ) SelectObject( UI.hdc, Style.font.regular );
    213 
    214 	TEXTMETRIC metrics;
    215 	GetTextMetrics( UI.hdc, &metrics );
    216 
    217 	Style.font.height = metrics.tmHeight;
    218 	Style.font.width = metrics.tmAveCharWidth;
    219 	Style.font.ascent = metrics.tmAscent;
    220 
    221 	ui_update_layout();
    222 	ui_redraw_everything();
    223 
    224 	return true;
    225 }
    226 
    227 void platform_set_clipboard( const char * str, size_t len ) {
    228 	if( OpenClipboard( NULL ) == FALSE )
    229 		return;
    230 
    231 	HGLOBAL mem = GlobalAlloc( GMEM_MOVEABLE, len );
    232 	if( mem == NULL ) {
    233 		CloseClipboard();
    234 		return;
    235 	}
    236 
    237 	void * p = GlobalLock( mem );
    238 	if( p == NULL )
    239 		FATAL( "GlobalLock" );
    240 
    241 	memcpy( p, str, len );
    242 	GlobalUnlock( mem );
    243 
    244 	EmptyClipboard();
    245 	SetClipboardData( CF_TEXT, mem );
    246 	CloseClipboard();
    247 }
    248 
    249 static LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ) {
    250 	ZoneScoped;
    251 
    252 	switch( msg ) {
    253 		case WM_CREATE: {
    254 			UI.hdc = GetDC( hwnd );
    255 			UI.back_buffer = CreateCompatibleDC( UI.hdc );
    256 
    257 			SetBkMode( UI.back_buffer, TRANSPARENT );
    258 
    259 			Style.bg             = RGB( 0x1a, 0x1a, 0x1a );
    260 			Style.status_bg      = RGB( 0x33, 0x33, 0x33 );
    261 			Style.cursor         = RGB( 0x00, 0xff, 0x00 );
    262 			Style.Colours.system = RGB( 0xff, 0xff, 0xff );
    263 
    264 			Style.Colours.black   = RGB( 0x1a, 0x1a, 0x1a );
    265 			Style.Colours.red     = RGB( 0xca, 0x44, 0x33 );
    266 			Style.Colours.green   = RGB( 0x17, 0x8a, 0x3a );
    267 			Style.Colours.yellow  = RGB( 0xdc, 0x7c, 0x2a );
    268 			Style.Colours.blue    = RGB( 0x41, 0x5e, 0x87 );
    269 			Style.Colours.magenta = RGB( 0x5e, 0x46, 0x8c );
    270 			Style.Colours.cyan    = RGB( 0x35, 0x78, 0x9b );
    271 			Style.Colours.white   = RGB( 0xb6, 0xc2, 0xc4 );
    272 
    273 			Style.Colours.lblack   = RGB( 0x66, 0x66, 0x66 );
    274 			Style.Colours.lred     = RGB( 0xff, 0x29, 0x54 );
    275 			Style.Colours.lgreen   = RGB( 0x5d, 0xd0, 0x30 );
    276 			Style.Colours.lyellow  = RGB( 0xfa, 0xfc, 0x4f );
    277 			Style.Colours.lblue    = RGB( 0x35, 0x81, 0xe1 );
    278 			Style.Colours.lmagenta = RGB( 0x87, 0x5f, 0xff );
    279 			Style.Colours.lcyan    = RGB( 0x29, 0xfb, 0xff );
    280 			Style.Colours.lwhite   = RGB( 0xce, 0xdb, 0xde );
    281 
    282 			ui_set_font( "", 14 );
    283 		} break;
    284 
    285 		case WM_SIZE: {
    286 			int width = LOWORD( lParam );
    287 			int height = HIWORD( lParam );
    288 
    289 			int old_max_width = UI.max_width;
    290 			int old_max_height = UI.max_height;
    291 
    292 			UI.max_width = max( UI.max_width, width );
    293 			UI.max_height = max( UI.max_height, height );
    294 
    295 			if( UI.max_width != old_max_width || UI.max_height != old_max_height ) {
    296 				if( old_max_width != -1 ) {
    297 					DeleteObject( UI.back_buffer_bitmap );
    298 				}
    299 				UI.back_buffer_bitmap = CreateCompatibleBitmap( UI.hdc, UI.max_width, UI.max_height );
    300 				SelectObject( UI.back_buffer, UI.back_buffer_bitmap );
    301 			}
    302 
    303 			ui_resize( width, height );
    304 			ui_redraw_everything();
    305 		} break;
    306 
    307 		case WM_PAINT: {
    308 			RECT r;
    309 			GetUpdateRect( UI.hwnd, &r, FALSE );
    310 			PAINTSTRUCT ps;
    311 			HDC hdc = BeginPaint( UI.hwnd, &ps );
    312 			BitBlt( UI.hdc, r.left, r.top, r.right - r.left, r.bottom - r.top, UI.back_buffer, r.left, r.top, SRCCOPY );
    313 			EndPaint( UI.hwnd, &ps );
    314 		} break;
    315 
    316 		case WM_ERASEBKGND:
    317 			return TRUE;
    318 
    319 		case WM_LBUTTONDOWN: {
    320 			ui_mouse_down( GET_X_LPARAM( lParam ), GET_Y_LPARAM( lParam ) );
    321 		} break;
    322 
    323 		case WM_MOUSEMOVE: {
    324 			ui_mouse_move( GET_X_LPARAM( lParam ), GET_Y_LPARAM( lParam ) );
    325 		} break;
    326 
    327 		case WM_LBUTTONUP: {
    328 			ui_mouse_up( GET_X_LPARAM( lParam ), GET_Y_LPARAM( lParam ) );
    329 		} break;
    330 
    331 		case WM_CLOSE: {
    332 			DestroyWindow( hwnd );
    333 		} break;
    334 
    335 		case WM_DESTROY: {
    336 			PostQuitMessage( 0 );
    337 		} break;
    338 
    339 		case WM_TIMER: {
    340 			script_fire_intervals();
    341 		} break;
    342 
    343 		case WM_CHAR: {
    344 			if( wParam >= ' ' && wParam < 127 ) {
    345 				char c = char( wParam );
    346 				input_add( &c, 1 );
    347 			}
    348 		} break;
    349 
    350 		case WM_KEYDOWN:
    351 		case WM_SYSKEYDOWN: {
    352 			#define ADD_MACRO( key, name ) \
    353 				case key: \
    354 					  script_doMacro( name, sizeof( name ) - 1, shift, ctrl, alt ); \
    355 					break
    356 
    357 			bool shift = ( GetKeyState( VK_SHIFT ) & 0x8000 ) != 0;
    358 			bool ctrl = ( GetKeyState( VK_CONTROL ) & 0x8000 ) != 0;
    359 			bool alt = ( GetKeyState( VK_MENU ) & 0x8000 ) != 0;
    360 
    361 			switch( wParam ) {
    362 				case VK_BACK:
    363 					input_backspace();
    364 					break;
    365 
    366 				case VK_DELETE:
    367 					input_delete();
    368 					break;
    369 
    370 				case VK_RETURN:
    371 					input_return();
    372 					break;
    373 
    374 				case VK_LEFT:
    375 					input_left();
    376 					break;
    377 
    378 				case VK_RIGHT:
    379 					input_right();
    380 					break;
    381 
    382 				case VK_UP:
    383 					input_up();
    384 					break;
    385 
    386 				case VK_DOWN:
    387 					input_down();
    388 					break;
    389 
    390 				case VK_PRIOR:
    391 					if( shift )
    392 						ui_scroll( 1 );
    393 					else
    394 						ui_page_up();
    395 					break;
    396 
    397 				case VK_NEXT:
    398 					if( shift )
    399 						ui_scroll( -1 );
    400 					else
    401 						ui_page_down();
    402 					break;
    403 
    404 				ADD_MACRO( VK_NUMPAD0, "kp0" );
    405 				ADD_MACRO( VK_NUMPAD1, "kp1" );
    406 				ADD_MACRO( VK_NUMPAD2, "kp2" );
    407 				ADD_MACRO( VK_NUMPAD3, "kp3" );
    408 				ADD_MACRO( VK_NUMPAD4, "kp4" );
    409 				ADD_MACRO( VK_NUMPAD5, "kp5" );
    410 				ADD_MACRO( VK_NUMPAD6, "kp6" );
    411 				ADD_MACRO( VK_NUMPAD7, "kp7" );
    412 				ADD_MACRO( VK_NUMPAD8, "kp8" );
    413 				ADD_MACRO( VK_NUMPAD9, "kp9" );
    414 
    415 				ADD_MACRO( VK_MULTIPLY, "kp*" );
    416 				ADD_MACRO( VK_DIVIDE, "kp/" );
    417 				ADD_MACRO( VK_SUBTRACT, "kp-" );
    418 				ADD_MACRO( VK_ADD, "kp+" );
    419 				ADD_MACRO( VK_DECIMAL, "kp." );
    420 
    421 				ADD_MACRO( VK_F1, "f1" );
    422 				ADD_MACRO( VK_F2, "f2" );
    423 				ADD_MACRO( VK_F3, "f3" );
    424 				ADD_MACRO( VK_F4, "f4" );
    425 				ADD_MACRO( VK_F5, "f5" );
    426 				ADD_MACRO( VK_F6, "f6" );
    427 				ADD_MACRO( VK_F7, "f7" );
    428 				ADD_MACRO( VK_F8, "f8" );
    429 				ADD_MACRO( VK_F9, "f9" );
    430 				ADD_MACRO( VK_F10, "f10" );
    431 				ADD_MACRO( VK_F11, "f11" );
    432 				ADD_MACRO( VK_F12, "f12" );
    433 
    434 				default: {
    435 					char c = MapVirtualKeyA( wParam, MAPVK_VK_TO_CHAR );
    436 					if( c == 0 )
    437 						break;
    438 					c = tolower( c );
    439 					if( ctrl && c == 'v' && !shift && !alt ) {
    440 						if( OpenClipboard( NULL ) == TRUE ) {
    441 							const char * clipboard = ( const char * ) GetClipboardData( CF_TEXT );
    442 							if( clipboard != NULL )
    443 								input_add( clipboard, strlen( clipboard ) );
    444 							CloseClipboard();
    445 						}
    446 					}
    447 					else if( ctrl || alt ) {
    448 						script_doMacro( &c, 1, shift, ctrl, alt );
    449 					}
    450 				} break;
    451 			}
    452 
    453 			#undef ADD_MACRO
    454 		} break;
    455 
    456 		case 12345: {
    457 			if( WSAGETSELECTERROR( lParam ) ) {
    458 				printf( "bye\n" );
    459 				break;
    460 			}
    461 
    462 			SOCKET fd = ( SOCKET ) wParam;
    463 			Socket * sock = socket_from_fd( fd );
    464 			if( sock == NULL )
    465 				break;
    466 
    467 			assert( WSAGETSELECTEVENT( lParam ) == FD_CLOSE || WSAGETSELECTEVENT( lParam ) == FD_READ );
    468 
    469 			while( true ) {
    470 				char buf[ 2048 ];
    471 				int n = recv( fd, buf, sizeof( buf ), 0 );
    472 				if( n > 0 ) {
    473 					script_socketData( sock, buf, n );
    474 				}
    475 				else if( n == 0 ) {
    476 					script_socketData( sock, NULL, n );
    477 					break;
    478 				}
    479 				else {
    480 					int err = WSAGetLastError();
    481 					if( err != WSAEWOULDBLOCK )
    482 						FATAL( "shit %d", err );
    483 					break;
    484 				}
    485 			}
    486 		} break;
    487 
    488 		default:
    489 			return DefWindowProc( hwnd, msg, wParam, lParam );
    490 	}
    491 
    492 	ui_redraw_dirty();
    493 
    494 	return 0;
    495 }
    496 
    497 static bool is_macro( const MSG * msg ) {
    498 	if( msg->message != WM_KEYDOWN && msg->message != WM_KEYUP
    499 	    && msg->message != WM_SYSKEYDOWN && msg->message != WM_SYSKEYUP )
    500 		return false;
    501 
    502 	WPARAM macro_vks[] = {
    503 		VK_NUMPAD0,
    504 		VK_NUMPAD1,
    505 		VK_NUMPAD2,
    506 		VK_NUMPAD3,
    507 		VK_NUMPAD4,
    508 		VK_NUMPAD5,
    509 		VK_NUMPAD6,
    510 		VK_NUMPAD7,
    511 		VK_NUMPAD8,
    512 		VK_NUMPAD9,
    513 		VK_MULTIPLY,
    514 		VK_DIVIDE,
    515 		VK_SUBTRACT,
    516 		VK_ADD,
    517 		VK_DECIMAL,
    518 	};
    519 
    520 	for( WPARAM vk : macro_vks ) {
    521 		if( msg->wParam == vk )
    522 			return true;
    523 	}
    524 
    525 	bool ctrl = ( GetKeyState( VK_CONTROL ) & 0x8000 ) != 0;
    526 	bool alt = ( GetKeyState( VK_MENU ) & 0x8000 ) != 0;
    527 
    528 	return ctrl || alt;
    529 }
    530 
    531 int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) {
    532 	for( Socket & s : sockets ) {
    533 		s.in_use = false;
    534 	}
    535 
    536 	UI = { };
    537 	UI.max_width = -1;
    538 
    539 	Style = { };
    540 
    541 	WNDCLASSEX wc = { };
    542 	wc.cbSize = sizeof( WNDCLASSEX );
    543 	wc.lpfnWndProc = WndProc;
    544 	wc.hInstance = hInstance;
    545 	wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    546 	wc.lpszClassName = WINDOW_CLASSNAME;
    547 	wc.hbrBackground = CreateSolidBrush( RGB( 0x1a, 0x1a, 0x1a ) );
    548 
    549 	if( !RegisterClassEx( &wc ) ) {
    550 		MessageBox( NULL, "Window Registration Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK );
    551 		return 0;
    552 	}
    553 
    554 	UI.hwnd = CreateWindowExA(
    555 		NULL,
    556 		WINDOW_CLASSNAME,
    557 		"Mud Gangster",
    558 		WS_OVERLAPPEDWINDOW | WS_MAXIMIZE | WS_VISIBLE,
    559 		CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
    560 		NULL, NULL, hInstance, NULL );
    561 
    562 	if( UI.hwnd == NULL ) {
    563 		MessageBox( NULL, "Window Creation Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK );
    564 		return 0;
    565 	}
    566 
    567 	ShowWindow( UI.hwnd, SW_MAXIMIZE );
    568 	UpdateWindow( UI.hwnd );
    569 	SetTimer( UI.hwnd, 1, 500, NULL );
    570 
    571 	net_init();
    572 	ui_init();
    573 	script_init();
    574 
    575 	FrameMark;
    576 
    577 	MSG msg;
    578 	while( GetMessage( &msg, NULL, 0, 0 ) > 0 ) {
    579 		if( !is_macro( &msg ) )
    580 			TranslateMessage( &msg );
    581 		DispatchMessage( &msg );
    582 	}
    583 
    584 	script_term();
    585 	ui_term();
    586 	net_term();
    587 
    588 	// TODO: clean up UI stuff
    589 
    590 	return 0;
    591 }