diff --git a/docs/README-migration.md b/docs/README-migration.md index 9f089ff146b09..6d649e52ae86e 100644 --- a/docs/README-migration.md +++ b/docs/README-migration.md @@ -397,6 +397,7 @@ The following hints have been removed: * Renamed hints SDL_HINT_VIDEODRIVER and SDL_HINT_AUDIODRIVER to SDL_HINT_VIDEO_DRIVER and SDL_HINT_AUDIO_DRIVER * Renamed environment variables SDL_VIDEODRIVER and SDL_AUDIODRIVER to SDL_VIDEO_DRIVER and SDL_AUDIO_DRIVER +* The environment variables SDL_VIDEO_X11_WMCLASS and SDL_VIDEO_WAYLAND_WMCLASS have been removed and replaced with the unified hint SDL_HINT_APP_ID ## SDL_init.h diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index 62038a3c7cbb6..544f323a0e876 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -158,6 +158,53 @@ extern "C" { */ #define SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY "SDL_ANDROID_ALLOW_RECREATE_ACTIVITY" +/** + * \brief A variable setting the app ID string. + * This string is used by desktop compositors to identify and group windows + * together, as well as match applications with associated desktop settings + * and icons. + * + * On Wayland this corresponds to the "app ID" window property and on X11 this + * corresponds to the WM_CLASS property. Windows inherit the value of this hint + * at creation time. Changing this hint after a window has been created will not + * change the app ID or class of existing windows. + * + * For *nix platforms, this string should be formatted in reverse-DNS notation + * and follow some basic rules to be valid: + * + * - The application ID must be composed of two or more elements separated by a + * period (‘.’) character. + * + * - Each element must contain one or more of the alphanumeric characters + * (A-Z, a-z, 0-9) plus underscore (‘_’) and hyphen (‘-’) and must not start + * with a digit. Note that hyphens, while technically allowed, should not be + * used if possible, as they are not supported by all components that use the ID, + * such as D-Bus. For maximum compatability, replace hyphens with an underscore. + * + * - The empty string is not a valid element (ie: your application ID may not + * start or end with a period and it is not valid to have two periods in a row). + * + * - The entire ID must be less than 255 characters in length. + * + * Examples of valid app ID strings: + * + * - org.MyOrg.MyApp + * - com.your_company.your_app + * + * Desktops such as GNOME and KDE require that the app ID string matches your + * application's .desktop file name (e.g. if the app ID string is 'org.MyOrg.MyApp', + * your application's .desktop file should be named 'org.MyOrg.MyApp.desktop'). + * + * If you plan to package your application in a container such as Flatpak, the + * app ID should match the name of your Flatpak container as well. + * + * If not set, SDL will attempt to use the application executable name. + * If the executable name cannot be retrieved, the generic string "SDL_App" will be used. + * + * On targets where this is not supported, this hint does nothing. + */ +#define SDL_HINT_APP_ID "SDL_APP_ID" + /** * \brief Specify an application name. * diff --git a/src/core/unix/SDL_appid.c b/src/core/unix/SDL_appid.c new file mode 100644 index 0000000000000..c7cddc5b9f79b --- /dev/null +++ b/src/core/unix/SDL_appid.c @@ -0,0 +1,76 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2023 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_appid.h" +#include + +const char *SDL_GetExeName() +{ + static const char *proc_name = NULL; + + /* TODO: Use a fallback if BSD has no mounted procfs (OpenBSD has no procfs at all) */ + if (!proc_name) { +#if defined(__LINUX__) || defined(__FREEBSD__) || defined (__NETBSD__) + static char linkfile[1024]; + int linksize; + +#if defined(__LINUX__) + const char *proc_path = "/proc/self/exe"; +#elif defined(__FREEBSD__) + const char *proc_path = "/proc/curproc/file"; +#elif defined(__NETBSD__) + const char *proc_path = "/proc/curproc/exe"; +#endif + linksize = readlink(proc_path, linkfile, sizeof(linkfile) - 1); + if (linksize > 0) { + linkfile[linksize] = '\0'; + proc_name = SDL_strrchr(linkfile, '/'); + if (proc_name) { + ++proc_name; + } else { + proc_name = linkfile; + } + } +#endif + } + + return proc_name; +} + +const char *SDL_GetAppID() +{ + /* Always check the hint, as it may have changed */ + const char *id_str = SDL_GetHint(SDL_HINT_APP_ID); + + if (!id_str) { + /* If the hint isn't set, try to use the application's executable name */ + id_str = SDL_GetExeName(); + } + + if (!id_str) { + /* Finally, use the default we've used forever */ + id_str = "SDL_App"; + } + + return id_str; +} diff --git a/src/core/unix/SDL_appid.h b/src/core/unix/SDL_appid.h new file mode 100644 index 0000000000000..d5acb009b4164 --- /dev/null +++ b/src/core/unix/SDL_appid.h @@ -0,0 +1,30 @@ +/* +Simple DirectMedia Layer +Copyright (C) 1997-2023 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_appid_h_ +#define SDL_appid_h_ + +extern const char *SDL_GetExeName(); +extern const char *SDL_GetAppID(); + +#endif /* SDL_appid_h_ */ diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index d7609f84c310f..ba6aecf31f4a1 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -82,60 +82,6 @@ static int Wayland_VideoInit(SDL_VideoDevice *_this); static int Wayland_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect); static void Wayland_VideoQuit(SDL_VideoDevice *_this); -/* Find out what class name we should use - * Based on src/video/x11/SDL_x11video.c */ -static char *get_classname(void) -{ - /* !!! FIXME: this is probably wrong, albeit harmless in many common cases. From protocol spec: - "The surface class identifies the general class of applications - to which the surface belongs. A common convention is to use the - file name (or the full path if it is a non-standard location) of - the application's .desktop file as the class." */ - - char *spot; -#if defined(__LINUX__) || defined(__FREEBSD__) - char procfile[1024]; - char linkfile[1024]; - int linksize; -#endif - - /* First allow environment variable override */ - spot = SDL_getenv("SDL_VIDEO_WAYLAND_WMCLASS"); - if (spot) { - return SDL_strdup(spot); - } else { - /* Fallback to the "old" envvar */ - spot = SDL_getenv("SDL_VIDEO_X11_WMCLASS"); - if (spot) { - return SDL_strdup(spot); - } - } - - /* Next look at the application's executable name */ -#if defined(__LINUX__) || defined(__FREEBSD__) -#ifdef __LINUX__ - (void)SDL_snprintf(procfile, SDL_arraysize(procfile), "/proc/%d/exe", getpid()); -#elif defined(__FREEBSD__) - (void)SDL_snprintf(procfile, SDL_arraysize(procfile), "/proc/%d/file", getpid()); -#else -#error Where can we find the executable name? -#endif - linksize = readlink(procfile, linkfile, sizeof(linkfile) - 1); - if (linksize > 0) { - linkfile[linksize] = '\0'; - spot = SDL_strrchr(linkfile, '/'); - if (spot) { - return SDL_strdup(spot + 1); - } else { - return SDL_strdup(linkfile); - } - } -#endif /* __LINUX__ || __FREEBSD__ */ - - /* Finally use the default we've used forever */ - return SDL_strdup("SDL_App"); -} - static const char *SDL_WAYLAND_surface_tag = "sdl-window"; static const char *SDL_WAYLAND_output_tag = "sdl-output"; @@ -945,9 +891,6 @@ int Wayland_VideoInit(SDL_VideoDevice *_this) Wayland_InitMouse(); - /* Get the surface class name, usually the name of the application */ - data->classname = get_classname(); - WAYLAND_wl_display_flush(data->display); Wayland_InitKeyboard(_this); @@ -1144,18 +1087,15 @@ SDL_bool Wayland_VideoReconnect(SDL_VideoDevice *_this) void Wayland_VideoQuit(SDL_VideoDevice *_this) { - SDL_VideoData *data = _this->driverdata; - Wayland_VideoCleanup(_this); #ifdef HAVE_LIBDECOR_H + SDL_VideoData *data = _this->driverdata; if (data->shell.libdecor) { libdecor_unref(data->shell.libdecor); data->shell.libdecor = NULL; } #endif - - SDL_free(data->classname); } #endif /* SDL_VIDEO_DRIVER_WAYLAND */ diff --git a/src/video/wayland/SDL_waylandvideo.h b/src/video/wayland/SDL_waylandvideo.h index eb4636550da03..bc55ee3e67f29 100644 --- a/src/video/wayland/SDL_waylandvideo.h +++ b/src/video/wayland/SDL_waylandvideo.h @@ -90,8 +90,6 @@ struct SDL_VideoData struct qt_windowmanager *windowmanager; #endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */ - char *classname; - int relative_mouse_mode; }; diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c index 8166e0d7ce4ce..fe52fe4084121 100644 --- a/src/video/wayland/SDL_waylandwindow.c +++ b/src/video/wayland/SDL_waylandwindow.c @@ -25,6 +25,7 @@ #include "../SDL_sysvideo.h" #include "../../events/SDL_events_c.h" +#include "../../core/unix/SDL_appid.h" #include "../SDL_egl_c.h" #include "SDL_waylandevents_c.h" #include "SDL_waylandwindow.h" @@ -1328,7 +1329,7 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) if (data->shell_surface.libdecor.frame == NULL) { SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Failed to create libdecor frame!"); } else { - libdecor_frame_set_app_id(data->shell_surface.libdecor.frame, c->classname); + libdecor_frame_set_app_id(data->shell_surface.libdecor.frame, data->app_id); libdecor_frame_map(data->shell_surface.libdecor.frame); } } else @@ -1390,7 +1391,7 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) } } else { data->shell_surface.xdg.roleobj.toplevel = xdg_surface_get_toplevel(data->shell_surface.xdg.surface); - xdg_toplevel_set_app_id(data->shell_surface.xdg.roleobj.toplevel, c->classname); + xdg_toplevel_set_app_id(data->shell_surface.xdg.roleobj.toplevel, data->app_id); xdg_toplevel_add_listener(data->shell_surface.xdg.roleobj.toplevel, &toplevel_listener_xdg, data); } } @@ -2047,6 +2048,9 @@ int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) data->outputs = NULL; data->num_outputs = 0; + /* Cache the app_id at creation time, as it may change before the window is mapped. */ + data->app_id = SDL_strdup(SDL_GetAppID()); + data->requested_window_width = window->w; data->requested_window_height = window->h; data->floating_width = window->windowed.w; @@ -2311,6 +2315,7 @@ void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) } SDL_free(wind->outputs); + SDL_free(wind->app_id); if (wind->gles_swap_frame_callback) { wl_callback_destroy(wind->gles_swap_frame_callback); diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h index 7861cb5fa6953..f0b102887f27f 100644 --- a/src/video/wayland/SDL_waylandwindow.h +++ b/src/video/wayland/SDL_waylandwindow.h @@ -111,6 +111,7 @@ struct SDL_WindowData SDL_Window *keyboard_focus; + char *app_id; float windowed_scale_factor; float pointer_scale_x; float pointer_scale_y; diff --git a/src/video/x11/SDL_x11keyboard.c b/src/video/x11/SDL_x11keyboard.c index 2c25f45427867..cf2d4c7eec283 100644 --- a/src/video/x11/SDL_x11keyboard.c +++ b/src/video/x11/SDL_x11keyboard.c @@ -203,7 +203,7 @@ int X11_InitKeyboard(SDL_VideoDevice *_this) (void)setlocale(LC_ALL, ""); X11_XSetLocaleModifiers(new_xmods); - data->im = X11_XOpenIM(data->display, NULL, data->classname, data->classname); + data->im = X11_XOpenIM(data->display, NULL, NULL, NULL); /* Reset the locale + X locale modifiers back to how they were, locale first because the X locale modifiers depend on it. */ diff --git a/src/video/x11/SDL_x11video.c b/src/video/x11/SDL_x11video.c index 9fa9a718e2e64..b3dd6fef4eb79 100644 --- a/src/video/x11/SDL_x11video.c +++ b/src/video/x11/SDL_x11video.c @@ -45,47 +45,6 @@ static int X11_VideoInit(SDL_VideoDevice *_this); static void X11_VideoQuit(SDL_VideoDevice *_this); -/* Find out what class name we should use */ -static char *get_classname(void) -{ - char *spot; -#if defined(__LINUX__) || defined(__FREEBSD__) - char procfile[1024]; - char linkfile[1024]; - int linksize; -#endif - - /* First allow environment variable override */ - spot = SDL_getenv("SDL_VIDEO_X11_WMCLASS"); - if (spot) { - return SDL_strdup(spot); - } - - /* Next look at the application's executable name */ -#if defined(__LINUX__) || defined(__FREEBSD__) -#ifdef __LINUX__ - (void)SDL_snprintf(procfile, SDL_arraysize(procfile), "/proc/%d/exe", getpid()); -#elif defined(__FREEBSD__) - (void)SDL_snprintf(procfile, SDL_arraysize(procfile), "/proc/%d/file", getpid()); -#else -#error Where can we find the executable name? -#endif - linksize = readlink(procfile, linkfile, sizeof(linkfile) - 1); - if (linksize > 0) { - linkfile[linksize] = '\0'; - spot = SDL_strrchr(linkfile, '/'); - if (spot) { - return SDL_strdup(spot + 1); - } else { - return SDL_strdup(linkfile); - } - } -#endif /* __LINUX__ || __FREEBSD__ */ - - /* Finally use the default we've used forever */ - return SDL_strdup("SDL_App"); -} - /* X11 driver bootstrap functions */ static int (*orig_x11_errhandler)(Display *, XErrorEvent *) = NULL; @@ -409,9 +368,6 @@ int X11_VideoInit(SDL_VideoDevice *_this) { SDL_VideoData *data = _this->driverdata; - /* Get the window class name, usually the name of the application */ - data->classname = get_classname(); - /* Get the process PID to be associated to the window */ data->pid = getpid(); @@ -491,7 +447,6 @@ void X11_VideoQuit(SDL_VideoDevice *_this) X11_XDestroyWindow(data->display, data->clipboard_window); } - SDL_free(data->classname); #ifdef X_HAVE_UTF8_STRING if (data->im) { X11_XCloseIM(data->im); diff --git a/src/video/x11/SDL_x11video.h b/src/video/x11/SDL_x11video.h index 6a1fd054f2fb6..2f6590dab795c 100644 --- a/src/video/x11/SDL_x11video.h +++ b/src/video/x11/SDL_x11video.h @@ -69,7 +69,6 @@ struct SDL_VideoData { Display *display; Display *request_display; - char *classname; pid_t pid; XIM im; Uint64 screensaver_activity; diff --git a/src/video/x11/SDL_x11window.c b/src/video/x11/SDL_x11window.c index c391127a3de60..408ac3910fbc6 100644 --- a/src/video/x11/SDL_x11window.c +++ b/src/video/x11/SDL_x11window.c @@ -27,6 +27,7 @@ #include "../../events/SDL_keyboard_c.h" #include "../../events/SDL_mouse_c.h" #include "../../events/SDL_events_c.h" +#include "../../core/unix/SDL_appid.h" #include "SDL_x11video.h" #include "SDL_x11mouse.h" @@ -634,8 +635,8 @@ int X11_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) /* Setup the class hints so we can get an icon (AfterStep) */ classhints = X11_XAllocClassHint(); - classhints->res_name = data->classname; - classhints->res_class = data->classname; + classhints->res_name = (char *)SDL_GetExeName(); + classhints->res_class = (char *)SDL_GetAppID(); /* Set the size, input and class hints, and define WM_CLIENT_MACHINE and WM_LOCALE_NAME */ X11_XSetWMProperties(display, w, NULL, NULL, NULL, 0, sizehints, wmhints, classhints);