#include #include #include #include #include #include "../../Common/bin.h" #include "../../Common/bits.h" #include "../../Common/buf.h" #include "../../Common/cfg.h" #include "../../Common/def.h" #include "../../Common/lzw.h" #include "../../Common/std.h" #include "../../Common/str.h" #include "../../Common/test.h" #include "../draw.h" #include "display.h" #if COM_DEF_TARGET == COM_DEF_TARGET_COSMO #include #else #include #endif /* https://hereket.com/posts/from-scratch-x11-windowing/ https://gaultier.github.io/blog/write_a_video_game_from_scratch_like_1987.html https://xorg.freedesktop.org/archive/X11R7.7-RC1/doc/xproto/x11protocol.html */ static struct { uint32_t id; uint32_t colormap; uint32_t white; uint32_t black; uint32_t input_mask; uint16_t width; uint16_t height; uint16_t width_mm; uint16_t height_mm; uint16_t maps_min; uint16_t maps_max; uint32_t root_visual_id; uint8_t backing_store; uint8_t save_unders; uint8_t root_depth; uint8_t depths_count; } plr_x11_root_screen; static struct { uint32_t resource_id_base; uint32_t resource_id_mask; } plr_x11_connection; static bool fileread(void *ctx, char *into, uint32_t bytes) { return com_std_read(ctx, into, bytes) == bytes; } static bool fileskip(void *ctx, uint32_t bytes) { return com_std_sfwd(ctx, bytes); } #define AUTH_ENTRY_MAGIC_COOKIE "MIT-MAGIC-COOKIE-1" static struct plr_x11_auth_entry { uint16_t family; struct com_buf *auth_name; struct com_buf *auth_data; bool success; } plr_x11_read_auth_entry(struct com_bin *b, struct com_buf *buf) { struct plr_x11_auth_entry result = {0}; if (!com_bin_read_u16(b, &result.family)) goto BREAK; uint16_t addres_len; if (!com_bin_read_u16(b, &addres_len)) goto BREAK; if (!com_bin_skip(b, addres_len)) goto BREAK; uint16_t display_len; if (!com_bin_read_u16(b, &display_len)) goto BREAK; if (!com_bin_skip(b, display_len)) goto BREAK; uint16_t name_len; if (!com_bin_read_u16(b, &name_len)) goto BREAK; uint32_t name_idx; if (!com_bin_read_bufbi(b, buf, &name_idx, name_len)) goto BREAK; uint16_t data_len; if (!com_bin_read_u16(b, &data_len)) goto BREAK; uint32_t data_idx; if (!com_bin_read_bufbi(b, buf, &data_idx, data_len)) goto BREAK; result.auth_name = com_buf_index_as(struct com_buf, buf, name_idx); result.auth_data = com_buf_index_as(struct com_buf, buf, data_idx); result.success = true; return result; BREAK: com_std_print('c', "Failure to initialize X11"); return result; } static bool plr_x11_handshake(com_std_fd xfd, struct plr_x11_auth_entry auth_entry) { struct { uint8_t endianness; uint8_t pad0; uint16_t major_version; uint16_t minor_version; uint16_t auth_len; uint16_t auth_data_len; uint16_t pad1; } __attribute__((packed)) request = { .endianness = COM_DEF_CPU_LITEND ? 'l' : 'B', .major_version = 11, .auth_len = sizeof(AUTH_ENTRY_MAGIC_COOKIE) - 1, // IS -1 NEEDED? .auth_data_len = auth_entry.auth_data->len, }; if (!com_std_write(xfd, &request, sizeof request)) return false; if (!com_std_write(xfd, AUTH_ENTRY_MAGIC_COOKIE, sizeof AUTH_ENTRY_MAGIC_COOKIE - 1)) return false; /* Use 2 bytes back for padding. */ if (!com_std_write(xfd, &auth_entry.auth_data->data[-2], auth_entry.auth_data->len + 2)) return false; struct { uint8_t success; uint8_t pad1; uint16_t major_version; uint16_t minor_version; uint16_t length; } __attribute__((packed)) response; if (!com_std_read(xfd, &response, sizeof response)) return false; if (response.success != 1) return false; /* https://xorg.freedesktop.org/archive/X11R7.7-RC1/doc/xproto/x11protocol.html#server_response */ struct { uint32_t release_number; uint32_t resource_id_base; uint32_t resource_id_mask; uint32_t motion_buffer_size; uint16_t vendor_length; uint16_t maximum_request_length; uint8_t screens_in_root_count; uint8_t formats_count; uint8_t image_byte_order; uint8_t bitmap_format_bit_order; uint8_t bitmap_format_scanline_unit; uint8_t bitmap_format_scanline_pad; uint8_t min_keycode; uint8_t max_keycode; uint32_t pad2; } __attribute__((packed)) dyn_response; if (!com_std_read(xfd, &dyn_response, sizeof dyn_response)) return false; /* Skip vendor and format info. */ char scratch[1024]; uint32_t skip = com_bits_nalign(dyn_response.vendor_length, 4); com_std_assert(skip <= sizeof scratch); com_std_assert(8 * dyn_response.formats_count <= sizeof scratch); if (!com_std_read(xfd, scratch, skip)) return false; if (!com_std_read(xfd, scratch, 8 * dyn_response.formats_count)) return false; /* Read first and hope it is what we need. */ if (!com_std_read(xfd, &plr_x11_root_screen, sizeof plr_x11_root_screen)) return false; /* Then drop all the things that proceed, such as other screens and their * visual profiles. */ while (com_std_has_data(xfd)) com_std_read(xfd, scratch, sizeof scratch); plr_x11_connection.resource_id_base = dyn_response.resource_id_base; plr_x11_connection.resource_id_mask = dyn_response.resource_id_mask; return true; } union plr_x11_response { struct { uint8_t type; uint8_t errcode; uint16_t sequenceid; uint32_t badval; uint16_t minorop; uint32_t majorop; char pad[18]; } __attribute__((packed)) error; struct { uint8_t type; uint8_t data; uint16_t sequenceid; uint32_t length; char pad[24]; } __attribute__((packed)) reply; }; static bool plr_x11_poll(com_std_fd xfd, bool wait, union plr_x11_response *response) { if (!wait && !com_std_has_data(xfd)) return false; com_std_read(xfd, response, sizeof(union plr_x11_response)); return true; } static bool plr_x11_poll_reply(com_std_fd xfd, uint16_t seqid, union plr_x11_response *response) { while (plr_x11_poll(xfd, true, response)) { /* Wait for match. */ if (response->reply.type == 1 && response->reply.sequenceid == seqid) break; /* Errors could be encountered here, report along the way. */ if (response->reply.type == 0) { com_std_print('c', "X11 error code=", 'u', response->error.errcode, 'c', " for seqid=", 'u', response->error.sequenceid); } } return true; } /* TODO: Instead we should poll for events, errors are only byproduct. */ static bool plr_x11_poll_errors(com_std_fd xfd) { union plr_x11_response response; while (plr_x11_poll(xfd, false, &response)) { /* There should be no replies unhandled. */ /* TODO: Write down encountered events. */ if (response.reply.type == 1) return false; if (response.reply.type == 0) com_std_print('c', "X11 error code=", 'u', response.error.errcode, 'c', " for seqid=", 'u', response.error.sequenceid); } return true; } static inline uint32_t plr_x11_next_id(void) { static uint32_t cur_id = 0; cur_id = 1 + ((plr_x11_connection.resource_id_mask & (cur_id)) | plr_x11_connection.resource_id_base); return cur_id; } /* Used to number requests. */ static inline uint32_t plr_x11_next_seqid(void) { static uint32_t cur_id = 1; return cur_id++; } static uint32_t plr_x11_create_graphical_context(com_std_fd xfd) { uint32_t const gc_id = plr_x11_next_id(); uint32_t const seq = plr_x11_next_seqid(); struct { uint8_t opcode; uint8_t pad1; uint16_t request_length; uint32_t id; uint32_t drawable; uint32_t bitmask; uint32_t value1; } __attribute__((packed)) request = { .opcode = 55, .request_length = sizeof request / 4, .id = gc_id, .drawable = plr_x11_root_screen.id, .bitmask = 8, /* TODO: Figure out. */ .value1 = 0x00000000, }; if (!com_std_write(xfd, &request, sizeof request)) return 0; return gc_id; } static uint32_t plr_x11_create_window(com_std_fd xfd) { /* TODO: Use the ones provided in headers ? */ #define FLAG_WIN_BG_PIXEL 2 #define FLAG_WIN_EVENT 0x800 #define EVENT_FLAG_EXPOSURE 0x8000 #define EVENT_FLAG_KEY_PRESS 0x1 #define EVENT_FLAG_KEY_RELEASE 0x2 #define EVENT_FLAG_BUTTON_PRESS 0x4 #define EVENT_FLAG_BUTTON_RELEASE 0x8 uint32_t const window_id = plr_x11_next_id(); uint32_t const seq = plr_x11_next_seqid(); struct { uint8_t opcode; uint8_t depth; uint16_t request_length; uint32_t window_id; uint32_t parent_id; uint16_t x; uint16_t y; uint16_t width; uint16_t height; uint16_t border_width; uint16_t class; uint32_t root_visual_id; uint32_t bitmask; uint32_t value1; uint32_t value2; } __attribute__((packed)) request = { .opcode = 1, .depth = COM_CFG_COLORS * 8, .request_length = sizeof request / 4, .window_id = window_id, .parent_id = plr_x11_root_screen.id, .width = COM_CFG_WIDTH, .height = COM_CFG_HEIGHT, .border_width = 0, .class = 1, .root_visual_id = plr_x11_root_screen.root_visual_id, .bitmask = FLAG_WIN_BG_PIXEL | FLAG_WIN_EVENT, .value1 = 0x00000000, .value2 = EVENT_FLAG_EXPOSURE | EVENT_FLAG_BUTTON_RELEASE | EVENT_FLAG_BUTTON_PRESS | EVENT_FLAG_KEY_PRESS | EVENT_FLAG_KEY_RELEASE, }; if (!com_std_write(xfd, &request, sizeof request)) return 0; return window_id; } static bool plr_x11_show_window(com_std_fd xfd, uint32_t window_id) { uint32_t const seq = plr_x11_next_seqid(); struct { uint8_t opcode; uint8_t pad1; uint16_t request_length; uint32_t window_id; } __attribute__((packed)) request = { .opcode = 8, .request_length = sizeof request / 4, .window_id = window_id, }; return com_std_write(xfd, &request, sizeof request); } static uint32_t plr_x11_create_pixmap(com_std_fd xfd, uint32_t window_id) { uint32_t const pixmap_id = plr_x11_next_id(); uint32_t const seq = plr_x11_next_seqid(); struct { uint8_t opcode; uint8_t depth; uint16_t request_length; uint32_t pixmap_id; uint32_t drawable; uint16_t width; uint16_t height; } __attribute__((packed)) request = { .opcode = 53, .depth = COM_CFG_COLORS * 8, .request_length = sizeof request / 4, .pixmap_id = pixmap_id, .drawable = window_id, .width = COM_CFG_WIDTH, .height = COM_CFG_HEIGHT, }; if (!com_std_write(xfd, &request, sizeof request)) return 0; return pixmap_id; } /* Returns opcode for given extension. */ static uint8_t plr_x11_query_extension(com_std_fd xfd, com_str const *ext) { uint32_t const seq = plr_x11_next_seqid(); uint16_t const pad = com_bits_nalign(ext->len, 4); struct { uint8_t opcode; uint8_t pad1; uint16_t request_length; uint16_t name_length; uint16_t pad2; } __attribute__((packed)) request = { .opcode = 98, .request_length = sizeof request / 4 + pad / 4, .name_length = ext->len, }; if (!com_std_write(xfd, &request, sizeof request)) return 0; if (!com_std_write(xfd, ext->data, ext->len)) return 0; if ((pad - ext->len) && !com_std_write(xfd, ext->data, pad - ext->len)) return 0; union plr_x11_response response; plr_x11_poll_reply(xfd, seq, &response); return response.reply.pad[1]; } /* This is needed as base X11 doesn't allow passing more than 16bit x 4bytes. https://xorg.freedesktop.org/archive/current/doc/bigreqsproto/bigreq.html */ static bool plr_x11_enable_big_requests(com_std_fd xfd) { static com_str bigreqext = com_str_lit("BIG-REQUESTS"); uint8_t const opcode = plr_x11_query_extension(xfd, &bigreqext); /* Request the thing. */ struct { uint8_t opcode; uint8_t micode; uint16_t request_length; } __attribute__((packed)) request = { .opcode = opcode, .request_length = sizeof request / 4, }; uint32_t const seq = plr_x11_next_seqid(); if (!com_std_write(xfd, &request, sizeof request)) return 0; /* Read the response. */ union plr_x11_response response; plr_x11_poll_reply(xfd, seq, &response); uint32_t const limit = *(uint32_t *)response.reply.pad; com_std_print('c', "Request limit: ", 'u', limit); if (limit < COM_CFG_FBSIZE) { com_std_print('c', "Big request can't accommodate for framebuffer."); return false; } return true; } static bool plr_x11_put_framebuffer(com_std_fd xfd, uint32_t gc_id, uint32_t pixmap_id, uint8_t pixels[COM_CFG_FBSIZE]) { uint32_t const padded = com_bits_nalign(COM_CFG_FBSIZE, 4); uint32_t const seq = plr_x11_next_seqid(); struct { uint8_t opcode; uint8_t format; uint16_t bigreq; /* Marked zero so that 32bit could be used. */ uint32_t request_length; uint32_t drawable_id; uint32_t gc_id; uint16_t width; uint16_t height; uint16_t dst_x; uint16_t dst_y; uint8_t left_pad; uint8_t depth; uint16_t pad1; } __attribute__((packed)) request = { .opcode = 72, .format = 2, // ZPixmap .request_length = sizeof request / 4 + padded / 4, .drawable_id = pixmap_id, .gc_id = gc_id, .width = COM_CFG_WIDTH, .height = COM_CFG_HEIGHT, .dst_x = 0, .dst_y = 0, .depth = COM_CFG_COLORS * 8, }; if (!com_std_write(xfd, &request, sizeof request)) return false; if (!com_std_write(xfd, pixels, COM_CFG_FBSIZE)) return false; /* Use request memory for pad, it doesn't matter what we write. */ if ((padded - COM_CFG_FBSIZE) && !com_std_write(xfd, &pixels, padded - COM_CFG_FBSIZE)) return false; return true; } static bool plr_x11_establish_connection(com_std_fd xfd, int argc, char const *const *argv) { com_buf_make_on_stack(char, xauthbuf, 1024, 0); /* TODO: Genralize inside std? */ #if COM_DEF_TARGET == COM_DEF_TARGET_COSMO char const *xauthpath = getenv("XAUTHORITY"); #else /* First we need to authenticate X11 connection, which requires some funk. */ /* We assume that $XAUTHORITY is set and I hope it is true. */ char const *xauthpath = NULL; for (char const *const *envp = &argv[argc + 1]; *envp != NULL; ++envp) { static const struct com_str xauthlit = com_str_lit("XAUTHORITY="); if (com_str_ext_prefixed(*envp, &xauthlit)) { xauthpath = &((*envp)[xauthlit.len]); break; } } #endif /* Open it and parse. */ com_std_fd const xauthfd = com_std_open(xauthpath, COM_STD_OPEN_READ); if (!xauthfd) { com_std_print('c', "Failure to initialize X11"); return false; } struct com_bin xauthbin = { .ctx = (void *)(int64_t)xauthfd, .flags = COM_BIN_BIG_ENDIAN, .interface = {.readn = fileread, .skipn = fileskip}}; /* Loop over auth entries until we find magic cookie one. */ struct plr_x11_auth_entry auth_entry; do { com_buf_clear(xauthbuf); auth_entry = plr_x11_read_auth_entry(&xauthbin, xauthbuf); } while (auth_entry.success && auth_entry.family != 256); if (!auth_entry.success) { com_std_print('c', "Failure to initialize X11"); return false; } com_std_close(xauthfd); /* TODO: Check for auth_entry.name == MAGIC ? */ if (!plr_x11_handshake(xfd, auth_entry)) { com_std_print('c', "Failure to initialize X11"); return false; } com_buf_free(xauthbuf); return true; } // TODO: correct error handling. extern int plr_display_x11_main(int argc, char const *const *argv) { char test_string[] = "what is this?"; char *compressed_string = NULL; uint32_t compressed_string_sz = 0; struct com_lzw_table table = com_lzw_infer_table(test_string, sizeof(test_string)); bool test = com_lzw_compress(&table, test_string, sizeof(test_string), &compressed_string, &compressed_string_sz); com_lzw_free_table(&table); com_std_assert(test); /* TODO: Move to separate executable maybe? */ #ifndef NDEBUG com_test_run(); #endif /* Open X11 socket for communication. */ com_std_fd const xfd = com_std_socket(false); if (!xfd) { com_std_print('c', "Failure to initialize X11"); return -1; } if (!com_std_connect(xfd, false, "/tmp/.X11-unix/X0")) { com_std_print('c', "Failure to initialize X11"); return -1; } if (!plr_x11_establish_connection(xfd, argc, argv)) { com_std_print('c', "Failure to initialize X11"); return -1; } if (!plr_x11_enable_big_requests(xfd)) { com_std_print('c', "Big requests unsupported for your X11"); return -1; } uint32_t const gc = plr_x11_create_graphical_context(xfd); if (!gc) { com_std_print('c', "Failure to initialize X11"); return -1; } uint32_t const win = plr_x11_create_window(xfd); if (!win) { com_std_print('c', "Failure to initialize X11"); return -1; } uint32_t const fb = plr_x11_create_pixmap(xfd, win); if (!fb) { com_std_print('c', "Failure to initialize X11"); return -1; } // Display *display = XOpenDisplay(NULL); // if (NULL == display) { // com_std_print('c', "Failed to initialize display"); // return -1; // } // Window root = DefaultRootWindow(display); // if (None == root) { // com_std_print('c', "No root window found"); // XCloseDisplay(display); // return -1; // } // int screen = DefaultScreen(display); // Visual *visual = DefaultVisual(display, screen); // int depth = DefaultDepth(display, screen); // Window window = // XCreateSimpleWindow(display, root, 0, 0, WIDTH, HEIGHT, 0, 0, // 0xffffffff); // if (None == window) { // com_std_print('c', "Failed to create window"); // XCloseDisplay(display); // return -1; // } // XSizeHints *hints = XAllocSizeHints(); // if (hints == NULL) // return -1; // // Pinning min and max to the same values disables resizing // hints->flags = PMinSize | PMaxSize; // hints->min_width = hints->max_width = WIDTH; // hints->min_height = hints->max_height = HEIGHT; // XSetWMNormalHints(display, window, hints); // XFree(hints); // XSelectInput(display, window, ExposureMask | KeyPressMask); // XMapWindow(display, window); // GC gc = XCreateGC(display, window, 0, NULL); // Atom wm_delete_window = XInternAtom(display, "WM_DELETE_WINDOW", // False); XSetWMProtocols(display, window, &wm_delete_window, 1); // // TODO: can't be not true // int bytes_per_pixel = 4; // // char *pixel_buffer = (char *)malloc(WIDTH * HEIGHT * // bytes_per_pixel); char pixel_buffer[WIDTH * HEIGHT * bytes_per_pixel]; // XImage *ximage = XCreateImage(display, visual, depth, ZPixmap, 0, // pixel_buffer, WIDTH, HEIGHT, 32, 0); // if (!ximage) { // com_std_print('c', "Failed to create XImage"); // // com_std_alloc(pixel_buffer, 0); // XCloseDisplay(display); // return -1; // } // int frame = 0; // XEvent event; // while (!quited) { // XNextEvent(display, &event); // switch (event.type) { // case ClientMessage: // if (event.xclient.data.l[0] == wm_delete_window) { // XDestroyWindow(display, window); // quited = true; // } // break; // case Expose: // frame += 2; // plr_draw_screen((uint8_t *)pixel_buffer); // // Draw the complete image onto the window when exposed // XPutImage(display, // window, // Target drawable // gc, // Graphics Context // ximage, // Source XImage // 0, 0, // Source coordinates (x, y) // 0, 0, // Destination coordinates (x, y) // WIDTH, HEIGHT // Dimensions to copy // ); // break; // } // } // XCloseDisplay(display); uint8_t *fbm = plr_draw_get_pixel_buffer(); plr_draw_screen(fbm); if (!plr_x11_put_framebuffer(xfd, gc, fb, fbm)) { com_std_print('c', "Failure to drive X11"); return -1; } if (!plr_x11_show_window(xfd, win)) { com_std_print('c', "Failure to drive X11"); return -1; } while (1) { if (!plr_x11_poll_errors(xfd)) { com_std_print('c', "X11 closed connection"); return -1; } } return 0; }