diff --git a/.gitignore b/.gitignore index 5269e44..06d5847 100644 --- a/.gitignore +++ b/.gitignore @@ -1037,4 +1037,4 @@ _deps modules.order Module.symvers Mkfile.old -dkms.conf +dkms.conf \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index e251ce2..662525d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20 FATAL_ERROR) -set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD 17) # this can be changed to 14 if Poco is earlier than version 1.13.0 set(CMAKE_CXX_STANDARD_REQUIRED YES) set(CMAKE_POSITION_INDEPENDENT_CODE YES) @@ -13,6 +13,12 @@ project(projectMSDL list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") +# the following should be set to the 'install' folder of projectM if you encounter issues with it finding the projectM libraries +#list(APPEND CMAKE_PREFIX_PATH "projectM 'install' folder") + +# the following should be set to the 'install' folder of projectM if you encounter issues with it finding the projectM libraries +#list(APPEND CMAKE_PREFIX_PATH "projectM 'install' folder") + # Default install layouts. option(ENABLE_FLAT_PACKAGE "Creates a \"flat\" install layout with files and preset/texture dirs directly in the main dir." OFF) if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND NOT ENABLE_FLAT_PACKAGE) @@ -71,6 +77,12 @@ if(NOT SDL2_LINKAGE STREQUAL "shared" AND NOT SDL2_LINKAGE STREQUAL "static") ) endif() +# Add utf8proc before Poco +#set(UTF8PROC_ROOT "utf8proc/") # if you get build errors about utf8proc, set this to the path to your utf8proc install +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + find_package(utf8proc REQUIRED) +endif() + find_package(projectM4 REQUIRED COMPONENTS Playlist) find_package(SDL2 REQUIRED) find_package(Poco REQUIRED COMPONENTS JSON XML Util Foundation) diff --git a/cmake/Findutf8proc.cmake b/cmake/Findutf8proc.cmake new file mode 100644 index 0000000..d89f373 --- /dev/null +++ b/cmake/Findutf8proc.cmake @@ -0,0 +1,32 @@ +# Find the utf8proc header directory +find_path(UTF8PROC_INCLUDE_DIR + NAMES utf8proc.h + PATHS + $ENV{UTF8PROC_ROOT}/include + /usr/local/include + /usr/include +) + +# Find the utf8proc library +find_library(UTF8PROC_LIBRARY + NAMES utf8proc + PATHS + $ENV{UTF8PROC_ROOT}/lib + /usr/local/lib + /usr/lib +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(utf8proc + REQUIRED_VARS + UTF8PROC_LIBRARY + UTF8PROC_INCLUDE_DIR +) + +if(utf8proc_FOUND AND NOT TARGET Utf8Proc::Utf8Proc) + add_library(Utf8Proc::Utf8Proc UNKNOWN IMPORTED) + set_target_properties(Utf8Proc::Utf8Proc PROPERTIES + IMPORTED_LOCATION "${UTF8PROC_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${UTF8PROC_INCLUDE_DIR}" + ) +endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fb65c11..97ad388 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -60,6 +60,7 @@ target_compile_definitions(projectMSDL PROJECTMSDL_VERSION="${PROJECT_VERSION}" ) + target_link_libraries(projectMSDL PRIVATE ProjectMSDL-GUI @@ -70,7 +71,67 @@ target_link_libraries(projectMSDL SDL2::SDL2main ) -if(MSVC) +# Add the dylib copying for macOS +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + function(copy_dylibs_to_frameworks TARGET_NAME DYLIB_SOURCE_DIR) + # Create Frameworks directory + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory + "$/../Frameworks" + ) + + # Find all .dylib files in the source directory + file(GLOB DYLIB_FILES "${DYLIB_SOURCE_DIR}/*.dylib") + + # Copy each .dylib file and fix its rpath + foreach(DYLIB_FILE ${DYLIB_FILES}) + get_filename_component(DYLIB_NAME ${DYLIB_FILE} NAME) + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + "${DYLIB_FILE}" + "$/../Frameworks/${DYLIB_NAME}" + ) + endforeach() + + # Add rpath to the main executable + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND install_name_tool -add_rpath "@executable_path/../Frameworks" + "$" + ) + endfunction() + + # Try to locate projectm + find_path(PROJECTM_LIBRARY_DIR + NAMES libprojectM-4.dylib # Use a known file from the library directory + PATHS + $ENV{PROJECTM_ROOT}/lib # Check an environment variable + /usr/local/lib/projectm # Common install location + /opt/homebrew/lib/projectm # Homebrew on Apple Silicon + /opt/local/lib/projectm # MacPorts location + PATH_SUFFIXES + lib # Look automatically appended subdirectories like 'lib' + NO_DEFAULT_PATH + ) + + if (NOT PROJECTM_LIBRARY_DIR) + find_path(PROJECTM_LIBRARY_DIR + NAMES libprojectM.dylib + PATHS ${CMAKE_PREFIX_PATH} + PATH_SUFFIXES lib + ) + endif() + + if (PROJECTM_LIBRARY_DIR) + message(STATUS "Found projectM libraries at: ${PROJECTM_LIBRARY_DIR}") + # Call the function to copy dylibs found in PROJECTM_LIBRARY_DIR + copy_dylibs_to_frameworks(projectMSDL "${PROJECTM_LIBRARY_DIR}") + else() + message(WARNING "Could not find projectM libraries. Please set PROJECTM_ROOT or specify the library location manually.") + endif() +endif() + + +if (MSVC) set_target_properties(projectMSDL PROPERTIES VS_DPI_AWARE "PerMonitor" diff --git a/src/ProjectMSDLApplication.cpp b/src/ProjectMSDLApplication.cpp index 8f8bb97..d61d3cc 100644 --- a/src/ProjectMSDLApplication.cpp +++ b/src/ProjectMSDLApplication.cpp @@ -202,6 +202,14 @@ void ProjectMSDLApplication::defineOptions(Poco::Util::OptionSet& options) false, "<0/1>", true) .binding("projectM.shuffleEnabled", _commandLineOverrides)); + options.addOption(Option("skipToDropped", "", "Skip to drag & dropped presets", + false, "<0/1>", true) + .binding("projectM.skipToDropped", _commandLineOverrides)); + + options.addOption(Option("droppedFolderOverride", "", "When dropping a folder, clear the playlist and add all presets from the folder.", + false, "<0/1>", true) + .binding("projectM.droppedFolderOverride", _commandLineOverrides)); + options.addOption(Option("presetDuration", "", "Preset duration. Any number > 1, default 30.", false, "", true) .binding("projectM.displayDuration", _commandLineOverrides)); diff --git a/src/RenderLoop.cpp b/src/RenderLoop.cpp index 8a8fbf1..5524c5f 100644 --- a/src/RenderLoop.cpp +++ b/src/RenderLoop.cpp @@ -10,6 +10,8 @@ #include +#include "ProjectMSDLApplication.h" + RenderLoop::RenderLoop() : _audioCapture(Poco::Util::Application::instance().getSubsystem()) , _projectMWrapper(Poco::Util::Application::instance().getSubsystem()) @@ -17,6 +19,7 @@ RenderLoop::RenderLoop() , _projectMHandle(_projectMWrapper.ProjectM()) , _playlistHandle(_projectMWrapper.Playlist()) , _projectMGui(Poco::Util::Application::instance().getSubsystem()) + , _userConfig(ProjectMSDLApplication::instance().UserConfiguration()) { } @@ -103,6 +106,81 @@ void RenderLoop::PollEvents() break; + case SDL_DROPFILE: { + char* droppedFilePath = event.drop.file; + + // first we want to get the config settings that are relevant ehre + // namely skipToDropped and droppedFolderOverride + // we can get them from the projectMWrapper, in the _projectMConfigView available on it + bool skipToDropped = _userConfig->getBool("projectM.skipToDropped", true); + bool droppedFolderOverride = _userConfig->getBool("projectM.droppedFolderOverride", false); + + + bool shuffle = projectm_playlist_get_shuffle(_playlistHandle); + if (shuffle && skipToDropped) { + // if shuffle is enabled, we disable it temporarily, so the dropped preset is played next + // if skipToDropped is false, we also keep shuffle enabled, as it doesn't matter since the current preset is unaffected + projectm_playlist_set_shuffle(_playlistHandle, false); + } + + int index = projectm_playlist_get_position(_playlistHandle) + 1; + + do { + Poco::File droppedFile(droppedFilePath); + if (!droppedFile.isDirectory()) { + // handle dropped preset file + Poco::Path droppedFileP(droppedFilePath); + if (!droppedFile.exists() || (droppedFileP.getExtension() != "milk" && droppedFileP.getExtension() != "prjm")) { + std::string toastMessage = std::string("Invalid preset file: ") + droppedFilePath; + Poco::NotificationCenter::defaultCenter().postNotification(new DisplayToastNotification(toastMessage)); + poco_information_f1(_logger, "%s", toastMessage); + break; // exit the block and go to the shuffle check + } + + if (projectm_playlist_insert_preset(_playlistHandle, droppedFilePath, index, true)) { + if(skipToDropped){ + projectm_playlist_play_next(_playlistHandle, true); + } + poco_information_f1(_logger, "Added preset: %s", std::string(droppedFilePath)); + // no need to toast single presets, as its obvious if a preset was loaded. + } + } else { + // handle dropped directory + + // if droppedFolderOverride is enabled, we clear the playlist first + // current edge case: if the dropped directory is invalid or contains no presets, then it still clears the playlist + if (droppedFolderOverride) { + projectm_playlist_clear(_playlistHandle); + index = 0; + } + + uint32_t addedFilesCount = projectm_playlist_insert_path(_playlistHandle, droppedFilePath, index, true, true); + if (addedFilesCount > 0) { + std::string toastMessage = "Added " + std::to_string(addedFilesCount) + " presets from " + droppedFilePath; + poco_information_f1(_logger, "%s", toastMessage); + if(skipToDropped || droppedFolderOverride){ + // if skip to dropped is true, or if a folder was dropped and it overrode the playlist, we skip to the next preset + projectm_playlist_play_next(_playlistHandle, true); + } + Poco::NotificationCenter::defaultCenter().postNotification(new DisplayToastNotification(toastMessage)); + + }else{ + std::string toastMessage = std::string("No presets found in: ") + droppedFilePath; + Poco::NotificationCenter::defaultCenter().postNotification(new DisplayToastNotification(toastMessage)); + poco_information_f1(_logger, "%s", toastMessage); + } + } + } while (false); + + if (shuffle && skipToDropped) { + projectm_playlist_set_shuffle(_playlistHandle, true); + } + + SDL_free(droppedFilePath); + break; + } + + case SDL_QUIT: _wantsToQuit = true; break; diff --git a/src/RenderLoop.h b/src/RenderLoop.h index 2c39054..8a63949 100644 --- a/src/RenderLoop.h +++ b/src/RenderLoop.h @@ -87,5 +87,7 @@ class RenderLoop ModifierKeyStates _keyStates; //!< Current "pressed" states of modifier keys + Poco::AutoPtr _userConfig; //!< View of the "projectM" configuration subkey in the "user" configuration. + Poco::Logger& _logger{Poco::Logger::get("RenderLoop")}; //!< The class logger. }; diff --git a/src/gui/ProjectMGUI.cpp b/src/gui/ProjectMGUI.cpp index 415bd7a..d73ba1c 100644 --- a/src/gui/ProjectMGUI.cpp +++ b/src/gui/ProjectMGUI.cpp @@ -43,7 +43,7 @@ void ProjectMGUI::initialize(Poco::Util::Application& app) _glContext = renderingWindow.GetGlContext(); ImGui_ImplSDL2_InitForOpenGL(_renderingWindow, _glContext); - ImGui_ImplOpenGL3_Init("#version 130"); + ImGui_ImplOpenGL3_Init("#version 150"); // changing this from '130' to '150' fixes the UI not displaying on MacOS 15 UpdateFontSize(); diff --git a/src/gui/SettingsWindow.cpp b/src/gui/SettingsWindow.cpp index 121a736..aec27a5 100644 --- a/src/gui/SettingsWindow.cpp +++ b/src/gui/SettingsWindow.cpp @@ -105,6 +105,14 @@ void SettingsWindow::DrawProjectMSettingsTab() LabelWithTooltip("Shuffle Presets", "Selects presets randomly from the current playlist."); BooleanSetting("projectM.shuffleEnabled", true); + ImGui::TableNextRow(); + LabelWithTooltip("Skip To Dropped Presets", "If enabled, will skip to the new presets when preset(s) are dropped onto the window and added to the playlist"); + BooleanSetting("projectM.skipToDropped", true); + + ImGui::TableNextRow(); + LabelWithTooltip("Dropped Folder Overrides Playlist", "When dropping a folder, clear the playlist and add all presets from the folder."); + BooleanSetting("projectM.droppedFolderOverride", false); + ImGui::TableNextRow(); LabelWithTooltip("Preset Display Duration", "Time in seconds a preset will be displayed before it's switched."); DoubleSetting("projectM.displayDuration", 30.0, 1.0, 240.0);