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