diff --git a/.ci/Brewfile b/.ci/Brewfile index 8f46e304916c..359992c22359 100644 --- a/.ci/Brewfile +++ b/.ci/Brewfile @@ -54,7 +54,6 @@ brew 'osm-gps-map' brew 'portmidi' brew 'potrace' brew 'pugixml' -brew 'sdl2' brew 'sdl3' brew 'shared-mime-info' brew 'curl' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b26cc1a8ca6b..7efb15221655 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -154,7 +154,7 @@ jobs: libraw-dev \ librsvg2-dev \ libsaxon-java \ - libsdl2-dev \ + libsdl3-dev \ libsecret-1-dev \ libsqlite3-dev \ libtiff5-dev \ @@ -275,7 +275,7 @@ jobs: portmidi:p potrace:p pugixml:p - SDL2:p + sdl3:p sqlite3:p zlib:p update: true diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 504b120074bf..0bb85e8b54a8 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -80,7 +80,7 @@ jobs: libpugixml-dev \ librsvg2-dev \ libsaxon-java \ - libsdl2-dev \ + libsdl3-dev \ libsecret-1-dev \ libsqlite3-dev \ libtiff5-dev \ @@ -264,7 +264,7 @@ jobs: portmidi:p potrace:p pugixml:p - SDL2:p + sdl3:p sqlite3:p webp-pixbuf-loader:p zlib:p diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile index aad8ffd807f6..21a79c917522 100644 --- a/.gitpod.Dockerfile +++ b/.gitpod.Dockerfile @@ -54,7 +54,7 @@ RUN sudo apt-get update && \ libpugixml-dev \ librsvg2-dev \ libsaxon-java \ - libsdl2-dev \ + libsdl3-dev \ libsecret-1-dev \ libsqlite3-dev \ libtiff5-dev \ diff --git a/DefineOptions.cmake b/DefineOptions.cmake index 15fcad411853..c0a4898f2f9b 100644 --- a/DefineOptions.cmake +++ b/DefineOptions.cmake @@ -36,7 +36,7 @@ option(BUILD_CURVE_TOOLS "Build tools for generating base and tone curves" OFF) option(USE_GMIC "Use G'MIC image processing framework." ON) option(USE_ICU "Use ICU - International Components for Unicode." ON) option(FORCE_COLORED_OUTPUT "Always produce ANSI-colored output (GNU/Clang only)." OFF) -option(USE_SDL2 "Enable SDL2 support" ON) +option(USE_SDL3 "Enable SDL3 support" ON) if (USE_OPENCL) option(TESTBUILD_OPENCL_PROGRAMS "Test-compile OpenCL programs (needs LLVM and Clang 7+)" ON) diff --git a/README.md b/README.md index ff91b26b1312..ecd056642d24 100644 --- a/README.md +++ b/README.md @@ -264,7 +264,7 @@ Optional dependencies (minimum version): Optional dependencies (no version requirement): * colord, Xatom *(for fetching the system display color profile)* * PortMidi *(for MIDI input support)* -* SDL2 *(for gamepad input support)* +* SDL3 *(for gamepad input support)* * CUPS *(for print mode support)* * OpenEXR *(for EXR import & export)* * OpenJPEG *(for JPEG 2000 import & export)* diff --git a/packaging/macosx/3_make_hb_darktable_package.sh b/packaging/macosx/3_make_hb_darktable_package.sh index dfe84ae31182..ec68b2eaeec8 100755 --- a/packaging/macosx/3_make_hb_darktable_package.sh +++ b/packaging/macosx/3_make_hb_darktable_package.sh @@ -249,15 +249,6 @@ for dtSharedObj in $dtSharedObjDirs; do cp -LR "$homebrewHome"/lib/"$dtSharedObj"/* "$dtResourcesDir"/lib/"$dtSharedObj"/ done -# Homebrew's `sdl2` is sdl2-compat, which dlopen()s libSDL3 at runtime — -# otool can't see that, so install_dependencies misses it; copy explicitly -sdl3Source="$homebrewHome/opt/sdl3/lib/libSDL3.dylib" -if [[ -f "$sdl3Source" ]]; then - cp -L "$sdl3Source" "$dtResourcesDir"/lib/libSDL3.dylib - install_name_tool -id "@executable_path/../Resources/lib/libSDL3.dylib" \ - "$dtResourcesDir"/lib/libSDL3.dylib || true -fi - dtSharedObjDirs="libgphoto2 libgphoto2_port" for dtSharedObj in $dtSharedObjDirs; do mkdir "$dtResourcesDir"/lib/"$dtSharedObj" diff --git a/packaging/macosx/BUILD-ARM64.txt b/packaging/macosx/BUILD-ARM64.txt index df6e4d08cc06..e46491f19999 100644 --- a/packaging/macosx/BUILD-ARM64.txt +++ b/packaging/macosx/BUILD-ARM64.txt @@ -4,7 +4,7 @@ How to make disk image with darktable application bundle (64 bit ARM only): They will need some tuning, so before you install anything add this line to /opt/local/etc/macports/variants.conf: +no_gnome +no_x11 +quartz -x11 -gnome -gfortran Install required dependencies: - $ sudo port install exiv2 libgphoto2 gtk-osx-application-gtk3 lensfun librsvg openexr json-glib GraphicsMagick openjpeg webp libsecret pugixml osm-gps-map adwaita-icon-theme intltool iso-codes libomp gmic-lib libheif portmidi libsdl2 libavif libjxl potrace + $ sudo port install exiv2 libgphoto2 gtk-osx-application-gtk3 lensfun librsvg openexr json-glib GraphicsMagick openjpeg webp libsecret pugixml osm-gps-map adwaita-icon-theme intltool iso-codes libomp gmic-lib libheif portmidi libsdl3 libavif libjxl potrace Clone darktable git repository (in this example into ~/src): $ mkdir ~/src $ cd ~/src diff --git a/packaging/macosx/BUILD.txt b/packaging/macosx/BUILD.txt index 291e3d62489e..8939f327d0d0 100644 --- a/packaging/macosx/BUILD.txt +++ b/packaging/macosx/BUILD.txt @@ -14,7 +14,7 @@ How to make disk image with darktable application bundle (64 bit Intel only): $ curl -Lo ~/ports/x11/pango/files/patch-meson-examples-tests.diff https://github.com/macports/macports-ports/raw/26241fac142ac2bbe2a9071918ff20b301c66f4b/x11/pango/files/patch-meson-examples-tests.diff $ portindex ~/ports - $ sudo port install exiv2 libgphoto2 gtk-osx-application-gtk3 lensfun librsvg openexr json-glib GraphicsMagick openjpeg webp libsecret pugixml osm-gps-map adwaita-icon-theme intltool iso-codes libomp gmic-lib libheif portmidi libsdl2 libavif libjxl potrace + $ sudo port install exiv2 libgphoto2 gtk-osx-application-gtk3 lensfun librsvg openexr json-glib GraphicsMagick openjpeg webp libsecret pugixml osm-gps-map adwaita-icon-theme intltool iso-codes libomp gmic-lib libheif portmidi libsdl3 libavif libjxl potrace Clone darktable git repository (in this example into ~/src): $ mkdir ~/src $ cd ~/src diff --git a/packaging/nix/flake.nix b/packaging/nix/flake.nix index f099d201f6fd..3e5b896c8216 100644 --- a/packaging/nix/flake.nix +++ b/packaging/nix/flake.nix @@ -43,7 +43,7 @@ buildInputs = with pkgs; [ - SDL2 + SDL3 adwaita-icon-theme cairo curl diff --git a/packaging/windows/README.md b/packaging/windows/README.md index e4c642b717a7..54aca8b616f1 100644 --- a/packaging/windows/README.md +++ b/packaging/windows/README.md @@ -15,7 +15,7 @@ The steps to build the darktable Windows executable and make the installer are a * Install required and recommended dependencies for darktable: ```bash - pacman -S --needed mingw-w64-ucrt-x86_64-{libxslt,python-jsonschema,curl,drmingw,exiv2,gettext,gmic,graphicsmagick,gtk3,icu,imath,iso-codes,lcms2,lensfun,libavif,libarchive,libgphoto2,libheif,libjpeg-turbo,libjxl,libpng,libraw,librsvg,libsecret,libtiff,libwebp,libxml2,lua54,openexr,openjpeg2,osm-gps-map,portmidi,potrace,pugixml,SDL2,sqlite3,webp-pixbuf-loader,zlib} + pacman -S --needed mingw-w64-ucrt-x86_64-{libxslt,python-jsonschema,curl,drmingw,exiv2,gettext,gmic,graphicsmagick,gtk3,icu,imath,iso-codes,lcms2,lensfun,libavif,libarchive,libgphoto2,libheif,libjpeg-turbo,libjxl,libpng,libraw,librsvg,libsecret,libtiff,libwebp,libxml2,lua54,openexr,openjpeg2,osm-gps-map,portmidi,potrace,pugixml,sdl3,sqlite3,webp-pixbuf-loader,zlib} ``` * Install the optional tool for building an installer image (currently x64 only): diff --git a/src/libs/CMakeLists.txt b/src/libs/CMakeLists.txt index 6c267ed8dd37..8edb1ffdf14b 100644 --- a/src/libs/CMakeLists.txt +++ b/src/libs/CMakeLists.txt @@ -75,17 +75,17 @@ if(PortMidi_FOUND) target_link_libraries (midi ${PortMidi_LIBRARY}) endif() -if(USE_SDL2) - find_package(SDL2) - if(SDL2_FOUND) +if(USE_SDL3) + find_package(SDL3) + if(SDL3_FOUND) add_definitions("-DHAVE_SDL") set(MODULES ${MODULES} gamepad) add_library(gamepad MODULE "tools/gamepad.c") - if(TARGET SDL2::SDL2) - target_link_libraries(gamepad SDL2::SDL2) + if(TARGET SDL3::SDL3) + target_link_libraries(gamepad SDL3::SDL3) else() - include_directories(${SDL2_INCLUDE_DIRS}) - target_link_libraries(gamepad ${SDL2_LIBRARIES}) + include_directories(${SDL3_INCLUDE_DIRS}) + target_link_libraries(gamepad ${SDL3_LIBRARIES}) endif() endif() endif() diff --git a/src/libs/tools/gamepad.c b/src/libs/tools/gamepad.c index 1d843f0755a7..d3045738bfb7 100644 --- a/src/libs/tools/gamepad.c +++ b/src/libs/tools/gamepad.c @@ -30,7 +30,7 @@ DT_MODULE(1) #ifdef HAVE_SDL -#include +#include const char *name(dt_lib_module_t *self) { @@ -50,12 +50,13 @@ uint32_t container(dt_lib_module_t *self) typedef struct dt_gamepad_device_t { dt_input_device_t id; - SDL_GameController *controller; + SDL_Gamepad *controller; Uint32 timestamp; - int value[SDL_CONTROLLER_AXIS_MAX]; - int location[SDL_CONTROLLER_AXIS_MAX]; + int value[SDL_GAMEPAD_AXIS_COUNT]; + int location[SDL_GAMEPAD_AXIS_COUNT]; } dt_gamepad_device_t; +// SDL3 face buttons are south/east/west/north but share indices 0–3 with former a/b/x/y static const char *_button_names[] = { N_("button a"), N_("button b"), N_("button x"), N_("button y"), N_("button back"), N_("button guide"), N_("button start"), @@ -65,10 +66,14 @@ static const char *_button_names[] N_("left trigger"), N_("right trigger"), NULL }; +static const struct { const char *alias; guint key; } _button_aliases[] + = { { N_("button south"), 0 }, { N_("button east"), 1 }, { N_("button west"), 2 }, { N_("button north"), 3 }, + { NULL, 0 } }; + static gchar *_key_to_string(const guint key, const gboolean display) { - const gchar *name = key < SDL_CONTROLLER_BUTTON_MAX + 2 ? _button_names[key] : N_("invalid gamepad button"); + const gchar *name = key < SDL_GAMEPAD_BUTTON_COUNT + 2 ? _button_names[key] : N_("invalid gamepad button"); return g_strdup(display ? _(name) : name); } @@ -82,6 +87,13 @@ static gboolean _string_to_key(const gchar *string, else (*key)++; + for(int i = 0; _button_aliases[i].alias; i++) + if(!strcmp(_button_aliases[i].alias, string)) + { + *key = _button_aliases[i].key; + return TRUE; + } + return FALSE; } @@ -93,7 +105,7 @@ static const char *_move_names[] static gchar *_move_to_string(const guint move, const gboolean display) { - const gchar *name = move < SDL_CONTROLLER_AXIS_MAX + 4 /* diagonals */ ? _move_names[move] : N_("invalid gamepad axis"); + const gchar *name = move < SDL_GAMEPAD_AXIS_COUNT + 4 /* diagonals */ ? _move_names[move] : N_("invalid gamepad axis"); return g_strdup(display ? _(name) : name); } @@ -126,7 +138,7 @@ static void _process_axis_timestep(dt_gamepad_device_t *gamepad, if(timestamp > gamepad->timestamp) { Uint32 time = timestamp - gamepad->timestamp; - for(SDL_GameControllerAxis axis = SDL_CONTROLLER_AXIS_LEFTX; axis <= SDL_CONTROLLER_AXIS_RIGHTY; axis++) + for(SDL_GamepadAxis axis = SDL_GAMEPAD_AXIS_LEFTX; axis <= SDL_GAMEPAD_AXIS_RIGHTY; axis++) { if(abs(gamepad->value[axis]) > 4000) gamepad->location[axis] += time * gamepad->value[axis]; @@ -145,7 +157,7 @@ static void _process_axis_and_send(dt_gamepad_device_t *gamepad, for(int side = 0; side < 2; side++) { - int stick = SDL_CONTROLLER_AXIS_LEFTX + 2 * side; + int stick = SDL_GAMEPAD_AXIS_LEFTX + 2 * side; gdouble angle = gamepad->location[stick] / (0.001 + gamepad->location[stick + 1]); @@ -170,7 +182,7 @@ static void _process_axis_and_send(dt_gamepad_device_t *gamepad, } else { - gamepad->location[stick] += size * step_size * angle; + gamepad->location[stick] += size * step_size * angle; dt_shortcut_move(gamepad->id, timestamp, stick + ((angle < 0) ? 5 : 4), size); } } @@ -188,14 +200,14 @@ static gboolean _poll_devices(gpointer user_data) dt_gamepad_device_t *gamepad = NULL; SDL_JoystickID prev_which = -1; - while(SDL_PollEvent(&event) > 0 ) + while(SDL_PollEvent(&event)) { num_events++; - if(event.cbutton.which != prev_which) + if(event.gbutton.which != prev_which) { - prev_which = event.cbutton.which; - SDL_GameController *controller = SDL_GameControllerFromInstanceID(prev_which); + prev_which = event.gbutton.which; + SDL_Gamepad *controller = SDL_GetGamepadFromID(prev_which); gamepad = NULL; for(GSList *gamepads = self->data; gamepads; gamepads = gamepads->next) if(((dt_gamepad_device_t *)(gamepads->data))->controller == controller) @@ -208,55 +220,55 @@ static gboolean _poll_devices(gpointer user_data) switch(event.type) { - case SDL_CONTROLLERBUTTONDOWN: - dt_print(DT_DEBUG_INPUT, "SDL button down event time %d id %d button %hhd state %hhd", event.cbutton.timestamp, event.cbutton.which, event.cbutton.button, event.cbutton.state); - _process_axis_and_send(gamepad, event.cbutton.timestamp); - dt_shortcut_key_press(gamepad->id, event.cbutton.timestamp, event.cbutton.button); + case SDL_EVENT_GAMEPAD_BUTTON_DOWN: + dt_print(DT_DEBUG_INPUT, "SDL button down event time %u id %u button %hhd down %hhd", (guint)SDL_NS_TO_MS(event.gbutton.timestamp), (guint)event.gbutton.which, event.gbutton.button, event.gbutton.down); + _process_axis_and_send(gamepad, SDL_NS_TO_MS(event.gbutton.timestamp)); + dt_shortcut_key_press(gamepad->id, SDL_NS_TO_MS(event.gbutton.timestamp), event.gbutton.button); break; - case SDL_CONTROLLERBUTTONUP: - dt_print(DT_DEBUG_INPUT, "SDL button up event time %d id %d button %hhd state %hhd", event.cbutton.timestamp, event.cbutton.which, event.cbutton.button, event.cbutton.state); - _process_axis_and_send(gamepad, event.cbutton.timestamp); - dt_shortcut_key_release(gamepad->id, event.cbutton.timestamp, event.cbutton.button); + case SDL_EVENT_GAMEPAD_BUTTON_UP: + dt_print(DT_DEBUG_INPUT, "SDL button up event time %u id %u button %hhd down %hhd", (guint)SDL_NS_TO_MS(event.gbutton.timestamp), (guint)event.gbutton.which, event.gbutton.button, event.gbutton.down); + _process_axis_and_send(gamepad, SDL_NS_TO_MS(event.gbutton.timestamp)); + dt_shortcut_key_release(gamepad->id, SDL_NS_TO_MS(event.gbutton.timestamp), event.gbutton.button); break; - case SDL_CONTROLLERAXISMOTION: - dt_print(DT_DEBUG_INPUT, "SDL axis event type %d time %d id %d axis %hhd value %hd", event.caxis.type, event.caxis.timestamp, event.caxis.which, event.caxis.axis, event.caxis.value); + case SDL_EVENT_GAMEPAD_AXIS_MOTION: + dt_print(DT_DEBUG_INPUT, "SDL axis event type %u time %u id %u axis %hhd value %hd", event.gaxis.type, (guint)SDL_NS_TO_MS(event.gaxis.timestamp), (guint)event.gaxis.which, event.gaxis.axis, event.gaxis.value); - if(event.caxis.axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT || event.caxis.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT) + if(event.gaxis.axis == SDL_GAMEPAD_AXIS_LEFT_TRIGGER || event.gaxis.axis == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) { - int trigger = event.caxis.axis - SDL_CONTROLLER_AXIS_TRIGGERLEFT; - if(event.caxis.value / 10500 > gamepad->value[event.caxis.axis]) + int trigger = event.gaxis.axis - SDL_GAMEPAD_AXIS_LEFT_TRIGGER; + if(event.gaxis.value / 10500 > gamepad->value[event.gaxis.axis]) { - dt_shortcut_key_release(gamepad->id, event.cbutton.timestamp, SDL_CONTROLLER_BUTTON_MAX + trigger); - dt_shortcut_key_press(gamepad->id, event.cbutton.timestamp, SDL_CONTROLLER_BUTTON_MAX + trigger); - gamepad->value[event.caxis.axis] = event.caxis.value / 10500; + dt_shortcut_key_release(gamepad->id, SDL_NS_TO_MS(event.gbutton.timestamp), SDL_GAMEPAD_BUTTON_COUNT + trigger); + dt_shortcut_key_press(gamepad->id, SDL_NS_TO_MS(event.gbutton.timestamp), SDL_GAMEPAD_BUTTON_COUNT + trigger); + gamepad->value[event.gaxis.axis] = event.gaxis.value / 10500; } - else if(event.caxis.value / 9500 < gamepad->value[event.caxis.axis]) + else if(event.gaxis.value / 9500 < gamepad->value[event.gaxis.axis]) { - dt_shortcut_key_release(gamepad->id, event.cbutton.timestamp, SDL_CONTROLLER_BUTTON_MAX + trigger); - gamepad->value[event.caxis.axis] = event.caxis.value / 9500; + dt_shortcut_key_release(gamepad->id, SDL_NS_TO_MS(event.gbutton.timestamp), SDL_GAMEPAD_BUTTON_COUNT + trigger); + gamepad->value[event.gaxis.axis] = event.gaxis.value / 9500; } } else { - _process_axis_timestep(gamepad, event.caxis.timestamp); - gamepad->value[event.caxis.axis] = event.caxis.value; + _process_axis_timestep(gamepad, SDL_NS_TO_MS(event.gaxis.timestamp)); + gamepad->value[event.gaxis.axis] = event.gaxis.value; } break; - case SDL_CONTROLLERDEVICEADDED: + case SDL_EVENT_GAMEPAD_ADDED: break; } } for(GSList *gamepads = self->data; gamepads; gamepads = gamepads->next) _process_axis_and_send(gamepads->data, SDL_GetTicks()); - if(num_events) dt_print(DT_DEBUG_INPUT, "sdl num_events: %d time: %u", num_events, SDL_GetTicks()); + if(num_events) dt_print(DT_DEBUG_INPUT, "sdl num_events: %d time: %u", num_events, (guint)SDL_GetTicks()); return G_SOURCE_CONTINUE; } static void _gamepad_open_devices(dt_lib_module_t *self) { - if(SDL_Init(SDL_INIT_GAMECONTROLLER)) + if(!SDL_Init(SDL_INIT_GAMEPAD)) { dt_print(DT_DEBUG_ALWAYS, "[_gamepad_open_devices] ERROR initialising SDL"); return; @@ -266,22 +278,24 @@ static void _gamepad_open_devices(dt_lib_module_t *self) dt_input_device_t id = dt_register_input_driver(self, &_driver_definition); - for(int i = 0; i < SDL_NumJoysticks() && i < 10; i++) + int count = 0; + SDL_JoystickID *ids = SDL_GetGamepads(&count); + if(ids) { - if(SDL_IsGameController(i)) + for(int i = 0; i < count && i < 10; i++) { - SDL_GameController *controller = SDL_GameControllerOpen(i); + SDL_Gamepad *controller = SDL_OpenGamepad(ids[i]); if(!controller) { dt_print(DT_DEBUG_ALWAYS, "[_gamepad_open_devices] ERROR opening game controller '%s'", - SDL_GameControllerNameForIndex(i)); + SDL_GetGamepadNameForID(ids[i])); continue; } else { dt_print(DT_DEBUG_ALWAYS, "[_gamepad_open_devices] opened game controller '%s'", - SDL_GameControllerNameForIndex(i)); + SDL_GetGamepadNameForID(ids[i])); } dt_gamepad_device_t *gamepad = g_malloc0(sizeof(dt_gamepad_device_t)); @@ -291,6 +305,7 @@ static void _gamepad_open_devices(dt_lib_module_t *self) self->data = g_slist_append(self->data, gamepad); } + SDL_free(ids); } if(self->data) { @@ -301,7 +316,7 @@ static void _gamepad_open_devices(dt_lib_module_t *self) static void _gamepad_device_free(dt_gamepad_device_t *gamepad) { - SDL_GameControllerClose(gamepad->controller); + SDL_CloseGamepad(gamepad->controller); g_free(gamepad); }