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