Atlas - SDL_zenitydialog.c

Home / ext / SDL / src / dialog / unix Lines: 6 | Size: 12324 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 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#include "SDL_internal.h" 22 23#include "../SDL_dialog_utils.h" 24#include "SDL_zenitydialog.h" 25#include "SDL_zenitymessagebox.h" 26 27#define X11_HANDLE_MAX_WIDTH 28 28typedef struct 29{ 30 SDL_DialogFileCallback callback; 31 void *userdata; 32 void *argv; 33 34 /* Zenity only works with X11 handles apparently */ 35 char x11_window_handle[X11_HANDLE_MAX_WIDTH]; 36 /* These are part of argv, but are tracked separately for deallocation purposes */ 37 int nfilters; 38 char **filters_slice; 39 char *filename; 40 char *title; 41 char *accept; 42 char *cancel; 43} zenityArgs; 44 45static char *zenity_clean_name(const char *name) 46{ 47 char *newname = SDL_strdup(name); 48 49 /* Filter out "|", which Zenity considers a special character. Let's hope 50 there aren't others. TODO: find something better. */ 51 for (char *c = newname; *c; c++) { 52 if (*c == '|') { 53 // Zenity doesn't support escaping with '\' 54 *c = '/'; 55 } 56 } 57 58 return newname; 59} 60 61static bool get_x11_window_handle(SDL_PropertiesID props, char *out) 62{ 63 SDL_Window *window = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL); 64 if (!window) { 65 return false; 66 } 67 SDL_PropertiesID window_props = SDL_GetWindowProperties(window); 68 if (!window_props) { 69 return false; 70 } 71 Uint64 handle = (Uint64)SDL_GetNumberProperty(window_props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0); 72 if (!handle) { 73 return false; 74 } 75 if (SDL_snprintf(out, X11_HANDLE_MAX_WIDTH, "0x%" SDL_PRIx64, handle) >= X11_HANDLE_MAX_WIDTH) { 76 return false; 77 }; 78 return true; 79} 80 81/* Exec call format: 82 * 83 * zenity --file-selection --separator=\n [--multiple] 84 * [--directory] [--save --confirm-overwrite] 85 * [--filename FILENAME] [--modal --attach 0x11w1nd0w] 86 * [--title TITLE] [--ok-label ACCEPT] 87 * [--cancel-label CANCEL] 88 * [--file-filter=Filter Name | *.filt *.fn ...]... 89 */ 90static zenityArgs *create_zenity_args(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props) 91{ 92 zenityArgs *args = SDL_calloc(1, sizeof(*args)); 93 int zenity_major = 0, zenity_minor = 0; 94 if (!args) { 95 return NULL; 96 } 97 args->callback = callback; 98 args->userdata = userdata; 99 args->nfilters = SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0); 100 101 const char **argv = SDL_malloc( 102 sizeof(*argv) * (3 /* zenity --file-selection --separator=\n */ 103 + 1 /* --multiple */ 104 + 2 /* --directory | --save --confirm-overwrite */ 105 + 2 /* --filename [file] */ 106 + 3 /* --modal --attach [handle] */ 107 + 2 /* --title [title] */ 108 + 2 /* --ok-label [label] */ 109 + 2 /* --cancel-label [label] */ 110 + args->nfilters + 1 /* NULL */)); 111 if (!argv) { 112 goto cleanup; 113 } 114 args->argv = argv; 115 116 SDL_get_zenity_version(&zenity_major, &zenity_minor); 117 118 /* Properties can be destroyed as soon as the function returns; copy over what we need. */ 119#define COPY_STRING_PROPERTY(dst, prop) \ 120 { \ 121 const char *str = SDL_GetStringProperty(props, prop, NULL); \ 122 if (str) { \ 123 dst = SDL_strdup(str); \ 124 if (!dst) { \ 125 goto cleanup; \ 126 } \ 127 } \ 128 } 129 130 COPY_STRING_PROPERTY(args->filename, SDL_PROP_FILE_DIALOG_LOCATION_STRING); 131 COPY_STRING_PROPERTY(args->title, SDL_PROP_FILE_DIALOG_TITLE_STRING); 132 COPY_STRING_PROPERTY(args->accept, SDL_PROP_FILE_DIALOG_ACCEPT_STRING); 133 COPY_STRING_PROPERTY(args->cancel, SDL_PROP_FILE_DIALOG_CANCEL_STRING); 134#undef COPY_STRING_PROPERTY 135 136 // ARGV PASS 137 int argc = 0; 138 argv[argc++] = "zenity"; 139 argv[argc++] = "--file-selection"; 140 argv[argc++] = "--separator=\n"; 141 142 if (SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false)) { 143 argv[argc++] = "--multiple"; 144 } 145 146 switch (type) { 147 case SDL_FILEDIALOG_OPENFILE: 148 break; 149 150 case SDL_FILEDIALOG_SAVEFILE: 151 argv[argc++] = "--save"; 152 /* Asking before overwriting while saving seems like a sane default */ 153 argv[argc++] = "--confirm-overwrite"; 154 break; 155 156 case SDL_FILEDIALOG_OPENFOLDER: 157 argv[argc++] = "--directory"; 158 break; 159 }; 160 161 if (args->filename) { 162 argv[argc++] = "--filename"; 163 argv[argc++] = args->filename; 164 } 165 166 if (get_x11_window_handle(props, args->x11_window_handle) && 167 (zenity_major > 3 || (zenity_major == 3 && zenity_minor >= 6))) { 168 argv[argc++] = "--modal"; 169 argv[argc++] = "--attach"; 170 argv[argc++] = args->x11_window_handle; 171 } 172 173 if (args->title) { 174 argv[argc++] = "--title"; 175 argv[argc++] = args->title; 176 } 177 178 if (args->accept) { 179 argv[argc++] = "--ok-label"; 180 argv[argc++] = args->accept; 181 } 182 183 if (args->cancel) { 184 argv[argc++] = "--cancel-label"; 185 argv[argc++] = args->cancel; 186 } 187 188 const SDL_DialogFileFilter *filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL); 189 if (filters) { 190 args->filters_slice = (char **)&argv[argc]; 191 for (int i = 0; i < args->nfilters; i++) { 192 char *filter_str = convert_filter(filters[i], 193 zenity_clean_name, 194 "--file-filter=", " | ", "", 195 "*.", " *.", ""); 196 197 if (!filter_str) { 198 while (i--) { 199 SDL_free(args->filters_slice[i]); 200 } 201 goto cleanup; 202 } 203 204 args->filters_slice[i] = filter_str; 205 } 206 argc += args->nfilters; 207 } 208 209 argv[argc] = NULL; 210 return args; 211 212cleanup: 213 SDL_free(args->filename); 214 SDL_free(args->title); 215 SDL_free(args->accept); 216 SDL_free(args->cancel); 217 SDL_free(argv); 218 SDL_free(args); 219 return NULL; 220} 221 222// TODO: Zenity survives termination of the parent 223 224static void run_zenity(SDL_DialogFileCallback callback, void *userdata, void *argv) 225{ 226 SDL_Process *process = NULL; 227 SDL_Environment *env = NULL; 228 int status = -1; 229 size_t bytes_read = 0; 230 char *container = NULL; 231 size_t narray = 1; 232 char **array = NULL; 233 bool result = false; 234 235 env = SDL_CreateEnvironment(true); 236 if (!env) { 237 goto done; 238 } 239 240 /* Recent versions of Zenity have different exit codes, but picks up 241 different codes from the environment */ 242 SDL_SetEnvironmentVariable(env, "ZENITY_OK", "0", true); 243 SDL_SetEnvironmentVariable(env, "ZENITY_CANCEL", "1", true); 244 SDL_SetEnvironmentVariable(env, "ZENITY_ESC", "1", true); 245 SDL_SetEnvironmentVariable(env, "ZENITY_EXTRA", "2", true); 246 SDL_SetEnvironmentVariable(env, "ZENITY_ERROR", "2", true); 247 SDL_SetEnvironmentVariable(env, "ZENITY_TIMEOUT", "2", true); 248 249 SDL_PropertiesID props = SDL_CreateProperties(); 250 SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, argv); 251 SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER, env); 252 SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_NULL); 253 SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP); 254 SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER, SDL_PROCESS_STDIO_NULL); 255 process = SDL_CreateProcessWithProperties(props); 256 SDL_DestroyProperties(props); 257 if (!process) { 258 goto done; 259 } 260 261 container = SDL_ReadProcess(process, &bytes_read, &status); 262 if (!container) { 263 goto done; 264 } 265 266 array = (char **)SDL_malloc((narray + 1) * sizeof(char *)); 267 if (!array) { 268 goto done; 269 } 270 array[0] = container; 271 array[1] = NULL; 272 273 for (int i = 0; i < bytes_read; i++) { 274 if (container[i] == '\n') { 275 container[i] = '\0'; 276 // Reading from a process often leaves a trailing \n, so ignore the last one 277 if (i < bytes_read - 1) { 278 array[narray] = container + i + 1; 279 narray++; 280 char **new_array = (char **)SDL_realloc(array, (narray + 1) * sizeof(char *)); 281 if (!new_array) { 282 goto done; 283 } 284 array = new_array; 285 array[narray] = NULL; 286 } 287 } 288 } 289 290 // 0 = the user chose one or more files, 1 = the user canceled the dialog 291 if (status == 0 || status == 1) { 292 callback(userdata, (const char *const *)array, -1); 293 } else { 294 SDL_SetError("Could not run zenity: exit code %d", status); 295 callback(userdata, NULL, -1); 296 } 297 298 result = true; 299 300done: 301 SDL_free(array); 302 SDL_free(container); 303 SDL_DestroyEnvironment(env); 304 SDL_DestroyProcess(process); 305 306 if (!result) { 307 callback(userdata, NULL, -1); 308 } 309} 310 311static void free_zenity_args(zenityArgs *args) 312{ 313 if (args->filters_slice) { 314 for (int i = 0; i < args->nfilters; i++) { 315 SDL_free(args->filters_slice[i]); 316 } 317 } 318 SDL_free(args->filename); 319 SDL_free(args->title); 320 SDL_free(args->accept); 321 SDL_free(args->cancel); 322 SDL_free(args->argv); 323 SDL_free(args); 324} 325 326static int run_zenity_thread(void *ptr) 327{ 328 zenityArgs *args = ptr; 329 run_zenity(args->callback, args->userdata, args->argv); 330 free_zenity_args(args); 331 return 0; 332} 333 334void SDL_Zenity_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props) 335{ 336 zenityArgs *args = create_zenity_args(type, callback, userdata, props); 337 if (!args) { 338 callback(userdata, NULL, -1); 339 return; 340 } 341 342 SDL_Thread *thread = SDL_CreateThread(run_zenity_thread, "SDL_ZenityFileDialog", (void *)args); 343 344 if (!thread) { 345 free_zenity_args(args); 346 callback(userdata, NULL, -1); 347 return; 348 } 349 350 SDL_DetachThread(thread); 351} 352 353bool SDL_Zenity_detect(void) 354{ 355 const char *args[] = { 356 "zenity", "--version", NULL 357 }; 358 int status = -1; 359 360 SDL_PropertiesID props = SDL_CreateProperties(); 361 SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, args); 362 SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_NULL); 363 SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_NULL); 364 SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER, SDL_PROCESS_STDIO_NULL); 365 SDL_Process *process = SDL_CreateProcessWithProperties(props); 366 SDL_DestroyProperties(props); 367 if (process) { 368 SDL_WaitProcess(process, true, &status); 369 SDL_DestroyProcess(process); 370 } 371 return (status == 0); 372} 373
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.