x11.cc (15112B)
1 #include <err.h> 2 #include <poll.h> 3 4 #include <X11/Xutil.h> 5 #include <X11/XKBlib.h> 6 #include <X11/cursorfont.h> 7 #include <X11/keysym.h> 8 9 #include "common.h" 10 #include "input.h" 11 #include "script.h" 12 #include "ui.h" 13 14 #include "platform_ui.h" 15 #include "platform_network.h" 16 17 #include "libclipboard/libclipboard.h" 18 19 struct Socket { 20 TCPSocket sock; 21 bool in_use; 22 }; 23 24 static Socket sockets[ 128 ]; 25 26 static bool closing = false; 27 28 static clipboard_c * clipboard; 29 30 void * platform_connect( const char ** err, const char * host, int port ) { 31 size_t idx; 32 { 33 bool ok = false; 34 for( size_t i = 0; i < ARRAY_COUNT( sockets ); i++ ) { 35 if( !sockets[ i ].in_use ) { 36 idx = i; 37 ok = true; 38 break; 39 } 40 } 41 42 if( !ok ) { 43 *err = "too many connections"; 44 return NULL; 45 } 46 } 47 48 NetAddress addr; 49 { 50 bool ok = dns_first( host, &addr ); 51 if( !ok ) { 52 *err = "couldn't resolve hostname"; // TODO: error from dns_first 53 return NULL; 54 } 55 } 56 addr.port = checked_cast< u16 >( port ); 57 58 TCPSocket sock; 59 bool ok = net_new_tcp( &sock, addr, err ); 60 if( !ok ) 61 return NULL; 62 63 sockets[ idx ].sock = sock; 64 sockets[ idx ].in_use = true; 65 66 return &sockets[ idx ]; 67 } 68 69 void platform_send( void * vsock, const char * data, size_t len ) { 70 Socket * sock = ( Socket * ) vsock; 71 net_send( sock->sock, data, len ); 72 } 73 74 void platform_close( void * vsock ) { 75 Socket * sock = ( Socket * ) vsock; 76 net_destroy( &sock->sock ); 77 sock->in_use = false; 78 } 79 80 struct { 81 Display * display; 82 int screen; 83 84 Pixmap back_buffer; 85 86 GC gc; 87 Colormap colorMap; 88 89 Window window; 90 91 int max_width, max_height; 92 int depth; 93 94 bool dirty; 95 int dirty_left, dirty_top, dirty_right, dirty_bottom; 96 97 bool has_focus; 98 } UI; 99 100 struct MudFont { 101 int width, height; 102 int ascent; 103 XFontStruct * regular; 104 XFontStruct * bold; 105 }; 106 107 struct { 108 ulong bg; 109 ulong status_bg; 110 ulong cursor; 111 112 MudFont font; 113 114 union { 115 struct { 116 ulong black; 117 ulong red; 118 ulong green; 119 ulong yellow; 120 ulong blue; 121 ulong magenta; 122 ulong cyan; 123 ulong white; 124 125 ulong lblack; 126 ulong lred; 127 ulong lgreen; 128 ulong lyellow; 129 ulong lblue; 130 ulong lmagenta; 131 ulong lcyan; 132 ulong lwhite; 133 134 ulong system; 135 } Colours; 136 137 ulong colours[ 2 ][ 8 ]; 138 }; 139 } Style; 140 141 static void set_fg( Colour colour, bool bold ) { 142 ulong c; 143 144 switch( colour ) { 145 case SYSTEM: 146 c = Style.Colours.system; 147 break; 148 149 case COLOUR_BG: 150 c = Style.bg; 151 break; 152 153 case COLOUR_STATUSBG: 154 c = Style.status_bg; 155 break; 156 157 case COLOUR_CURSOR: 158 c = Style.cursor; 159 break; 160 161 default: 162 c = Style.colours[ bold ][ colour ]; 163 break; 164 } 165 166 XSetForeground( UI.display, UI.gc, c ); 167 } 168 169 void platform_make_dirty( int left, int top, int width, int height ) { 170 int right = left + width; 171 int bottom = top + height; 172 173 if( !UI.dirty ) { 174 UI.dirty = true; 175 UI.dirty_left = left; 176 UI.dirty_top = top; 177 UI.dirty_right = right; 178 UI.dirty_bottom = bottom; 179 } 180 else { 181 UI.dirty_left = min( UI.dirty_left, left ); 182 UI.dirty_top = min( UI.dirty_top, top ); 183 UI.dirty_right = max( UI.dirty_right, right ); 184 UI.dirty_bottom = max( UI.dirty_bottom, bottom ); 185 } 186 } 187 188 void platform_fill_rect( int left, int top, int width, int height, Colour colour, bool bold ) { 189 set_fg( colour, bold ); 190 XFillRectangle( UI.display, UI.back_buffer, UI.gc, left, top, width, height ); 191 } 192 193 void platform_draw_char( int left, int top, char c, Colour colour, bool bold, bool force_bold_font ) { 194 XSetFont( UI.display, UI.gc, ( bold || force_bold_font ? Style.font.bold : Style.font.regular )->fid ); 195 set_fg( colour, bold ); 196 XDrawString( UI.display, UI.back_buffer, UI.gc, left, top + Style.font.ascent + SPACING, &c, 1 ); 197 } 198 199 static Atom wmDeleteWindow; 200 201 static void event_mouse_down( XEvent * xevent ) { 202 ui_mouse_down( xevent->xbutton.x, xevent->xbutton.y ); 203 } 204 205 static void event_mouse_move( XEvent * xevent ) { 206 ui_mouse_move( xevent->xmotion.x, xevent->xmotion.y ); 207 } 208 209 static void event_mouse_up( XEvent * xevent ) { 210 ui_mouse_up( xevent->xbutton.x, xevent->xbutton.y ); 211 } 212 213 static void event_message( XEvent * xevent ) { 214 if( ( Atom ) xevent->xclient.data.l[ 0 ] == wmDeleteWindow ) { 215 script_handleClose(); 216 closing = true; 217 } 218 } 219 220 static void event_resize( XEvent * xevent ) { 221 int width = xevent->xconfigure.width; 222 int height = xevent->xconfigure.height; 223 224 int old_max_width = UI.max_width; 225 int old_max_height = UI.max_height; 226 227 UI.max_width = max( UI.max_width, width ); 228 UI.max_height = max( UI.max_height, height ); 229 230 if( UI.max_width != old_max_width || UI.max_height != old_max_height ) { 231 if( old_max_width != -1 ) { 232 XFreePixmap( UI.display, UI.back_buffer ); 233 } 234 UI.back_buffer = XCreatePixmap( UI.display, UI.window, UI.max_width, UI.max_height, UI.depth ); 235 } 236 237 ui_resize( width, height ); 238 } 239 240 static void event_expose( XEvent * xevent ) { 241 ui_redraw_everything(); 242 } 243 244 static void event_key_press( XEvent * xevent ) { 245 #define ADD_MACRO( key, name ) \ 246 case key: \ 247 script_doMacro( name, sizeof( name ) - 1, shift, ctrl, alt ); \ 248 break 249 250 XKeyEvent * event = &xevent->xkey; 251 252 char keyBuffer[ 32 ]; 253 KeySym key; 254 255 bool shift = event->state & ShiftMask; 256 bool ctrl = event->state & ControlMask; 257 bool alt = event->state & Mod1Mask; 258 259 int len = XLookupString( event, keyBuffer, sizeof( keyBuffer ), &key, NULL ); 260 261 event->state &= ~ShiftMask; 262 char noShiftKeyBuffer[ 32 ]; 263 int noShiftLen = XLookupString( event, noShiftKeyBuffer, sizeof( noShiftKeyBuffer ), NULL, NULL ); 264 265 switch( key ) { 266 case XK_Return: 267 input_return(); 268 break; 269 270 case XK_BackSpace: 271 input_backspace(); 272 break; 273 274 case XK_Delete: 275 input_delete(); 276 break; 277 278 case XK_Page_Up: 279 if( shift ) 280 ui_scroll( 1 ); 281 else 282 ui_page_up(); 283 break; 284 285 case XK_Page_Down: 286 if( shift ) 287 ui_scroll( -1 ); 288 else 289 ui_page_down(); 290 break; 291 292 case XK_Up: 293 input_up(); 294 break; 295 296 case XK_Down: 297 input_down(); 298 break; 299 300 case XK_Left: 301 input_left(); 302 break; 303 304 case XK_Right: 305 input_right(); 306 break; 307 308 case XK_Escape: 309 break; 310 311 ADD_MACRO( XK_KP_1, "kp1" ); 312 ADD_MACRO( XK_KP_End, "kp1" ); 313 314 ADD_MACRO( XK_KP_2, "kp2" ); 315 ADD_MACRO( XK_KP_Down, "kp2" ); 316 317 ADD_MACRO( XK_KP_3, "kp3" ); 318 ADD_MACRO( XK_KP_Page_Down, "kp3" ); 319 320 ADD_MACRO( XK_KP_4, "kp4" ); 321 ADD_MACRO( XK_KP_Left, "kp4" ); 322 323 ADD_MACRO( XK_KP_5, "kp5" ); 324 ADD_MACRO( XK_KP_Begin, "kp5" ); 325 326 ADD_MACRO( XK_KP_6, "kp6" ); 327 ADD_MACRO( XK_KP_Right, "kp6" ); 328 329 ADD_MACRO( XK_KP_7, "kp7" ); 330 ADD_MACRO( XK_KP_Home, "kp7" ); 331 332 ADD_MACRO( XK_KP_8, "kp8" ); 333 ADD_MACRO( XK_KP_Up, "kp8" ); 334 335 ADD_MACRO( XK_KP_9, "kp9" ); 336 ADD_MACRO( XK_KP_Page_Up, "kp9" ); 337 338 ADD_MACRO( XK_KP_0, "kp0" ); 339 ADD_MACRO( XK_KP_Insert, "kp0" ); 340 341 ADD_MACRO( XK_KP_Multiply, "kp*" ); 342 ADD_MACRO( XK_KP_Divide, "kp/" ); 343 ADD_MACRO( XK_KP_Subtract, "kp-" ); 344 ADD_MACRO( XK_KP_Add, "kp+" ); 345 346 ADD_MACRO( XK_KP_Delete, "kp." ); 347 ADD_MACRO( XK_KP_Decimal, "kp." ); 348 349 ADD_MACRO( XK_F1, "f1" ); 350 ADD_MACRO( XK_F2, "f2" ); 351 ADD_MACRO( XK_F3, "f3" ); 352 ADD_MACRO( XK_F4, "f4" ); 353 ADD_MACRO( XK_F5, "f5" ); 354 ADD_MACRO( XK_F6, "f6" ); 355 ADD_MACRO( XK_F7, "f7" ); 356 ADD_MACRO( XK_F8, "f8" ); 357 ADD_MACRO( XK_F9, "f9" ); 358 ADD_MACRO( XK_F10, "f10" ); 359 ADD_MACRO( XK_F11, "f11" ); 360 ADD_MACRO( XK_F12, "f12" ); 361 362 default: 363 if( ctrl && key == XK_v ) { 364 int len; 365 char * contents = clipboard_text_ex( clipboard, &len, LCB_CLIPBOARD ); 366 if( contents != NULL ) { 367 input_add( contents, len ); 368 free( contents ); 369 } 370 } 371 else if( ctrl || alt ) { 372 script_doMacro( noShiftKeyBuffer, noShiftLen, shift, ctrl, alt ); 373 } 374 else if( len > 0 ) { 375 input_add( keyBuffer, len ); 376 } 377 378 break; 379 } 380 381 #undef ADD_MACRO 382 } 383 384 static void event_defocus( XEvent * xevent ) { 385 UI.has_focus = false; 386 } 387 388 static void event_focus( XEvent * xevent ) { 389 UI.has_focus = true; 390 391 XWMHints * hints = XGetWMHints( UI.display, UI.window ); 392 hints->flags &= ~XUrgencyHint; 393 XSetWMHints( UI.display, UI.window, hints ); 394 XFree( hints ); 395 } 396 397 static void ui_handleXEvents() { 398 ZoneScoped; 399 400 void ( *event_handlers[ LASTEvent ] )( XEvent * ) = { }; 401 event_handlers[ ButtonPress ] = event_mouse_down; 402 event_handlers[ MotionNotify ] = event_mouse_move; 403 event_handlers[ ButtonRelease ] = event_mouse_up; 404 event_handlers[ ClientMessage ] = event_message; 405 event_handlers[ ConfigureNotify ] = event_resize; 406 event_handlers[ Expose ] = event_expose; 407 event_handlers[ KeyPress ] = event_key_press; 408 event_handlers[ FocusOut ] = event_defocus; 409 event_handlers[ FocusIn ] = event_focus; 410 411 const char * event_names[ LASTEvent ] = { }; 412 event_names[ ButtonPress ] = "ButtonPress"; 413 event_names[ ButtonRelease ] = "ButtonRelease"; 414 event_names[ MotionNotify ] = "MotionNotify"; 415 event_names[ ClientMessage ] = "ClientMessage"; 416 event_names[ ConfigureNotify ] = "ConfigureNotify"; 417 event_names[ Expose ] = "Expose"; 418 event_names[ KeyPress ] = "KeyPress"; 419 event_names[ FocusOut ] = "FocusOut"; 420 event_names[ FocusIn ] = "FocusIn"; 421 422 do { 423 while( XPending( UI.display ) ) { 424 XEvent event; 425 XNextEvent( UI.display, &event ); 426 427 if( event_handlers[ event.type ] != NULL ) { 428 // printf( "%s\n", event_names[ event.type ] ); 429 event_handlers[ event.type ]( &event ); 430 } 431 } 432 433 ui_redraw_dirty(); 434 435 if( UI.dirty ) { 436 XCopyArea( UI.display, UI.back_buffer, UI.window, UI.gc, UI.dirty_left, UI.dirty_top, UI.dirty_right - UI.dirty_left, UI.dirty_bottom - UI.dirty_top, UI.dirty_left, UI.dirty_top ); 437 UI.dirty = false; 438 ui_handleXEvents(); 439 } 440 } while( UI.dirty ); 441 } 442 443 static MudFont load_font( const char * regular_name, const char * bold_name ) { 444 MudFont font; 445 446 font.regular = XLoadQueryFont( UI.display, regular_name ); 447 if( font.regular == NULL ) 448 errx( 1, "XLoadQueryFont: %s", regular_name ); 449 450 font.bold = XLoadQueryFont( UI.display, bold_name ); 451 if( font.bold == NULL ) 452 errx( 1, "XLoadQueryFont: %s", bold_name ); 453 454 font.ascent = font.regular->ascent; 455 font.width = font.regular->max_bounds.rbearing - font.regular->min_bounds.lbearing; 456 font.height = font.ascent + font.regular->descent; 457 458 return font; 459 } 460 461 static ulong make_color( const char * hex ) { 462 XColor color; 463 XAllocNamedColor( UI.display, UI.colorMap, hex, &color, &color ); 464 return color.pixel; 465 } 466 467 static void initStyle() { 468 Style.bg = make_color( "#1a1a1a" ); 469 Style.status_bg = make_color( "#333333" ); 470 Style.cursor = make_color( "#00ff00" ); 471 Style.Colours.system = make_color( "#ffffff" ); 472 473 Style.Colours.black = make_color( "#1a1a1a" ); 474 Style.Colours.red = make_color( "#ca4433" ); 475 Style.Colours.green = make_color( "#178a3a" ); 476 Style.Colours.yellow = make_color( "#dc7c2a" ); 477 Style.Colours.blue = make_color( "#415e87" ); 478 Style.Colours.magenta = make_color( "#5e468c" ); 479 Style.Colours.cyan = make_color( "#35789b" ); 480 Style.Colours.white = make_color( "#b6c2c4" ); 481 482 Style.Colours.lblack = make_color( "#666666" ); 483 Style.Colours.lred = make_color( "#ff2954" ); 484 Style.Colours.lgreen = make_color( "#5dd030" ); 485 Style.Colours.lyellow = make_color( "#fafc4f" ); 486 Style.Colours.lblue = make_color( "#3581e1" ); 487 Style.Colours.lmagenta = make_color( "#875fff" ); 488 Style.Colours.lcyan = make_color( "#29fbff" ); 489 Style.Colours.lwhite = make_color( "#cedbde" ); 490 491 Style.font = load_font( "-windows-dina-medium-r-normal--10-*-*-*-c-0-*-*", "-windows-dina-bold-r-normal--10-*-*-*-c-0-*-*" ); 492 } 493 494 void platform_ui_init() { 495 ZoneScoped; 496 497 for( Socket & s : sockets ) { 498 s.in_use = false; 499 } 500 501 int default_width = 800; 502 int default_height = 600; 503 504 UI = { }; 505 UI.display = XOpenDisplay( NULL ); 506 UI.screen = XDefaultScreen( UI.display ); 507 UI.max_width = -1; 508 UI.max_height = -1; 509 510 Window root = XRootWindow( UI.display, UI.screen ); 511 UI.depth = XDefaultDepth( UI.display, UI.screen ); 512 Visual * visual = XDefaultVisual( UI.display, UI.screen ); 513 UI.colorMap = XDefaultColormap( UI.display, UI.screen ); 514 515 initStyle(); 516 517 XSetWindowAttributes attr = { }; 518 attr.event_mask = ExposureMask | StructureNotifyMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask; 519 attr.colormap = UI.colorMap; 520 521 UI.window = XCreateWindow( UI.display, root, 0, 0, default_height, default_width, 0, UI.depth, InputOutput, visual, CWBackPixel | CWEventMask | CWColormap, &attr ); 522 UI.gc = XCreateGC( UI.display, UI.window, 0, NULL ); 523 524 XWMHints * hints = XAllocWMHints(); 525 XSetWMHints( UI.display, UI.window, hints ); 526 XFree( hints ); 527 528 Cursor cursor = XCreateFontCursor( UI.display, XC_xterm ); 529 XDefineCursor( UI.display, UI.window, cursor ); 530 531 XStoreName( UI.display, UI.window, "Mud Gangster" ); 532 XMapWindow( UI.display, UI.window ); 533 534 wmDeleteWindow = XInternAtom( UI.display, "WM_DELETE_WINDOW", false ); 535 XSetWMProtocols( UI.display, UI.window, &wmDeleteWindow, 1 ); 536 537 XEvent xev; 538 xev.xconfigure.width = default_width; 539 xev.xconfigure.height = default_height; 540 event_resize( &xev ); 541 542 ui_handleXEvents(); 543 } 544 545 void ui_urgent() { 546 if( !UI.has_focus ) { 547 XWMHints * hints = XGetWMHints( UI.display, UI.window ); 548 hints->flags |= XUrgencyHint; 549 XSetWMHints( UI.display, UI.window, hints ); 550 XFree( hints ); 551 } 552 } 553 554 int ui_display_fd() { 555 return ConnectionNumber( UI.display ); 556 } 557 558 void ui_get_font_size( int * fw, int * fh ) { 559 *fw = Style.font.width; 560 *fh = Style.font.height; 561 } 562 563 bool ui_set_font( const char * name, int size ) { 564 // TODO 565 return false; 566 } 567 568 void platform_set_clipboard( const char * str, size_t len ) { 569 // len includes the \0 570 clipboard_set_text_ex( clipboard, str, len - 1, LCB_CLIPBOARD ); 571 } 572 573 void platform_ui_term() { 574 XFreeFont( UI.display, Style.font.regular ); 575 XFreeFont( UI.display, Style.font.bold ); 576 577 XFreePixmap( UI.display, UI.back_buffer ); 578 XFreeGC( UI.display, UI.gc ); 579 XDestroyWindow( UI.display, UI.window ); 580 XCloseDisplay( UI.display ); 581 } 582 583 static Socket * socket_from_fd( int fd ) { 584 for( Socket & sock : sockets ) { 585 if( sock.in_use && sock.sock.fd == fd ) { 586 return &sock; 587 } 588 } 589 590 return NULL; 591 } 592 593 int main() { 594 net_init(); 595 ui_init(); 596 platform_ui_init(); 597 script_init(); 598 599 clipboard = clipboard_new( NULL ); 600 if( clipboard == NULL ) { 601 FATAL( "clipboard_new" ); 602 } 603 604 FrameMark; 605 606 ui_handleXEvents(); 607 608 while( !closing ) { 609 pollfd fds[ ARRAY_COUNT( sockets ) + 1 ] = { }; 610 nfds_t num_fds = 1; 611 612 fds[ 0 ].fd = ConnectionNumber( UI.display ); 613 fds[ 0 ].events = POLLIN; 614 615 for( Socket sock : sockets ) { 616 if( sock.in_use ) { 617 fds[ num_fds ].fd = sock.sock.fd; 618 fds[ num_fds ].events = POLLIN; 619 num_fds++; 620 } 621 } 622 623 int ok = poll( fds, num_fds, 500 ); 624 if( ok == -1 ) 625 FATAL( "poll" ); 626 627 script_fire_intervals(); 628 629 for( size_t i = 1; i < ARRAY_COUNT( fds ); i++ ) { 630 if( fds[ i ].revents & POLLIN ) { 631 Socket * sock = socket_from_fd( fds[ i ].fd ); 632 assert( sock != NULL ); 633 634 char buf[ 8192 ]; 635 size_t n; 636 TCPRecvResult res = net_recv( sock->sock, buf, sizeof( buf ), &n ); 637 if( res == TCP_OK ) { 638 script_socketData( sock, buf, n ); 639 } 640 else if( res == TCP_CLOSED ) { 641 script_socketData( sock, NULL, 0 ); 642 } 643 else { 644 FATAL( "net_recv" ); 645 } 646 } 647 } 648 649 ui_handleXEvents(); 650 } 651 652 clipboard_free( clipboard ); 653 654 script_term(); 655 platform_ui_term(); 656 ui_term(); 657 net_term(); 658 659 return 0; 660 }