diff --git a/include/SDL3/SDL_dialog.h b/include/SDL3/SDL_dialog.h index 41ef1f9b81394..00f17823774a0 100644 --- a/include/SDL3/SDL_dialog.h +++ b/include/SDL3/SDL_dialog.h @@ -334,6 +334,52 @@ extern SDL_DECLSPEC void SDLCALL SDL_ShowFileDialogWithProperties(SDL_FileDialog #define SDL_PROP_FILE_DIALOG_ACCEPT_STRING "SDL.filedialog.accept" #define SDL_PROP_FILE_DIALOG_CANCEL_STRING "SDL.filedialog.cancel" +/** + * Callback used by input dialog functions. + * + * If `value` is NULL, an error occured. Details can be obtained with + * SDL_GetError(). + * + * If the user cancels the dialog, the callback will be invoked with an empty + * string. It is not possible to differentiate cancelling and inputting an empty + * string. + * + * The value argument should not be freed; it will automatically be freed + * when the callback returns. If you need to keep the string after the callback + * returns, you must duplicate it with SDL_strdup() or some other means. + * + * \param userdata an app-provided pointer, for the callback's use. + * \param value the value provided by the user, or NULL if an error occured. + * + * \since This datatype is available since SDL 3.4.0. + * + * \sa SDL_ShowSimpleInputDialog + */ +typedef void (SDLCALL *SDL_DialogInputCallback)(void *userdata, const char *value); + +/** + * Show a simple dialog prompting the user to input a string. + * + * \param callback a function pointer to be invoked when the user inputs a + * string and confirms, or cancels the dialog, or an error + * occurs. + * \param userdata an optional pointer to pass extra data to the callback when + * it will be invoked. + * \param title the title of the dialog. May be NULL. + * \param message the message of the dialog. May be NULL. + * \param value the default value of the dialog. May be NULL. + * \param window the window the dialog should be modal for. May be NULL. + * + * \threadsafety This function should be called only from the main thread. The + * callback may be invoked from the same thread or from a + * different one, depending on the OS's constraints. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_DialogInputCallback + */ +extern SDL_DECLSPEC void SDLCALL SDL_ShowSimpleInputDialog(SDL_DialogInputCallback callback, void *userdata, const char *title, const char *message, const char *value, SDL_Window *window); + /* Ends C function definitions when using C++ */ #ifdef __cplusplus } diff --git a/src/dialog/unix/SDL_portaldialog.c b/src/dialog/unix/SDL_portaldialog.c index a7f96a3381994..58f7a6bd3a9d9 100644 --- a/src/dialog/unix/SDL_portaldialog.c +++ b/src/dialog/unix/SDL_portaldialog.c @@ -534,6 +534,12 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog SDL_free(location_folder); } +void SDL_Portal_ShowSimpleInputDialog(SDL_DialogInputCallback callback, void *userdata, const char *title, const char *message, const char *value, SDL_Window *window) +{ + SDL_Unsupported(); + callback(userdata, NULL); +} + bool SDL_Portal_detect(void) { SDL_DBusContext *dbus = SDL_DBus_GetContext(); @@ -600,6 +606,12 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog callback(userdata, NULL, -1); } +void SDL_Portal_ShowSimpleInputDialog(SDL_DialogInputCallback callback, void *userdata, const char *title, const char *message, const char *value, SDL_Window *window) +{ + SDL_Unsupported(); + callback(userdata, NULL); +} + bool SDL_Portal_detect(void) { return false; diff --git a/src/dialog/unix/SDL_portaldialog.h b/src/dialog/unix/SDL_portaldialog.h index 4497287cc8508..4ce85241e4583 100644 --- a/src/dialog/unix/SDL_portaldialog.h +++ b/src/dialog/unix/SDL_portaldialog.h @@ -23,5 +23,7 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props); +void SDL_Portal_ShowSimpleInputDialog(SDL_DialogInputCallback callback, void *userdata, const char *title, const char *message, const char *value, SDL_Window *window); + /** @returns non-zero if available, zero if unavailable */ bool SDL_Portal_detect(void); diff --git a/src/dialog/unix/SDL_unixdialog.c b/src/dialog/unix/SDL_unixdialog.c index bec2ac95a6871..d92a12192f617 100644 --- a/src/dialog/unix/SDL_unixdialog.c +++ b/src/dialog/unix/SDL_unixdialog.c @@ -24,7 +24,8 @@ #include "./SDL_portaldialog.h" #include "./SDL_zenitydialog.h" -static void (*detected_function)(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props) = NULL; +static void (*detected_dialog)(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props) = NULL; +static void (*detected_input)(SDL_DialogInputCallback callback, void *userdata, const char *title, const char *message, const char *value, SDL_Window *window) = NULL; void SDLCALL hint_callback(void *userdata, const char *name, const char *oldValue, const char *newValue); @@ -47,14 +48,16 @@ static int detect_available_methods(const char *value) if (driver == NULL || SDL_strcmp(driver, "portal") == 0) { if (SDL_Portal_detect()) { - detected_function = SDL_Portal_ShowFileDialogWithProperties; + detected_dialog = SDL_Portal_ShowFileDialogWithProperties; + detected_input = SDL_Zenity_ShowSimpleInputDialog; return 1; } } if (driver == NULL || SDL_strcmp(driver, "zenity") == 0) { if (SDL_Zenity_detect()) { - detected_function = SDL_Zenity_ShowFileDialogWithProperties; + detected_dialog = SDL_Zenity_ShowFileDialogWithProperties; + detected_input = SDL_Zenity_ShowSimpleInputDialog; return 2; } } @@ -71,11 +74,23 @@ void SDLCALL hint_callback(void *userdata, const char *name, const char *oldValu void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props) { // Call detect_available_methods() again each time in case the situation changed - if (!detected_function && !detect_available_methods(NULL)) { + if (!detected_dialog && !detect_available_methods(NULL)) { // SetError() done by detect_available_methods() callback(userdata, NULL, -1); return; } - detected_function(type, callback, userdata, props); + detected_dialog(type, callback, userdata, props); +} + +void SDL_ShowSimpleInputDialog(SDL_DialogInputCallback callback, void *userdata, const char *title, const char *message, const char *value, SDL_Window *window) +{ + // Call detect_available_methods() again each time in case the situation changed + if (!detected_input && !detect_available_methods(NULL)) { + // SetError() done by detect_available_methods() + callback(userdata, NULL); + return; + } + + detected_input(callback, userdata, title, message, value, window); } diff --git a/src/dialog/unix/SDL_zenitydialog.c b/src/dialog/unix/SDL_zenitydialog.c index 4632c8e16a2b6..5657cfcda880b 100644 --- a/src/dialog/unix/SDL_zenitydialog.c +++ b/src/dialog/unix/SDL_zenitydialog.c @@ -344,6 +344,92 @@ void SDL_Zenity_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog SDL_DetachThread(thread); } +void SDL_Zenity_ShowSimpleInputDialog(SDL_DialogInputCallback callback, void *userdata, const char *title, const char *message, const char *value, SDL_Window *window) +{ + SDL_Process *process = NULL; + SDL_Environment *env = NULL; + int status = -1; + size_t bytes_read = 0; + char *container = NULL; + bool result = false; + const char *argv[9]; + int argc = 0; + + argv[argc++] = "zenity"; + argv[argc++] = "--entry"; + + if (title) { + argv[argc++] = "--title"; + argv[argc++] = title; + } + + if (message) { + argv[argc++] = "--text"; + argv[argc++] = message; + } + + if (value) { + argv[argc++] = "--entry-text"; + argv[argc++] = value; + } + + argv[argc] = NULL; + + env = SDL_CreateEnvironment(true); + if (!env) { + goto done; + } + + /* Recent versions of Zenity have different exit codes, but picks up + different codes from the environment */ + SDL_SetEnvironmentVariable(env, "ZENITY_OK", "0", true); + SDL_SetEnvironmentVariable(env, "ZENITY_CANCEL", "1", true); + SDL_SetEnvironmentVariable(env, "ZENITY_ESC", "1", true); + SDL_SetEnvironmentVariable(env, "ZENITY_EXTRA", "2", true); + SDL_SetEnvironmentVariable(env, "ZENITY_ERROR", "2", true); + SDL_SetEnvironmentVariable(env, "ZENITY_TIMEOUT", "2", true); + + SDL_PropertiesID props = SDL_CreateProperties(); + SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, argv); + SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER, env); + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_NULL); + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP); + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER, SDL_PROCESS_STDIO_NULL); + process = SDL_CreateProcessWithProperties(props); + SDL_DestroyProperties(props); + if (!process) { + goto done; + } + + container = SDL_ReadProcess(process, &bytes_read, &status); + if (!container) { + goto done; + } + + // Strings returned by Zenity finish with '\n'; swap that with a '\0' + container[bytes_read - 1] = '\0'; + + if (status == 0) { + callback(userdata, container); + } else if (status == 1) { + callback(userdata, ""); + } else { + SDL_SetError("Could not run zenity: exit code %d", status); + callback(userdata, NULL); + } + + result = true; + +done: + SDL_free(container); + SDL_DestroyEnvironment(env); + SDL_DestroyProcess(process); + + if (!result) { + callback(userdata, NULL); + } +} + bool SDL_Zenity_detect(void) { const char *args[] = { diff --git a/src/dialog/unix/SDL_zenitydialog.h b/src/dialog/unix/SDL_zenitydialog.h index 4cfe8926694a2..2fd49a8332cf9 100644 --- a/src/dialog/unix/SDL_zenitydialog.h +++ b/src/dialog/unix/SDL_zenitydialog.h @@ -23,5 +23,7 @@ extern void SDL_Zenity_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props); +extern void SDL_Zenity_ShowSimpleInputDialog(SDL_DialogInputCallback callback, void *userdata, const char *title, const char *message, const char *value, SDL_Window *window); + /** @returns non-zero if available, zero if unavailable */ extern bool SDL_Zenity_detect(void); diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 0e4b0aa6d89a3..2f6348265e658 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -1257,6 +1257,7 @@ SDL3_0.0.0 { SDL_hid_get_properties; SDL_GetPixelFormatFromGPUTextureFormat; SDL_GetGPUTextureFormatFromPixelFormat; + SDL_ShowSimpleInputDialog; # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 686e5472ed930..c999c3477e84d 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -1282,3 +1282,4 @@ #define SDL_hid_get_properties SDL_hid_get_properties_REAL #define SDL_GetPixelFormatFromGPUTextureFormat SDL_GetPixelFormatFromGPUTextureFormat_REAL #define SDL_GetGPUTextureFormatFromPixelFormat SDL_GetGPUTextureFormatFromPixelFormat_REAL +#define SDL_ShowSimpleInputDialog SDL_ShowSimpleInputDialog_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 8516bfe26a0b7..5dd1c0b454035 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -1290,3 +1290,4 @@ SDL_DYNAPI_PROC(Uint32,SDL_AddAtomicU32,(SDL_AtomicU32 *a,int b),(a,b),return) SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_hid_get_properties,(SDL_hid_device *a),(a),return) SDL_DYNAPI_PROC(SDL_PixelFormat,SDL_GetPixelFormatFromGPUTextureFormat,(SDL_GPUTextureFormat a),(a),return) SDL_DYNAPI_PROC(SDL_GPUTextureFormat,SDL_GetGPUTextureFormatFromPixelFormat,(SDL_PixelFormat a),(a),return) +SDL_DYNAPI_PROC(void,SDL_ShowSimpleInputDialog,(SDL_DialogInputCallback a,void *b,const char *c,const char *d,const char *e,SDL_Window *f),(a,b,c,d,e,f),) diff --git a/test/testdialog.c b/test/testdialog.c index 37d005f4c5249..b371b484997ac 100644 --- a/test/testdialog.c +++ b/test/testdialog.c @@ -55,6 +55,10 @@ static void SDLCALL callback(void *userdata, const char * const *files, int filt } } +static void SDLCALL input_callback(void *userdata, const char *input) { + SDL_Log("Input: %s\n", input); +} + char *concat_strings(const char *a, const char *b) { char *out = NULL; @@ -80,6 +84,7 @@ int main(int argc, char *argv[]) const SDL_FRect open_file_rect = { 50, 50, 220, 140 }; const SDL_FRect save_file_rect = { 50, 290, 220, 140 }; const SDL_FRect open_folder_rect = { 370, 50, 220, 140 }; + const SDL_FRect input_rect = { 370, 290, 220, 140 }; int i; const char *default_filename = "Untitled.index"; const char *initial_path = NULL; @@ -153,6 +158,8 @@ int main(int argc, char *argv[]) } SDL_ShowSaveFileDialog(callback, &last_saved_path, w, filters, SDL_arraysize(filters), save_path ? save_path : default_filename); SDL_free(save_path); + } else if (SDL_PointInRectFloat(&p, &input_rect)) { + SDL_ShowSimpleInputDialog(input_callback, NULL, NULL, NULL, NULL, w); } } } @@ -173,10 +180,14 @@ int main(int argc, char *argv[]) SDL_SetRenderDrawColor(r, 0, 0, 255, SDL_ALPHA_OPAQUE); SDL_RenderFillRect(r, &open_folder_rect); + SDL_SetRenderDrawColor(r, 255, 255, 0, SDL_ALPHA_OPAQUE); + SDL_RenderFillRect(r, &input_rect); + SDL_SetRenderDrawColor(r, 0, 0, 0, SDL_ALPHA_OPAQUE); SDLTest_DrawString(r, open_file_rect.x+5, open_file_rect.y+open_file_rect.h/2, "Open File..."); SDLTest_DrawString(r, save_file_rect.x+5, save_file_rect.y+save_file_rect.h/2, "Save File..."); SDLTest_DrawString(r, open_folder_rect.x+5, open_folder_rect.y+open_folder_rect.h/2, "Open Folder..."); + SDLTest_DrawString(r, input_rect.x+5, input_rect.y+input_rect.h/2, "Input text..."); SDL_RenderPresent(r); }