1 module gfx.window.xcb;
2
3 version(linux):
4
5 import gfx.core.log : LogTag;
6 import gfx.graal : Instance;
7 import gfx.window;
8 import gfx.window.xkeyboard;
9 import xcb.xcb;
10 import xcb.xkb;
11
12 version (VkXcb)
13 {
14 }
15 else
16 {
17 static assert(false, "gfx.window must be compiled with VkXcb version enabled");
18 }
19
20 enum gfxXcbLogMask = 0x0800_0000;
21 package immutable gfxXcbLog = LogTag("GFX-XCB");
22
23 /// List of X atoms that are fetched automatically
24 enum Atom
25 {
26 UTF8_STRING,
27
28 WM_PROTOCOLS,
29 WM_DELETE_WINDOW,
30 WM_TRANSIENT_FOR,
31 WM_CHANGE_STATE,
32 WM_STATE,
33 _NET_WM_STATE,
34 _NET_WM_STATE_MODAL,
35 _NET_WM_STATE_STICKY,
36 _NET_WM_STATE_MAXIMIZED_VERT,
37 _NET_WM_STATE_MAXIMIZED_HORZ,
38 _NET_WM_STATE_SHADED,
39 _NET_WM_STATE_SKIP_TASKBAR,
40 _NET_WM_STATE_SKIP_PAGER,
41 _NET_WM_STATE_HIDDEN,
42 _NET_WM_STATE_FULLSCREEN,
43 _NET_WM_STATE_ABOVE,
44 _NET_WM_STATE_BELOW,
45 _NET_WM_STATE_DEMANDS_ATTENTION,
46 _NET_WM_STATE_FOCUSED,
47 _NET_WM_NAME,
48 }
49
50 /// get the response_type field masked for
51 @property ubyte xcbEventType(EvT)(EvT* e)
52 {
53 return (e.response_type & ~0x80);
54 }
55
56 class XcbDisplay : Display
57 {
58 import gfx.core.rc : atomicRcCode, Rc;
59 import gfx.graal : Backend;
60 import X11.Xlib : XDisplay = Display;
61
62 mixin(atomicRcCode);
63
64 private XDisplay *_dpy;
65 private xcb_connection_t* _conn;
66 private xcb_atom_t[Atom] _atoms;
67 private int _mainScreenNum;
68 private uint _xkbFirstEv;
69 private XcbKeyboard _xkb;
70 private xcb_screen_t*[] _screens;
71
72 private Rc!Instance _instance;
73
74 private Window[] _windows;
75 private XcbWindow[] _xcbWindows;
76
77 this(DisplayCreateInfo createInfo)
78 {
79 import std.exception : enforce;
80 import X11.Xlib : XCloseDisplay, XDefaultScreen, XOpenDisplay;
81 import X11.Xlib_xcb : XGetXCBConnection, XSetEventQueueOwner, XCBOwnsEventQueue;
82
83 gfxXcbLog.trace("opening X display");
84 _dpy = enforce(XOpenDisplay(null));
85 scope(failure) {
86 XCloseDisplay(_dpy);
87 }
88 _conn = enforce(cast(xcb_connection_t*)XGetXCBConnection(_dpy));
89 XSetEventQueueOwner(_dpy, XCBOwnsEventQueue);
90 _mainScreenNum = XDefaultScreen(_dpy);
91
92 initializeAtoms();
93 initializeScreens();
94 initializeInstance(createInfo);
95
96 _xkb = new XcbKeyboard(_conn, _xkbFirstEv);
97 }
98
99 override void dispose()
100 {
101 import X11.Xlib : XCloseDisplay;
102
103 if (_windows.length) {
104 auto ws = _windows.dup;
105 foreach (w; ws) w.close();
106 }
107 assert(!_windows.length);
108
109 _xkb.dispose();
110
111 _instance.unload();
112 gfxXcbLog.trace("closing X display");
113 XCloseDisplay(_dpy);
114 }
115
116 private void initializeAtoms()
117 {
118 import core.stdc.stdlib : free;
119 import std.conv : to;
120 import std.string : toStringz;
121 import std.traits : EnumMembers;
122
123 xcb_intern_atom_cookie_t[] cookies;
124 cookies.reserve(EnumMembers!Atom.length);
125
126 foreach (immutable atom; EnumMembers!Atom) // static foreach
127 {
128 auto name = atom.to!string;
129 cookies ~= xcb_intern_atom(_conn, 1,
130 cast(ushort)name.length, toStringz(name));
131 }
132
133 foreach (i, immutable atom; EnumMembers!Atom) // static foreach
134 {
135 immutable name = atom.to!string;
136 xcb_generic_error_t* err;
137 auto reply = xcb_intern_atom_reply(_conn, cookies[i], &err);
138 if (err)
139 {
140 throw new Exception("failed initializing atom " ~ name ~ ": ",
141 (*err).to!string);
142 }
143 if (reply.atom == XCB_ATOM_NONE)
144 {
145 throw new Exception("could not retrieve atom " ~ name);
146 }
147 _atoms[atom] = reply.atom;
148 free(reply);
149 }
150 }
151
152 private void initializeScreens()
153 {
154 xcb_screen_iterator_t iter;
155 for (iter = xcb_setup_roots_iterator(xcb_get_setup(_conn)); iter.rem;
156 xcb_screen_next(&iter))
157 {
158 _screens ~= iter.data;
159 }
160 }
161
162 private void initializeInstance(DisplayCreateInfo createInfo)
163 {
164 assert(!_instance);
165
166 foreach (b; createInfo.backendCreateOrder) {
167 final switch (b) {
168 case Backend.vulkan:
169 try {
170 import gfx.vulkan : createVulkanInstance, debugReportInstanceExtensions,
171 lunarGValidationLayers, VulkanCreateInfo, vulkanInit;
172 import gfx.vulkan.wsi : xcbSurfaceInstanceExtensions;
173
174 gfxXcbLog.trace("Attempting to instantiate Vulkan");
175 vulkanInit();
176 VulkanCreateInfo vci;
177 vci.mandatoryExtensions = xcbSurfaceInstanceExtensions;
178 vci.optionalExtensions = createInfo.debugCallbackEnabled ?
179 debugReportInstanceExtensions : [];
180 vci.optionalLayers = createInfo.validationEnabled ?
181 lunarGValidationLayers : [];
182 _instance = createVulkanInstance(vci);
183 }
184 catch (Exception ex) {
185 gfxXcbLog.warningf("Vulkan is not available. %s", ex.msg);
186 }
187 break;
188 case Backend.gl3:
189 version (GfxGl3)
190 {
191 try {
192 import gfx.core.rc : makeRc;
193 import gfx.gl3 : GlInstance;
194 import gfx.gl3.context : GlAttribs;
195 import gfx.window.xcb.context : XcbGlContext;
196 gfxXcbLog.trace("Attempting to instantiate OpenGL");
197 auto w = new XcbWindow(this, null, "", true);
198 w.show(10, 10);
199 scope(exit) w.close();
200 auto ctx = makeRc!XcbGlContext(_dpy, _mainScreenNum, GlAttribs.init, w._win);
201 gfxXcbLog.trace("Creating an OpenGL instance");
202 _instance = new GlInstance(ctx);
203 }
204 catch (Exception ex) {
205 gfxXcbLog.warningf("OpenGL is not available. %s", ex.msg);
206 }
207 break;
208 }
209 else
210 {
211 assert(false, "OpenGL3 support is not enabled");
212 }
213 }
214 if (_instance) break;
215 }
216
217 if (!_instance) {
218 throw new Exception("Could not instantiate a backend");
219 }
220 }
221
222 override @property Instance instance() {
223 return _instance;
224 }
225
226 override @property Window[] windows()
227 {
228 return _windows;
229 }
230
231 override Window createWindow(in string title)
232 {
233 return new XcbWindow(this, _instance, title, false);
234 }
235
236 override void pollAndDispatch()
237 {
238 while (true) {
239 auto e = xcb_poll_for_event(_conn);
240 if (!e) break;
241 handleEvent(e);
242 }
243 }
244
245 private XcbWindow xcbWindow(xcb_window_t win) {
246 foreach(w; _xcbWindows) {
247 if (w._win == win) return w;
248 }
249 return null;
250 }
251
252 void registerWindow(XcbWindow window) {
253 _windows ~= window;
254 _xcbWindows ~= window;
255 }
256
257 void unregisterWindow(XcbWindow window) {
258 import std.algorithm : remove;
259 _windows = _windows.remove!(w => w is window);
260 _xcbWindows = _xcbWindows.remove!(w => w is window);
261 }
262
263 private @property int mainScreenNum()
264 {
265 return _mainScreenNum;
266 }
267
268 private @property xcb_screen_t* mainScreen()
269 {
270 return _screens[_mainScreenNum];
271 }
272
273 private void handleEvent(xcb_generic_event_t* e)
274 {
275 immutable xcbType = xcbEventType(e);
276
277 switch (xcbType)
278 {
279 case XCB_KEY_PRESS:
280 auto ev = cast(xcb_key_press_event_t*)e;
281 auto xcbWin = xcbWindow(ev.event);
282 _xkb.processKeyDown(ev.detail, xcbWin ? xcbWin._onKeyOnHandler : null);
283 break;
284 case XCB_KEY_RELEASE:
285 auto ev = cast(xcb_key_press_event_t*)e;
286 auto xcbWin = xcbWindow(ev.event);
287 _xkb.processKeyUp(ev.detail, xcbWin ? xcbWin._onKeyOffHandler : null);
288 break;
289 case XCB_BUTTON_PRESS:
290 auto ev = cast(xcb_button_press_event_t*)e;
291 auto xcbWin = xcbWindow(ev.event);
292 if (xcbWin && xcbWin._onHandler)
293 xcbWin._onHandler(MouseEvent(ev.event_x, ev.event_y, _xkb.mods));
294 break;
295 case XCB_BUTTON_RELEASE:
296 auto ev = cast(xcb_button_press_event_t*)e;
297 auto xcbWin = xcbWindow(ev.event);
298 if (xcbWin && xcbWin._offHandler)
299 xcbWin._offHandler(MouseEvent(ev.event_x, ev.event_y, _xkb.mods));
300 break;
301 case XCB_MOTION_NOTIFY:
302 auto ev = cast(xcb_motion_notify_event_t*)e;
303 auto xcbWin = xcbWindow(ev.event);
304 if (xcbWin && xcbWin._moveHandler)
305 xcbWin._moveHandler(MouseEvent(ev.event_x, ev.event_y, _xkb.mods));
306 break;
307 case XCB_CONFIGURE_NOTIFY:
308 auto ev = cast(xcb_configure_notify_event_t*)e;
309 auto xcbWin = xcbWindow(ev.event);
310 if (xcbWin) xcbWin.handleConfigureNotify(ev);
311 break;
312 case XCB_PROPERTY_NOTIFY:
313 // auto ev = cast(xcb_configure_notify_event_t*)e;
314 // auto xcbWin = xcbWindow(ev.window);
315 break;
316 case XCB_CLIENT_MESSAGE:
317 auto ev = cast(xcb_client_message_event_t*)e;
318 if (ev.data.data32[0] == atom(Atom.WM_DELETE_WINDOW)) {
319 auto win = xcbWindow(ev.window);
320 if (win._onCloseHandler) {
321 win._closeFlag = win._onCloseHandler();
322 }
323 else {
324 win._closeFlag = true;
325 }
326 }
327 break;
328 default:
329 if (xcbType == _xkbFirstEv)
330 {
331 auto genKbd = cast(XkbGenericEvent*)e;
332 if (genKbd.common.deviceID == _xkb.device)
333 {
334 switch (genKbd.common.xkbType)
335 {
336 case XCB_XKB_STATE_NOTIFY:
337 auto es = &genKbd.state;
338 _xkb.updateState(
339 es.baseMods, es.latchedMods, es.lockedMods,
340 es.baseGroup, es.latchedGroup, es.lockedGroup
341 );
342 break;
343 default:
344 break;
345 }
346 }
347 }
348 break;
349 }
350 }
351
352 private xcb_atom_t atom(Atom atom) const
353 {
354 auto at = (atom in _atoms);
355 if (at)
356 return *at;
357 return XCB_ATOM_NONE;
358 }
359 }
360
361 private union XkbGenericEvent
362 {
363
364 struct CommonFields
365 {
366 ubyte response_type;
367 ubyte xkbType;
368 ushort sequence;
369 xcb_timestamp_t time;
370 ubyte deviceID;
371 }
372
373 CommonFields common;
374 xcb_xkb_new_keyboard_notify_event_t newKbd;
375 xcb_xkb_map_notify_event_t map;
376 xcb_xkb_state_notify_event_t state;
377 }
378
379
380 private class XcbKeyboard : XKeyboard
381 {
382 private uint _device;
383
384 this(xcb_connection_t *connection, out uint xkbFirstEv)
385 {
386 import core.stdc.stdlib : free;
387 import std.exception : enforce;
388 import xkbcommon.x11;
389 import xkbcommon.xkbcommon;
390
391 xcb_prefetch_extension_data(connection, &xcb_xkb_id);
392
393 auto reply = xcb_get_extension_data(connection, &xcb_xkb_id);
394 if (!reply || !reply.present) {
395 throw new Exception("XKB extension not supported by X server");
396 }
397 xkbFirstEv = reply.first_event;
398
399 auto cookie = xcb_xkb_use_extension(connection,
400 XKB_X11_MIN_MAJOR_XKB_VERSION,
401 XKB_X11_MIN_MINOR_XKB_VERSION);
402 auto xkbReply = xcb_xkb_use_extension_reply(connection, cookie, null);
403 if (!xkbReply) {
404 throw new Exception("could not get xkb extension");
405 }
406 else if(!xkbReply.supported) {
407 free(xkbReply);
408 throw new Exception("xkb required version not supported");
409 }
410 free(xkbReply);
411
412 ushort mapParts =
413 XCB_XKB_MAP_PART_KEY_TYPES |
414 XCB_XKB_MAP_PART_KEY_SYMS |
415 XCB_XKB_MAP_PART_MODIFIER_MAP |
416 XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS |
417 XCB_XKB_MAP_PART_KEY_ACTIONS |
418 XCB_XKB_MAP_PART_KEY_BEHAVIORS |
419 XCB_XKB_MAP_PART_VIRTUAL_MODS |
420 XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP;
421
422 ushort events =
423 XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY |
424 XCB_XKB_EVENT_TYPE_MAP_NOTIFY |
425 XCB_XKB_EVENT_TYPE_STATE_NOTIFY;
426
427 auto cookie2 = xcb_xkb_select_events_checked(
428 connection, XCB_XKB_ID_USE_CORE_KBD,
429 events, 0, events, mapParts, mapParts, null);
430 auto err = xcb_request_check(connection, cookie2);
431 if (err) {
432 throw new Exception("failed to select notify events from xcb xkb");
433 }
434
435 auto ctx = enforce(
436 xkb_context_new(XKB_CONTEXT_NO_FLAGS), "Could not alloc XKB context"
437 );
438 scope(failure) xkb_context_unref(ctx);
439
440 _device = xkb_x11_get_core_keyboard_device_id(connection);
441 enforce (_device != -1, "Could not get X11 keyboard device id");
442
443 auto keymap = enforce(
444 xkb_x11_keymap_new_from_device(ctx, connection, _device,
445 XKB_KEYMAP_COMPILE_NO_FLAGS),
446 "Could not get X11 Keymap");
447 scope(failure) xkb_keymap_unref(keymap);
448
449 auto state = enforce(
450 xkb_x11_state_new_from_device(keymap, connection, _device),
451 "Could not alloc X11 XKB state"
452 );
453
454 super(ctx, keymap, state);
455 }
456
457 @property uint device()
458 {
459 return _device;
460 }
461 }
462
463 class XcbWindow : Window
464 {
465 import gfx.graal.presentation : Surface;
466
467 private XcbDisplay _dpy;
468 private Instance _instance;
469 private xcb_window_t _win;
470 private Surface _surface;
471 private string _title;
472 private uint _width;
473 private uint _height;
474 private ResizeHandler _resizeHandler;
475 private MouseHandler _moveHandler;
476 private MouseHandler _onHandler;
477 private MouseHandler _offHandler;
478 private KeyHandler _onKeyOnHandler;
479 private KeyHandler _onKeyOffHandler;
480 private CloseHandler _onCloseHandler;
481 private bool _closeFlag;
482 private bool _dummy;
483
484 this(XcbDisplay dpy, Instance instance, in string title, bool dummy)
485 {
486 assert(dpy && (dummy || instance));
487 _dpy = dpy;
488 _instance = instance;
489 _title = title;
490 _dummy = dummy;
491 }
492
493 override @property string title()
494 {
495 return _title;
496 }
497
498 override void setTitle(in string title)
499 {
500 import std.string : toStringz;
501
502 if (_win) {
503 xcb_change_property(_dpy._conn, cast(ubyte) XCB_PROP_MODE_REPLACE, _win,
504 cast(xcb_atom_t) XCB_ATOM_WM_NAME, cast(xcb_atom_t) XCB_ATOM_STRING,
505 8, cast(uint) title.length, toStringz(title));
506 xcb_change_property(_dpy._conn, cast(ubyte) XCB_PROP_MODE_REPLACE, _win,
507 cast(xcb_atom_t) XCB_ATOM_WM_ICON_NAME, cast(xcb_atom_t) XCB_ATOM_STRING,
508 8, cast(uint) title.length, toStringz(title));
509 }
510 _title = title;
511 }
512
513 override @property void onResize(ResizeHandler handler) {
514 _resizeHandler = handler;
515 }
516 override @property void onMouseMove(MouseHandler handler) {
517 _moveHandler = handler;
518 }
519 override @property void onMouseOn(MouseHandler handler) {
520 _onHandler = handler;
521 }
522 override @property void onMouseOff(MouseHandler handler) {
523 _offHandler = handler;
524 }
525 override @property void onKeyOn(KeyHandler handler) {
526 _onKeyOnHandler = handler;
527 }
528 override @property void onKeyOff(KeyHandler handler) {
529 _onKeyOffHandler = handler;
530 }
531 override @property void onClose(CloseHandler handler) {
532 _onCloseHandler = handler;
533 }
534
535 override @property Surface surface() {
536 return _surface;
537 }
538
539 override @property bool closeFlag() const {
540 return _closeFlag;
541 }
542
543 override @property void closeFlag(in bool flag) {
544 _closeFlag = flag;
545 }
546
547 override void show(uint width, uint height)
548 {
549 const screen = _dpy.mainScreen;
550
551 const cmap = xcb_generate_id(_dpy._conn);
552 _win = xcb_generate_id(_dpy._conn);
553
554 auto visual = drawArgbVisual(screen);
555 const depth = drawVisualDepth(screen, visual.visual_id);
556
557 xcb_create_colormap(_dpy._conn, XCB_COLORMAP_ALLOC_NONE, cmap, screen.root, visual.visual_id);
558
559 immutable mask = XCB_CW_BACK_PIXMAP | XCB_CW_BORDER_PIXEL | XCB_CW_COLORMAP;
560 const uint[] values = [XCB_BACK_PIXMAP_NONE, 0, cmap, 0];
561
562 auto cookie = xcb_create_window_checked(_dpy._conn, depth,
563 _win, screen.root, 50, 50, cast(ushort) width, cast(ushort) height, 0,
564 XCB_WINDOW_CLASS_INPUT_OUTPUT, visual.visual_id, mask, &values[0]);
565
566 auto err = xcb_request_check(_dpy._conn, cookie);
567 if (err) {
568 import std.format : format;
569 throw new Exception(format("GFX-XCB: could not create window: %s", err.error_code));
570 }
571
572 _width = width;
573 _height = height;
574
575 if (_dummy) {
576 xcb_flush(_dpy._conn);
577 return;
578 }
579
580 // register regular events
581 {
582 const uint[] attrs = [
583 XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS
584 | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_ENTER_WINDOW
585 | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_POINTER_MOTION
586 | XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_EXPOSURE
587 | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE, 0
588 ];
589 xcb_change_window_attributes(_dpy._conn, _win,
590 XCB_CW_EVENT_MASK, &attrs[0]);
591 }
592 // register window close event
593 {
594 const xcb_atom_t[] props = [atom(Atom.WM_DELETE_WINDOW), 0];
595 xcb_change_property(_dpy._conn, XCB_PROP_MODE_REPLACE, _win,
596 atom(Atom.WM_PROTOCOLS), XCB_ATOM_ATOM, 32, 1, &props[0]);
597 }
598 // setting title
599 setTitle(_title);
600
601 _dpy.registerWindow(this);
602
603 xcb_map_window(_dpy._conn, _win);
604 xcb_flush(_dpy._conn);
605
606 import gfx.graal : Backend;
607 final switch (_instance.backend) {
608 case Backend.vulkan:
609 import gfx.vulkan.wsi : createVulkanXcbSurface;
610 _surface = createVulkanXcbSurface(_instance, _dpy._conn, _win);
611 break;
612 case Backend.gl3:
613 version (GfxGl3)
614 {
615 import gfx.gl3 : GlInstance;
616 import gfx.gl3.swapchain : GlSurface;
617
618 _surface = new GlSurface(_win);
619 auto glInst = cast(GlInstance)_instance;
620 auto ctx = glInst.ctx;
621 ctx.makeCurrent(_win);
622 break;
623 }
624 else
625 {
626 assert(false, "OpenGL3 support is disabled");
627 }
628 }
629 }
630
631 override void close()
632 {
633 if (_dummy) {
634 if (_win) {
635 xcb_destroy_window(_dpy._conn, _win);
636 _win = 0;
637 }
638 }
639 else {
640 if (_win) {
641 xcb_unmap_window(_dpy._conn, _win);
642 xcb_destroy_window(_dpy._conn, _win);
643 xcb_flush(_dpy._conn);
644 _win = 0;
645 }
646 _dpy.unregisterWindow(this);
647 }
648 }
649
650 private xcb_atom_t atom(Atom atom) const
651 {
652 return _dpy.atom(atom);
653 }
654
655 private void handleConfigureNotify(xcb_configure_notify_event_t* e)
656 {
657 if (!_resizeHandler) return;
658 if (e.width != _width || e.height != _height) {
659 _width = e.width;
660 _height = e.height;
661 _resizeHandler(_width, _height);
662 }
663 }
664 }
665
666 xcb_visualtype_t *drawArgbVisual(const xcb_screen_t *s)
667 {
668 xcb_depth_iterator_t depth_iter = xcb_screen_allowed_depths_iterator(s);
669
670 if(depth_iter.data) {
671 for(; depth_iter.rem; xcb_depth_next (&depth_iter)) {
672 if(depth_iter.data.depth == 32) {
673 for(xcb_visualtype_iterator_t visual_iter = xcb_depth_visuals_iterator(depth_iter.data);
674 visual_iter.rem; xcb_visualtype_next (&visual_iter))
675 {
676 if (visual_iter.data && visual_iter.data.class_ == XCB_VISUAL_CLASS_TRUE_COLOR)
677 return visual_iter.data;
678 }
679 }
680 }
681 }
682
683 throw new Exception("could not find a draw visual");
684 }
685
686 ubyte drawVisualDepth(const xcb_screen_t *s, xcb_visualid_t vis)
687 {
688 xcb_depth_iterator_t depth_iter = xcb_screen_allowed_depths_iterator(s);
689
690 if(depth_iter.data) {
691 for(; depth_iter.rem; xcb_depth_next (&depth_iter)) {
692 for(xcb_visualtype_iterator_t visual_iter = xcb_depth_visuals_iterator(depth_iter.data);
693 visual_iter.rem; xcb_visualtype_next (&visual_iter))
694 {
695 if(vis == visual_iter.data.visual_id)
696 return depth_iter.data.depth;
697 }
698 }
699 }
700 throw new Exception("could not find a visuals depth");
701 }