1 /// wayland window impl 2 module gfx.window.wayland; 3 4 version(linux): 5 6 import gfx.core.log : LogTag; 7 import gfx.graal : Instance; 8 import gfx.graal.presentation; 9 import gfx.vulkan.wsi; 10 import gfx.window; 11 import gfx.window.wayland.zxdg_shell_v6; 12 13 import wayland.client; 14 import wayland.native.util; 15 import wayland.util; 16 17 enum gfxWlLogMask = 0x0800_0000; 18 package immutable gfxWlLog = LogTag("GFX-WL", gfxWlLogMask); 19 20 // FIXME: multithreading 21 22 class WaylandDisplay : Display 23 { 24 import gfx.core.rc : atomicRcCode, Rc; 25 mixin(atomicRcCode); 26 27 private WlDisplay display; 28 private WlCompositor compositor; 29 private WlSeat seat; 30 private WlPointer pointer; 31 private WlKeyboard kbd; 32 33 private WlShell wlShell; 34 private ZxdgShellV6 xdgShell; 35 36 private Rc!Instance _instance; 37 38 private WaylandWindowBase[] wldWindows; 39 private WaylandWindowBase pointedWindow; 40 private WaylandWindowBase focusWindow; 41 private Window[] _windows; 42 43 this() { 44 { 45 // Only vulkan is supported. Let failure throw it. 46 import gfx.vulkan : createVulkanInstance, vulkanInit; 47 vulkanInit(); 48 _instance = createVulkanInstance(); 49 } 50 51 display = WlDisplay.connect(); 52 auto reg = display.getRegistry(); 53 reg.onGlobal = (WlRegistry reg, uint name, string iface, uint ver) { 54 import std.algorithm : min; 55 if(iface == WlCompositor.iface.name) 56 { 57 compositor = cast(WlCompositor)reg.bind( 58 name, WlCompositor.iface, min(ver, 4) 59 ); 60 } 61 else if(iface == WlSeat.iface.name) 62 { 63 seat = cast(WlSeat)reg.bind( 64 name, WlSeat.iface, min(ver, 2) 65 ); 66 seat.onCapabilities = &seatCapChanged; 67 } 68 else if(iface == WlShell.iface.name) 69 { 70 wlShell = cast(WlShell)reg.bind( 71 name, WlShell.iface, min(ver, 1) 72 ); 73 } 74 else if (iface == ZxdgShellV6.iface.name) 75 { 76 xdgShell = cast(ZxdgShellV6)reg.bind( 77 name, ZxdgShellV6.iface, min(ver, 1) 78 ); 79 xdgShell.onPing = (ZxdgShellV6 shell, uint serial) { 80 shell.pong(serial); 81 }; 82 } 83 }; 84 display.roundtrip(); 85 reg.destroy(); 86 } 87 88 override @property Instance instance() { 89 return _instance; 90 } 91 92 override @property Window[] windows() { 93 return _windows; 94 } 95 96 override Window createWindow() { 97 if (xdgShell) { 98 auto w = new XdgWaylandWindow(this, _instance, xdgShell); 99 wldWindows ~= w; 100 _windows ~= w; 101 return w; 102 } 103 else if (wlShell) { 104 auto w = new WaylandWindow(this, _instance, wlShell); 105 wldWindows ~= w; 106 _windows ~= w; 107 return w; 108 } 109 else { 110 return null; 111 } 112 } 113 114 override void pollAndDispatch() { 115 while (display.prepareRead() != 0) { 116 display.dispatchPending(); 117 } 118 display.flush(); 119 display.readEvents(); 120 display.dispatchPending(); 121 } 122 123 124 package void unrefWindow(WaylandWindowBase window) { 125 import std.algorithm : remove; 126 wldWindows = wldWindows.remove!(w => w is window); 127 _windows = _windows.remove!(w => w is window); 128 if (window is pointedWindow) pointedWindow = null; 129 if (window is focusWindow) focusWindow = null; 130 } 131 132 private void seatCapChanged (WlSeat seat, WlSeat.Capability cap) 133 { 134 if ((cap & WlSeat.Capability.pointer) && !pointer) 135 { 136 pointer = seat.getPointer(); 137 pointer.onEnter = &pointerEnter; 138 pointer.onButton = &pointerButton; 139 pointer.onMotion = &pointerMotion; 140 pointer.onLeave = &pointerLeave; 141 } 142 else if (!(cap & WlSeat.Capability.pointer) && pointer) 143 { 144 pointer.destroy(); 145 pointer = null; 146 } 147 148 if ((cap & WlSeat.Capability.keyboard) && !kbd) 149 { 150 kbd = seat.getKeyboard(); 151 kbd.onKey = &kbdKey; 152 } 153 else if (!(cap & WlSeat.Capability.keyboard) && kbd) 154 { 155 kbd.destroy(); 156 kbd = null; 157 } 158 } 159 160 161 private void pointerEnter(WlPointer pointer, uint serial, WlSurface surface, 162 WlFixed surfaceX, WlFixed surfaceY) 163 { 164 foreach (w; wldWindows) { 165 if (w.wlSurface is surface) { 166 pointedWindow = w; 167 w.pointerEnter(surfaceX, surfaceY); 168 break; 169 } 170 } 171 } 172 173 private void pointerButton(WlPointer, uint serial, uint time, uint button, 174 WlPointer.ButtonState state) 175 { 176 if (pointedWindow) { 177 pointedWindow.pointerButton(state); 178 focusWindow = pointedWindow; 179 } 180 } 181 182 private void pointerMotion(WlPointer, uint, WlFixed surfaceX, WlFixed surfaceY) 183 { 184 if (pointedWindow) { 185 pointedWindow.pointerMotion(surfaceX, surfaceY); 186 } 187 } 188 189 private void pointerLeave(WlPointer pointer, uint serial, WlSurface surface) 190 { 191 if (pointedWindow && pointedWindow.wlSurface is surface) { 192 pointedWindow.pointerLeave(); 193 pointedWindow = null; 194 } 195 else { 196 foreach (w; wldWindows) { 197 if (w.wlSurface is surface) { 198 w.pointerLeave(); 199 break; 200 } 201 } 202 } 203 } 204 205 private void kbdKey(WlKeyboard keyboard, uint serial, uint time, uint key, 206 WlKeyboard.KeyState state) 207 { 208 if (focusWindow) { 209 focusWindow.key(key, state); 210 } 211 else if (wldWindows.length) { 212 wldWindows[0].key(key, state); 213 } 214 } 215 216 217 override void dispose() 218 { 219 if (wldWindows.length) { 220 auto ws = wldWindows.dup; 221 foreach (w; ws) w.close(); 222 } 223 assert(!wldWindows.length); 224 assert(!_windows.length); 225 226 if (pointer) { 227 pointer.destroy(); 228 pointer = null; 229 } 230 if (seat) { 231 seat.destroy(); 232 seat = null; 233 } 234 if (wlShell) { 235 wlShell.destroy(); 236 wlShell = null; 237 } 238 if (compositor) { 239 compositor.destroy(); 240 compositor = null; 241 } 242 display.disconnect(); 243 display = null; 244 } 245 } 246 247 private abstract class WaylandWindowBase : Window 248 { 249 this(WaylandDisplay display, Instance instance) 250 { 251 this.dpy = display; 252 this.instance = instance; 253 } 254 255 override void close() { 256 closeShell(); 257 wlSurface.destroy(); 258 wlSurface = null; 259 dpy.unrefWindow(this); 260 } 261 262 abstract protected void prepareShell(WlSurface wlSurf); 263 264 override void show (uint width, uint height) 265 { 266 import std.exception : enforce; 267 268 wlSurface = dpy.compositor.createSurface(); 269 prepareShell(wlSurface); 270 gfxSurface = enforce( 271 createVulkanWaylandSurface(instance, dpy.display, wlSurface), 272 "Could ont create a Vulkan surface" 273 ); 274 wlSurface.commit(); 275 } 276 277 abstract protected void closeShell(); 278 279 override @property void onMouseMove(MouseHandler handler) { 280 moveHandler = handler; 281 } 282 override @property void onMouseOn(MouseHandler handler) { 283 onHandler = handler; 284 } 285 override @property void onMouseOff(MouseHandler handler) { 286 offHandler = handler; 287 } 288 override @property void onKeyOn(KeyHandler handler) { 289 onKeyOnHandler = handler; 290 } 291 override @property void onKeyOff(KeyHandler handler) { 292 onKeyOffHandler = handler; 293 } 294 override @property void onClose(CloseHandler handler) { 295 onCloseHandler = handler; 296 } 297 298 override @property Surface surface() { 299 return gfxSurface; 300 } 301 302 override @property bool closeFlag() const { 303 return _closeFlag; 304 } 305 306 override @property void closeFlag(in bool flag) { 307 _closeFlag = flag; 308 } 309 310 311 private void pointerButton(WlPointer.ButtonState state) { 312 switch (state) { 313 case WlPointer.ButtonState.pressed: 314 if (onHandler) onHandler(cast(int)curX, cast(int)curY); 315 break; 316 case WlPointer.ButtonState.released: 317 if (offHandler) offHandler(cast(int)curX, cast(int)curY); 318 break; 319 default: 320 break; 321 } 322 } 323 324 private void pointerMotion(WlFixed x, WlFixed y) { 325 curX = x; curY = y; 326 if (moveHandler) { 327 moveHandler(cast(int)x, cast(int)y); 328 } 329 } 330 331 332 private void pointerEnter(WlFixed x, WlFixed y) { 333 curX = x; curY = y; 334 } 335 336 private void pointerLeave() {} 337 338 private void key(uint key, WlKeyboard.KeyState state) { 339 switch (state) { 340 case WlKeyboard.KeyState.pressed: 341 if (onKeyOnHandler) onKeyOnHandler(key); 342 break; 343 case WlKeyboard.KeyState.released: 344 if (onKeyOffHandler) onKeyOffHandler(key); 345 break; 346 default: 347 break; 348 } 349 } 350 351 private WaylandDisplay dpy; 352 private Instance instance; 353 354 private WlSurface wlSurface; 355 private Surface gfxSurface; 356 357 private MouseHandler moveHandler; 358 private MouseHandler onHandler; 359 private MouseHandler offHandler; 360 private KeyHandler onKeyOnHandler; 361 private KeyHandler onKeyOffHandler; 362 private CloseHandler onCloseHandler; 363 private WlFixed curX; 364 private WlFixed curY; 365 private bool _closeFlag; 366 } 367 368 private class WaylandWindow : WaylandWindowBase 369 { 370 this (WaylandDisplay display, Instance instance, WlShell wlShell) { 371 super(display, instance); 372 this.wlShell = wlShell; 373 } 374 375 override protected void prepareShell(WlSurface wlSurf) 376 { 377 wlShellSurf = wlShell.getShellSurface(wlSurf); 378 wlShellSurf.onPing = (WlShellSurface ss, uint serial) 379 { 380 ss.pong(serial); 381 }; 382 383 wlShellSurf.setToplevel(); 384 } 385 386 override protected void closeShell() { 387 wlShellSurf.destroy(); 388 } 389 390 private WlShell wlShell; 391 private WlShellSurface wlShellSurf; 392 } 393 394 private class XdgWaylandWindow : WaylandWindowBase 395 { 396 this (WaylandDisplay display, Instance instance, ZxdgShellV6 xdgShell) 397 { 398 super(display, instance); 399 this.xdgShell = xdgShell; 400 } 401 402 override protected void prepareShell(WlSurface wlSurf) 403 { 404 xdgSurf = xdgShell.getXdgSurface(wlSurf); 405 xdgTopLevel = xdgSurf.getToplevel(); 406 407 xdgTopLevel.onConfigure = &onTLConfigure; 408 xdgTopLevel.onClose = &onTLClose; 409 xdgTopLevel.setTitle("Gfx-d Wayland window"); 410 411 xdgSurf.onConfigure = (ZxdgSurfaceV6 surf, uint serial) 412 { 413 surf.ackConfigure(serial); 414 configured = true; 415 }; 416 } 417 418 void onTLConfigure(ZxdgToplevelV6, int width, int height, wl_array* states) 419 { 420 } 421 422 void onTLClose(ZxdgToplevelV6) 423 { 424 } 425 426 override protected void closeShell() { 427 xdgTopLevel.destroy(); 428 xdgSurf.destroy(); 429 } 430 431 private bool configured; 432 private ZxdgShellV6 xdgShell; 433 private ZxdgSurfaceV6 xdgSurf; 434 private ZxdgToplevelV6 xdgTopLevel; 435 }