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 }