kiba-engine
window.c
Go to the documentation of this file.
1 
6 #include <kiba/platform/window.h>
7 
8 #include <kiba/core/event.h>
9 #include <kiba/core/input.h>
10 #include <kiba/core/log.h>
12 
13 #include <stdlib.h>
14 #include <string.h>
15 #include <xcb/xkb.h>
16 
17 b8 window_create(platform_window *window, const char *title, u32 x, u32 y, u32 w, u32 h, allocator *alloc) {
18  KB_ASSERT(window != KB_NULL, "window to populate must not be nullptr");
19  KB_ASSERT(title != KB_NULL, "window title must point to some data");
20  linux_window_state *state =
22  if (state == KB_NULL) {
23  KB_ERROR("could not allocate {usize} bytes for the window state", sizeof(linux_window_state));
24  return false;
25  }
26  window->state = state;
27 
28  // connect to X server
29  i32 screenp = 0;
30  state->connection = xcb_connect(NULL, &screenp);
31  if (xcb_connection_has_error(state->connection)) {
32  KB_ERROR("failed to establish connection to X server");
33  return false;
34  }
35 
36  // check for xkb extension
37  const xcb_query_extension_reply_t *ext_reply = xcb_get_extension_data(state->connection, &xcb_xkb_id);
38  if (ext_reply == KB_NULL) {
39  KB_ERROR("xkb extension not available on X server");
40  return false;
41  }
42 
43  // load xkb
44  xcb_xkb_use_extension_cookie_t use_ext_cookie =
45  xcb_xkb_use_extension(state->connection, XCB_XKB_MAJOR_VERSION, XCB_XKB_MINOR_VERSION);
46  xcb_generic_error_t *error = KB_NULL;
47  xcb_xkb_use_extension_reply_t *use_ext_reply = KB_NULL;
48  use_ext_reply = xcb_xkb_use_extension_reply(state->connection, use_ext_cookie, &error);
49  if (use_ext_reply == KB_NULL) {
50  KB_ERROR("failed to load xkb extension of X server");
51  return false;
52  }
53  if (!use_ext_reply->supported || error != KB_NULL) {
54  KB_ERROR("xkb extension is not supported on this X server");
55  free(use_ext_reply);
56  return false;
57  }
58  free(use_ext_reply);
59 
60  // deactivate key repeat
61  xcb_xkb_per_client_flags_cookie_t pcf_cookie;
62  xcb_xkb_per_client_flags_reply_t *pcf_reply;
63  pcf_cookie = xcb_xkb_per_client_flags(state->connection,
64  XCB_XKB_ID_USE_CORE_KBD,
65  XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT,
66  XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT,
67  0,
68  0,
69  0);
70  pcf_reply = xcb_xkb_per_client_flags_reply(state->connection, pcf_cookie, &error);
71  free(pcf_reply);
72  if (error != KB_NULL) {
73  KB_ERROR("failed to set xkb per-client flag, error code: {u8}", error->major_code);
74  return false;
75  }
76 
77  // get keysym
78  state->syms = xcb_key_symbols_alloc(state->connection);
79 
80  // gather data
81  const xcb_setup_t *setup = xcb_get_setup(state->connection);
82  state->screen = xcb_setup_roots_iterator(setup).data;
83  state->window = xcb_generate_id(state->connection);
84 
85  // register event types
86  u32 event_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
87  u32 event_values = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_KEY_PRESS
88  | XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_POINTER_MOTION
89  | XCB_EVENT_MASK_STRUCTURE_NOTIFY;
90  u32 value_list[] = {state->screen->black_pixel, event_values};
91 
92  // create window
93  xcb_create_window(state->connection,
94  XCB_COPY_FROM_PARENT,
95  state->window,
96  state->screen->root,
97  (i16) x,
98  (i16) y,
99  (u16) w,
100  (u16) h,
101  0,
102  XCB_WINDOW_CLASS_INPUT_OUTPUT,
103  state->screen->root_visual,
104  event_mask,
105  value_list);
106 
107  // set title
108  xcb_change_property(state->connection,
109  XCB_PROP_MODE_REPLACE,
110  state->window,
111  XCB_ATOM_WM_NAME,
112  XCB_ATOM_STRING,
113  8,
114  (u32) strlen(title),
115  title);
116 
117  // listen on window manager commands
118  const char *wm_delete_cmd_name = "WM_DELETE_WINDOW";
119  const char *wm_protocols_cmd_name = "WM_PROTOCOLS";
120  xcb_intern_atom_cookie_t wm_delete_cookie =
121  xcb_intern_atom(state->connection, 0, (u16) strlen(wm_delete_cmd_name), wm_delete_cmd_name);
122  xcb_intern_atom_cookie_t wm_protocols_cookie =
123  xcb_intern_atom(state->connection, 0, (u16) strlen(wm_protocols_cmd_name), wm_protocols_cmd_name);
124  xcb_intern_atom_reply_t *wm_delete_reply = xcb_intern_atom_reply(state->connection, wm_delete_cookie, NULL);
125  xcb_intern_atom_reply_t *wm_protocols_reply = xcb_intern_atom_reply(state->connection, wm_protocols_cookie, NULL);
126  state->wm_delete_win = wm_delete_reply->atom;
127  state->wm_protocols = wm_protocols_reply->atom;
128  xcb_change_property(state->connection,
129  XCB_PROP_MODE_REPLACE,
130  state->window,
131  state->wm_protocols,
132  4,
133  32,
134  1,
135  &wm_delete_reply->atom);
136 
137  // map window to screen
138  xcb_map_window(state->connection, state->window);
139 
140  // flush stream
141  i32 stream_result = xcb_flush(state->connection);
142  if (stream_result <= 0) {
143  KB_ERROR("failed to flush the stream, result: {i32}", stream_result);
144  return false;
145  }
146  gpu_surface surface = allocator_allocate_aligned(alloc, sizeof(struct gpu_surface), ALIGN_OF(struct gpu_surface));
147  if (!surface) {
148  KB_ERROR("failed to allocate memory for window's surface");
149  return false;
150  }
151  if (!gpu_surface_create(surface, state)) {
152  KB_ERROR("failed to create window surface");
153  return false;
154  }
155  window->title = title;
156  window->x = x;
157  window->y = y;
158  window->w = w;
159  window->h = h;
160  window->surface = surface;
161  window->alloc = alloc;
162  return true;
163 }
164 
166  if (window->surface) {
167  gpu_surface_destroy(window->surface);
168  allocator_free(window->alloc, window->surface);
169  }
170  if (window->state) {
171  linux_window_state *state = (linux_window_state *) window->state;
172  xcb_destroy_window(state->connection, state->window);
173  xcb_key_symbols_free(state->syms);
174  xcb_disconnect(state->connection);
175  allocator_free(window->alloc, state);
176  window->state = KB_NULL;
177  }
178 }
179 
187 key_code translate_keysym(xcb_keysym_t key_code);
195 key_code translate_button(xcb_button_t button);
196 
198  linux_window_state *state = (linux_window_state *) window->state;
199  xcb_generic_event_t *event;
200  b8 quit = false;
201  while ((event = xcb_poll_for_event(state->connection))) {
202  event_context context;
203  i32 type = event->response_type & ~0x80;
204  switch (type) {
205  case XCB_KEY_PRESS:
206  case XCB_KEY_RELEASE: {
207  xcb_key_press_event_t *key_event = (xcb_key_press_event_t *) event;
208  xcb_keysym_t key_sym = xcb_key_symbols_get_keysym(state->syms, key_event->detail, 0);
209  key_code code = translate_keysym(key_sym);
210  b8 pressed = type == XCB_KEY_PRESS;
211  input_process_key(code, pressed);
212  context.data.u32[0] = code;
213  event_fire(pressed ? EVENT_CODE_KEY_PRESSED : EVENT_CODE_KEY_RELEASED, state, context);
214  } break;
215  case XCB_BUTTON_PRESS:
216  case XCB_BUTTON_RELEASE: {
217  xcb_button_press_event_t *button_event = (xcb_button_press_event_t *) event;
218  if (button_event->detail > 3) {
219  i8 wheel_dir = button_event->detail == 4 ? -1 : 1;
220  input_process_mouse_wheel(wheel_dir);
221  context.data.i8[0] = wheel_dir;
222  event_fire(EVENT_CODE_MOUSE_WHEEL, state, context);
223  } else {
224  key_code code = translate_button(button_event->detail);
225  b8 pressed = type == XCB_BUTTON_PRESS;
226  input_process_key(code, pressed);
227  context.data.u32[0] = code;
228  event_fire(pressed ? EVENT_CODE_KEY_PRESSED : EVENT_CODE_KEY_RELEASED, state, context);
229  }
230  } break;
231  case XCB_MOTION_NOTIFY: {
232  xcb_motion_notify_event_t *motion_event = (xcb_motion_notify_event_t *) event;
233  input_process_mouse_position((u32) motion_event->event_x, (u32) motion_event->event_y);
234  context.data.u32[0] = (u32) motion_event->event_x;
235  context.data.u32[1] = (u32) motion_event->event_y;
236  if (input_mouse_moved(context.data.u32 + 2, context.data.u32 + 3)) {
237  event_fire(EVENT_CODE_MOUSE_MOVED, state, context);
238  }
239  } break;
240  case XCB_CONFIGURE_NOTIFY: {
241  xcb_configure_notify_event_t *configure_event = (xcb_configure_notify_event_t *) event;
242  window->x = (u32) configure_event->x;
243  window->y = (u32) configure_event->y;
244  window->w = configure_event->width;
245  window->h = configure_event->height;
246 
247  context.data.u32[0] = window->w;
248  context.data.u32[1] = window->h;
249  event_fire(EVENT_CODE_WINDOW_RESIZE, state, context);
250  } break;
251  case XCB_MAP_NOTIFY: {
252  context.data.c[0] = 'R';
253  event_fire(EVENT_CODE_WINDOW_RAISED, state, context);
254  } break;
255  case XCB_UNMAP_NOTIFY: {
256  context.data.c[0] = 'M';
257  event_fire(EVENT_CODE_WINDOW_MINIMIZED, state, context);
258  } break;
259  case XCB_CLIENT_MESSAGE: {
260  xcb_client_message_event_t *message_event = (xcb_client_message_event_t *) event;
261  quit = message_event->data.data32[0] == state->wm_delete_win;
262  } break;
263  default:
264  break;
265  }
266  free(event);
267  }
268  return !quit;
269 }
270 
271 key_code translate_keysym(xcb_keysym_t x_key_code) {
272  if ((x_key_code >> 8) == 0 && x_key_code >= 0x0061 && x_key_code <= 0x007a) {
273  x_key_code -= (0x0061 - 0x0041);
274  }
275  switch (x_key_code) {
276  case 0xff08:
277  return KEY_BACKSPACE;
278  case 0xff0d:
279  return KEY_ENTER;
280  case 0xff09:
281  return KEY_TAB;
282  case 0xffe1:
283  return KEY_SHIFT_L;
284  case 0xffe2:
285  return KEY_SHIFT_R;
286  case 0xffe3:
287  return KEY_CONTROL_L;
288  case 0xffe4:
289  return KEY_CONTROL_R;
290  case 0xffe9:
291  return KEY_ALT_L;
292  case 0xffea:
293  return KEY_ALT_R;
294  case 0xffeb:
295  return KEY_SUPER_L;
296  // TODO find out right super key
297  case 0xff13:
298  return KEY_PAUSE;
299  case 0xffe5:
300  return KEY_CAPITAL;
301  case 0xff1b:
302  return KEY_ESCAPE;
303  case 0x0020:
304  return KEY_SPACE;
305  case 0xff50:
306  return KEY_HOME;
307  case 0xff57:
308  return KEY_END;
309  case 0xff55:
310  return KEY_PAGE_UP;
311  case 0xff56:
312  return KEY_PAGE_DOWN;
313  case 0xff51:
314  return KEY_LEFT;
315  case 0xff52:
316  return KEY_UP;
317  case 0xff54:
318  return KEY_DOWN;
319  case 0xff53:
320  return KEY_RIGHT;
321  case 0xff61:
322  return KEY_PRINT;
323  case 0xff63:
324  return KEY_INSERT;
325  case 0xffff:
326  return KEY_DELETE;
327  case 0x0041:
328  return KEY_A;
329  case 0x0042:
330  return KEY_B;
331  case 0x0043:
332  return KEY_C;
333  case 0x0044:
334  return KEY_D;
335  case 0x0045:
336  return KEY_E;
337  case 0x0046:
338  return KEY_F;
339  case 0x0047:
340  return KEY_G;
341  case 0x0048:
342  return KEY_H;
343  case 0x0049:
344  return KEY_I;
345  case 0x004a:
346  return KEY_J;
347  case 0x004b:
348  return KEY_K;
349  case 0x004c:
350  return KEY_L;
351  case 0x004d:
352  return KEY_M;
353  case 0x004e:
354  return KEY_N;
355  case 0x004f:
356  return KEY_O;
357  case 0x0050:
358  return KEY_P;
359  case 0x0051:
360  return KEY_Q;
361  case 0x0052:
362  return KEY_R;
363  case 0x0053:
364  return KEY_S;
365  case 0x0054:
366  return KEY_T;
367  case 0x0055:
368  return KEY_U;
369  case 0x0056:
370  return KEY_V;
371  case 0x0057:
372  return KEY_W;
373  case 0x0058:
374  return KEY_X;
375  case 0x0059:
376  return KEY_Y;
377  case 0x005a:
378  return KEY_Z;
379  case 0x003b:
380  return KEY_SEMICOLON;
381  case 0x0023:
382  return KEY_PLUS;
383  case 0x003d:
384  return KEY_EQUAL;
385  case 0x002c:
386  return KEY_COMMA;
387  case 0x002d:
388  return KEY_MINUS;
389  case 0x002e:
390  return KEY_PERIOD;
391  case 0x002f:
392  return KEY_SLASH;
393  case 0x0060:
394  return KEY_GRAVE;
395  case 0x0027:
396  return KEY_QUOTE;
397  case 0x005b:
398  return KEY_BRACKET_L;
399  case 0x005d:
400  return KEY_BRACKET_R;
401  case 0x005c:
402  return KEY_BACK_SLASH;
403  case 0x0030:
404  return KEY_0;
405  case 0x0031:
406  return KEY_1;
407  case 0x0032:
408  return KEY_2;
409  case 0x0033:
410  return KEY_3;
411  case 0x0034:
412  return KEY_4;
413  case 0x0035:
414  return KEY_5;
415  case 0x0036:
416  return KEY_6;
417  case 0x0037:
418  return KEY_7;
419  case 0x0038:
420  return KEY_8;
421  case 0x0039:
422  return KEY_9;
423  case 0xff9e:
424  return KEY_NUMPAD0;
425  case 0xff9c:
426  return KEY_NUMPAD1;
427  case 0xff99:
428  return KEY_NUMPAD2;
429  case 0xff9b:
430  return KEY_NUMPAD3;
431  case 0xff96:
432  return KEY_NUMPAD4;
433  case 0xff9d:
434  return KEY_NUMPAD5;
435  case 0xff98:
436  return KEY_NUMPAD6;
437  case 0xff95:
438  return KEY_NUMPAD7;
439  case 0xff97:
440  return KEY_NUMPAD8;
441  case 0xff9a:
442  return KEY_NUMPAD9;
443  case 0xff8d:
444  return KEY_NUMPAD_ENTER;
445  case 0xff9f:
446  return KEY_DECIMAL;
447  case 0xffaa:
448  return KEY_MULTIPLY;
449  case 0xffab:
450  return KEY_ADD;
451  case 0xffad:
452  return KEY_SUBTRACT;
453  case 0xffaf:
454  return KEY_DIVIDE;
455  case 0xff7f:
456  return KEY_NUMLOCK;
457  case 0xffbe:
458  return KEY_F1;
459  case 0xffbf:
460  return KEY_F2;
461  case 0xffc0:
462  return KEY_F3;
463  case 0xffc1:
464  return KEY_F4;
465  case 0xffc2:
466  return KEY_F5;
467  case 0xffc3:
468  return KEY_F6;
469  case 0xffc4:
470  return KEY_F7;
471  case 0xffc5:
472  return KEY_F8;
473  case 0xffc6:
474  return KEY_F9;
475  case 0xffc7:
476  return KEY_F10;
477  case 0xffc8:
478  return KEY_F11;
479  case 0xffc9:
480  return KEY_F12;
481  }
482  KB_WARN("unmapped key code received {u32}", x_key_code);
483  return KEY_CODE_MAX;
484 }
485 
486 key_code translate_button(xcb_button_t button) {
487  switch (button) {
488  case 1:
489  return KEY_MOUSE_LEFT;
490  case 2:
491  return KEY_MOUSE_MIDDLE;
492  case 3:
493  return KEY_MOUSE_RIGHT;
494  }
495  KB_WARN("unmapped button code received {u32}", button);
496  return KEY_CODE_MAX;
497 }
void * allocator_allocate_aligned(allocator *alloc, usize size, usize alignment)
Allocate aligned memory.
Definition: allocator.c:94
void allocator_free(allocator *alloc, void *mem)
Give back memory to the allocator.
Definition: allocator.c:134
#define ALIGN_OF(x)
Get alignment of type.
Definition: defines.h:27
#define KB_NULL
Value of an invalid ptr (nullptr).
Definition: defines.h:18
b8 event_fire(u16 event_code, void *sender, event_context context)
Fire an event.
Definition: event.c:81
Event system.
void input_process_mouse_position(u32 x, u32 y)
Set current state for the mouse position.
Definition: input.c:100
void input_process_mouse_wheel(i8 wheel_pos)
Set current scrolling wheel direction for the mouse.
Definition: input.c:105
void input_process_key(key_code key, b8 pressed)
Set current state for a key.
Definition: input.c:95
b8 input_mouse_moved(u32 *x, u32 *y)
Check if mouse moved.
Definition: input.c:87
Input abstraction layer.
key_code
All available keycodes.
Definition: input.h:13
Logging system.
#define KB_ASSERT(expr,...)
Perform runtime assertion and log failures.
Definition: log.h:133
#define KB_WARN(...)
Log entry with warn log level.
Definition: log.h:161
#define KB_ERROR(...)
Log entry with error log level.
Definition: log.h:142
Central allocator structure.
Definition: allocator.h:87
Context used to store data of an event.
Definition: event.h:15
xcb_window_t window
X window.
Definition: window.h:16
xcb_atom_t wm_protocols
Window manager protocol atom.
Definition: window.h:20
xcb_key_symbols_t * syms
Pointer to xcb keysyms.
Definition: window.h:24
xcb_connection_t * connection
Connection to X server.
Definition: window.h:14
xcb_screen_t * screen
X screen.
Definition: window.h:18
xcb_atom_t wm_delete_win
Window manager delete atom.
Definition: window.h:22
Structure holding information about a window.
Definition: window.h:13
allocator * alloc
Allocator used for on-the-fly allocations.
Definition: window.h:29
void * state
Definition: window.h:15
u32 h
Height of the window in pixels.
Definition: window.h:25
u32 y
y position on the screen.
Definition: window.h:21
gpu_surface surface
Surface to render to using the gpu lib.
Definition: window.h:27
const char * title
Title of the window.
Definition: window.h:17
u32 x
x position on the screen.
Definition: window.h:19
u32 w
Width of the window in pixels.
Definition: window.h:23
key_code translate_keysym(xcb_keysym_t key_code)
Translates xcb keysym to kiba key code.
Definition: window.c:271
void window_destroy(platform_window *window)
Destroy platform window and clean up internal state.
Definition: window.c:165
b8 window_poll_events(platform_window *window)
Poll for events emited by the window.
Definition: window.c:197
key_code translate_button(xcb_button_t button)
Translates xcb button to kiba key code.
Definition: window.c:486
b8 window_create(platform_window *window, const char *title, u32 x, u32 y, u32 w, u32 h, allocator *alloc)
Create a platform window.
Definition: window.c:17
Interface to access platform specific windowing functionality.