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