diff --git a/src/core/unix/SDL_gtk.c b/src/core/unix/SDL_gtk.c new file mode 100644 index 0000000000..f5f62ce935 --- /dev/null +++ b/src/core/unix/SDL_gtk.c @@ -0,0 +1,229 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" +#include "SDL_gtk.h" + +#define SDL_GTK_SYM2_OPTIONAL(ctx, lib, sub, fn, sym) \ + ctx.sub.fn = (void *)SDL_LoadFunction(lib, #sym) + +#define SDL_GTK_SYM2(ctx, lib, sub, fn, sym) \ + if (!(ctx.sub.fn = (void *)SDL_LoadFunction(lib, #sym))) { \ + return SDL_SetError("Could not load GTK functions"); \ + } + +#define SDL_GTK_SYM_OPTIONAL(ctx, lib, sub, fn) \ + SDL_GTK_SYM2_OPTIONAL(ctx, lib, sub, fn, sub##_##fn) + +#define SDL_GTK_SYM(ctx, lib, sub, fn) \ + SDL_GTK_SYM2(ctx, lib, sub, fn, sub##_##fn) + +// we never link directly to gtk +static const char *gdk_names[] = { +#ifdef SDL_PLATFORM_OPENBSD + "libgdk-3.so", +#else + "libgdk-3.so.0", +#endif + NULL +}; + +static const char *gtk_names[] = { +#ifdef SDL_PLATFORM_OPENBSD + "libgtk-3.so", +#else + "libgtk-3.so.0", +#endif + NULL +}; + +static void *libgdk = NULL; +static void *libgtk = NULL; + +static SDL_GtkContext gtk; +static GMainContext *sdl_main_context; + +gulong signal_connect(gpointer instance, const gchar *detailed_signal, void *c_handler, gpointer data) +{ + return gtk.g.signal_connect_data(instance, detailed_signal, SDL_G_CALLBACK(c_handler), data, NULL, (SDL_GConnectFlags)0); +} + +static void QuitGtk(void) +{ + SDL_UnloadObject(libgdk); + SDL_UnloadObject(libgtk); + + libgdk = NULL; + libgtk = NULL; +} + +static void *FindLib(const char **names) +{ + const char **name_ptr = names; + void *handle = NULL; + + do { + handle = SDL_LoadObject(*name_ptr); + } while (*++name_ptr && !handle); + + return handle; +} + +static bool IsGtkInit() +{ + return libgdk != NULL && libgtk != NULL; +} + +static bool InitGtk(void) +{ + if (!SDL_GetHintBoolean("SDL_ENABLE_GTK", true)) { + return false; + } + + if (IsGtkInit()) { + return true; + } + + libgdk = FindLib(gdk_names); + libgtk = FindLib(gtk_names); + + if (!libgdk || !libgtk) { + QuitGtk(); + return SDL_SetError("Could not load GTK libraries"); + } + + SDL_GTK_SYM(gtk, libgtk, gtk, init_check); + SDL_GTK_SYM(gtk, libgtk, gtk, menu_new); + SDL_GTK_SYM(gtk, libgtk, gtk, separator_menu_item_new); + SDL_GTK_SYM(gtk, libgtk, gtk, menu_item_new_with_label); + SDL_GTK_SYM(gtk, libgtk, gtk, menu_item_set_submenu); + SDL_GTK_SYM(gtk, libgtk, gtk, menu_item_get_label); + SDL_GTK_SYM(gtk, libgtk, gtk, menu_item_set_label); + SDL_GTK_SYM(gtk, libgtk, gtk, menu_shell_append); + SDL_GTK_SYM(gtk, libgtk, gtk, menu_shell_insert); + SDL_GTK_SYM(gtk, libgtk, gtk, check_menu_item_new_with_label); + SDL_GTK_SYM(gtk, libgtk, gtk, check_menu_item_get_active); + SDL_GTK_SYM(gtk, libgtk, gtk, check_menu_item_set_active); + SDL_GTK_SYM(gtk, libgtk, gtk, widget_show); + SDL_GTK_SYM(gtk, libgtk, gtk, widget_destroy); + SDL_GTK_SYM(gtk, libgtk, gtk, widget_get_sensitive); + SDL_GTK_SYM(gtk, libgtk, gtk, widget_set_sensitive); + SDL_GTK_SYM(gtk, libgtk, gtk, settings_get_default); + + SDL_GTK_SYM(gtk, libgdk, g, signal_connect_data); + SDL_GTK_SYM(gtk, libgdk, g, mkdtemp); + SDL_GTK_SYM(gtk, libgdk, g, object_ref); + SDL_GTK_SYM(gtk, libgdk, g, object_ref_sink); + SDL_GTK_SYM(gtk, libgdk, g, object_unref); + SDL_GTK_SYM(gtk, libgdk, g, object_get); + SDL_GTK_SYM(gtk, libgdk, g, signal_handler_disconnect); + SDL_GTK_SYM(gtk, libgdk, g, main_context_push_thread_default); + SDL_GTK_SYM(gtk, libgdk, g, main_context_pop_thread_default); + SDL_GTK_SYM(gtk, libgdk, g, main_context_new); + SDL_GTK_SYM(gtk, libgdk, g, main_context_acquire); + SDL_GTK_SYM(gtk, libgdk, g, main_context_iteration); + + gtk.g.signal_connect = signal_connect; + + if (gtk.gtk.init_check(0, NULL) == GTK_FALSE) { + QuitGtk(); + return SDL_SetError("Could not init GTK"); + } + + sdl_main_context = gtk.g.main_context_new(); + if (!sdl_main_context) { + QuitGtk(); + return SDL_SetError("Could not create GTK context"); + } + + if (!gtk.g.main_context_acquire(sdl_main_context)) { + QuitGtk(); + return SDL_SetError("Could not acquire GTK context"); + } + + return true; +} + +static SDL_InitState gtk_init; + +bool SDL_Gtk_Init(void) +{ + static bool is_gtk_available = true; + + if (!is_gtk_available) { + return false; // don't keep trying if this fails. + } + + if (SDL_ShouldInit(>k_init)) { + if (InitGtk()) { + SDL_SetInitialized(>k_init, true); + } else { + is_gtk_available = false; + SDL_SetInitialized(>k_init, true); + SDL_Gtk_Quit(); + } + } + + return IsGtkInit(); +} + +void SDL_Gtk_Quit(void) +{ + if (!SDL_ShouldQuit(>k_init)) { + return; + } + + QuitGtk(); + SDL_zero(gtk); + sdl_main_context = NULL; + + SDL_SetInitialized(>k_init, false); +} + +SDL_GtkContext *SDL_Gtk_GetContext(void) +{ + return IsGtkInit() ? >k : NULL; +} + +SDL_GtkContext *SDL_Gtk_EnterContext(void) +{ + SDL_Gtk_Init(); + + if (IsGtkInit()) { + gtk.g.main_context_push_thread_default(sdl_main_context); + return >k; + } + + return NULL; +} + +void SDL_Gtk_ExitContext(SDL_GtkContext *ctx) +{ + if (ctx) { + ctx->g.main_context_pop_thread_default(sdl_main_context); + } +} + +void SDL_UpdateGtk(void) +{ + if (IsGtkInit()) { + gtk.g.main_context_iteration(sdl_main_context, GTK_FALSE); + } +} diff --git a/src/core/unix/SDL_gtk.h b/src/core/unix/SDL_gtk.h new file mode 100644 index 0000000000..e76720ddb6 --- /dev/null +++ b/src/core/unix/SDL_gtk.h @@ -0,0 +1,123 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#ifndef SDL_gtk_h_ +#define SDL_gtk_h_ + +/* Glib 2.0 */ + +typedef unsigned long gulong; +typedef void *gpointer; +typedef char gchar; +typedef int gint; +typedef unsigned int guint; +typedef double gdouble; +typedef gint gboolean; +typedef void (*GCallback)(void); +typedef struct _GClosure GClosure; +typedef void (*GClosureNotify) (gpointer data, GClosure *closure); +typedef gboolean (*GSourceFunc) (gpointer user_data); + +typedef struct _GParamSpec GParamSpec; +typedef struct _GMainContext GMainContext; + +typedef enum SDL_GConnectFlags +{ + SDL_G_CONNECT_DEFAULT = 0, + SDL_G_CONNECT_AFTER = 1 << 0, + SDL_G_CONNECT_SWAPPED = 1 << 1 +} SDL_GConnectFlags; + +#define SDL_G_CALLBACK(f) ((GCallback) (f)) +#define SDL_G_TYPE_CIC(ip, gt, ct) ((ct*) ip) +#define SDL_G_TYPE_CHECK_INSTANCE_CAST(instance, g_type, c_type) (SDL_G_TYPE_CIC ((instance), (g_type), c_type)) + +#define GTK_FALSE 0 +#define GTK_TRUE 1 + + +/* GTK 3.0 */ + +typedef struct _GtkMenu GtkMenu; +typedef struct _GtkMenuItem GtkMenuItem; +typedef struct _GtkMenuShell GtkMenuShell; +typedef struct _GtkWidget GtkWidget; +typedef struct _GtkCheckMenuItem GtkCheckMenuItem; +typedef struct _GtkSettings GtkSettings; + +#define GTK_MENU_ITEM(obj) (SDL_G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_MENU_ITEM, GtkMenuItem)) +#define GTK_WIDGET(widget) (SDL_G_TYPE_CHECK_INSTANCE_CAST ((widget), GTK_TYPE_WIDGET, GtkWidget)) +#define GTK_CHECK_MENU_ITEM(obj) (SDL_G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_CHECK_MENU_ITEM, GtkCheckMenuItem)) +#define GTK_MENU(obj) (SDL_G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_MENU, GtkMenu)) + + +typedef struct SDL_GtkContext +{ + /* Glib 2.0 */ + struct + { + gulong (*signal_connect)(gpointer instance, const gchar *detailed_signal, void *c_handler, gpointer data); + gulong (*signal_connect_data)(gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data, GClosureNotify destroy_data, SDL_GConnectFlags connect_flags); + void (*object_unref)(gpointer object); + gchar *(*mkdtemp)(gchar *template); + gpointer (*object_ref_sink)(gpointer object); + gpointer (*object_ref)(gpointer object); + void (*object_get)(gpointer object, const gchar *first_property_name, ...); + void (*signal_handler_disconnect)(gpointer instance, gulong handler_id); + void (*main_context_push_thread_default)(GMainContext *context); + void (*main_context_pop_thread_default)(GMainContext *context); + GMainContext *(*main_context_new)(void); + gboolean (*main_context_acquire)(GMainContext *context); + gboolean (*main_context_iteration)(GMainContext *context, gboolean may_block); + } g; + + /* GTK 3.0 */ + struct + { + gboolean (*init_check)(int *argc, char ***argv); + GtkWidget *(*menu_new)(void); + GtkWidget *(*separator_menu_item_new)(void); + GtkWidget *(*menu_item_new_with_label)(const gchar *label); + void (*menu_item_set_submenu)(GtkMenuItem *menu_item, GtkWidget *submenu); + GtkWidget *(*check_menu_item_new_with_label)(const gchar *label); + void (*check_menu_item_set_active)(GtkCheckMenuItem *check_menu_item, gboolean is_active); + void (*widget_set_sensitive)(GtkWidget *widget, gboolean sensitive); + void (*widget_show)(GtkWidget *widget); + void (*menu_shell_append)(GtkMenuShell *menu_shell, GtkWidget *child); + void (*menu_shell_insert)(GtkMenuShell *menu_shell, GtkWidget *child, gint position); + void (*widget_destroy)(GtkWidget *widget); + const gchar *(*menu_item_get_label)(GtkMenuItem *menu_item); + void (*menu_item_set_label)(GtkMenuItem *menu_item, const gchar *label); + gboolean (*check_menu_item_get_active)(GtkCheckMenuItem *check_menu_item); + gboolean (*widget_get_sensitive)(GtkWidget *widget); + GtkSettings *(*settings_get_default)(void); + } gtk; +} SDL_GtkContext; + +extern bool SDL_Gtk_Init(void); +extern void SDL_Gtk_Quit(void); +extern SDL_GtkContext *SDL_Gtk_EnterContext(void); +extern void SDL_Gtk_ExitContext(SDL_GtkContext *gtk); +extern void SDL_UpdateGtk(void); + +#endif // SDL_gtk_h_