SDL_zenitydialog.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. /*
  2. Simple DirectMedia Layer
  3. Copyright (C) 1997-2024 Sam Lantinga <[email protected]>
  4. This software is provided 'as-is', without any express or implied
  5. warranty. In no event will the authors be held liable for any damages
  6. arising from the use of this software.
  7. Permission is granted to anyone to use this software for any purpose,
  8. including commercial applications, and to alter it and redistribute it
  9. freely, subject to the following restrictions:
  10. 1. The origin of this software must not be misrepresented; you must not
  11. claim that you wrote the original software. If you use this software
  12. in a product, an acknowledgment in the product documentation would be
  13. appreciated but is not required.
  14. 2. Altered source versions must be plainly marked as such, and must not be
  15. misrepresented as being the original software.
  16. 3. This notice may not be removed or altered from any source distribution.
  17. */
  18. #include "SDL_internal.h"
  19. #include "../SDL_dialog_utils.h"
  20. #include <errno.h>
  21. #include <sys/types.h>
  22. #include <sys/wait.h>
  23. #include <unistd.h>
  24. typedef enum
  25. {
  26. ZENITY_MULTIPLE = 0x1,
  27. ZENITY_DIRECTORY = 0x2,
  28. ZENITY_SAVE = 0x4
  29. } zenityFlags;
  30. typedef struct
  31. {
  32. SDL_DialogFileCallback callback;
  33. void* userdata;
  34. const char* filename;
  35. const SDL_DialogFileFilter *filters;
  36. int nfilters;
  37. Uint32 flags;
  38. } zenityArgs;
  39. #define CLEAR_AND_RETURN() \
  40. { \
  41. while (--nextarg >= 0) { \
  42. SDL_free(argv[nextarg]); \
  43. } \
  44. SDL_free(argv); \
  45. return NULL; \
  46. }
  47. #define CHECK_OOM() \
  48. { \
  49. if (!argv[nextarg - 1]) { \
  50. CLEAR_AND_RETURN() \
  51. } \
  52. \
  53. if (nextarg > argc) { \
  54. SDL_SetError("Zenity dialog problem: argc (%d) < nextarg (%d)", \
  55. argc, nextarg); \
  56. CLEAR_AND_RETURN() \
  57. } \
  58. }
  59. char *zenity_clean_name(const char *name)
  60. {
  61. char *newname = SDL_strdup(name);
  62. /* Filter out "|", which Zenity considers a special character. Let's hope
  63. there aren't others. TODO: find something better. */
  64. for (char *c = newname; *c; c++) {
  65. if (*c == '|') {
  66. // Zenity doesn't support escaping with '\'
  67. *c = '/';
  68. }
  69. }
  70. return newname;
  71. }
  72. /* Exec call format:
  73. *
  74. * /usr/bin/env zenity --file-selection --separator=\n [--multiple]
  75. * [--directory] [--save] [--filename FILENAME]
  76. * [--file-filter=Filter Name | *.filt *.fn ...]...
  77. */
  78. static char** generate_args(const zenityArgs* info)
  79. {
  80. int argc = 4;
  81. int nextarg = 0;
  82. char **argv = NULL;
  83. // ARGC PASS
  84. if (info->flags & ZENITY_MULTIPLE) {
  85. argc++;
  86. }
  87. if (info->flags & ZENITY_DIRECTORY) {
  88. argc++;
  89. }
  90. if (info->flags & ZENITY_SAVE) {
  91. argc++;
  92. }
  93. if (info->filename) {
  94. argc += 2;
  95. }
  96. if (info->filters) {
  97. argc += info->nfilters;
  98. }
  99. argv = SDL_malloc(sizeof(char *) * argc + 1);
  100. if (!argv) {
  101. return NULL;
  102. }
  103. argv[nextarg++] = SDL_strdup("/usr/bin/env");
  104. CHECK_OOM()
  105. argv[nextarg++] = SDL_strdup("zenity");
  106. CHECK_OOM()
  107. argv[nextarg++] = SDL_strdup("--file-selection");
  108. CHECK_OOM()
  109. argv[nextarg++] = SDL_strdup("--separator=\n");
  110. CHECK_OOM()
  111. // ARGV PASS
  112. if (info->flags & ZENITY_MULTIPLE) {
  113. argv[nextarg++] = SDL_strdup("--multiple");
  114. CHECK_OOM()
  115. }
  116. if (info->flags & ZENITY_DIRECTORY) {
  117. argv[nextarg++] = SDL_strdup("--directory");
  118. CHECK_OOM()
  119. }
  120. if (info->flags & ZENITY_SAVE) {
  121. argv[nextarg++] = SDL_strdup("--save");
  122. CHECK_OOM()
  123. }
  124. if (info->filename) {
  125. argv[nextarg++] = SDL_strdup("--filename");
  126. CHECK_OOM()
  127. argv[nextarg++] = SDL_strdup(info->filename);
  128. CHECK_OOM()
  129. }
  130. if (info->filters) {
  131. for (int i = 0; i < info->nfilters; i++) {
  132. char *filter_str = convert_filter(info->filters[i],
  133. zenity_clean_name,
  134. "--file-filter=", " | ", "",
  135. "*.", " *.", "");
  136. if (!filter_str) {
  137. CLEAR_AND_RETURN()
  138. }
  139. argv[nextarg++] = filter_str;
  140. CHECK_OOM()
  141. }
  142. }
  143. argv[nextarg++] = NULL;
  144. return argv;
  145. }
  146. void free_args(char **argv)
  147. {
  148. char **ptr = argv;
  149. while (*ptr) {
  150. SDL_free(*ptr);
  151. ptr++;
  152. }
  153. SDL_free(argv);
  154. }
  155. // TODO: Zenity survives termination of the parent
  156. static void run_zenity(zenityArgs* arg_struct)
  157. {
  158. SDL_DialogFileCallback callback = arg_struct->callback;
  159. void* userdata = arg_struct->userdata;
  160. SDL_Process *process = NULL;
  161. char **args = NULL;
  162. SDL_Environment *env = NULL;
  163. int status = -1;
  164. size_t bytes_read = 0;
  165. char *container = NULL;
  166. size_t narray = 1;
  167. char **array = NULL;
  168. bool result = false;
  169. args = generate_args(arg_struct);
  170. if (!args) {
  171. goto done;
  172. }
  173. env = SDL_CreateEnvironment(true);
  174. if (!env) {
  175. goto done;
  176. }
  177. /* Recent versions of Zenity have different exit codes, but picks up
  178. different codes from the environment */
  179. SDL_SetEnvironmentVariable(env, "ZENITY_OK", "0", SDL_TRUE);
  180. SDL_SetEnvironmentVariable(env, "ZENITY_CANCEL", "1", SDL_TRUE);
  181. SDL_SetEnvironmentVariable(env, "ZENITY_ESC", "1", SDL_TRUE);
  182. SDL_SetEnvironmentVariable(env, "ZENITY_EXTRA", "2", SDL_TRUE);
  183. SDL_SetEnvironmentVariable(env, "ZENITY_ERROR", "2", SDL_TRUE);
  184. SDL_SetEnvironmentVariable(env, "ZENITY_TIMEOUT", "2", SDL_TRUE);
  185. SDL_PropertiesID props = SDL_CreateProperties();
  186. SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, args);
  187. SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER, env);
  188. SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_NULL);
  189. SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP);
  190. SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER, SDL_PROCESS_STDIO_NULL);
  191. process = SDL_CreateProcessWithProperties(props);
  192. SDL_DestroyProperties(props);
  193. if (!process) {
  194. goto done;
  195. }
  196. container = SDL_ReadProcess(process, &bytes_read, &status);
  197. if (!container) {
  198. goto done;
  199. }
  200. array = (char **)SDL_malloc((narray + 1) * sizeof(char *));
  201. if (!array) {
  202. goto done;
  203. }
  204. array[0] = container;
  205. array[1] = NULL;
  206. for (int i = 0; i < bytes_read; i++) {
  207. if (container[i] == '\n') {
  208. container[i] = '\0';
  209. // Reading from a process often leaves a trailing \n, so ignore the last one
  210. if (i < bytes_read - 1) {
  211. array[narray] = container + i + 1;
  212. narray++;
  213. char **new_array = (char **) SDL_realloc(array, (narray + 1) * sizeof(char *));
  214. if (!new_array) {
  215. goto done;
  216. }
  217. array = new_array;
  218. array[narray] = NULL;
  219. }
  220. }
  221. }
  222. // 0 = the user chose one or more files, 1 = the user canceled the dialog
  223. if (status == 0 || status == 1) {
  224. callback(userdata, (const char * const*)array, -1);
  225. } else {
  226. SDL_SetError("Could not run zenity: exit code %d", status);
  227. callback(userdata, NULL, -1);
  228. }
  229. result = true;
  230. done:
  231. SDL_free(array);
  232. SDL_free(container);
  233. free_args(args);
  234. SDL_DestroyEnvironment(env);
  235. SDL_DestroyProcess(process);
  236. if (!result) {
  237. callback(userdata, NULL, -1);
  238. }
  239. }
  240. static int run_zenity_thread(void* ptr)
  241. {
  242. run_zenity(ptr);
  243. SDL_free(ptr);
  244. return 0;
  245. }
  246. void SDL_Zenity_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location, bool allow_many)
  247. {
  248. zenityArgs *args;
  249. SDL_Thread *thread;
  250. args = SDL_malloc(sizeof(*args));
  251. if (!args) {
  252. callback(userdata, NULL, -1);
  253. return;
  254. }
  255. args->callback = callback;
  256. args->userdata = userdata;
  257. args->filename = default_location;
  258. args->filters = filters;
  259. args->nfilters = nfilters;
  260. args->flags = (allow_many == true) ? ZENITY_MULTIPLE : 0;
  261. thread = SDL_CreateThread(run_zenity_thread, "SDL_ShowOpenFileDialog", (void *) args);
  262. if (thread == NULL) {
  263. callback(userdata, NULL, -1);
  264. return;
  265. }
  266. SDL_DetachThread(thread);
  267. }
  268. void SDL_Zenity_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location)
  269. {
  270. zenityArgs *args;
  271. SDL_Thread *thread;
  272. args = SDL_malloc(sizeof(zenityArgs));
  273. if (args == NULL) {
  274. callback(userdata, NULL, -1);
  275. return;
  276. }
  277. args->callback = callback;
  278. args->userdata = userdata;
  279. args->filename = default_location;
  280. args->filters = filters;
  281. args->nfilters = nfilters;
  282. args->flags = ZENITY_SAVE;
  283. thread = SDL_CreateThread(run_zenity_thread, "SDL_ShowSaveFileDialog", (void *) args);
  284. if (thread == NULL) {
  285. callback(userdata, NULL, -1);
  286. return;
  287. }
  288. SDL_DetachThread(thread);
  289. }
  290. void SDL_Zenity_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, bool allow_many)
  291. {
  292. zenityArgs *args;
  293. SDL_Thread *thread;
  294. args = SDL_malloc(sizeof(zenityArgs));
  295. if (args == NULL) {
  296. callback(userdata, NULL, -1);
  297. return;
  298. }
  299. args->callback = callback;
  300. args->userdata = userdata;
  301. args->filename = default_location;
  302. args->filters = NULL;
  303. args->nfilters = 0;
  304. args->flags = ((allow_many == true) ? ZENITY_MULTIPLE : 0) | ZENITY_DIRECTORY;
  305. thread = SDL_CreateThread(run_zenity_thread, "SDL_ShowOpenFolderDialog", (void *) args);
  306. if (thread == NULL) {
  307. callback(userdata, NULL, -1);
  308. return;
  309. }
  310. SDL_DetachThread(thread);
  311. }
  312. bool SDL_Zenity_detect(void)
  313. {
  314. const char *args[] = {
  315. "/usr/bin/env", "zenity", "--version", NULL
  316. };
  317. int status = -1;
  318. SDL_PropertiesID props = SDL_CreateProperties();
  319. SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, args);
  320. SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_NULL);
  321. SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_NULL);
  322. SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER, SDL_PROCESS_STDIO_NULL);
  323. SDL_Process *process = SDL_CreateProcessWithProperties(props);
  324. SDL_DestroyProperties(props);
  325. if (process) {
  326. SDL_WaitProcess(process, true, &status);
  327. SDL_DestroyProcess(process);
  328. }
  329. return (status == 0);
  330. }