struct rtb_window * window_impl_open(struct rutabaga *rtb, int w, int h, const char *title, intptr_t parent) { struct xcb_rutabaga *xrtb = (void *) rtb; struct xrtb_window *self; Display *dpy; xcb_connection_t *xcb_conn; int default_screen; GLXFBConfig *fb_configs, fb_config; XVisualInfo *visual; int nfb_configs; uint32_t event_mask = XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_VISIBILITY_CHANGE | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_KEYMAP_STATE; uint32_t value_mask = XCB_CW_BORDER_PIXEL | XCB_CW_BACK_PIXMAP | XCB_CW_BIT_GRAVITY | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP; uint32_t value_list[6]; xcb_colormap_t colormap; xcb_void_cookie_t ck_window, ck_map; xcb_generic_error_t *err; assert(rtb); assert(h > 0); assert(w > 0); if (!(self = calloc(1, sizeof(*self)))) goto err_malloc; self->xrtb = xrtb; dpy = xrtb->dpy; xcb_conn = xrtb->xcb_conn; default_screen = DefaultScreen(dpy); self->screen = find_xcb_screen(xcb_conn, default_screen); if (!self->screen) { ERR("couldn't find XCB screen\n"); goto err_screen; } /** * gl configuration */ fb_configs = glXGetFBConfigs(dpy, default_screen, &nfb_configs); if (!fb_configs || !nfb_configs) { ERR("no GL configurations, bailing out\n"); goto err_gl_config; } fb_config = find_reasonable_fb_config(dpy, xcb_conn, fb_configs, nfb_configs, 0); if (!fb_config) { ERR("no reasonable GL configurations, bailing out\n"); goto err_gl_config; } visual = glXGetVisualFromFBConfig(dpy, fb_config); self->gl_ctx = new_gl_context(dpy, fb_config); if (!self->gl_ctx) { ERR("couldn't create GLX context\n"); goto err_gl_ctx; } /** * window setup */ colormap = xcb_generate_id(xcb_conn); self->xcb_win = xcb_generate_id(xcb_conn); xcb_create_colormap( xcb_conn, XCB_COLORMAP_ALLOC_NONE, colormap, self->screen->root, visual->visualid); value_list[0] = 0; value_list[1] = 0; value_list[2] = XCB_GRAVITY_STATIC; value_list[3] = event_mask; value_list[4] = colormap; value_list[5] = 0; ck_window = xcb_create_window_checked( xcb_conn, visual->depth, self->xcb_win, parent ? (xcb_window_t) parent : self->screen->root, 0, 0, w, h, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visual->visualid, value_mask, value_list); free(visual); if ((err = xcb_request_check(xcb_conn, ck_window))) { ERR("can't create XCB window: %d\n", err->error_code); goto err_xcb_win; } get_dpi(dpy, default_screen, &self->dpi.x, &self->dpi.y); self->gl_win = glXCreateWindow(dpy, fb_config, self->xcb_win, 0); if (!self->gl_win) { ERR("couldn't create GL window\n"); goto err_gl_win; } if (set_xprop(xcb_conn, self->xcb_win, XCB_ATOM_WM_NAME, title)) set_xprop(xcb_conn, self->xcb_win, XCB_ATOM_WM_NAME, "oh no"); self->gl_draw = self->gl_win; if (!glXMakeContextCurrent( dpy, self->gl_draw, self->gl_draw, self->gl_ctx)) { ERR("couldn't activate GLX context\n"); goto err_gl_make_current; } ck_map = xcb_map_window_checked(xcb_conn, self->xcb_win); if ((err = xcb_request_check(xcb_conn, ck_map))) { ERR("can't map XCB window: %d\n", err->error_code); goto err_win_map; } if (parent) raise_window(xcb_conn, self->xcb_win); else xcb_icccm_set_wm_protocols(xcb_conn, self->xcb_win, xrtb->atoms.wm_protocols, 1, &xrtb->atoms.wm_delete_window); free(fb_configs); uv_mutex_init(&self->lock); return RTB_WINDOW(self); err_win_map: err_gl_make_current: err_gl_win: xcb_destroy_window(xcb_conn, self->xcb_win); err_xcb_win: glXDestroyContext(dpy, self->gl_ctx); err_gl_ctx: err_gl_config: free(fb_configs); err_screen: free(self); err_malloc: return NULL; }
// === SetAsPanel() === void LXCB::SetAsPanel(WId win){ if(DEBUG){ qDebug() << "XCB: SetAsPanel()"; } if(win==0){ return; } SetDisableWMActions(win); //also need to disable WM actions for this window //Disable Input focus (panel activation ruins task manager window detection routines) // - Disable Input flag in WM_HINTS xcb_icccm_wm_hints_t hints; //qDebug() << " - Disable WM_HINTS input flag"; xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_hints_unchecked(QX11Info::connection(), win); //qDebug() << " -- got cookie"; if(1 == xcb_icccm_get_wm_hints_reply(QX11Info::connection(), cookie, &hints, NULL) ){ //qDebug() << " -- Set no inputs flag"; xcb_icccm_wm_hints_set_input(&hints, false); //set no input focus xcb_icccm_set_wm_hints(QX11Info::connection(), win, &hints); //save hints back to window } // - Remove WM_TAKE_FOCUS from the WM_PROTOCOLS for the window // - - Generate the necessary atoms //qDebug() << " - Generate WM_PROTOCOLS and WM_TAKE_FOCUS atoms"; xcb_atom_t WM_PROTOCOLS, WM_TAKE_FOCUS; //the two atoms needed xcb_intern_atom_reply_t *preply = xcb_intern_atom_reply(QX11Info::connection(), \ xcb_intern_atom(QX11Info::connection(), 0, 12, "WM_PROTOCOLS"), NULL); xcb_intern_atom_reply_t *freply = xcb_intern_atom_reply(QX11Info::connection(), \ xcb_intern_atom(QX11Info::connection(), 0, 13, "WM_TAKE_FOCUS"), NULL); bool gotatoms = false; if(preply && freply){ WM_PROTOCOLS = preply->atom; WM_TAKE_FOCUS = freply->atom; free(preply); free(freply); gotatoms = true; //qDebug() << " -- success"; } // - - Now update the protocols for the window if(gotatoms){ //requires the atoms //qDebug() << " - Get WM_PROTOCOLS"; xcb_icccm_get_wm_protocols_reply_t proto; if( 1 == xcb_icccm_get_wm_protocols_reply(QX11Info::connection(), \ xcb_icccm_get_wm_protocols_unchecked(QX11Info::connection(), win, WM_PROTOCOLS), \ &proto, NULL) ){ //Found the current protocols, see if it has the focus atom set //remove the take focus atom and re-save them bool needremove = false; //Note: This first loop is required so that we can initialize the modified list with a valid size //qDebug() << " -- Check current protocols"; for(unsigned int i=0; i<proto.atoms_len; i++){ if(proto.atoms[i] == WM_TAKE_FOCUS){ needremove = true; break;} } if(needremove){ //qDebug() << " -- Remove WM_TAKE_FOCUS protocol"; xcb_atom_t *protolist = new xcb_atom_t[proto.atoms_len-1]; int num = 0; for(unsigned int i=0; i<proto.atoms_len; i++){ if(proto.atoms[i] != WM_TAKE_FOCUS){ protolist[num] = proto.atoms[i]; num++; } } //qDebug() << " -- Re-save modified protocols"; xcb_icccm_set_wm_protocols(QX11Info::connection(), win, WM_PROTOCOLS, num, protolist); } //qDebug() << " -- Clear protocols reply"; xcb_icccm_get_wm_protocols_reply_wipe(&proto); }//end of get protocols check } //end of gotatoms check //Make sure it has the "dock" window type // - get the current window types (Not necessary, only 1 type of window needed) // - set the adjusted window type(s) //qDebug() << " - Adjust window type"; xcb_atom_t list[1]; list[0] = EWMH._NET_WM_WINDOW_TYPE_DOCK; xcb_ewmh_set_wm_window_type(&EWMH, win, 1, list); //Make sure it is on all workspaces //qDebug() << " - Set window as sticky"; SetAsSticky(win); }
int main() { hhxcb_state state = {}; hhxcb_get_binary_name(&state); char source_game_code_library_path[HHXCB_STATE_FILE_NAME_LENGTH]; char *game_code_filename = (char *) _HHXCB_QUOTE(GAME_CODE_FILENAME); hhxcb_build_full_filename(&state, game_code_filename, sizeof(source_game_code_library_path), source_game_code_library_path); hhxcb_game_code game_code = {}; hhxcb_load_game(&game_code, source_game_code_library_path); hhxcb_context context = {}; /* Open the connection to the X server. Use the DISPLAY environment variable */ int screenNum; context.connection = xcb_connect (NULL, &screenNum); context.key_symbols = xcb_key_symbols_alloc(context.connection); /* * TODO(nbm): This is X-wide, so it really isn't a good option in reality. * We have to be careful and clean up at the end. If we crash, auto-repeat * is left off. */ { uint32_t values[1] = {XCB_AUTO_REPEAT_MODE_OFF}; xcb_change_keyboard_control(context.connection, XCB_KB_AUTO_REPEAT_MODE, values); } load_atoms(&context); context.setup = xcb_get_setup(context.connection); xcb_screen_iterator_t iter = xcb_setup_roots_iterator(context.setup); xcb_screen_t *screen = iter.data; context.fmt = hhxcb_find_format(&context, 32, 24, 32); int monitor_refresh_hz = 60; real32 game_update_hz = (monitor_refresh_hz / 2.0f); // Should almost always be an int... long target_nanoseconds_per_frame = (1000 * 1000 * 1000) / game_update_hz; uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; uint32_t values[2] = { 0x0000ffff, //screen->black_pixel, 0 | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE , }; #define START_WIDTH 960 #define START_HEIGHT 540 context.window = xcb_generate_id(context.connection); // NOTE: changed to not have a border width, so the min/max/close // buttons align on compiz, maybe other window managers xcb_create_window(context.connection, XCB_COPY_FROM_PARENT, context.window, screen->root, 0, 0, START_WIDTH, START_HEIGHT, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, mask, values); xcb_icccm_set_wm_name(context.connection, context.window, XCB_ATOM_STRING, 8, strlen("hello"), "hello"); load_and_set_cursor(&context); xcb_map_window(context.connection, context.window); xcb_atom_t protocols[] = { context.wm_delete_window, }; xcb_icccm_set_wm_protocols(context.connection, context.window, context.wm_protocols, 1, protocols); xcb_size_hints_t hints = {}; xcb_icccm_size_hints_set_max_size(&hints, START_WIDTH, START_HEIGHT); xcb_icccm_size_hints_set_min_size(&hints, START_WIDTH, START_HEIGHT); xcb_icccm_set_wm_size_hints(context.connection, context.window, XCB_ICCCM_WM_STATE_NORMAL, &hints); hhxcb_offscreen_buffer buffer = {}; hhxcb_resize_backbuffer(&context, &buffer, START_WIDTH, START_HEIGHT); xcb_flush(context.connection); hhxcb_sound_output sound_output = {}; sound_output.samples_per_second = 48000; sound_output.bytes_per_sample = sizeof(int16) * 2; sound_output.secondary_buffer_size = sound_output.samples_per_second * sound_output.bytes_per_sample; hhxcb_init_alsa(&context, &sound_output); int16 *sample_buffer = (int16 *)malloc(sound_output.secondary_buffer_size); thread_context t = {}; game_memory m = {}; m.PermanentStorageSize = 256 * 1024 * 1024; m.TransientStorageSize = 256 * 1024 * 1024; state.total_size = m.PermanentStorageSize + m.TransientStorageSize; state.game_memory_block = calloc(state.total_size, sizeof(uint8)); m.PermanentStorage = (uint8 *)state.game_memory_block; m.TransientStorage = (uint8_t *)m.PermanentStorage + m.TransientStorageSize; #ifdef HANDMADE_INTERNAL m.DEBUGPlatformFreeFileMemory = debug_xcb_free_file_memory; m.DEBUGPlatformReadEntireFile = debug_xcb_read_entire_file; m.DEBUGPlatformWriteEntireFile = debug_xcb_write_entire_file; #endif hhxcb_init_replays(&state); bool ending = 0; timespec last_counter = {}; timespec flip_wall_clock = {}; // Actually monotonic clock clock_gettime(HHXCB_CLOCK, &last_counter); clock_gettime(HHXCB_CLOCK, &flip_wall_clock); game_input input[2] = {}; game_input *new_input = &input[0]; game_input *old_input = &input[1]; int64_t next_controller_refresh = 0; while(!ending) { if (last_counter.tv_sec >= next_controller_refresh) { hhxcb_refresh_controllers(&context); next_controller_refresh = last_counter.tv_sec + 1; } struct stat library_statbuf = {}; stat(source_game_code_library_path, &library_statbuf); if (library_statbuf.st_mtime != game_code.library_mtime) { hhxcb_unload_game(&game_code); hhxcb_load_game(&game_code, source_game_code_library_path); } new_input->dtForFrame = target_nanoseconds_per_frame / (1024.0 * 1024 * 1024); hhxcb_process_events(&context, &state, new_input, old_input); if (context.ending_flag) { break; } // NOTE: setup game_buffer.Memory upside down and set // game_buffer.pitch negative, so the game would fill the // backbuffer upside down. XCB doesn't seem to have an // option to flip the image. game_offscreen_buffer game_buffer = {}; game_buffer.Memory = ((uint8*)buffer.xcb_image->data)+ (buffer.width*(buffer.height-1)*buffer.bytes_per_pixel); game_buffer.Width = buffer.width; game_buffer.Height = buffer.height; game_buffer.Pitch = -buffer.pitch; if (state.recording_index) { hhxcb_record_input(&state, new_input); } if (state.playback_index) { hhxcb_playback_input(&state, new_input); } if (game_code.UpdateAndRender) { game_code.UpdateAndRender(&t, &m, new_input, &game_buffer); HandleDebugCycleCounter(&m); } game_sound_output_buffer sound_buffer; sound_buffer.SamplesPerSecond = sound_output.samples_per_second; sound_buffer.SampleCount = sound_output.samples_per_second / 30; sound_buffer.Samples = sample_buffer; int err, frames; snd_pcm_sframes_t delay, avail; snd_pcm_avail_delay(context.handle, &avail, &delay); if (avail == sound_output.secondary_buffer_size) { // NOTE(nbm): Full available buffer, starting with ~60ms of silence bzero(sample_buffer, sound_buffer.SampleCount * sound_output.bytes_per_sample); snd_pcm_writei(context.handle, sample_buffer, sound_buffer.SampleCount); snd_pcm_writei(context.handle, sample_buffer, sound_buffer.SampleCount); snd_pcm_writei(context.handle, sample_buffer, sound_buffer.SampleCount); snd_pcm_writei(context.handle, sample_buffer, sound_buffer.SampleCount); } else { uint32 target_available_frames = sound_output.secondary_buffer_size; target_available_frames -= (sound_buffer.SampleCount * 1); if (avail - target_available_frames < sound_buffer.SampleCount) { sound_buffer.SampleCount += avail - target_available_frames; } } game_code.GetSoundSamples(&t, &m, &sound_buffer); if (sound_buffer.SampleCount > 0) { frames = snd_pcm_writei(context.handle, sample_buffer, sound_buffer.SampleCount); if (frames < 0) { frames = snd_pcm_recover(context.handle, frames, 0); } if (frames < 0) { printf("snd_pcm_writei failed: %s\n", snd_strerror(frames)); break; } if (frames > 0 && frames < sound_buffer.SampleCount) { printf("Short write (expected %i, wrote %i)\n", sound_buffer.SampleCount, frames); } } xcb_image_put(context.connection, buffer.xcb_pixmap_id, buffer.xcb_gcontext_id, buffer.xcb_image, 0, 0, 0); xcb_flush(context.connection); timespec target_counter = {}; target_counter.tv_sec = last_counter.tv_sec; target_counter.tv_nsec = last_counter.tv_nsec + target_nanoseconds_per_frame; if (target_counter.tv_nsec > (1000 * 1000 * 1000)) { target_counter.tv_sec++; target_counter.tv_nsec %= (1000 * 1000 * 1000); } timespec work_counter = {}; clock_gettime(HHXCB_CLOCK, &work_counter); bool32 might_need_sleep = 0; if (work_counter.tv_sec < target_counter.tv_sec) { might_need_sleep = 1; } else if ((work_counter.tv_sec == target_counter.tv_sec) && (work_counter.tv_nsec < target_counter.tv_nsec)) { might_need_sleep = 1; } if (might_need_sleep) { timespec sleep_counter = {}; sleep_counter.tv_nsec = target_counter.tv_nsec - work_counter.tv_nsec; if (sleep_counter.tv_nsec < 0) { sleep_counter.tv_nsec += (1000 * 1000 * 1000); } // To closest ms sleep_counter.tv_nsec -= sleep_counter.tv_nsec % (1000 * 1000); if (sleep_counter.tv_nsec > 0) { timespec remaining_sleep_counter = {}; nanosleep(&sleep_counter, &remaining_sleep_counter); } else { // TODO(nbm): Log missed sleep } } timespec spin_counter = {}; clock_gettime(HHXCB_CLOCK, &spin_counter); while (spin_counter.tv_sec <= target_counter.tv_sec && spin_counter.tv_nsec < target_counter.tv_nsec) { clock_gettime(HHXCB_CLOCK, &spin_counter); } timespec end_counter = {}; clock_gettime(HHXCB_CLOCK, &end_counter); long ns_per_frame = end_counter.tv_nsec - last_counter.tv_nsec; if (ns_per_frame < 0) { ns_per_frame += (1000 * 1000 * 1000) * (end_counter.tv_sec - last_counter.tv_sec); } last_counter = end_counter; real32 ms_per_frame = ns_per_frame / (1000 * 1000.0); xcb_copy_area(context.connection, buffer.xcb_pixmap_id, context.window, buffer.xcb_gcontext_id, 0,0, 0, 0, buffer.xcb_image->width, buffer.xcb_image->height); xcb_flush(context.connection); game_input *temp_input = new_input; new_input = old_input; old_input = temp_input; } snd_pcm_close(context.handle); // NOTE(nbm): Since auto-repeat seems to be a X-wide thing, let's be nice // and return it to where it was before? { uint32_t values[1] = {XCB_AUTO_REPEAT_MODE_DEFAULT}; xcb_change_keyboard_control(context.connection, XCB_KB_AUTO_REPEAT_MODE, values); } xcb_flush(context.connection); xcb_disconnect(context.connection); }