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