├── cat.png ├── shm.h ├── README.md ├── .gitignore ├── LICENSE ├── Makefile ├── shm.c └── main.c /cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emersion/hello-wayland/HEAD/cat.png -------------------------------------------------------------------------------- /shm.h: -------------------------------------------------------------------------------- 1 | #ifndef SHM_H 2 | #define SHM_H 3 | 4 | #include 5 | 6 | int create_shm_file(off_t size); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hello-wayland 2 | 3 | A hello world Wayland client, 2018 edition. 4 | 5 | This is a simple client showing a picture. It uses the [xdg-shell] protocol. 6 | 7 | ## Dependencies 8 | 9 | The following dependencies are required for the Makefile to function properly: 10 | 11 | - libwayland 12 | - wayland-protocols 13 | - ImageMagick 14 | 15 | ## License 16 | 17 | MIT 18 | 19 | [xdg-shell]: https://gitlab.freedesktop.org/wayland/wayland-protocols/-/tree/master/stable/xdg-shell 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | /hello-wayland 55 | /xdg-shell-client-protocol.h 56 | /xdg-shell-protocol.c 57 | /cat.h 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 emersion 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS ?= -std=c11 -Wall -Wextra -Werror -Wno-unused-parameter -g 2 | PKG_CONFIG ?= pkg-config 3 | 4 | # Host deps 5 | WAYLAND_FLAGS = $(shell $(PKG_CONFIG) wayland-client --cflags --libs) 6 | WAYLAND_PROTOCOLS_DIR = $(shell $(PKG_CONFIG) wayland-protocols --variable=pkgdatadir) 7 | 8 | # Build deps 9 | WAYLAND_SCANNER = $(shell pkg-config --variable=wayland_scanner wayland-scanner) 10 | 11 | XDG_SHELL_PROTOCOL = $(WAYLAND_PROTOCOLS_DIR)/stable/xdg-shell/xdg-shell.xml 12 | 13 | HEADERS=cat.h xdg-shell-client-protocol.h shm.h 14 | SOURCES=main.c xdg-shell-protocol.c shm.c 15 | 16 | all: hello-wayland 17 | 18 | hello-wayland: $(HEADERS) $(SOURCES) 19 | $(CC) $(CFLAGS) -o $@ $(SOURCES) -lrt $(WAYLAND_FLAGS) 20 | 21 | xdg-shell-client-protocol.h: 22 | $(WAYLAND_SCANNER) client-header $(XDG_SHELL_PROTOCOL) xdg-shell-client-protocol.h 23 | 24 | xdg-shell-protocol.c: 25 | $(WAYLAND_SCANNER) private-code $(XDG_SHELL_PROTOCOL) xdg-shell-protocol.c 26 | 27 | cat.h: cat.png 28 | convert cat.png -define h:format=bgra -depth 8 cat.h 29 | 30 | .PHONY: clean 31 | clean: 32 | $(RM) hello-wayland cat.h xdg-shell-protocol.c xdg-shell-client-protocol.h 33 | -------------------------------------------------------------------------------- /shm.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 200809L 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | /** 10 | * Boilerplate to create an in-memory shared file. 11 | * 12 | * Link with `-lrt`. 13 | */ 14 | 15 | static void randname(char *buf) { 16 | struct timespec ts; 17 | clock_gettime(CLOCK_REALTIME, &ts); 18 | long r = ts.tv_nsec; 19 | for (int i = 0; i < 6; ++i) { 20 | buf[i] = 'A'+(r&15)+(r&16)*2; 21 | r >>= 5; 22 | } 23 | } 24 | 25 | static int anonymous_shm_open(void) { 26 | char name[] = "/hello-wayland-XXXXXX"; 27 | int retries = 100; 28 | 29 | do { 30 | randname(name + strlen(name) - 6); 31 | 32 | --retries; 33 | // shm_open guarantees that O_CLOEXEC is set 34 | int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); 35 | if (fd >= 0) { 36 | shm_unlink(name); 37 | return fd; 38 | } 39 | } while (retries > 0 && errno == EEXIST); 40 | 41 | return -1; 42 | } 43 | 44 | int create_shm_file(off_t size) { 45 | int fd = anonymous_shm_open(); 46 | if (fd < 0) { 47 | return fd; 48 | } 49 | 50 | if (ftruncate(fd, size) < 0) { 51 | close(fd); 52 | return -1; 53 | } 54 | 55 | return fd; 56 | } 57 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "cat.h" 12 | #include "shm.h" 13 | #include "xdg-shell-client-protocol.h" 14 | 15 | static const int width = 128; 16 | static const int height = 128; 17 | 18 | static bool configured = false; 19 | static bool running = true; 20 | 21 | static struct wl_shm *shm = NULL; 22 | static struct wl_compositor *compositor = NULL; 23 | static struct xdg_wm_base *xdg_wm_base = NULL; 24 | 25 | static void *shm_data = NULL; 26 | static struct wl_surface *surface = NULL; 27 | static struct xdg_toplevel *xdg_toplevel = NULL; 28 | 29 | static void noop() { 30 | // This space intentionally left blank 31 | } 32 | 33 | static void xdg_wm_base_handle_ping(void *data, 34 | struct xdg_wm_base *xdg_wm_base, uint32_t serial) { 35 | // The compositor will send us a ping event to check that we're responsive. 36 | // We need to send back a pong request immediately. 37 | xdg_wm_base_pong(xdg_wm_base, serial); 38 | } 39 | 40 | static const struct xdg_wm_base_listener xdg_wm_base_listener = { 41 | .ping = xdg_wm_base_handle_ping, 42 | }; 43 | 44 | static void xdg_surface_handle_configure(void *data, 45 | struct xdg_surface *xdg_surface, uint32_t serial) { 46 | // The compositor configures our surface, acknowledge the configure event 47 | xdg_surface_ack_configure(xdg_surface, serial); 48 | 49 | if (configured) { 50 | // If this isn't the first configure event we've received, we already 51 | // have a buffer attached, so no need to do anything. Commit the 52 | // surface to apply the configure acknowledgement. 53 | wl_surface_commit(surface); 54 | } 55 | 56 | configured = true; 57 | } 58 | 59 | static const struct xdg_surface_listener xdg_surface_listener = { 60 | .configure = xdg_surface_handle_configure, 61 | }; 62 | 63 | static void xdg_toplevel_handle_close(void *data, 64 | struct xdg_toplevel *xdg_toplevel) { 65 | // Stop running if the user requests to close the toplevel 66 | running = false; 67 | } 68 | 69 | static const struct xdg_toplevel_listener xdg_toplevel_listener = { 70 | .configure = noop, 71 | .close = xdg_toplevel_handle_close, 72 | }; 73 | 74 | static void pointer_handle_button(void *data, struct wl_pointer *pointer, 75 | uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { 76 | struct wl_seat *seat = data; 77 | 78 | // If the user presses the left pointer button, start an interactive move 79 | // of the toplevel 80 | if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED) { 81 | xdg_toplevel_move(xdg_toplevel, seat, serial); 82 | } 83 | } 84 | 85 | static const struct wl_pointer_listener pointer_listener = { 86 | .enter = noop, 87 | .leave = noop, 88 | .motion = noop, 89 | .button = pointer_handle_button, 90 | .axis = noop, 91 | }; 92 | 93 | static void seat_handle_capabilities(void *data, struct wl_seat *seat, 94 | uint32_t capabilities) { 95 | // If the wl_seat has the pointer capability, start listening to pointer 96 | // events 97 | if (capabilities & WL_SEAT_CAPABILITY_POINTER) { 98 | struct wl_pointer *pointer = wl_seat_get_pointer(seat); 99 | wl_pointer_add_listener(pointer, &pointer_listener, seat); 100 | } 101 | } 102 | 103 | static const struct wl_seat_listener seat_listener = { 104 | .capabilities = seat_handle_capabilities, 105 | }; 106 | 107 | static void handle_global(void *data, struct wl_registry *registry, 108 | uint32_t name, const char *interface, uint32_t version) { 109 | if (strcmp(interface, wl_shm_interface.name) == 0) { 110 | shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); 111 | } else if (strcmp(interface, wl_seat_interface.name) == 0) { 112 | struct wl_seat *seat = 113 | wl_registry_bind(registry, name, &wl_seat_interface, 1); 114 | wl_seat_add_listener(seat, &seat_listener, NULL); 115 | } else if (strcmp(interface, wl_compositor_interface.name) == 0) { 116 | compositor = wl_registry_bind(registry, name, 117 | &wl_compositor_interface, 1); 118 | } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { 119 | xdg_wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1); 120 | xdg_wm_base_add_listener(xdg_wm_base, &xdg_wm_base_listener, NULL); 121 | } 122 | } 123 | 124 | static void handle_global_remove(void *data, struct wl_registry *registry, 125 | uint32_t name) { 126 | // Who cares 127 | } 128 | 129 | static const struct wl_registry_listener registry_listener = { 130 | .global = handle_global, 131 | .global_remove = handle_global_remove, 132 | }; 133 | 134 | static struct wl_buffer *create_buffer(void) { 135 | int stride = width * 4; 136 | int size = stride * height; 137 | 138 | // Allocate a shared memory file with the right size 139 | int fd = create_shm_file(size); 140 | if (fd < 0) { 141 | fprintf(stderr, "creating a buffer file for %d B failed: %m\n", size); 142 | return NULL; 143 | } 144 | 145 | // Map the shared memory file 146 | shm_data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 147 | if (shm_data == MAP_FAILED) { 148 | fprintf(stderr, "mmap failed: %m\n"); 149 | close(fd); 150 | return NULL; 151 | } 152 | 153 | // Create a wl_buffer from our shared memory file descriptor 154 | struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size); 155 | struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, width, height, 156 | stride, WL_SHM_FORMAT_ARGB8888); 157 | wl_shm_pool_destroy(pool); 158 | 159 | // Now that we've mapped the file and created the wl_buffer, we no longer 160 | // need to keep file descriptor opened 161 | close(fd); 162 | 163 | // Copy pixels into our shared memory file (MagickImage is from cat.h) 164 | memcpy(shm_data, MagickImage, size); 165 | 166 | return buffer; 167 | } 168 | 169 | int main(int argc, char *argv[]) { 170 | // Connect to the Wayland compositor 171 | struct wl_display *display = wl_display_connect(NULL); 172 | if (display == NULL) { 173 | fprintf(stderr, "failed to create display\n"); 174 | return EXIT_FAILURE; 175 | } 176 | 177 | // Obtain the wl_registry and fetch the list of globals 178 | struct wl_registry *registry = wl_display_get_registry(display); 179 | wl_registry_add_listener(registry, ®istry_listener, NULL); 180 | if (wl_display_roundtrip(display) == -1) { 181 | return EXIT_FAILURE; 182 | } 183 | 184 | // Check that all globals we require are available 185 | if (shm == NULL || compositor == NULL || xdg_wm_base == NULL) { 186 | fprintf(stderr, "no wl_shm, wl_compositor or xdg_wm_base support\n"); 187 | return EXIT_FAILURE; 188 | } 189 | 190 | // Create a wl_surface, a xdg_surface and a xdg_toplevel 191 | surface = wl_compositor_create_surface(compositor); 192 | struct xdg_surface *xdg_surface = 193 | xdg_wm_base_get_xdg_surface(xdg_wm_base, surface); 194 | xdg_toplevel = xdg_surface_get_toplevel(xdg_surface); 195 | 196 | xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL); 197 | xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL); 198 | 199 | // Perform the initial commit and wait for the first configure event 200 | wl_surface_commit(surface); 201 | while (wl_display_dispatch(display) != -1 && !configured) { 202 | // This space intentionally left blank 203 | } 204 | 205 | // Create a wl_buffer, attach it to the surface and commit the surface 206 | struct wl_buffer *buffer = create_buffer(); 207 | if (buffer == NULL) { 208 | return EXIT_FAILURE; 209 | } 210 | 211 | wl_surface_attach(surface, buffer, 0, 0); 212 | wl_surface_commit(surface); 213 | 214 | // Continue dispatching events until the user closes the toplevel 215 | while (wl_display_dispatch(display) != -1 && running) { 216 | // This space intentionally left blank 217 | } 218 | 219 | xdg_toplevel_destroy(xdg_toplevel); 220 | xdg_surface_destroy(xdg_surface); 221 | wl_surface_destroy(surface); 222 | wl_buffer_destroy(buffer); 223 | 224 | return EXIT_SUCCESS; 225 | } 226 | --------------------------------------------------------------------------------