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 }