1 #define _GNU_SOURCE
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <unistd.h>
6 #include "context.h"
7 #include <EGL/eglext.h>
8 #include "linux-dmabuf-unstable-v1.h"
9
10 // --- dmabuf_frame event handlers ---
11
dmabuf_frame_event_frame(void * data,struct zwlr_export_dmabuf_frame_v1 * frame,uint32_t width,uint32_t height,uint32_t x,uint32_t y,uint32_t buffer_flags,uint32_t frame_flags,uint32_t format,uint32_t mod_high,uint32_t mod_low,uint32_t num_objects)12 static void dmabuf_frame_event_frame(
13 void * data, struct zwlr_export_dmabuf_frame_v1 * frame,
14 uint32_t width, uint32_t height, uint32_t x, uint32_t y,
15 uint32_t buffer_flags, uint32_t frame_flags, uint32_t format,
16 uint32_t mod_high, uint32_t mod_low, uint32_t num_objects
17 ) {
18 ctx_t * ctx = (ctx_t *)data;
19 log_debug(ctx, "dmabuf_frame: received %dx%d frame with %d objects\n", width, height, num_objects);
20 if (ctx->mirror.state != STATE_WAIT_FRAME) {
21 log_error("dmabuf_frame: got frame while in state %d\n", ctx->mirror.state);
22 exit_fail(ctx);
23 } else if (num_objects > 4) {
24 log_error("dmabuf_frame: got frame with more than 4 objects\n");
25 exit_fail(ctx);
26 }
27
28 uint32_t unhandled_buffer_flags = buffer_flags & ~(
29 ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT
30 );
31 if (unhandled_buffer_flags != 0) {
32 log_error("dmabuf_frame: frame uses unhandled buffer flags, buffer_flags = {");
33 if (buffer_flags & ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT) fprintf(stderr, "Y_INVERT, ");
34 if (buffer_flags & ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_INTERLACED) fprintf(stderr, "INTERLACED, ");
35 if (buffer_flags & ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_BOTTOM_FIRST) fprintf(stderr, "BOTTOM_FIRST, ");
36 fprintf(stderr, "}\n");
37 }
38
39 uint32_t unhandled_frame_flags = frame_flags & ~(
40 ZWLR_EXPORT_DMABUF_FRAME_V1_FLAGS_TRANSIENT
41 );
42 if (unhandled_frame_flags != 0) {
43 log_error("dmabuf_frame: frame uses unhandled frame flags, frame_flags = {");
44 if (frame_flags & ZWLR_EXPORT_DMABUF_FRAME_V1_FLAGS_TRANSIENT) fprintf(stderr, "TRANSIENT, ");
45 fprintf(stderr, "}\n");
46 }
47
48 ctx->mirror.width = width;
49 ctx->mirror.height = height;
50 ctx->mirror.x = x;
51 ctx->mirror.y = y;
52 ctx->mirror.buffer_flags = buffer_flags;
53 ctx->mirror.frame_flags = frame_flags;
54 ctx->mirror.format = format;
55 ctx->mirror.modifier_high = mod_high;
56 ctx->mirror.modifier_low = mod_low;
57 ctx->mirror.num_objects = num_objects;
58
59 ctx->mirror.state = STATE_WAIT_OBJECTS;
60 ctx->mirror.processed_objects = 0;
61
62 (void)frame;
63 }
64
dmabuf_frame_event_object(void * data,struct zwlr_export_dmabuf_frame_v1 * frame,uint32_t index,int32_t fd,uint32_t size,uint32_t offset,uint32_t stride,uint32_t plane_index)65 static void dmabuf_frame_event_object(
66 void * data, struct zwlr_export_dmabuf_frame_v1 * frame,
67 uint32_t index, int32_t fd, uint32_t size,
68 uint32_t offset, uint32_t stride, uint32_t plane_index
69 ) {
70 ctx_t * ctx = (ctx_t *)data;
71 log_debug(ctx, "dmabuf_frame: received %d byte object with plane_index %d\n", size, plane_index);
72 if (ctx->mirror.state != STATE_WAIT_OBJECTS) {
73 log_error("dmabuf_frame: got object while in state %d\n", ctx->mirror.state);
74 exit_fail(ctx);
75 } else if (index >= ctx->mirror.num_objects) {
76 log_error("dmabuf_frame: got object with out-of-bounds index %d\n", index);
77 exit_fail(ctx);
78 }
79
80 ctx->mirror.objects[index].fd = fd;
81 ctx->mirror.objects[index].size = size;
82 ctx->mirror.objects[index].offset = offset;
83 ctx->mirror.objects[index].stride = stride;
84 ctx->mirror.objects[index].plane_index = plane_index;
85
86 ctx->mirror.processed_objects++;
87 if (ctx->mirror.processed_objects == ctx->mirror.num_objects) {
88 ctx->mirror.state = STATE_WAIT_READY;
89 }
90
91 (void)frame;
92 }
93
dmabuf_frame_event_ready(void * data,struct zwlr_export_dmabuf_frame_v1 * frame,uint32_t sec_hi,uint32_t sec_lo,uint32_t nsec)94 static void dmabuf_frame_event_ready(
95 void * data, struct zwlr_export_dmabuf_frame_v1 * frame,
96 uint32_t sec_hi, uint32_t sec_lo, uint32_t nsec
97 ) {
98 ctx_t * ctx = (ctx_t *)data;
99 log_debug(ctx, "dmabuf_frame: frame is ready\n");
100 if (ctx->mirror.state != STATE_WAIT_READY) {
101 log_error("dmabuf_frame: got ready while in state %d\n", ctx->mirror.state);
102 exit_fail(ctx);
103 }
104
105 if (ctx->mirror.frame_image != EGL_NO_IMAGE) {
106 log_debug(ctx, "dmabuf_frame: destroying old EGL image\n");
107 eglDestroyImage(ctx->egl.display, ctx->mirror.frame_image);
108 }
109
110 log_debug(ctx, "dmabuf_frame: creating EGL image from dmabuf\n");
111 int i = 0;
112 EGLAttrib image_attribs[6 + 10 * 4 + 1];
113
114 image_attribs[i++] = EGL_WIDTH;
115 image_attribs[i++] = ctx->mirror.width;
116 image_attribs[i++] = EGL_HEIGHT;
117 image_attribs[i++] = ctx->mirror.height;
118 image_attribs[i++] = EGL_LINUX_DRM_FOURCC_EXT;
119 image_attribs[i++] = ctx->mirror.format;
120
121 if (ctx->mirror.num_objects >= 1) {
122 image_attribs[i++] = EGL_DMA_BUF_PLANE0_FD_EXT;
123 image_attribs[i++] = ctx->mirror.objects[0].fd;
124 image_attribs[i++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT;
125 image_attribs[i++] = ctx->mirror.objects[0].offset;
126 image_attribs[i++] = EGL_DMA_BUF_PLANE0_PITCH_EXT;
127 image_attribs[i++] = ctx->mirror.objects[0].stride;
128 image_attribs[i++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT;
129 image_attribs[i++] = ctx->mirror.modifier_low;
130 image_attribs[i++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT;
131 image_attribs[i++] = ctx->mirror.modifier_high;
132 }
133
134 if (ctx->mirror.num_objects >= 2) {
135 image_attribs[i++] = EGL_DMA_BUF_PLANE1_FD_EXT;
136 image_attribs[i++] = ctx->mirror.objects[1].fd;
137 image_attribs[i++] = EGL_DMA_BUF_PLANE1_OFFSET_EXT;
138 image_attribs[i++] = ctx->mirror.objects[1].offset;
139 image_attribs[i++] = EGL_DMA_BUF_PLANE1_PITCH_EXT;
140 image_attribs[i++] = ctx->mirror.objects[1].stride;
141 image_attribs[i++] = EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT;
142 image_attribs[i++] = ctx->mirror.modifier_low;
143 image_attribs[i++] = EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT;
144 image_attribs[i++] = ctx->mirror.modifier_high;
145 }
146
147 if (ctx->mirror.num_objects >= 3) {
148 image_attribs[i++] = EGL_DMA_BUF_PLANE2_FD_EXT;
149 image_attribs[i++] = ctx->mirror.objects[2].fd;
150 image_attribs[i++] = EGL_DMA_BUF_PLANE2_OFFSET_EXT;
151 image_attribs[i++] = ctx->mirror.objects[2].offset;
152 image_attribs[i++] = EGL_DMA_BUF_PLANE2_PITCH_EXT;
153 image_attribs[i++] = ctx->mirror.objects[2].stride;
154 image_attribs[i++] = EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT;
155 image_attribs[i++] = ctx->mirror.modifier_low;
156 image_attribs[i++] = EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT;
157 image_attribs[i++] = ctx->mirror.modifier_high;
158 }
159
160 if (ctx->mirror.num_objects >= 4) {
161 image_attribs[i++] = EGL_DMA_BUF_PLANE3_FD_EXT;
162 image_attribs[i++] = ctx->mirror.objects[3].fd;
163 image_attribs[i++] = EGL_DMA_BUF_PLANE3_OFFSET_EXT;
164 image_attribs[i++] = ctx->mirror.objects[3].offset;
165 image_attribs[i++] = EGL_DMA_BUF_PLANE3_PITCH_EXT;
166 image_attribs[i++] = ctx->mirror.objects[3].stride;
167 image_attribs[i++] = EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT;
168 image_attribs[i++] = ctx->mirror.modifier_low;
169 image_attribs[i++] = EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT;
170 image_attribs[i++] = ctx->mirror.modifier_high;
171 }
172
173 image_attribs[i++] = EGL_NONE;
174
175 ctx->mirror.frame_image = eglCreateImage(ctx->egl.display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, image_attribs);
176 if (ctx->mirror.frame_image == EGL_NO_IMAGE) {
177 log_error("dmabuf_frame: failed to create EGL image from dmabuf\n");
178 exit_fail(ctx);
179 }
180
181 log_debug(ctx, "dmabuf_frame: binding image to EGL texture\n");
182 glBindTexture(GL_TEXTURE_2D, ctx->egl.texture);
183 ctx->egl.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, ctx->mirror.frame_image);
184 ctx->egl.texture_initialized = true;
185
186 log_debug(ctx, "dmabuf_frame: setting buffer flags\n");
187 ctx->mirror.invert_y = ctx->mirror.buffer_flags & ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT;
188
189 log_debug(ctx, "dmabuf_frame: setting frame aspect ratio\n");
190 ctx->egl.width = ctx->mirror.width;
191 ctx->egl.height = ctx->mirror.height;
192 resize_viewport_egl(ctx);
193
194 log_debug(ctx, "dmabuf_frame: closing dmabuf fds\n");
195 for (unsigned int i = 0; i < ctx->mirror.num_objects; i++) {
196 close(ctx->mirror.objects[i].fd);
197 ctx->mirror.objects[i].fd = -1;
198 }
199
200 zwlr_export_dmabuf_frame_v1_destroy(ctx->mirror.frame);
201 ctx->mirror.frame = NULL;
202 ctx->mirror.state = STATE_READY;
203
204 (void)frame;
205 (void)sec_hi;
206 (void)sec_lo;
207 (void)nsec;
208 }
209
dmabuf_frame_event_cancel(void * data,struct zwlr_export_dmabuf_frame_v1 * frame,enum zwlr_export_dmabuf_frame_v1_cancel_reason reason)210 static void dmabuf_frame_event_cancel(
211 void * data, struct zwlr_export_dmabuf_frame_v1 * frame,
212 enum zwlr_export_dmabuf_frame_v1_cancel_reason reason
213 ) {
214 ctx_t * ctx = (ctx_t *)data;
215 log_debug(ctx, "dmabuf_frame: frame was canceled\n");
216
217 zwlr_export_dmabuf_frame_v1_destroy(ctx->mirror.frame);
218 ctx->mirror.frame = NULL;
219 ctx->mirror.state = STATE_CANCELED;
220
221 switch (reason) {
222 case ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_PERMANENT:
223 log_error("dmabuf_frame: permanent cancellation\n");
224 exit_fail(ctx);
225 break;
226
227 case ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_TEMPORARY:
228 log_debug(ctx, "dmabuf_frame: temporary cancellation\n");
229 break;
230
231 case ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_RESIZING:
232 log_debug(ctx, "dmabuf_frame: cancellation due to output resize\n");
233 break;
234
235 default:
236 log_error("dmabuf_frame: unknown cancellation reason %d\n", reason);
237 exit_fail(ctx);
238 break;
239 }
240
241 log_debug(ctx, "dmabuf_frame: closing files\n");
242 for (unsigned int i = 0; i < ctx->mirror.num_objects; i++) {
243 if (ctx->mirror.objects[i].fd != -1) close(ctx->mirror.objects[i].fd);
244 ctx->mirror.objects[i].fd = -1;
245 }
246
247 (void)frame;
248 }
249
250 static const struct zwlr_export_dmabuf_frame_v1_listener dmabuf_frame_listener = {
251 .frame = dmabuf_frame_event_frame,
252 .object = dmabuf_frame_event_object,
253 .ready = dmabuf_frame_event_ready,
254 .cancel = dmabuf_frame_event_cancel
255 };
256
257 // --- frame_callback event handlers ---
258
259 static const struct wl_callback_listener frame_callback_listener;
260
frame_callback_event_done(void * data,struct wl_callback * frame_callback,uint32_t msec)261 static void frame_callback_event_done(
262 void * data, struct wl_callback * frame_callback, uint32_t msec
263 ) {
264 ctx_t * ctx = (ctx_t *)data;
265
266 wl_callback_destroy(ctx->mirror.frame_callback);
267 ctx->mirror.frame_callback = NULL;
268
269 log_debug(ctx, "frame_callback: requesting next callback\n");
270 ctx->mirror.frame_callback = wl_surface_frame(ctx->wl.surface);
271 wl_callback_add_listener(ctx->mirror.frame_callback, &frame_callback_listener, (void *)ctx);
272
273 log_debug(ctx, "frame_callback: rendering frame\n");
274 draw_texture_egl(ctx);
275
276 log_debug(ctx, "frame_callback: swapping buffers\n");
277 eglSwapInterval(ctx->egl.display, 0);
278 if (eglSwapBuffers(ctx->egl.display, ctx->egl.surface) != EGL_TRUE) {
279 log_error("frame_callback: failed to swap buffers\n");
280 exit_fail(ctx);
281 }
282
283 if (ctx->mirror.state != STATE_WAIT_FRAME) {
284 log_debug(ctx, "frame_callback: clearing dmabuf_frame state\n");
285 ctx->mirror.width = 0;
286 ctx->mirror.height = 0;
287 ctx->mirror.x = 0;
288 ctx->mirror.y = 0;
289 ctx->mirror.buffer_flags = 0;
290 ctx->mirror.frame_flags = 0;
291 ctx->mirror.modifier_high = 0;
292 ctx->mirror.modifier_low = 0;
293 ctx->mirror.format = 0;
294 ctx->mirror.num_objects = 0;
295
296 dmabuf_object_t empty_obj = {
297 .fd = -1,
298 .size = 0,
299 .offset = 0,
300 .stride = 0,
301 .plane_index = 0
302 };
303 ctx->mirror.objects[0] = empty_obj;
304 ctx->mirror.objects[1] = empty_obj;
305 ctx->mirror.objects[2] = empty_obj;
306 ctx->mirror.objects[3] = empty_obj;
307
308 ctx->mirror.state = STATE_WAIT_FRAME;
309 ctx->mirror.processed_objects = 0;
310
311 log_debug(ctx, "frame_callback: creating wlr_dmabuf_export_frame\n");
312 ctx->mirror.frame = zwlr_export_dmabuf_manager_v1_capture_output(
313 ctx->wl.dmabuf_manager, ctx->opt.show_cursor, ctx->mirror.current_target->output
314 );
315 if (ctx->mirror.frame == NULL) {
316 log_error("frame_callback: failed to create wlr_dmabuf_export_frame\n");
317 exit_fail(ctx);
318 }
319
320 log_debug(ctx, "frame_callback: adding dmabuf_frame event listener\n");
321 zwlr_export_dmabuf_frame_v1_add_listener(ctx->mirror.frame, &dmabuf_frame_listener, (void *)ctx);
322 }
323
324 (void)frame_callback;
325 (void)msec;
326 }
327
328 static const struct wl_callback_listener frame_callback_listener = {
329 .done = frame_callback_event_done
330 };
331
332 // --- init_mirror ---
333
init_mirror(ctx_t * ctx)334 void init_mirror(ctx_t * ctx) {
335 log_debug(ctx, "init_mirror: initializing context structure\n");
336
337 ctx->mirror.current_target = NULL;
338 ctx->mirror.frame_callback = NULL;
339 ctx->mirror.frame = NULL;
340 ctx->mirror.current_region = (region_t){ .x = 0, .y = 0, .width = 0, .height = 0 };
341 ctx->mirror.invert_y = false;
342
343 ctx->mirror.width = 0;
344 ctx->mirror.height = 0;
345 ctx->mirror.x = 0;
346 ctx->mirror.y = 0;
347 ctx->mirror.buffer_flags = 0;
348 ctx->mirror.frame_flags = 0;
349 ctx->mirror.modifier_high = 0;
350 ctx->mirror.modifier_low = 0;
351 ctx->mirror.format = 0;
352 ctx->mirror.num_objects = 0;
353
354 dmabuf_object_t empty_obj = {
355 .fd = -1,
356 .size = 0,
357 .offset = 0,
358 .stride = 0,
359 .plane_index = 0
360 };
361 ctx->mirror.objects[0] = empty_obj;
362 ctx->mirror.objects[1] = empty_obj;
363 ctx->mirror.objects[2] = empty_obj;
364 ctx->mirror.objects[3] = empty_obj;
365
366 ctx->mirror.frame_image = EGL_NO_IMAGE;
367
368 ctx->mirror.state = STATE_CANCELED;
369 ctx->mirror.processed_objects = 0;
370 ctx->mirror.initialized = true;
371
372 if (!find_output_opt(ctx, &ctx->mirror.current_target, &ctx->mirror.current_region)) {
373 log_error("init_mirror: failed to find output\n");
374 exit_fail(ctx);
375 }
376
377 update_options_mirror(ctx);
378
379 log_debug(ctx, "init_mirror: requesting render callback\n");
380 ctx->mirror.frame_callback = wl_surface_frame(ctx->wl.surface);
381 wl_callback_add_listener(ctx->mirror.frame_callback, &frame_callback_listener, (void *)ctx);
382 }
383
384 // --- output_removed_mirror ---
385
output_removed_mirror(ctx_t * ctx,output_list_node_t * node)386 void output_removed_mirror(ctx_t * ctx, output_list_node_t * node) {
387 if (!ctx->mirror.initialized) return;
388 if (ctx->mirror.current_target == NULL) return;
389 if (ctx->mirror.current_target != node) return;
390
391 log_error("output_removed_mirror: output disappeared, closing\n");
392 exit_fail(ctx);
393 }
394
395 // --- update_options_mirror ---
396
update_options_mirror(ctx_t * ctx)397 void update_options_mirror(ctx_t * ctx) {
398 log_debug(ctx, "init_mirror: formatting window title\n");
399 char * title = NULL;
400 int status = asprintf(&title, "Wayland Output Mirror for %s", ctx->mirror.current_target->name);
401 if (status == -1) {
402 log_error("init_mirror: failed to format window title\n");
403 exit_fail(ctx);
404 }
405
406 log_debug(ctx, "init_mirror: setting window title\n");
407 xdg_toplevel_set_title(ctx->wl.xdg_toplevel, title);
408 free(title);
409 }
410
411 // --- cleanup_mirror ---
412
cleanup_mirror(ctx_t * ctx)413 void cleanup_mirror(ctx_t *ctx) {
414 if (!ctx->mirror.initialized) return;
415
416 log_debug(ctx, "cleanup_mirror: destroying mirror objects\n");
417 if (ctx->mirror.frame_callback != NULL) wl_callback_destroy(ctx->mirror.frame_callback);
418 if (ctx->mirror.frame != NULL) zwlr_export_dmabuf_frame_v1_destroy(ctx->mirror.frame);
419 if (ctx->mirror.frame_image != EGL_NO_IMAGE) eglDestroyImage(ctx->egl.display, ctx->mirror.frame_image);
420
421 for (int i = 0; i < 4; i++) {
422 if (ctx->mirror.objects[i].fd != -1) close(ctx->mirror.objects[i].fd);
423 ctx->mirror.objects[i].fd = -1;
424 }
425
426 ctx->mirror.initialized = false;
427 }
428