1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23
24#ifdef SDL_VIDEO_DRIVER_WAYLAND
25
26#include <errno.h>
27#include <fcntl.h>
28#include <limits.h>
29#include <signal.h>
30#include <sys/mman.h>
31#include <unistd.h>
32
33#include "SDL_waylandshmbuffer.h"
34#include "SDL_waylandvideo.h"
35
36static bool SetTempFileSize(int fd, off_t size)
37{
38#ifdef HAVE_POSIX_FALLOCATE
39 sigset_t set, old_set;
40 int ret;
41
42 /* SIGALRM can potentially block a large posix_fallocate() operation
43 * from succeeding, so block it.
44 */
45 sigemptyset(&set);
46 sigaddset(&set, SIGALRM);
47 sigprocmask(SIG_BLOCK, &set, &old_set);
48
49 do {
50 ret = posix_fallocate(fd, 0, size);
51 } while (ret == EINTR);
52
53 sigprocmask(SIG_SETMASK, &old_set, NULL);
54
55 if (ret == 0) {
56 return true;
57 } else if (ret != EINVAL && errno != EOPNOTSUPP) {
58 return false;
59 }
60#endif
61
62 if (ftruncate(fd, size) < 0) {
63 return false;
64 }
65 return true;
66}
67
68static int CreateTempFD(off_t size)
69{
70 int fd;
71
72#ifdef HAVE_MEMFD_CREATE
73 fd = memfd_create("SDL", MFD_CLOEXEC | MFD_ALLOW_SEALING);
74 if (fd >= 0) {
75 fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
76 } else
77#endif
78 {
79 static const char template[] = "/sdl-shared-XXXXXX";
80 const char *xdg_path;
81 char tmp_path[PATH_MAX];
82
83 xdg_path = SDL_getenv("XDG_RUNTIME_DIR");
84 if (!xdg_path) {
85 return -1;
86 }
87
88 SDL_strlcpy(tmp_path, xdg_path, PATH_MAX);
89 SDL_strlcat(tmp_path, template, PATH_MAX);
90
91 fd = mkostemp(tmp_path, O_CLOEXEC);
92 if (fd < 0) {
93 return -1;
94 }
95
96 // Need to manually unlink the temp files, or they can persist after close and fill up the temp storage.
97 unlink(tmp_path);
98 }
99
100 if (!SetTempFileSize(fd, size)) {
101 close(fd);
102 return -1;
103 }
104
105 return fd;
106}
107
108static void buffer_handle_release(void *data, struct wl_buffer *wl_buffer)
109{
110 // NOP
111}
112
113static struct wl_buffer_listener buffer_listener = {
114 buffer_handle_release
115};
116
117bool Wayland_AllocSHMBuffer(int width, int height, struct Wayland_SHMBuffer *shmBuffer)
118{
119 SDL_VideoDevice *vd = SDL_GetVideoDevice();
120 SDL_VideoData *data = vd->internal;
121 struct wl_shm_pool *shm_pool;
122 const Uint32 SHM_FMT = WL_SHM_FORMAT_ARGB8888;
123
124 if (!shmBuffer) {
125 return SDL_InvalidParamError("shmBuffer");
126 }
127
128 const int stride = width * 4;
129 shmBuffer->shm_data_size = stride * height;
130
131 const int shm_fd = CreateTempFD(shmBuffer->shm_data_size);
132 if (shm_fd < 0) {
133 return SDL_SetError("Creating SHM buffer failed.");
134 }
135
136 shmBuffer->shm_data = mmap(NULL, shmBuffer->shm_data_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
137 if (shmBuffer->shm_data == MAP_FAILED) {
138 shmBuffer->shm_data = NULL;
139 close(shm_fd);
140 return SDL_SetError("mmap() failed.");
141 }
142
143 SDL_assert(shmBuffer->shm_data != NULL);
144
145 shm_pool = wl_shm_create_pool(data->shm, shm_fd, shmBuffer->shm_data_size);
146 shmBuffer->wl_buffer = wl_shm_pool_create_buffer(shm_pool, 0, width, height, stride, SHM_FMT);
147 wl_buffer_add_listener(shmBuffer->wl_buffer, &buffer_listener, shmBuffer);
148
149 wl_shm_pool_destroy(shm_pool);
150 close(shm_fd);
151
152 return true;
153}
154
155void Wayland_ReleaseSHMBuffer(struct Wayland_SHMBuffer *shmBuffer)
156{
157 if (shmBuffer) {
158 if (shmBuffer->wl_buffer) {
159 wl_buffer_destroy(shmBuffer->wl_buffer);
160 shmBuffer->wl_buffer = NULL;
161 }
162 if (shmBuffer->shm_data) {
163 munmap(shmBuffer->shm_data, shmBuffer->shm_data_size);
164 shmBuffer->shm_data = NULL;
165 }
166 shmBuffer->shm_data_size = 0;
167 }
168}
169
170#endif
171