summaryrefslogtreecommitdiff
path: root/source/base/base_platform_linux_wayland.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/base/base_platform_linux_wayland.cpp')
-rw-r--r--source/base/base_platform_linux_wayland.cpp365
1 files changed, 233 insertions, 132 deletions
diff --git a/source/base/base_platform_linux_wayland.cpp b/source/base/base_platform_linux_wayland.cpp
index 772080b..5823a69 100644
--- a/source/base/base_platform_linux_wayland.cpp
+++ b/source/base/base_platform_linux_wayland.cpp
@@ -1,193 +1,294 @@
1// https://gaultier.github.io/blog/wayland_from_scratch.html#creating-a-registry
2// TODO: replace unix_get_env with a generic macro
3 1
2//- software framebuffer
3// TODO: delete this section once vulkan is presenting. vkCreateWaylandSurfaceKHR only needs
4// state->display and state->surface, the driver then creates and attaches buffers itself.
4 5
6// creates a wl_buffer backed by shared memory that both we and the compositor can read.
7internal struct wl_buffer *wayland_create_shm_buffer(WaylandState *state) {
8 state->stride = state->width * 4; // 4 bytes per pixel (XRGB8888)
9 state->size = state->stride * state->height;
5 10
6//- wayland 11 int fd = memfd_create("loo-shared-memory", MFD_CLOEXEC);
7internal int wayland_display_connect(MemArena *arena) { 12 AssertLog((fd != -1), "Failed to open buffer for shared memory.");
13 AssertLog((ftruncate(fd, state->size) != -1), "Failed to truncate buffer.");
8 14
9 String8 str8_xdg_runtime_dir = unix_get_env("XDG_RUNTIME_DIR"); 15 state->pixels = (u8 *)mmap(NULL, state->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
10 char *xdg_runtime_dir = null_terminate(arena, &str8_xdg_runtime_dir); 16 AssertLog((state->pixels != MAP_FAILED), "Failed to mmap shared memory.");
11 sockaddr_un addr = {};
12 u64 socket_path_len = 0;
13 17
14// copy the xdg_runtime_dir value to the beginning of the bfufer 18 // a pool is the compositor-side view of our shared memory. libwayland sends the fd
15{ 19 // over the socket for us (the SCM_RIGHTS dance, in the other direction).
16 addr.sun_family = AF_UNIX; 20 struct wl_shm_pool *pool = wl_shm_create_pool(state->shm, fd, state->size);
17 MemCpy(addr.sun_path, xdg_runtime_dir, str8_xdg_runtime_dir.size); 21
18 socket_path_len += str8_xdg_runtime_dir.size; 22 // a buffer is a typed slice of the pool: offset, size, stride and pixel format
19 addr.sun_path[socket_path_len++] = '/'; // add an ending slash 23 struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, state->width, state->height,
20} 24 state->stride, WL_SHM_FORMAT_XRGB8888);
21 25
22 String8 str8_wayland_display_value = unix_get_env("WAYLAND_DISPLAY"); 26 wl_shm_pool_destroy(pool);
23Assert(str8_wayland_display_value.size != 0); 27 close(fd);
24 char *wayland_display = null_terminate(arena, &str8_wayland_display_value); 28
29 return buffer;
30}
25 31
26 if(wayland_display == NULL) 32internal void wayland_draw_gradient(WaylandState *state) {
27 { 33 u32 *pixels = (u32 *)state->pixels;
28 const char *wayland_display_default = "wayland-0"; 34 for (u32 y = 0; y < state->height; y++) {
29 u64 wayland_display_default_len = cstring8_length((u8 *)wayland_display_default); 35 for (u32 x = 0; x < state->width; x++) {
30 MemCpy(addr.sun_path, wayland_display_default, wayland_display_default_len); 36 u8 r = (u8)(x * 255 / state->width);
37 u8 g = (u8)(y * 255 / state->height);
38 u8 b = 0x80;
39 pixels[y * state->width + x] = (r << 16) | (g << 8) | b;
40 }
31 } 41 }
32 else
33 {
34 MemCpy(addr.sun_path + str8_xdg_runtime_dir.size + 1, wayland_display, str8_wayland_display_value.size);
35} 42}
36 43
37 int fd = socket(AF_UNIX, SOCK_STREAM, 0); 44internal void draw_square(u32 *pixels, WaylandState *state, u32 x_position, u32 y_position) {
38 45
39#if DEBUG // helps to read those errors? i guess i can just read stderr? 46for(u32 y = y_position; y < y_position + 10; ++y) {
40 47
48for(u32 x = x_position; x < x_position + 10; ++x) {
41 49
50u8 r = 0xff;
51 u8 g = 0xff;
52u8 b = 0xff;
53 pixels[y * state->width + x] = (r << 16) | (g << 8) | b;
42 54
43if(fd == -1) 55}
44{
45perror("failed to open socket");
46} 56}
47 57
58}
48 59
49if(connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) 60internal void wayland_draw_weird_gradient(WaylandState *state) {
50{
51perror("failed to connect to socket");
52 61
53} 62local_persist u32 frame = 0;
63frame += 1;
54 64
55#endif 65 local_persist u32 start_x = state->width / 2;
66 local_persist u32 start_y = state->width / 2;
56 67
68start_x += frame;
57 69
58 AssertLog((fd != -1), "failed to open socket\n"); 70draw_square((u32 *)state->pixels, state, start_x , start_y);
59 AssertLog((connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1), "failed to connect to socket\n");
60 71
61return fd;
62 72
63} 73}
64 74
65 75
66//- helper functions 76//- xdg_wm_base events
67
68internal void buf_write_u32(char *buf, u64 *buf_size, u64 buf_cap,
69 u32 x) {
70 Assert(*buf_size + sizeof(x) <= buf_cap);
71 Assert(((size_t)buf + *buf_size) % sizeof(x) == 0);
72 77
73 *(u32 *)(buf + *buf_size) = x; 78// ping: the compositor checks we're still alive. if we don't pong, it may show the
74 *buf_size += sizeof(x); 79internal void wayland_xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, u32 serial) {
80 xdg_wm_base_pong(xdg_wm_base, serial);
75} 81}
76 82
77internal void buf_write_u16(char *buf, u64 *buf_size, u64 buf_cap, 83global_variable const struct xdg_wm_base_listener wayland_xdg_wm_base_listener = {
78 u16 x) { 84 wayland_xdg_wm_base_ping,
79 Assert(*buf_size + sizeof(x) <= buf_cap); 85};
80 Assert(((size_t)buf + *buf_size) % sizeof(x) == 0); 86
87
88//- frame
89
90
91internal void wayland_frame_done(void *data, struct wl_callback *callback, u32 time_ms);
92
93global_variable const struct wl_callback_listener wayland_frame_listener = {
94wayland_frame_done,
95
96};
81 97
82 *(u16 *)(buf + *buf_size) = x;
83 *buf_size += sizeof(x);
84}
85 98
86internal void buf_write_string(char *buf, u64 *buf_size, u64 buf_cap, 99internal void wayland_frame_done(void *data, struct wl_callback *callback, u32 time_ms) {
87 char *src, u32 src_len) { 100
88 Assert(*buf_size + src_len <= buf_cap); 101WaylandState *state = (WaylandState *)data;
102wl_callback_destroy(callback);
103
104wayland_draw_weird_gradient(state);
105 struct wl_callback *next = wl_surface_frame(state->surface);
106 wl_callback_add_listener(next, &wayland_frame_listener, state);
107
108 wl_surface_attach(state->surface, state->buffer, 0, 0);
109 wl_surface_damage(state->surface, 0, 0, state->width, state->height);
110 wl_surface_commit(state->surface);
89 111
90 buf_write_u32(buf, buf_size, buf_cap, src_len);
91 memcpy(buf + *buf_size, src, roundup_4(src_len));
92 *buf_size += roundup_4(src_len);
93} 112}
94 113
95internal u32 buf_read_u32(char **buf, u64 *buf_size) {
96 Assert(*buf_size >= sizeof(u32));
97 Assert((size_t)*buf % sizeof(u32) == 0);
98 114
99 u32 res = *(u32 *)(*buf);
100 *buf += sizeof(res);
101 *buf_size -= sizeof(res);
102 115
103 return res; 116//- xdg_surface events
117
118internal void wayland_xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, u32 serial) {
119 WaylandState *state = (WaylandState *)data;
120
121 xdg_surface_ack_configure(xdg_surface, serial);
122
123 if (state->buffer == NULL) {
124 state->buffer = wayland_create_shm_buffer(state);
125wayland_draw_weird_gradient(state);
126
127 struct wl_callback *cb = wl_surface_frame(state->surface);
128 wl_callback_add_listener(cb, &wayland_frame_listener, state);
129 wl_surface_attach(state->surface, state->buffer, 0, 0);
130 wl_surface_damage(state->surface, 0, 0, state->width, state->height);
131 wl_surface_commit(state->surface);
132 }
133
134 // attach = "this buffer is the surface's content", damage = "this region changed",
135 // commit = "apply everything I sent since the last commit, atomically".
136 // a wl_surface has pending state and current state, nothing is visible until commit.
137 wl_surface_attach(state->surface, state->buffer, 0, 0);
138 wl_surface_damage(state->surface, 0, 0, state->width, state->height);
139 wl_surface_commit(state->surface);
104} 140}
105 141
106internal u16 buf_read_u16(char **buf, u64 *buf_size) { 142global_variable const struct xdg_surface_listener wayland_xdg_surface_listener = {
107 Assert(*buf_size >= sizeof(u16)); 143 wayland_xdg_surface_configure,
108 Assert((size_t)*buf % sizeof(u16) == 0); 144};
145
146
147//- xdg_toplevel events
109 148
110 u16 res = *(u16 *)(*buf); 149// TODO: handle resizing. with vulkan that means recreating the swapchain.
111 *buf += sizeof(res); 150internal void wayland_xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel,
112 *buf_size -= sizeof(res); 151 s32 width, s32 height, struct wl_array *states) {
152
153WaylandState *state = (WaylandState *)data;
154 Log("<- xdg_toplevel.configure: width=%d height=%d\n", width, height);
155state->height = height;
156state->width = width;
157
158}
113 159
114 return res; 160internal void wayland_xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) {
161 WaylandState *state = (WaylandState *)data;
162 Log("<- xdg_toplevel.close\n");
163 state->should_close = true;
115} 164}
116 165
117internal void buf_read_n(char **buf, u64 *buf_size, char *dst, u64 n) { 166internal void wayland_xdg_toplevel_resize_edge(void *data, struct xdg_toplevel *xdg_toplevel,
118 Assert(*buf_size >= n); 167 s32 width, s32 height, struct wl_array *states) {
119 168
120 memcpy(dst, *buf, n);
121 169
122 *buf += n; 170}
123 *buf_size -= n; 171internal void wayland_xdg_toplevel_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel,
172 s32 width, s32 height) {
124} 173}
125 174
126global_variable u64 wayland_current_id = 1; 175internal void wayland_xdg_toplevel_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel,
176 struct wl_array *capabilities) {
177}
127 178
128internal u32 wayland_wl_display_get_registry(int fd) { 179global_variable const struct xdg_toplevel_listener wayland_xdg_toplevel_listener = {
129 uint64_t msg_size = 0; 180 wayland_xdg_toplevel_configure,
130 char msg[128] = ""; 181 wayland_xdg_toplevel_close,
131 buf_write_u32(msg, &msg_size, sizeof(msg), wayland_display_object_id); 182 wayland_xdg_toplevel_configure_bounds,
183 wayland_xdg_toplevel_wm_capabilities,
184};
132 185
133 buf_write_u16(msg, &msg_size, sizeof(msg),
134 wayland_wl_display_get_registry_opcode);
135 186
136 u16 msg_announced_size = wayland_header_size + sizeof(wayland_current_id); 187//- wl_registry events
137 Assert(roundup_4(msg_announced_size) == msg_announced_size);
138 buf_write_u16(msg, &msg_size, sizeof(msg), msg_announced_size);
139 188
140 wayland_current_id++; 189internal void wayland_registry_global(void *data, struct wl_registry *registry, u32 name,
141 buf_write_u32(msg, &msg_size, sizeof(msg), wayland_current_id); 190 const char *interface, u32 version) {
191 WaylandState *state = (WaylandState *)data;
142 192
143 if ((s64)msg_size != send(fd, msg, msg_size, MSG_DONTWAIT)) 193 Log("<- wl_registry.global: name=%u interface=%s version=%u\n", name, interface, version);
144 exit(1);
145 194
146 Log("-> wl_display@%u.get_registry: wl_registry=%u\n", 195 if (CstringIsMatch(interface, wl_compositor_interface.name)) {
147 wayland_display_object_id, wayland_current_id); 196 state->compositor = (struct wl_compositor *)
197 wl_registry_bind(registry, name, &wl_compositor_interface, Min(version, 4));
198 } else if (CstringIsMatch(interface, wl_shm_interface.name)) {
199 state->shm = (struct wl_shm *)
200 wl_registry_bind(registry, name, &wl_shm_interface, 1);
201 } else if(CstringIsMatch(interface, xdg_wm_base_interface.name)) {
202 state->xdg_wm_base = (struct xdg_wm_base *)
203 wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
204 xdg_wm_base_add_listener(state->xdg_wm_base, &wayland_xdg_wm_base_listener, state);
205 } else if (CstringIsMatch(interface, wl_seat_interface.name)) {
206 state->seat = (struct wl_seat *)
207 wl_registry_bind(registry, name, &wl_seat_interface, Min(version, 5));
208 wl_seat_add_listener(state->seat, &wayland_seat_listener, state); // has the keyboard handler
209 }
210}
148 211
149 return wayland_current_id; 212internal void wayland_registry_global_remove(void *data, struct wl_registry *registry, u32 name) {
150} 213}
151 214
215global_variable const struct wl_registry_listener wayland_registry_listener = {
216 wayland_registry_global,
217 wayland_registry_global_remove,
218};
152 219
220//- entrypoint
153 221
154 internal void create_shared_memory_file(u64 size, state_t *state) { 222internal void wayland_entrypoint(MemArena *arena, LinuxWindowParams *params) {
155 char name[255] = "/"; 223
156 for (u64 index = 1; index < cstring8_length((u8 *)name); ++index) { 224 WaylandState state = {};
157 name[index] = ((double)rand()) / (double)RAND_MAX * 26 + 'a'; 225
158 } 226 state.display = wl_display_connect(NULL);
159 227 AssertLog((state.display != NULL), "failed to connect to wayland display\n");
160 int fd = shm_open(name, O_RDWR | O_EXCL | O_CREAT, 0600); 228
161 AssertLog((fd != -1), "Failed to open buffer for shared memory."); 229 // ask for the registry and listen to its global events
162 230 state.registry = wl_display_get_registry(state.display);
163 Assert(shm_unlink(name) != -1); 231 wl_registry_add_listener(state.registry, &wayland_registry_listener, &state);
164 232
165 AssertLog((ftruncate(fd, size) != -1), "Failed to truncate buffer."); 233 wl_display_roundtrip(state.display);
166 234
167 state->shm_pool_data = (u8 *)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 235 AssertLog((state.compositor != NULL), "compositor has no wl_compositor\n");
168 Assert((void*)-1 != state->shm_pool_data); 236 AssertLog((state.shm != NULL), "compositor has no wl_shm\n");
169 Assert(state->shm_pool_data != NULL); 237 AssertLog((state.xdg_wm_base != NULL), "compositor has no xdg_wm_base\n");
170 state->shm_fd = fd; 238 if (state.seat == NULL) {
239 Log("no wl_seat: running without keyboard input\n");
240 }
241
242 // build the window: surface -> give it the xdg role -> make it a toplevel
243 state.surface = wl_compositor_create_surface(state.compositor);
244
245 state.xdg_surface = xdg_wm_base_get_xdg_surface(state.xdg_wm_base, state.surface);
246 xdg_surface_add_listener(state.xdg_surface, &wayland_xdg_surface_listener, &state);
247
248 state.xdg_toplevel = xdg_surface_get_toplevel(state.xdg_surface);
249 xdg_toplevel_add_listener(state.xdg_toplevel, &wayland_xdg_toplevel_listener, &state);
250 xdg_toplevel_set_title(state.xdg_toplevel, params->name);
251 xdg_toplevel_set_app_id(state.xdg_toplevel, params->name);
252
253 wl_surface_commit(state.surface);
254
255 //
256 // TODO: blocking is fine while the picture is static. for a game loop that renders
257 // every frame we'll instead use wl_display_dispatch_pending + a non blocking read
258 // (wl_display_prepare_read / wl_display_read_events with poll on wl_display_get_fd).
259 while (!state.should_close) {
260
261 if (wl_display_dispatch(state.display) == -1) {
262 Log("wayland connection lost\n");
263goto regular_exit;
264 }
265
266if(state.keys_down[KEY_W]) { Log("Move Forward"); }
267if(state.keys_down[KEY_A]) { Log("Move Left"); }
268 if(state.keys_down[KEY_D]) { Log("Move Right"); }
269 if(state.keys_down[KEY_S]) { Log("Move Back"); }
270if(state.keys_down[KEY_Q]) {
271goto cleanup_exit;
171} 272}
172 273
173 internal void wayland_handle_message(int fd, state_t *state, char **msg, 274}
174 uint64_t *msg_len) { 275
175 Assert(*msg_len >= 8); 276
176 277 cleanup_exit:
177 u32 object_id = buf_read_u32(msg, msg_len);
178 Assert(object_id <= wayland_current_id);
179
180 u16 opcode = buf_read_u16(msg, msg_len);
181
182 u16 announced_size = buf_read_u16(msg, msg_len);
183 Assert(roundup_4(announced_size) <= announced_size);
184
185 u32 header_size = sizeof(object_id) + sizeof(opcode) + sizeof(announced_size);
186Assert(announced_size <= header_size + *msg_len);
187 278
188 if (object_id == state->wl_registry && 279 if (state.keyboard) wl_keyboard_destroy(state.keyboard);
189 opcode == wayland_wl_registry_event_global) { 280 if (state.buffer) wl_buffer_destroy(state.buffer);
190 // TODO 281 if (state.pixels) munmap(state.pixels, state.size);
191 } 282 xdg_toplevel_destroy(state.xdg_toplevel);
192 // Following: Lots of `if (opcode == ...) {... } else if (opcode = ...) { ... } [...]` 283 xdg_surface_destroy(state.xdg_surface);
284 wl_surface_destroy(state.surface);
285 if (state.seat) wl_seat_destroy(state.seat);
286 xdg_wm_base_destroy(state.xdg_wm_base);
287 wl_shm_destroy(state.shm);
288 wl_compositor_destroy(state.compositor);
289 wl_registry_destroy(state.registry);
290return;
291
292regular_exit:
293return;
193} 294}