Wayland++  1.0.0
C++ Bindings for Wayland
egl.cpp
1 /*
2  * Copyright (c) 2014-2022, 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 
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>
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 
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 
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 
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 
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 
303  void run()
304  {
305  // event loop
306  running = true;
307  while(running)
308  display.dispatch();
309  }
310 };
311 
312 int main()
313 {
314  example e;
315  e.run();
316  return 0;
317 }
create desktop-style surfaces
shared memory support
std::function< void(uint32_t)> & on_ping()
ping client
void pong(uint32_t serial)
respond to a ping event
std::function< void(uint32_t)> & on_done()
done event
content for a wl_surface
registry_t get_registry()
get global registry object
void set_cursor(uint32_t serial, surface_t const &surface, int32_t hotspot_x, int32_t hotspot_y)
set the pointer surface
group of input devices
Represents a connection to the compositor and acts as a proxy to the display singleton object...
desktop user interface surface base interface
std::function< void(uint32_t, uint32_t, uint32_t, keyboard_key_state)> & on_key()
key event
std::function< void(uint32_t)> & on_ping()
check if the client is alive
proxy_t bind(uint32_t name, proxy_t &interface, uint32_t version)
bind an object to the display
Native EGL window.
Definition: wayland-egl.hpp:42
desktop-style metadata interface
void move(seat_t const &seat, uint32_t serial)
start an interactive move
std::function< void()> & on_close()
surface wants to be closed
void move(seat_t const &seat, uint32_t serial)
start an interactive move
void commit()
commit pending surface state
void pong(uint32_t serial)
respond to a ping event
std::function< void(uint32_t, uint32_t, uint32_t, pointer_button_state)> & on_button()
pointer button event
std::function< void(uint32_t, std::string, uint32_t)> & on_global()
announce global object
pointer_t get_pointer()
return pointer object
global registry object
void set_title(std::string const &title)
set surface title
the compositor singleton
static const wayland::detail::bitfield< 3, 12 > pointer
the seat has pointer devices
void ack_configure(uint32_t serial)
ack a configure event
static const wayland::detail::bitfield< 3, 12 > keyboard
the seat has one or more keyboards
keyboard input device
xdg_surface_t get_xdg_surface(surface_t const &surface)
create a shell surface from a surface
void set_toplevel()
make the surface a toplevel surface
int roundtrip() const
Block until all pending request are processed by the server.
shell_surface_t get_shell_surface(surface_t const &surface)
create a shell surface from a surface
create desktop-style surfaces
void damage(int32_t x, int32_t y, int32_t width, int32_t height)
mark part of the surface damaged
void attach(buffer_t const &buffer, int32_t x, int32_t y)
set the surface contents
surface_t create_surface()
create new surface
xdg_toplevel_t get_toplevel()
assign the xdg_toplevel surface role
std::function< void(seat_capability)> & on_capabilities()
seat capabilities changed
std::function< void(uint32_t)> & on_configure()
suggest a surface change
callback_t frame()
request a frame throttling hint
keyboard_t get_keyboard()
return keyboard object
std::function< void(uint32_t, surface_t, double, double)> & on_enter()
enter event
int dispatch() const
Process incoming events.
void set_title(std::string const &title)
set surface title