1 /* Portions of this file taken from sway, MIT licensed */
2 #include <assert.h>
3 #include <cairo/cairo.h>
4 #include <errno.h>
5 #include <fcntl.h>
6 #include <pango/pangocairo.h>
7 #include <stdbool.h>
8 #include <string.h>
9 #include <sys/mman.h>
10 #include <time.h>
11 #include <unistd.h>
12 #include <wayland-client.h>
13 #include "shm.h"
14 
randname(char * buf)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 
create_shm_file(void)25 int create_shm_file(void) {
26 	int retries = 100;
27 	do {
28 		char name[] = "/wl_shm-XXXXXX";
29 		randname(name + strlen(name) - 6);
30 
31 		--retries;
32 		// CLOEXEC is guaranteed to be set by shm_open
33 		int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
34 		if (fd >= 0) {
35 			shm_unlink(name);
36 			return fd;
37 		}
38 	} while (retries > 0 && errno == EEXIST);
39 
40 	return -1;
41 }
42 
allocate_shm_file(size_t size)43 int allocate_shm_file(size_t size) {
44 	int fd = create_shm_file();
45 	if (fd < 0) {
46 		return -1;
47 	}
48 
49 	int ret;
50 	do {
51 		ret = ftruncate(fd, size);
52 	} while (ret < 0 && errno == EINTR);
53 	if (ret < 0) {
54 		close(fd);
55 		return -1;
56 	}
57 
58 	return fd;
59 }
60 
buffer_release(void * data,struct wl_buffer * wl_buffer)61 static void buffer_release(void *data, struct wl_buffer *wl_buffer) {
62 	struct pool_buffer *buffer = data;
63 	buffer->busy = false;
64 }
65 
66 static const struct wl_buffer_listener buffer_listener = {
67 	.release = buffer_release
68 };
69 
create_buffer(struct wl_shm * shm,struct pool_buffer * buf,int32_t width,int32_t height,uint32_t format)70 static struct pool_buffer *create_buffer(struct wl_shm *shm,
71 		struct pool_buffer *buf, int32_t width, int32_t height,
72 		uint32_t format) {
73 	uint32_t stride = width * 4;
74 	size_t size = stride * height;
75 
76 	int fd = allocate_shm_file(size);
77 	assert(fd != -1);
78 	void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
79 	struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);
80 	buf->buffer = wl_shm_pool_create_buffer(pool, 0,
81 			width, height, stride, format);
82 	wl_shm_pool_destroy(pool);
83 	close(fd);
84 
85 	buf->size = size;
86 	buf->width = width;
87 	buf->height = height;
88 	buf->data = data;
89 	buf->surface = cairo_image_surface_create_for_data(data,
90 			CAIRO_FORMAT_ARGB32, width, height, stride);
91 	buf->cairo = cairo_create(buf->surface);
92 	buf->pango = pango_cairo_create_context(buf->cairo);
93 
94 	wl_buffer_add_listener(buf->buffer, &buffer_listener, buf);
95 	return buf;
96 }
97 
destroy_buffer(struct pool_buffer * buffer)98 void destroy_buffer(struct pool_buffer *buffer) {
99 	if (buffer->buffer) {
100 		wl_buffer_destroy(buffer->buffer);
101 	}
102 	if (buffer->cairo) {
103 		cairo_destroy(buffer->cairo);
104 	}
105 	if (buffer->surface) {
106 		cairo_surface_destroy(buffer->surface);
107 	}
108 	if (buffer->pango) {
109 		g_object_unref(buffer->pango);
110 	}
111 	if (buffer->data) {
112 		munmap(buffer->data, buffer->size);
113 	}
114 	memset(buffer, 0, sizeof(struct pool_buffer));
115 }
116 
get_next_buffer(struct wl_shm * shm,struct pool_buffer pool[static2],uint32_t width,uint32_t height)117 struct pool_buffer *get_next_buffer(struct wl_shm *shm,
118 		struct pool_buffer pool[static 2], uint32_t width, uint32_t height) {
119 	struct pool_buffer *buffer = NULL;
120 
121 	for (size_t i = 0; i < 2; ++i) {
122 		if (pool[i].busy) {
123 			continue;
124 		}
125 		buffer = &pool[i];
126 	}
127 
128 	if (!buffer) {
129 		return NULL;
130 	}
131 
132 	if (buffer->width != width || buffer->height != height) {
133 		destroy_buffer(buffer);
134 	}
135 
136 	if (!buffer->buffer) {
137 		if (!create_buffer(shm, buffer, width, height,
138 					WL_SHM_FORMAT_ARGB8888)) {
139 			return NULL;
140 		}
141 	}
142 	buffer->busy = true;
143 	return buffer;
144 }
145