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 }