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 xcb.xcb; 9 10 enum gfxXcbLogMask = 0x0800_0000; 11 package immutable gfxXcbLog = LogTag("GFX-XCB"); 12 13 /// List of X atoms that are fetched automatically 14 enum Atom 15 { 16 UTF8_STRING, 17 18 WM_PROTOCOLS, 19 WM_DELETE_WINDOW, 20 WM_TRANSIENT_FOR, 21 WM_CHANGE_STATE, 22 WM_STATE, 23 _NET_WM_STATE, 24 _NET_WM_STATE_MODAL, 25 _NET_WM_STATE_STICKY, 26 _NET_WM_STATE_MAXIMIZED_VERT, 27 _NET_WM_STATE_MAXIMIZED_HORZ, 28 _NET_WM_STATE_SHADED, 29 _NET_WM_STATE_SKIP_TASKBAR, 30 _NET_WM_STATE_SKIP_PAGER, 31 _NET_WM_STATE_HIDDEN, 32 _NET_WM_STATE_FULLSCREEN, 33 _NET_WM_STATE_ABOVE, 34 _NET_WM_STATE_BELOW, 35 _NET_WM_STATE_DEMANDS_ATTENTION, 36 _NET_WM_STATE_FOCUSED, 37 _NET_WM_NAME, 38 } 39 40 /// get the response_type field masked for 41 @property ubyte xcbEventType(EvT)(EvT* e) 42 { 43 return (e.response_type & ~0x80); 44 } 45 46 class XcbDisplay : Display 47 { 48 import gfx.core.rc : atomicRcCode, Rc; 49 import gfx.graal : Backend; 50 import X11.Xlib : XDisplay = Display; 51 52 mixin(atomicRcCode); 53 54 private XDisplay *_dpy; 55 private xcb_connection_t* _conn; 56 private xcb_atom_t[Atom] _atoms; 57 private int _mainScreenNum; 58 private xcb_screen_t*[] _screens; 59 60 private Rc!Instance _instance; 61 62 private Window[] _windows; 63 private XcbWindow[] _xcbWindows; 64 65 this(in Backend[] loadOrder) 66 { 67 import std.exception : enforce; 68 import X11.Xlib : XCloseDisplay, XDefaultScreen, XOpenDisplay; 69 import X11.Xlib_xcb : XGetXCBConnection, XSetEventQueueOwner, XCBOwnsEventQueue; 70 71 gfxXcbWndLog.trace("opening X display"); 72 _dpy = enforce(XOpenDisplay(null)); 73 scope(failure) { 74 XCloseDisplay(_dpy); 75 } 76 _conn = enforce(XGetXCBConnection(_dpy)); 77 XSetEventQueueOwner(_dpy, XCBOwnsEventQueue); 78 _mainScreenNum = XDefaultScreen(_dpy); 79 80 initializeAtoms(); 81 initializeScreens(); 82 initializeInstance(loadOrder); 83 } 84 85 override void dispose() 86 { 87 import X11.Xlib : XCloseDisplay; 88 89 if (_windows.length) { 90 auto ws = _windows.dup; 91 foreach (w; ws) w.close(); 92 } 93 assert(!_windows.length); 94 95 _instance.unload(); 96 xXcbWndLog.trace("closing X display"); 97 XCloseDisplay(_dpy); 98 } 99 100 private void initializeAtoms() 101 { 102 import core.stdc.stdlib : free; 103 import std.conv : to; 104 import std.string : toStringz; 105 import std.traits : EnumMembers; 106 107 xcb_intern_atom_cookie_t[] cookies; 108 cookies.reserve(EnumMembers!Atom.length); 109 110 foreach (immutable atom; EnumMembers!Atom) // static foreach 111 { 112 auto name = atom.to!string; 113 cookies ~= xcb_intern_atom(_conn, 1, 114 cast(ushort)name.length, toStringz(name)); 115 } 116 117 foreach (i, immutable atom; EnumMembers!Atom) // static foreach 118 { 119 immutable name = atom.to!string; 120 xcb_generic_error_t* err; 121 auto reply = xcb_intern_atom_reply(_conn, cookies[i], &err); 122 if (err) 123 { 124 throw new Exception("failed initializing atom " ~ name ~ ": ", 125 (*err).to!string); 126 } 127 if (reply.atom == XCB_ATOM_NONE) 128 { 129 throw new Exception("could not retrieve atom " ~ name); 130 } 131 _atoms[atom] = reply.atom; 132 free(reply); 133 } 134 } 135 136 private void initializeScreens() 137 { 138 xcb_screen_iterator_t iter; 139 for (iter = xcb_setup_roots_iterator(xcb_get_setup(_conn)); iter.rem; 140 xcb_screen_next(&iter)) 141 { 142 _screens ~= iter.data; 143 } 144 } 145 146 private void initializeInstance(in Backend[] loadOrder) 147 { 148 assert(!_instance); 149 150 foreach (b; loadOrder) { 151 final switch (b) { 152 case Backend.vulkan: 153 try { 154 gfxXcbWndLog.trace("Attempting to instantiate Vulkan"); 155 import gfx.vulkan : createVulkanInstance, vulkanInit; 156 vulkanInit(); 157 _instance = createVulkanInstance(); 158 gfxXcbWndLog.info("Creating a Vulkan instance"); 159 } 160 catch (Exception ex) { 161 warningf("Vulkan is not available. %s", ex.msg); 162 } 163 break; 164 case Backend.gl3: 165 try { 166 import gfx.core.rc : makeRc; 167 import gfx.gl3 : GlInstance; 168 import gfx.gl3.context : GlAttribs; 169 import gfx.window.xcb.context : XcbGlContext; 170 gfxXcbWndLog.trace("Attempting to instantiate OpenGL"); 171 auto w = new XcbWindow(this, null, true); 172 w.show(10, 10); 173 scope(exit) w.close(); 174 auto ctx = makeRc!XcbGlContext(_dpy, _mainScreenNum, GlAttribs.init, w._win); 175 gfxXcbWndLog.trace("Creating an OpenGL instance"); 176 _instance = new GlInstance(ctx); 177 } 178 catch (Exception ex) { 179 gfxXcbWndLog.warningf("OpenGL is not available. %s", ex.msg); 180 } 181 break; 182 } 183 if (_instance) break; 184 } 185 186 if (!_instance) { 187 throw new Exception("Could not instantiate a backend"); 188 } 189 } 190 191 override @property Instance instance() { 192 return _instance; 193 } 194 195 override @property Window[] windows() 196 { 197 return _windows; 198 } 199 200 override Window createWindow() 201 { 202 return new XcbWindow(this, _instance, false); 203 } 204 205 override void pollAndDispatch() 206 { 207 while (true) { 208 auto e = xcb_poll_for_event(_conn); 209 if (!e) break; 210 handleEvent(e); 211 } 212 } 213 214 private XcbWindow xcbWindow(xcb_window_t win) { 215 foreach(w; _xcbWindows) { 216 if (w._win == win) return w; 217 } 218 return null; 219 } 220 221 void registerWindow(XcbWindow window) { 222 _windows ~= window; 223 _xcbWindows ~= window; 224 } 225 226 void unregisterWindow(XcbWindow window) { 227 import std.algorithm : remove; 228 _windows = _windows.remove!(w => w is window); 229 _xcbWindows = _xcbWindows.remove!(w => w is window); 230 } 231 232 private @property int mainScreenNum() 233 { 234 return _mainScreenNum; 235 } 236 237 private @property xcb_screen_t* mainScreen() 238 { 239 return _screens[_mainScreenNum]; 240 } 241 242 private void handleEvent(xcb_generic_event_t* e) 243 { 244 immutable xcbType = xcbEventType(e); 245 246 switch (xcbType) 247 { 248 case XCB_KEY_PRESS: 249 auto ev = cast(xcb_key_press_event_t*)e; 250 auto xcbWin = xcbWindow(ev.event); 251 if (xcbWin && xcbWin._onKeyOnHandler) 252 xcbWin._onKeyOnHandler(ev.detail); 253 break; 254 case XCB_KEY_RELEASE: 255 auto ev = cast(xcb_key_press_event_t*)e; 256 auto xcbWin = xcbWindow(ev.event); 257 if (xcbWin && xcbWin._onKeyOffHandler) 258 xcbWin._onKeyOffHandler(ev.detail); 259 break; 260 case XCB_BUTTON_PRESS: 261 auto ev = cast(xcb_button_press_event_t*)e; 262 auto xcbWin = xcbWindow(ev.event); 263 if (xcbWin && xcbWin._onHandler) 264 xcbWin._onHandler(ev.event_x, ev.event_y); 265 break; 266 case XCB_BUTTON_RELEASE: 267 auto ev = cast(xcb_button_press_event_t*)e; 268 auto xcbWin = xcbWindow(ev.event); 269 if (xcbWin && xcbWin._offHandler) 270 xcbWin._offHandler(ev.event_x, ev.event_y); 271 break; 272 case XCB_MOTION_NOTIFY: 273 auto ev = cast(xcb_motion_notify_event_t*)e; 274 auto xcbWin = xcbWindow(ev.event); 275 if (xcbWin && xcbWin._moveHandler) 276 xcbWin._moveHandler(ev.event_x, ev.event_y); 277 break; 278 case XCB_CONFIGURE_NOTIFY: 279 break; 280 case XCB_PROPERTY_NOTIFY: 281 break; 282 case XCB_CLIENT_MESSAGE: 283 auto ev = cast(xcb_client_message_event_t*)e; 284 if (ev.data.data32[0] == atom(Atom.WM_DELETE_WINDOW)) { 285 auto win = xcbWindow(ev.window); 286 if (win._onCloseHandler) { 287 win._closeFlag = win._onCloseHandler(); 288 } 289 else { 290 win._closeFlag = true; 291 } 292 } 293 break; 294 default: 295 break; 296 } 297 } 298 299 private xcb_atom_t atom(Atom atom) const 300 { 301 auto at = (atom in _atoms); 302 if (at) 303 return *at; 304 return XCB_ATOM_NONE; 305 } 306 } 307 308 class XcbWindow : Window 309 { 310 import gfx.graal.presentation : Surface; 311 312 private XcbDisplay _dpy; 313 private Instance _instance; 314 private xcb_window_t _win; 315 private Surface _surface; 316 private MouseHandler _moveHandler; 317 private MouseHandler _onHandler; 318 private MouseHandler _offHandler; 319 private KeyHandler _onKeyOnHandler; 320 private KeyHandler _onKeyOffHandler; 321 private CloseHandler _onCloseHandler; 322 private bool _closeFlag; 323 private bool _dummy; 324 325 this(XcbDisplay dpy, Instance instance, bool dummy) 326 { 327 assert(dpy && (dummy || instance)); 328 _dpy = dpy; 329 _instance = instance; 330 _dummy = dummy; 331 } 332 333 override @property void onMouseMove(MouseHandler handler) { 334 _moveHandler = handler; 335 } 336 override @property void onMouseOn(MouseHandler handler) { 337 _onHandler = handler; 338 } 339 override @property void onMouseOff(MouseHandler handler) { 340 _offHandler = handler; 341 } 342 override @property void onKeyOn(KeyHandler handler) { 343 _onKeyOnHandler = handler; 344 } 345 override @property void onKeyOff(KeyHandler handler) { 346 _onKeyOffHandler = handler; 347 } 348 override @property void onClose(CloseHandler handler) { 349 _onCloseHandler = handler; 350 } 351 352 override @property Surface surface() { 353 return _surface; 354 } 355 356 override @property bool closeFlag() const { 357 return _closeFlag; 358 } 359 360 override @property void closeFlag(in bool flag) { 361 _closeFlag = flag; 362 } 363 364 override void show(uint width, uint height) 365 { 366 const screen = _dpy.mainScreen; 367 368 const cmap = xcb_generate_id(_dpy._conn); 369 _win = xcb_generate_id(_dpy._conn); 370 371 auto visual = drawArgbVisual(screen); 372 const depth = drawVisualDepth(screen, visual.visual_id); 373 374 xcb_create_colormap(_dpy._conn, XCB_COLORMAP_ALLOC_NONE, cmap, screen.root, visual.visual_id); 375 376 immutable mask = XCB_CW_BACK_PIXMAP | XCB_CW_BORDER_PIXEL | XCB_CW_COLORMAP; 377 const uint[] values = [XCB_BACK_PIXMAP_NONE, 0, cmap, 0]; 378 379 auto cookie = xcb_create_window_checked(_dpy._conn, depth, 380 _win, screen.root, 50, 50, cast(ushort) width, cast(ushort) height, 0, 381 XCB_WINDOW_CLASS_INPUT_OUTPUT, visual.visual_id, mask, &values[0]); 382 383 auto err = xcb_request_check(_dpy._conn, cookie); 384 if (err) { 385 import std.format : format; 386 throw new Exception(format("GFX-XCB: could not create window: %s", err.error_code)); 387 } 388 389 // register regular events 390 { 391 const uint[] attrs = [ 392 XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS 393 | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_ENTER_WINDOW 394 | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_POINTER_MOTION 395 | XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_EXPOSURE 396 | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE, 0 397 ]; 398 xcb_change_window_attributes(_dpy._conn, _win, 399 XCB_CW_EVENT_MASK, &attrs[0]); 400 } 401 // register window close event 402 { 403 const xcb_atom_t[] props = [atom(Atom.WM_DELETE_WINDOW), 0]; 404 xcb_change_property(_dpy._conn, XCB_PROP_MODE_REPLACE, _win, 405 atom(Atom.WM_PROTOCOLS), XCB_ATOM_ATOM, 32, 1, &props[0]); 406 } 407 408 if (_dummy) { 409 xcb_flush(_dpy._conn); 410 return; 411 } 412 413 _dpy.registerWindow(this); 414 415 xcb_map_window(_dpy._conn, _win); 416 xcb_flush(_dpy._conn); 417 418 import gfx.graal : Backend; 419 final switch (_instance.backend) { 420 case Backend.vulkan: 421 import gfx.vulkan.wsi : createVulkanXcbSurface; 422 _surface = createVulkanXcbSurface(_instance, _dpy._conn, _win); 423 break; 424 case Backend.gl3: 425 import gfx.gl3 : GlInstance; 426 import gfx.gl3.swapchain : GlSurface; 427 _surface = new GlSurface(_win); 428 auto glInst = cast(GlInstance)_instance; 429 auto ctx = glInst.ctx; 430 ctx.makeCurrent(_win); 431 break; 432 } 433 } 434 435 override void close() 436 { 437 if (_dummy) { 438 if (_win) { 439 xcb_destroy_window(_dpy._conn, _win); 440 _win = 0; 441 } 442 } 443 else { 444 if (_win) { 445 xcb_unmap_window(_dpy._conn, _win); 446 xcb_destroy_window(_dpy._conn, _win); 447 xcb_flush(_dpy._conn); 448 _win = 0; 449 } 450 _dpy.unregisterWindow(this); 451 } 452 } 453 454 private xcb_atom_t atom(Atom atom) const 455 { 456 return _dpy.atom(atom); 457 } 458 } 459 460 xcb_visualtype_t *drawArgbVisual(const xcb_screen_t *s) 461 { 462 xcb_depth_iterator_t depth_iter = xcb_screen_allowed_depths_iterator(s); 463 464 if(depth_iter.data) { 465 for(; depth_iter.rem; xcb_depth_next (&depth_iter)) { 466 if(depth_iter.data.depth == 32) { 467 for(xcb_visualtype_iterator_t visual_iter = xcb_depth_visuals_iterator(depth_iter.data); 468 visual_iter.rem; xcb_visualtype_next (&visual_iter)) 469 { 470 if (visual_iter.data && visual_iter.data.class_ == XCB_VISUAL_CLASS_TRUE_COLOR) 471 return visual_iter.data; 472 } 473 } 474 } 475 } 476 477 throw new Exception("could not find a draw visual"); 478 } 479 480 ubyte drawVisualDepth(const xcb_screen_t *s, xcb_visualid_t vis) 481 { 482 xcb_depth_iterator_t depth_iter = xcb_screen_allowed_depths_iterator(s); 483 484 if(depth_iter.data) { 485 for(; depth_iter.rem; xcb_depth_next (&depth_iter)) { 486 for(xcb_visualtype_iterator_t visual_iter = xcb_depth_visuals_iterator(depth_iter.data); 487 visual_iter.rem; xcb_visualtype_next (&visual_iter)) 488 { 489 if(vis == visual_iter.data.visual_id) 490 return depth_iter.data.depth; 491 } 492 } 493 } 494 throw new Exception("could not find a visuals depth"); 495 }