1 module backend.x11;
2 
3 import backend;
4 import compositor;
5 import output;
6 
7 import wayland.server;
8 import wayland.util.shm_helper;
9 import linux.input;
10 import xcb.xcb;
11 import xcb.xkb;
12 import xcb.shm;
13 import X11.Xlib;
14 import X11.Xlib_xcb;
15 import xkbcommon.xkbcommon;
16 import xkbcommon.x11;
17 
18 import std.exception;
19 import std.stdio;
20 import core.stdc.stdlib;
21 import core.sys.posix.sys.mman;
22 import core.sys.posix.unistd;
23 
24 /// X11 backend implementation
25 final class X11Backend : Backend
26 {
27     private {
28         BackendConfig config;
29         Compositor comp;
30 
31         WlEventLoop loop;
32         WlFdEventSource xcbSource;
33 
34         Display* dpy;
35         xcb_connection_t* conn;
36         Atoms atoms;
37 
38         X11Output[] _outputs;
39     }
40 
41 
42     override @property string name()
43     {
44         return "x11";
45     }
46 
47     override void initialize(BackendConfig config, Compositor comp)
48     {
49         this.config = config;
50         this.comp = comp;
51         loop = comp.display.eventLoop;
52 
53         dpy = XOpenDisplay(null);
54         if (dpy is null) throw new Exception("can't open X11 display");
55 
56         scope(failure) XCloseDisplay(dpy);
57 
58         conn = XGetXCBConnection(dpy);
59         XSetEventQueueOwner(dpy, XCBOwnsEventQueue);
60 
61         if (xcb_connection_has_error(conn))
62         {
63             throw new Exception("XCB connection has error");
64         }
65 
66         atoms = new Atoms(conn);
67 
68         xcbSource = loop.addFd(
69             xcb_get_file_descriptor(conn),
70             WL_EVENT_READABLE,
71             &handleEvent
72         );
73         xcbSource.check();
74     }
75 
76     override Output createOutput()
77     {
78         auto res = new X11Output(this);
79         res.initShm();
80         _outputs ~= res;
81         return res;
82     }
83 
84     override void terminate()
85     {
86         xcbSource.remove();
87         XCloseDisplay(dpy);
88     }
89 
90 private:
91 
92 
93     @property xcb_screen_t* defaultScreen()
94     {
95         int num = XDefaultScreen(dpy);
96 
97         auto iter = xcb_setup_roots_iterator(xcb_get_setup(conn));
98         while (num && iter.rem)
99         {
100             xcb_screen_next(&iter);
101             --num;
102         }
103         return iter.data;
104     }
105 
106     int handleEvent(int, uint mask)
107     {
108         int count;
109         while(1)
110         {
111             auto ev = (mask & WL_EVENT_READABLE) ?
112                 xcb_poll_for_event(conn) :
113                 xcb_poll_for_queued_event(conn);
114             if (!ev) break;
115 
116             auto respType = ev.response_type & ~0x80;
117             switch(respType)
118             {
119             case XCB_BUTTON_PRESS:
120             case XCB_BUTTON_RELEASE:
121                 deliverButtonEvent(cast(xcb_button_press_event_t*)ev);
122                 break;
123             case XCB_CLIENT_MESSAGE:
124                 auto clEv = cast(xcb_client_message_event_t*)ev;
125                 auto atom = clEv.data.data32[0];
126                 auto win = clEv.window;
127                 if (atom == atoms.wm_delete_window)
128                 {
129                     loop.addIdle({
130                         foreach(op; _outputs)
131                         {
132                             if (op._win == win)
133                             {
134                                 destroyOutput(op);
135                                 break;
136                             }
137                         }
138                     });
139                 }
140                 break;
141             default:
142                 break;
143             }
144 
145             ++count;
146         }
147         return count;
148     }
149 
150     void deliverButtonEvent(xcb_button_press_event_t* ev)
151     {
152         uint but;
153         switch (ev.detail) {
154         case 1:
155             but = BTN_LEFT;
156             break;
157         case 2:
158             but = BTN_MIDDLE;
159             break;
160         case 3:
161             but = BTN_RIGHT;
162             break;
163         default:
164             stderr.writeln("X11 backend unknown button code: ", ev.detail);
165             break;
166         }
167 
168         immutable state = ev.response_type == XCB_BUTTON_PRESS ?
169             WlPointer.ButtonState.pressed : WlPointer.ButtonState.released;
170 
171         comp.eventMouseButton(ev.event_x, ev.event_y, but, state);
172     }
173 
174     void destroyOutput(X11Output op)
175     {
176         import std.algorithm : remove;
177         _outputs = _outputs.remove!(o => o is op);
178         op.destroy();
179         if (!_outputs.length) comp.exit();
180     }
181 }
182 
183 private:
184 
185 
186 struct WmNormalHints
187 {
188     enum minSize = 16;
189     enum maxSize = 32;
190 
191     uint flags;
192 	uint[4] pad;
193 	int minWidth, minHeight;
194 	int maxWidth, maxHeight;
195     int widthInc, heightInc;
196     int minAspectX, minAspectY;
197     int maxAspectX, maxAspectY;
198 	int baseWidth, baseHeight;
199 	int winGravity;
200 }
201 
202 
203 xcb_visualtype_t* findVisualById(xcb_screen_t* screen, xcb_visualid_t id)
204 {
205 	xcb_depth_iterator_t i;
206 	xcb_visualtype_iterator_t j;
207 	for (i = xcb_screen_allowed_depths_iterator(screen);
208 	     i.rem;
209 	     xcb_depth_next(&i)) {
210 		for (j = xcb_depth_visuals_iterator(i.data);
211 		     j.rem;
212 		     xcb_visualtype_next(&j)) {
213 			if (j.data.visual_id == id)
214 				return j.data;
215 		}
216 	}
217 	return null;
218 }
219 
220 ubyte getDepthOfVisual(xcb_screen_t* screen, xcb_visualid_t id)
221 {
222 	xcb_depth_iterator_t i;
223 	xcb_visualtype_iterator_t j;
224 	for (i = xcb_screen_allowed_depths_iterator(screen);
225 	     i.rem;
226 	     xcb_depth_next(&i)) {
227 		for (j = xcb_depth_visuals_iterator(i.data);
228 		     j.rem;
229 		     xcb_visualtype_next(&j)) {
230 			if (j.data.visual_id == id)
231 				return i.data.depth;
232 		}
233 	}
234 	return 0;
235 }
236 
237 final class X11Output : Output
238 {
239     private X11Backend _backend;
240     private xcb_connection_t* _conn;
241     private xcb_screen_t* _screen;
242     private Atoms _atoms;
243     private bool _fullscreen;
244     private int _width;
245     private int _height;
246     private int _widthDPI;
247     private int _heightDPI;
248     private xcb_window_t _win;
249 
250     private ubyte _depth;
251     private xcb_format_t* _xcbFmt;
252     private int _shmFd;
253     private xcb_shm_seg_t _shmSeg;
254     private uint[] _buf;
255     private xcb_gcontext_t _gc;
256 
257     this (X11Backend backend)
258     {
259         _backend = backend;
260         _conn = backend.conn;
261         _screen = backend.defaultScreen;
262         _atoms = backend.atoms;
263         _fullscreen = backend.config.fullscreen;
264         _width = backend.config.width;
265         _height = backend.config.height;
266         _widthDPI = cast(int) (25.4 * _screen.width_in_pixels) / _screen.width_in_millimeters;
267         _heightDPI = cast(int) (25.4 * _screen.height_in_pixels) / _screen.height_in_millimeters;
268 
269         super(backend.comp);
270 
271         assert(_fullscreen || _width*_height > 0);
272 
273         immutable uint mask = XCB_CW_EVENT_MASK;
274         uint[2] values = [
275             XCB_EVENT_MASK_EXPOSURE |
276             XCB_EVENT_MASK_STRUCTURE_NOTIFY |
277 			XCB_EVENT_MASK_KEY_PRESS |
278 			XCB_EVENT_MASK_KEY_RELEASE |
279 			XCB_EVENT_MASK_BUTTON_PRESS |
280 			XCB_EVENT_MASK_BUTTON_RELEASE |
281 			XCB_EVENT_MASK_POINTER_MOTION |
282 			XCB_EVENT_MASK_ENTER_WINDOW |
283 			XCB_EVENT_MASK_LEAVE_WINDOW |
284 			XCB_EVENT_MASK_KEYMAP_STATE |
285 			XCB_EVENT_MASK_FOCUS_CHANGE,
286             0
287         ];
288         _win = xcb_generate_id(_conn);
289         xcb_create_window(_conn,
290                 cast(ubyte)XCB_COPY_FROM_PARENT,
291                 _win,
292                 _screen.root,
293                 0, 0,
294                 cast(ushort)_width, cast(ushort)_height,
295                 0,
296                 XCB_WINDOW_CLASS_INPUT_OUTPUT,
297                 _screen.root_visual,
298                 mask, values.ptr);
299 
300         if (_fullscreen)
301         {
302             xcb_change_property(_conn, XCB_PROP_MODE_REPLACE, _win,
303                         _atoms.net_wm_state,
304                         XCB_ATOM_ATOM, 32, 1, &_atoms.net_wm_state_fullscreen);
305         }
306         else
307         {
308             WmNormalHints hints;
309             hints.flags = hints.maxSize | hints.minSize;
310             hints.minWidth = _width;
311             hints.minHeight = _height;
312             hints.maxWidth = _width;
313             hints.maxHeight = _height;
314             xcb_change_property(_conn, XCB_PROP_MODE_REPLACE, _win,
315                         _atoms.wm_normal_hints,
316                         _atoms.wm_size_hints, 32,
317                         hints.sizeof / 4,
318                         cast(ubyte*)&hints);
319         }
320 
321         enum title = "Wayland compositor";
322         xcb_change_property(_conn, XCB_PROP_MODE_REPLACE, _win,
323 				    _atoms.net_wm_name, _atoms.utf8_string, 8,
324 				    title.length, title.ptr);
325 
326 	    xcb_change_property (_conn, XCB_PROP_MODE_REPLACE, _win,
327 		            _atoms.wm_protocols,
328 		            XCB_ATOM_ATOM, 32, 1, &_atoms.wm_delete_window);
329         xcb_map_window(_conn, _win);
330         xcb_flush(_conn);
331     }
332 
333     void initShm()
334     {
335         auto shmExt = xcb_get_extension_data(_conn, &xcb_shm_id);
336         enforce(shmExt && shmExt.present);
337         auto visType = findVisualById(_screen, _screen.root_visual);
338         _depth = getDepthOfVisual(_screen, _screen.root_visual);
339 
340         for (auto fmt = xcb_setup_pixmap_formats_iterator(xcb_get_setup(_conn));
341                 fmt.rem;
342                 xcb_format_next(&fmt)) {
343             if (fmt.data.depth == _depth) {
344                 _xcbFmt = fmt.data;
345                 break;
346             }
347         }
348         enforce(_xcbFmt && _xcbFmt.bits_per_pixel == 32);
349 
350         immutable segSize = _width*_height*4;
351 
352         _shmFd = createMmapableFile(segSize);
353         auto ptr = cast(ubyte*)enforce(mmap(
354             null, segSize, PROT_READ | PROT_WRITE, MAP_SHARED, _shmFd, 0
355         ));
356         _buf = cast(uint[])(ptr[0 .. segSize]);
357         _shmSeg = xcb_generate_id(_conn);
358         auto err = xcb_request_check(_conn, xcb_shm_attach_fd_checked(
359             _conn, _shmSeg, _shmFd, 0
360         ));
361         enforce(!err);
362 
363         _gc = xcb_generate_id(_conn);
364         xcb_create_gc(_conn, _gc, _win, 0, null);
365     }
366 
367     override @property int width()
368     {
369         return _width;
370     }
371 
372     override @property int height()
373     {
374         return _height;
375     }
376 
377     override @property uint[] buf()
378     {
379         return _buf;
380     }
381 
382     override void blitBuf()
383     {
384 	    auto err = xcb_request_check(_conn,
385             xcb_shm_put_image_checked(
386                 _conn, _win, _gc,
387                 cast(ushort)_width, cast(ushort)_height, 0, 0,
388                 cast(ushort)_width, cast(ushort)_height, 0, 0,
389                 _depth, XCB_IMAGE_FORMAT_Z_PIXMAP,
390                 0, _shmSeg, 0
391             )
392         );
393         if (err) {
394             stderr.writeln("error while blitting x11");
395         }
396     }
397 
398     override void destroy()
399     {
400         super.destroy();
401         xcb_unmap_window(_conn, _win);
402         xcb_destroy_window(_conn, _win);
403         xcb_flush(_conn);
404     }
405 }
406 
407 
408 final class Atoms
409 {
410     xcb_atom_t		 wm_protocols;
411     xcb_atom_t		 wm_normal_hints;
412     xcb_atom_t		 wm_size_hints;
413     xcb_atom_t		 wm_delete_window;
414     xcb_atom_t		 wm_class;
415     xcb_atom_t		 net_wm_name;
416     xcb_atom_t		 net_supporting_wm_check;
417     xcb_atom_t		 net_supported;
418     xcb_atom_t		 net_wm_icon;
419     xcb_atom_t		 net_wm_state;
420     xcb_atom_t		 net_wm_state_fullscreen;
421     xcb_atom_t		 str;
422     xcb_atom_t		 utf8_string;
423     xcb_atom_t		 cardinal;
424     xcb_atom_t		 xkb_names;
425 
426     this(xcb_connection_t* conn)
427     {
428         enum numAtoms = 15;
429         struct AtomName
430         {
431             string name;
432             xcb_atom_t* atom;
433         }
434         AtomName[numAtoms] atomNames = [
435             AtomName("WM_PROTOCOLS",             &wm_protocols),
436             AtomName("WM_NORMAL_HINTS",          &wm_normal_hints),
437             AtomName("WM_SIZE_HINTS",            &wm_size_hints),
438             AtomName("WM_DELETE_WINDOW",         &wm_delete_window),
439             AtomName("WM_CLASS", 	             &wm_class),
440             AtomName("_NET_WM_NAME",             &net_wm_name),
441             AtomName("_NET_WM_ICON",             &net_wm_icon),
442             AtomName("_NET_WM_STATE",            &net_wm_state),
443             AtomName("_NET_WM_STATE_FULLSCREEN", &net_wm_state_fullscreen),
444             AtomName("_NET_SUPPORTING_WM_CHECK", &net_supporting_wm_check),
445             AtomName("_NET_SUPPORTED",           &net_supported),
446             AtomName("STRING",                   &str),
447             AtomName("UTF8_STRING",              &utf8_string),
448             AtomName("CARDINAL",                 &cardinal),
449             AtomName("_XKB_RULES_NAMES",         &xkb_names),
450         ];
451 
452         xcb_intern_atom_cookie_t[numAtoms] cookies = void;
453         foreach (i; 0..numAtoms)
454         {
455             cookies[i] = xcb_intern_atom (conn, 0,
456                     cast(ushort)atomNames[i].name.length,
457                     atomNames[i].name.ptr);
458         }
459         foreach (i; 0..numAtoms)
460         {
461             auto rep = xcb_intern_atom_reply(conn, cookies[i], null);
462             *atomNames[i].atom = rep.atom;
463             free(rep);
464         }
465     }
466 }