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 }