1 STATIC
void npv_xcb_fatal(u8
*fmt
, ...)
8 va_end(ap
); /* unreachable */
10 STATIC
void npv_xcb_warning(u8
*fmt
, ...)
16 npv_vwarning(fmt
, ap
);
19 STATIC
void npv_xcb_pout(u8
*fmt
, ...)
28 STATIC
void npv_xcb_libs_load(void)
30 npv_xcb_lib_l
= dlopen("libxcb.so.1", RTLD_LAZY
);
31 if (npv_xcb_lib_l
== 0)
32 npv_xcb_fatal("%s:unable to load the xcb dynamic shared library\n", dlerror());
33 npv_xcb_xfixes_lib_l
= dlopen("libxcb-xfixes.so.0", RTLD_LAZY
);
34 if (npv_xcb_xfixes_lib_l
== 0)
35 npv_xcb_fatal("%s:unable to load the xcb xfixes dynamic shared library\n", dlerror());
37 #define XCB_DLSYM(x) \
38 npv_dl_##x = dlsym(npv_xcb_lib_l, #x); \
39 if (npv_dl_##x == 0) \
40 npv_xcb_fatal("%s:unable to find " #x "\n", dlerror());
41 #define XCB_XFIXES_DLSYM(x) \
42 npv_dl_##x = dlsym(npv_xcb_xfixes_lib_l, #x); \
43 if (npv_dl_##x == 0) \
44 npv_xcb_fatal("%s:unable to find " #x "\n", dlerror());
45 STATIC
void npv_xcb_syms(void)
47 XCB_DLSYM(xcb_connect
);
48 XCB_DLSYM(xcb_get_file_descriptor
);
49 XCB_DLSYM(xcb_generate_id
);
50 XCB_DLSYM(xcb_connection_has_error
);
51 XCB_DLSYM(xcb_get_setup
);
52 XCB_DLSYM(xcb_setup_roots_length
);
53 XCB_DLSYM(xcb_setup_roots_iterator
);
54 XCB_DLSYM(xcb_screen_next
);
55 XCB_DLSYM(xcb_create_window
);
56 XCB_DLSYM(xcb_map_window
);
57 XCB_DLSYM(xcb_map_window_checked
);
58 XCB_DLSYM(xcb_request_check
);
60 XCB_DLSYM(xcb_wait_for_event
);
61 XCB_DLSYM(xcb_poll_for_event
);
62 XCB_DLSYM(xcb_change_property
);
63 XCB_DLSYM(xcb_disconnect
);
64 XCB_DLSYM(xcb_force_screen_saver
);
65 XCB_DLSYM(xcb_get_extension_data
);
66 XCB_DLSYM(xcb_query_pointer
);
67 XCB_DLSYM(xcb_query_pointer_reply
);
68 XCB_DLSYM(xcb_send_event
);
69 XCB_DLSYM(xcb_intern_atom
);
70 XCB_DLSYM(xcb_intern_atom_reply
);
71 XCB_XFIXES_DLSYM(xcb_xfixes_id
);
72 XCB_XFIXES_DLSYM(xcb_xfixes_query_version
);
73 XCB_XFIXES_DLSYM(xcb_xfixes_show_cursor
);
74 XCB_XFIXES_DLSYM(xcb_xfixes_hide_cursor
);
77 STATIC
void npv_xcb_win_create(void)
82 npv_xcb_p
.win_id
= npv_dl_xcb_generate_id(npv_xcb_p
.c
);
83 npv_xcb_pout("'%s':connection:%p:screen:%d:root window id:%#x:window id=%#x\n", npv_xcb_p
.disp_env
, npv_xcb_p
.c
, npv_xcb_p
.scr_idx
, npv_xcb_p
.scr
->root
, npv_xcb_p
.win_id
);
85 value_mask
= XCB_CW_BACK_PIXEL
| XCB_CW_EVENT_MASK
;
86 value_list
[0] = npv_xcb_p
.scr
->black_pixel
;
87 value_list
[1] = XCB_EVENT_MASK_KEY_RELEASE
88 | XCB_EVENT_MASK_STRUCTURE_NOTIFY
89 | XCB_EVENT_MASK_POINTER_MOTION
90 | XCB_EVENT_MASK_BUTTON_MOTION
91 | XCB_EVENT_MASK_LEAVE_WINDOW
92 | XCB_EVENT_MASK_ENTER_WINDOW
;
93 npv_dl_xcb_create_window(npv_xcb_p
.c
, XCB_COPY_FROM_PARENT
,
95 npv_xcb_p
.scr
->root
, 0, 0,
96 npv_xcb_p
.width
, npv_xcb_p
.height
,
98 XCB_WINDOW_CLASS_INPUT_OUTPUT
,
99 npv_xcb_p
.scr
->root_visual
,
100 value_mask
, value_list
);
102 STATIC
void npv_xcb_win_map(void)
104 xcb_void_cookie_t cookie
;
105 xcb_generic_error_t
*e
;
107 cookie
= npv_dl_xcb_map_window_checked(npv_xcb_p
.c
, npv_xcb_p
.win_id
);
108 npv_xcb_pout("'%s':connection:%p:screen:%d:root window id:%#x:window id:%#x:map window request cookie=%#x\n", npv_xcb_p
.disp_env
, npv_xcb_p
.c
, npv_xcb_p
.scr_idx
, npv_xcb_p
.scr
->root
, npv_xcb_p
.win_id
, cookie
);
110 e
= npv_dl_xcb_request_check(npv_xcb_p
.c
, cookie
);
112 npv_xcb_fatal("'%s':connection:%p:screen:%d:root window id:%#x:window id:%#x:unable to map window\n", npv_xcb_p
.disp_env
, npv_xcb_p
.c
, npv_xcb_p
.scr_idx
, npv_xcb_p
.scr
->root
, npv_xcb_p
.win_id
);
113 npv_xcb_pout("'%s':connection:%p:screen:%d:root window id:%#x:window id:%#x:window mapped\n", npv_xcb_p
.disp_env
, npv_xcb_p
.c
, npv_xcb_p
.scr_idx
, npv_xcb_p
.scr
->root
, npv_xcb_p
.win_id
);
116 * a disp is n scrs and 1 [set of] keyboard[s] and 1 [set of] mouse[s]
117 * 1 scr could be n monitors
118 * nowdays: usually 1 scr per display
119 * 1 scr has 1 root win
121 STATIC
void npv_xcb_connect(void)
125 npv_xcb_p
.disp_env
= getenv("DISPLAY");
126 if (npv_xcb_p
.disp_env
== 0 || npv_xcb_p
.disp_env
[0] == 0)
127 npv_xcb_fatal("no x11 DISPLAY environment variable, exiting\n");
129 npv_xcb_p
.scr_idx
= 0;
130 /* should be 0 though */
131 npv_xcb_p
.c
= npv_dl_xcb_connect(0, &npv_xcb_p
.scr_idx
);
132 r
= npv_dl_xcb_connection_has_error(npv_xcb_p
.c
);
134 npv_xcb_fatal("%d:%s:error while connecting to the x11 server\n", r
, npv_xcb_p
.disp_env
);
135 npv_xcb_pout("'%s':connection=%p, default screen index is %d (should be 0)\n", npv_xcb_p
.disp_env
, npv_xcb_p
.c
, npv_xcb_p
.scr_idx
);
137 npv_xcb_p
.fd
= npv_dl_xcb_get_file_descriptor(npv_xcb_p
.c
);
138 if (npv_xcb_p
.fd
== -1)
139 npv_xcb_fatal("'%s':unable to get the connection file descriptor for epoll\n", npv_xcb_p
.disp_env
);
140 npv_xcb_pout("'%s':connection:%p:file descriptor %d\n", npv_xcb_p
.disp_env
, npv_xcb_p
.c
, npv_xcb_p
.fd
);
142 STATIC
void npv_xcb_scr_get(void)
144 xcb_screen_iterator_t iter
;
148 npv_xcb_p
.setup
= npv_dl_xcb_get_setup(npv_xcb_p
.c
);
150 scrs_n
= npv_dl_xcb_setup_roots_length(npv_xcb_p
.setup
);
151 npv_xcb_pout("'%s':connection:%p:has %d screens (should be 1)\n", npv_xcb_p
.disp_env
, npv_xcb_p
.c
, scrs_n
);
153 iter
= npv_dl_xcb_setup_roots_iterator(npv_xcb_p
.setup
);
158 break; /* no more scr to iterate on */
160 if (i
== npv_xcb_p
.scr_idx
) {
161 npv_xcb_p
.scr
= iter
.data
;
164 npv_dl_xcb_screen_next(&iter
);
166 npv_xcb_pout("'%s':connection:%p:screen:%d:root window id:%#x:width=%d pixels\n", npv_xcb_p
.disp_env
, npv_xcb_p
.c
, npv_xcb_p
.scr_idx
, npv_xcb_p
.scr
->root
, npv_xcb_p
.scr
->width_in_pixels
);
167 npv_xcb_pout("'%s':connection:%p:screen:%d:root window id:%#x:height=%d pixels\n", npv_xcb_p
.disp_env
, npv_xcb_p
.c
, npv_xcb_p
.scr_idx
, npv_xcb_p
.scr
->root
, npv_xcb_p
.scr
->height_in_pixels
);
168 npv_xcb_pout("'%s':connection:%p:screen:%d:root window id:%#x:white pixel=0x%08x\n", npv_xcb_p
.disp_env
, npv_xcb_p
.c
, npv_xcb_p
.scr_idx
, npv_xcb_p
.scr
->root
, npv_xcb_p
.scr
->white_pixel
);
169 npv_xcb_pout("'%s':connection:%p:screen:%d:root window id:%#x:black pixel=0x%08x\n", npv_xcb_p
.disp_env
, npv_xcb_p
.c
, npv_xcb_p
.scr_idx
, npv_xcb_p
.scr
->root
, npv_xcb_p
.scr
->black_pixel
);
171 #define MIN_SZ_BIT (1 << 4)
172 #define MAX_SZ_BIT (1 << 5)
173 #define BASE_SZ_BIT (1 << 8)
175 /* 4 padding dwords */
180 #define BASE_WIDTH 15
181 #define BASE_HEIGHT 16
183 STATIC
void npv_xcb_wm_hints(void)
186 /* the following will tell the wm the win is not resizable */
189 memset(data
, 0, sizeof(data
));
190 data
[FLAGS
] = MIN_SZ_BIT
| MAX_SZ_BIT
;
192 * XXX: min == max --> the wm must understand this information as the
193 * win being non-resizeable.
194 * next: unlock the size and recreate the vulkan swpchn at each x11
197 data
[MIN_WIDTH
] = npv_xcb_p
.width
;
198 data
[MIN_HEIGHT
] = npv_xcb_p
.height
;
199 data
[MAX_WIDTH
] = npv_xcb_p
.width
;
200 data
[MAX_HEIGHT
] = npv_xcb_p
.height
;
201 npv_dl_xcb_change_property(npv_xcb_p
.c
, XCB_PROP_MODE_REPLACE
,
202 npv_xcb_p
.win_id
, XCB_ATOM_WM_NORMAL_HINTS
,
203 XCB_ATOM_WM_SIZE_HINTS
, 32, DWORDS_N
, data
);
206 /* the following will set the base size */
209 memset(data
, 0, sizeof(data
));
210 data
[FLAGS
] = BASE_SZ_BIT
;
211 data
[BASE_WIDTH
] = npv_xcb_p
.width
;
212 data
[BASE_HEIGHT
] = npv_xcb_p
.height
;
214 npv_dl_xcb_change_property(npv_xcb_p
.c
, XCB_PROP_MODE_REPLACE
,
215 npv_xcb_p
.win_id
, XCB_ATOM_WM_NORMAL_HINTS
,
216 XCB_ATOM_WM_SIZE_HINTS
, 32, DWORDS_N
, data
);
230 #define NPV_WM_CLASS "npv\0npv"
231 STATIC
void npv_xcb_wm_class(void)
233 npv_dl_xcb_change_property(npv_xcb_p
.c
, XCB_PROP_MODE_REPLACE
,
234 npv_xcb_p
.win_id
, XCB_ATOM_WM_CLASS
,
235 XCB_ATOM_STRING
, 8, sizeof(NPV_WM_CLASS
),
239 #define NPV_WM_NAME "npv"
240 STATIC
void npv_xcb_wm_name(void)
242 npv_dl_xcb_change_property(npv_xcb_p
.c
, XCB_PROP_MODE_REPLACE
,
243 npv_xcb_p
.win_id
, XCB_ATOM_WM_NAME
,
244 XCB_ATOM_STRING
, 8, strlen(NPV_WM_NAME
),
248 STATIC
void npv_xcb_evt_key_release(xcb_generic_event_t
*evt
)
251 xcb_key_release_event_t
*key
;
253 key
= (xcb_key_release_event_t
*)evt
;
256 if (b
== ARRAY_N(npv_x11_binds
))
258 if (key
->detail
== npv_x11_binds
[b
].keycode
) {
259 npv_xcb_pout("'%s':connection:%p:event:key release:keycode:%#02x:running command for bind \"%s\"\n", npv_xcb_p
.disp_env
, npv_xcb_p
.c
, key
->detail
, npv_x11_binds
[b
].name
);
260 npv_x11_binds
[b
].cmd();
265 npv_xcb_pout("'%s':connection:%p:event:key release:keycode:%#02x\n", npv_xcb_p
.disp_env
, npv_xcb_p
.c
, key
->detail
);
267 STATIC
void npv_xcb_evt_leave_win(xcb_generic_event_t
*evt
)
269 if (npv_xcb_p
.mouse_hidden
) {
270 npv_dl_xcb_xfixes_show_cursor(npv_xcb_p
.c
, npv_xcb_p
.win_id
);
271 npv_dl_xcb_flush(npv_xcb_p
.c
);
273 npv_xcb_p
.mouse_hidden
= false;
274 npv_xcb_p
.last_root_x
= -1;
275 npv_xcb_p
.last_root_y
= -1;
277 STATIC
void npv_xcb_mouse_visibility_timer_start(void)
282 memset(&t
, 0, sizeof(t
));
283 t
.it_value
.tv_sec
= npv_xcb_mouse_visibility_interval_s
;
284 r
= timerfd_settime(npv_xcb_p
.mouse_visibility_timer_fd
, 0, &t
, 0);
286 npv_xcb_fatal("unable to arm the mouse visibility timer to %u seconds\n", npv_xcb_mouse_visibility_interval_s
);
288 STATIC
void npv_xcb_evt_enter_win(xcb_generic_event_t
*evt
)
290 xcb_enter_notify_event_t
*ene
;
292 ene
= (xcb_enter_notify_event_t
*)evt
;
293 npv_dl_xcb_xfixes_show_cursor(npv_xcb_p
.c
, npv_xcb_p
.win_id
);
294 npv_dl_xcb_flush(npv_xcb_p
.c
);
295 npv_xcb_p
.mouse_hidden
= false;
296 npv_xcb_p
.last_root_x
= ene
->root_x
;
297 npv_xcb_p
.last_root_y
= ene
->root_y
;
298 npv_xcb_mouse_visibility_timer_start();
300 STATIC
void npv_xcb_evt_motion(xcb_generic_event_t
*evt
)
302 xcb_motion_notify_event_t
*mne
;
304 mne
= (xcb_motion_notify_event_t
*)evt
;
305 if (npv_xcb_p
.mouse_hidden
) {
306 npv_dl_xcb_xfixes_show_cursor(npv_xcb_p
.c
, npv_xcb_p
.win_id
);
307 npv_dl_xcb_flush(npv_xcb_p
.c
);
309 npv_xcb_p
.mouse_hidden
= false;
310 npv_xcb_p
.last_root_x
= mne
->root_x
;
311 npv_xcb_p
.last_root_y
= mne
->root_y
;
312 npv_xcb_mouse_visibility_timer_start();
315 typedef struct xcb_configure_notify_event_t
{
316 uint8_t response_type
;
321 xcb_window_t above_sibling
;
326 uint16_t border_width
;
327 uint8_t override_redirect
;
329 } xcb_configure_notify_event_t
;
331 STATIC
void npv_xcb_evt_cfg_notify(xcb_generic_event_t
*evt
)
333 xcb_configure_notify_event_t
*cne
;
335 cne
= (xcb_configure_notify_event_t
*)evt
;
336 npv_xcb_p
.width
= cne
->width
;
337 npv_xcb_p
.height
= cne
->height
;
339 PERR("CFG_NOTIFY_EVT:x=%d y=%d w=%u h=%u bw=%u\n", cne
->x
, cne
->y
, cne
->width
, cne
->height
, cne
->border_width
);
342 STATIC
void npv_xcb_evt_handle(xcb_generic_event_t
*evt
)
346 * do not discriminate evts generated by clients using sendevent
347 * requests (note: "client message" evts have always their most
348 * significant bit set)
350 evt_code
= evt
->response_type
& 0x7f;
353 case XCB_KEY_RELEASE
:
354 npv_xcb_evt_key_release(evt
);
356 case XCB_CONFIGURE_NOTIFY
:
357 npv_xcb_evt_cfg_notify(evt
);
359 case XCB_MOTION_NOTIFY
:
360 npv_xcb_evt_motion(evt
);
362 case XCB_LEAVE_NOTIFY
:
363 npv_xcb_evt_leave_win(evt
);
365 case XCB_ENTER_NOTIFY
:
366 npv_xcb_evt_enter_win(evt
);
372 STATIC
void npv_xcb_screensaver_heartbeat_timer_init_once(void)
375 npv_xcb_p
.screensaver_heartbeat_timer_fd
= timerfd_create(
376 CLOCK_MONOTONIC
, TFD_NONBLOCK
);
377 if (npv_xcb_p
.screensaver_heartbeat_timer_fd
== -1)
378 npv_xcb_fatal("unable to get a timer file descriptor for the screensaver heartbeat:%s\n", strerror(errno
));
380 STATIC
void npv_xcb_mouse_visibilty_init_once(void)
382 struct xcb_query_extension_reply_t
const *xfixes
;
383 xcb_xfixes_query_version_cookie_t qec
;
385 xfixes
= npv_dl_xcb_get_extension_data(npv_xcb_p
.c
,
386 npv_dl_xcb_xfixes_id
);
387 if (xfixes
->response_type
!= 1 || xfixes
->present
== 0)
388 npv_xcb_warning("the server is missing xfixes extension\n");
389 /* the hice/show cursor is supported from version 4 */
390 npv_dl_xcb_xfixes_query_version(npv_xcb_p
.c
, 4, 0);
391 /*--------------------------------------------------------------------*/
392 npv_xcb_p
.mouse_hidden
= false;
393 npv_xcb_p
.last_root_x
= -1;
394 npv_xcb_p
.last_root_y
= -1;
397 npv_xcb_p
.mouse_visibility_timer_fd
= timerfd_create(CLOCK_MONOTONIC
,
399 if (npv_xcb_p
.mouse_visibility_timer_fd
== -1)
400 npv_xcb_fatal("unable to get a timer file descriptor for the mouse visibility management:%s\n", strerror(errno
));
402 #define NET_WM_STATE "_NET_WM_STATE"
403 #define NET_WM_STATE_FULLSCREEN "_NET_WM_STATE_FULLSCREEN"
404 #define SOURCE_NORMAL_APP 1
405 #define STRLEN(x) (sizeof(x)-1)
406 #define DONT_PROPAGATE_TO_ANCESTORS 0
407 STATIC
void npv_xcb_fullscreen_action(u32 action
)
409 xcb_intern_atom_cookie_t cookie_net_wm_state
;
410 xcb_intern_atom_cookie_t cookie_net_wm_state_fullscreen
;
411 xcb_intern_atom_reply_t
*reply
;
412 xcb_generic_error_t
*err
;
413 xcb_atom_t net_wm_state
;
414 xcb_atom_t net_wm_state_fullscreen
;
415 xcb_client_message_event_t cm_evt
;
417 cookie_net_wm_state
= npv_dl_xcb_intern_atom(npv_xcb_p
.c
, 1,
418 STRLEN(NET_WM_STATE
), NET_WM_STATE
);
419 cookie_net_wm_state_fullscreen
= npv_dl_xcb_intern_atom(npv_xcb_p
.c
, 1,
420 STRLEN(NET_WM_STATE_FULLSCREEN
), NET_WM_STATE_FULLSCREEN
);
422 reply
= npv_dl_xcb_intern_atom_reply(npv_xcb_p
.c
, cookie_net_wm_state
,
425 npv_xcb_warning("unable to set fullscreen:unable to get the _NET_WM_STATE atom:%d\n", err
->error_code
);
429 net_wm_state
= reply
->atom
;
432 reply
= npv_dl_xcb_intern_atom_reply(npv_xcb_p
.c
,
433 cookie_net_wm_state_fullscreen
, &err
);
435 npv_xcb_warning("unable to set fullscreen:unable to get the _NET_WM_STATE_FULLSCREEN atom:%d\n", err
->error_code
);
439 net_wm_state_fullscreen
= reply
->atom
;
441 /*--------------------------------------------------------------------*/
442 memset(&cm_evt
, 0, sizeof(cm_evt
));
443 cm_evt
.response_type
= XCB_CLIENT_MESSAGE
;
445 cm_evt
.window
= npv_xcb_p
.win_id
;
446 cm_evt
.type
= net_wm_state
;
447 cm_evt
.data
.data32
[0] = action
;
448 cm_evt
.data
.data32
[1] = net_wm_state_fullscreen
;
449 cm_evt
.data
.data32
[3] = SOURCE_NORMAL_APP
;
451 * client message send event req must use the evt mask below and must
452 * not propagate the evt, see ewmh specs
454 npv_dl_xcb_send_event(npv_xcb_p
.c
, DONT_PROPAGATE_TO_ANCESTORS
,
455 npv_xcb_p
.scr
->root
, XCB_EVENT_MASK_STRUCTURE_NOTIFY
456 | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT
, (u8
*)&cm_evt
);
457 npv_dl_xcb_flush(npv_xcb_p
.c
);
460 #undef NET_WM_STATE_FULLSCREEN
461 #undef NET_WM_STATE_ADD
462 #undef SOURCE_NORMAL_APP
464 #undef DONT_PROPAGATE_TO_ANCESTORS
465 #define NET_WM_STATE_ADD 1
466 STATIC
void npv_xcb_fullscreen_init_once(bool start_fullscreen
)
468 xcb_intern_atom_cookie_t cookie_net_wm_state
;
469 xcb_intern_atom_cookie_t cookie_net_wm_state_fullscreen
;
470 xcb_intern_atom_reply_t
*reply
;
471 xcb_generic_error_t
*err
;
472 xcb_atom_t net_wm_state
;
473 xcb_atom_t net_wm_state_fullscreen
;
474 xcb_client_message_event_t cm_evt
;
476 if (!start_fullscreen
)
478 npv_xcb_fullscreen_action(NET_WM_STATE_ADD
);
480 #undef NET_WM_STATE_ADD