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 }