1 /*
2  * Copyright (c) 2014-2019, Nils Christopher Brause, Philipp Kerling, Zsolt Bölöny
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright notice, this
9  *    list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright notice,
11  *    this list of conditions and the following disclaimer in the documentation
12  *    and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 
26 /** \example egl.cpp
27  * This is an example of how to use the Wayland C++ bindings with EGL and OpenGL.
28  */
29 
30 #include <stdexcept>
31 #include <iostream>
32 #include <array>
33 #include <wayland-client.hpp>
34 #include <wayland-client-protocol-extra.hpp>
35 #include <wayland-egl.hpp>
36 #include <GL/gl.h>
37 #include <linux/input.h>
38 #include <wayland-cursor.hpp>
39 
40 using namespace wayland;
41 
42 // helper to create a std::function out of a member function and an object
43 template <typename R, typename T, typename... Args>
bind_mem_fn(R (T::* func)(Args...),T * t)44 std::function<R(Args...)> bind_mem_fn(R(T::* func)(Args...), T *t)
45 {
46   return [func, t] (Args... args)
47     {
48       return (t->*func)(args...);
49     };
50 }
51 
52 // example Wayland client
53 class example
54 {
55 private:
56   // global objects
57   display_t display;
58   registry_t registry;
59   compositor_t compositor;
60   shell_t shell;
61   xdg_wm_base_t xdg_wm_base;
62   seat_t seat;
63   shm_t shm;
64 
65   // local objects
66   surface_t surface;
67   shell_surface_t shell_surface;
68   xdg_surface_t xdg_surface;
69   xdg_toplevel_t xdg_toplevel;
70   pointer_t pointer;
71   keyboard_t keyboard;
72   callback_t frame_cb;
73   cursor_image_t cursor_image;
74   buffer_t cursor_buffer;
75   surface_t cursor_surface;
76 
77   // EGL
78   egl_window_t egl_window;
79   EGLDisplay egldisplay = nullptr;
80   EGLSurface eglsurface = nullptr;
81   EGLContext eglcontext = nullptr;
82 
83   bool running;
84   bool has_pointer;
85   bool has_keyboard;
86 
init_egl()87   void init_egl()
88   {
89     egldisplay = eglGetDisplay(display);
90     if(egldisplay == EGL_NO_DISPLAY)
91       throw std::runtime_error("eglGetDisplay");
92 
93     EGLint major = 0;
94     EGLint minor = 0;
95     if(eglInitialize(egldisplay, &major, &minor) == EGL_FALSE)
96       throw std::runtime_error("eglInitialize");
97     if(!((major == 1 && minor >= 4) || major >= 2))
98       throw std::runtime_error("EGL version too old");
99 
100     if(eglBindAPI(EGL_OPENGL_API) == EGL_FALSE)
101       throw std::runtime_error("eglBindAPI");
102 
103     std::array<EGLint, 13> config_attribs = {{
104       EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
105       EGL_RED_SIZE, 8,
106       EGL_GREEN_SIZE, 8,
107       EGL_BLUE_SIZE, 8,
108       EGL_ALPHA_SIZE, 8,
109       EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
110       EGL_NONE
111       }};
112 
113     EGLConfig config = nullptr;
114     EGLint num = 0;
115     if(eglChooseConfig(egldisplay, config_attribs.data(), &config, 1, &num) == EGL_FALSE || num == 0)
116       throw std::runtime_error("eglChooseConfig");
117 
118     std::array<EGLint, 3> context_attribs = {{
119       EGL_CONTEXT_CLIENT_VERSION, 2,
120       EGL_NONE
121       }};
122 
123     eglcontext = eglCreateContext(egldisplay, config, EGL_NO_CONTEXT, context_attribs.data());
124     if(eglcontext == EGL_NO_CONTEXT)
125       throw std::runtime_error("eglCreateContext");
126 
127     eglsurface = eglCreateWindowSurface(egldisplay, config, egl_window, nullptr);
128     if(eglsurface == EGL_NO_SURFACE)
129       throw std::runtime_error("eglCreateWindowSurface");
130 
131     if(eglMakeCurrent(egldisplay, eglsurface, eglsurface, eglcontext) == EGL_FALSE)
132       throw std::runtime_error("eglMakeCurrent");
133   }
134 
draw(uint32_t serial=0)135   void draw(uint32_t serial = 0)
136   {
137     float h = static_cast<float>((serial >> 4) & 0xFF)/255.0F;
138     float s = 1;
139     float v = 1;
140 
141     int hi = static_cast<int>(h*6);
142     float f = h*6 - static_cast<float>(hi);
143     float p = v*(1-s);
144     float q = v*(1-s*f);
145     float t = v*(1-s*(1-f));
146     float r = 0;
147     float g = 0;
148     float b = 0;
149 
150     switch(hi)
151       {
152       case 1:
153         r = q; g = v; b = p;
154         break;
155       case 2:
156         r = p; g = v; b = t;
157         break;
158       case 3:
159         r = p; g = q; b = v;
160         break;
161       case 4:
162         r = t; g = p; b = v;
163         break;
164       case 5:
165         r = v; g = p; b = q;
166         break;
167       default: // 0,6
168         r = v; g = t; b = p;
169         break;
170       }
171 
172     // draw stuff
173     glClearColor(r, g, b, 0.5F);
174     glClear(GL_COLOR_BUFFER_BIT);
175 
176     // schedule next draw
177     frame_cb = surface.frame();
178     frame_cb.on_done() = bind_mem_fn(&example::draw, this);
179 
180     // swap buffers
181     if(eglSwapBuffers(egldisplay, eglsurface) == EGL_FALSE)
182       throw std::runtime_error("eglSwapBuffers");
183   }
184 
185 public:
186   example(const example&) = delete;
187   example(example&&) noexcept = delete;
188   example& operator=(const example&) = delete;
189   example& operator=(example&&) noexcept = delete;
190 
example()191   example()
192   {
193     // retrieve global objects
194     registry = display.get_registry();
195     registry.on_global() = [&] (uint32_t name, const std::string& interface, uint32_t version)
196       {
197         if(interface == compositor_t::interface_name)
198           registry.bind(name, compositor, version);
199         else if(interface == shell_t::interface_name)
200           registry.bind(name, shell, version);
201         else if(interface == xdg_wm_base_t::interface_name)
202           registry.bind(name, xdg_wm_base, version);
203         else if(interface == seat_t::interface_name)
204           registry.bind(name, seat, version);
205         else if(interface == shm_t::interface_name)
206           registry.bind(name, shm, version);
207       };
208     display.roundtrip();
209 
210     seat.on_capabilities() = [&] (const seat_capability& capability)
211       {
212         has_keyboard = capability & seat_capability::keyboard;
213         has_pointer = capability & seat_capability::pointer;
214       };
215 
216     // create a surface
217     surface = compositor.create_surface();
218 
219     // create a shell surface
220     if(xdg_wm_base)
221       {
222         xdg_wm_base.on_ping() = [&] (uint32_t serial) { xdg_wm_base.pong(serial); };
223         xdg_surface = xdg_wm_base.get_xdg_surface(surface);
224         xdg_surface.on_configure() = [&] (uint32_t serial) { xdg_surface.ack_configure(serial); };
225         xdg_toplevel = xdg_surface.get_toplevel();
226         xdg_toplevel.set_title("Window");
227         xdg_toplevel.on_close() = [&] () { running = false; };
228       }
229     else
230       {
231         shell_surface = shell.get_shell_surface(surface);
232         shell_surface.on_ping() = [&] (uint32_t serial) { shell_surface.pong(serial); };
233         shell_surface.set_title("Window");
234         shell_surface.set_toplevel();
235       }
236     surface.commit();
237 
238     display.roundtrip();
239 
240     // Get input devices
241     if(!has_keyboard)
242       throw std::runtime_error("No keyboard found.");
243     if(!has_pointer)
244       throw std::runtime_error("No pointer found.");
245 
246     pointer = seat.get_pointer();
247     keyboard = seat.get_keyboard();
248 
249     // load cursor theme
250     cursor_theme_t cursor_theme = cursor_theme_t("default", 16, shm);
251     cursor_t cursor = cursor_theme.get_cursor("cross");
252     cursor_image = cursor.image(0);
253     cursor_buffer = cursor_image.get_buffer();
254 
255     // create cursor surface
256     cursor_surface = compositor.create_surface();
257 
258     // draw cursor
259     pointer.on_enter() = [&] (uint32_t serial, const surface_t& /*unused*/, int32_t /*unused*/, int32_t /*unused*/)
260       {
261         cursor_surface.attach(cursor_buffer, 0, 0);
262         cursor_surface.damage(0, 0, cursor_image.width(), cursor_image.height());
263         cursor_surface.commit();
264         pointer.set_cursor(serial, cursor_surface, 0, 0);
265       };
266 
267     // window movement
268     pointer.on_button() = [&] (uint32_t serial, uint32_t /*unused*/, uint32_t button, pointer_button_state state)
269       {
270         if(button == BTN_LEFT && state == pointer_button_state::pressed)
271           {
272             if(xdg_toplevel)
273               xdg_toplevel.move(seat, serial);
274             else
275               shell_surface.move(seat, serial);
276           }
277       };
278 
279     // press 'q' to exit
280     keyboard.on_key() = [&] (uint32_t /*unused*/, uint32_t /*unused*/, uint32_t key, keyboard_key_state state)
281       {
282         if(key == KEY_Q && state == keyboard_key_state::pressed)
283           running = false;
284       };
285 
286     // intitialize egl
287     egl_window = egl_window_t(surface, 320, 240);
288     init_egl();
289 
290     // draw stuff
291     draw();
292   }
293 
~example()294   ~example() noexcept
295   {
296     // finialize EGL
297     if(eglDestroyContext(egldisplay, eglcontext) == EGL_FALSE)
298       std::cerr << "eglDestroyContext failed.";
299     if(eglTerminate(egldisplay) == EGL_FALSE)
300       std::cerr << "eglTerminate failed.";
301   }
302 
run()303   void run()
304   {
305     // event loop
306     running = true;
307     while(running)
308       display.dispatch();
309   }
310 };
311 
main()312 int main()
313 {
314   example e;
315   e.run();
316   return 0;
317 }
318