From 8baa6d2a2035d2a2f9062d3e4742d4635866d653 Mon Sep 17 00:00:00 2001 From: dakrk Date: Thu, 6 Mar 2025 20:09:04 +0000 Subject: [PATCH] Implement music attenuation on Linux This is implemented by listening for event changes from MPRIS2 clients over D-Bus. Unfortunately there is no good API for this nor a good way to identify current player as there is on Windows, and as such we have to track and queue them manually just to get a seemingly good enough result. This also gives us the issue of needing to guess and manually prioritise which player to use when we first start, as we cannot determine when something before us started. playerctld is preferred to be read on systems that have it available, however that has the behaviour of setting its status to the most recent change. Those who use this and have media key scripts use it would likely be expecting such behaviour though. (All my editors were eager to fix inconsistent CRLF's in the CMakeLists, sorry) --- UnleashedRecomp/CMakeLists.txt | 135 ++++---- UnleashedRecomp/os/linux/media_linux.cpp | 386 +++++++++++++++++++++- UnleashedRecomp/patches/audio_patches.cpp | 2 + 3 files changed, 455 insertions(+), 68 deletions(-) diff --git a/UnleashedRecomp/CMakeLists.txt b/UnleashedRecomp/CMakeLists.txt index 10863c0..94c5eb0 100644 --- a/UnleashedRecomp/CMakeLists.txt +++ b/UnleashedRecomp/CMakeLists.txt @@ -119,7 +119,7 @@ endif() set(UNLEASHED_RECOMP_APU_CXX_SOURCES "apu/audio.cpp" - "apu/embedded_player.cpp" + "apu/embedded_player.cpp" "apu/driver/sdl2_driver.cpp" ) @@ -149,16 +149,16 @@ set(UNLEASHED_RECOMP_PATCHES_CXX_SOURCES set(UNLEASHED_RECOMP_UI_CXX_SOURCES "ui/achievement_menu.cpp" - "ui/achievement_overlay.cpp" + "ui/achievement_overlay.cpp" "ui/black_bar.cpp" "ui/button_guide.cpp" "ui/fader.cpp" - "ui/game_window.cpp" + "ui/game_window.cpp" "ui/imgui_utils.cpp" "ui/installer_wizard.cpp" "ui/message_window.cpp" "ui/options_menu.cpp" - "ui/options_menu_thumbnails.cpp" + "ui/options_menu_thumbnails.cpp" "ui/tv_static.cpp" ) @@ -180,7 +180,7 @@ set(UNLEASHED_RECOMP_INSTALL_CXX_SOURCES set(UNLEASHED_RECOMP_USER_CXX_SOURCES "user/achievement_data.cpp" "user/achievement_manager.cpp" - "user/config.cpp" + "user/config.cpp" "user/registry.cpp" "user/paths.cpp" "user/persistent_data.cpp" @@ -250,11 +250,11 @@ set(UNLEASHED_RECOMP_CXX_SOURCES ${UNLEASHED_RECOMP_USER_CXX_SOURCES} ${UNLEASHED_RECOMP_MOD_CXX_SOURCES} ${UNLEASHED_RECOMP_THIRDPARTY_SOURCES} -) +) -include("version.cmake") - -set(VERSION_TXT "${PROJECT_SOURCE_DIR}/res/version.txt") +include("version.cmake") + +set(VERSION_TXT "${PROJECT_SOURCE_DIR}/res/version.txt") # Only show Git info and build type if not Release. set(SHOW_GIT_INFO_AND_BUILD_TYPE 0) @@ -270,43 +270,43 @@ GenerateVersionSources( BUILD_TYPE ${CMAKE_BUILD_TYPE} SHOW_GIT_INFO ${SHOW_GIT_INFO_AND_BUILD_TYPE} SHOW_BUILD_TYPE ${SHOW_GIT_INFO_AND_BUILD_TYPE} -) +) if (WIN32) - # Create binary version number for Win32 integer attributes. - CreateVersionString( - VERSION_TXT ${VERSION_TXT} - OUTPUT_CSV 1 - OUTPUT_VAR WIN32_VERSION_BINARY - ) - - # Create string version number for Win32 detailed attributes. - CreateVersionString( - VERSION_TXT ${VERSION_TXT} - BUILD_TYPE ${CMAKE_BUILD_TYPE} - SHOW_GIT_INFO ${SHOW_GIT_INFO_AND_BUILD_TYPE} - SHOW_BUILD_TYPE ${SHOW_GIT_INFO_AND_BUILD_TYPE} - OUTPUT_VAR WIN32_VERSION_STRING - ) - + # Create binary version number for Win32 integer attributes. + CreateVersionString( + VERSION_TXT ${VERSION_TXT} + OUTPUT_CSV 1 + OUTPUT_VAR WIN32_VERSION_BINARY + ) + + # Create string version number for Win32 detailed attributes. + CreateVersionString( + VERSION_TXT ${VERSION_TXT} + BUILD_TYPE ${CMAKE_BUILD_TYPE} + SHOW_GIT_INFO ${SHOW_GIT_INFO_AND_BUILD_TYPE} + SHOW_BUILD_TYPE ${SHOW_GIT_INFO_AND_BUILD_TYPE} + OUTPUT_VAR WIN32_VERSION_STRING + ) + # Set Win32 icon path. - set(WIN32_ICON_PATH "${PROJECT_SOURCE_DIR}/../UnleashedRecompResources/images/game_icon.ico") + set(WIN32_ICON_PATH "${PROJECT_SOURCE_DIR}/../UnleashedRecompResources/images/game_icon.ico") configure_file("res/win32/res.rc.template" "${CMAKE_BINARY_DIR}/res.rc" @ONLY) - add_executable(UnleashedRecomp ${UNLEASHED_RECOMP_CXX_SOURCES} "${CMAKE_BINARY_DIR}/res.rc") - - # Hide console for release configurations. - if (${CMAKE_BUILD_TYPE} MATCHES "Release") - target_link_options(UnleashedRecomp PRIVATE "/SUBSYSTEM:WINDOWS" "/ENTRY:mainCRTStartup") + add_executable(UnleashedRecomp ${UNLEASHED_RECOMP_CXX_SOURCES} "${CMAKE_BINARY_DIR}/res.rc") + + # Hide console for release configurations. + if (${CMAKE_BUILD_TYPE} MATCHES "Release") + target_link_options(UnleashedRecomp PRIVATE "/SUBSYSTEM:WINDOWS" "/ENTRY:mainCRTStartup") endif() else() add_executable(UnleashedRecomp ${UNLEASHED_RECOMP_CXX_SOURCES}) endif() if (UNLEASHED_RECOMP_FLATPAK) - target_compile_definitions(UnleashedRecomp PRIVATE - "UNLEASHED_RECOMP_FLATPAK" - "GAME_INSTALL_DIRECTORY=\"/var/data\"" + target_compile_definitions(UnleashedRecomp PRIVATE + "UNLEASHED_RECOMP_FLATPAK" + "GAME_INSTALL_DIRECTORY=\"/var/data\"" ) endif() @@ -352,9 +352,9 @@ file(CHMOD ${DIRECTX_DXC_TOOL} PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE) if (WIN32) target_link_libraries(UnleashedRecomp PRIVATE - comctl32 + comctl32 dwmapi - ntdll + ntdll Shcore Synchronization winmm @@ -383,9 +383,12 @@ target_include_directories(UnleashedRecomp PRIVATE ) if (CMAKE_SYSTEM_NAME MATCHES "Linux") + find_package(PkgConfig REQUIRED) find_package(X11 REQUIRED) - target_include_directories(UnleashedRecomp PRIVATE ${X11_INCLUDE_DIR}) - target_link_libraries(UnleashedRecomp PRIVATE ${X11_LIBRARIES}) + pkg_search_module(GLIB REQUIRED glib-2.0) + pkg_search_module(GIO REQUIRED gio-2.0) + target_include_directories(UnleashedRecomp PRIVATE ${X11_INCLUDE_DIR} ${GLIB_INCLUDE_DIRS} ${GIO_INCLUDE_DIRS}) + target_link_libraries(UnleashedRecomp PRIVATE ${X11_LIBRARIES} ${GLIB_LIBRARIES} ${GIO_LIBRARIES}) endif() target_precompile_headers(UnleashedRecomp PUBLIC ${UNLEASHED_RECOMP_PRECOMPILED_HEADERS}) @@ -417,9 +420,9 @@ function(compile_pixel_shader FILE_PATH) compile_shader(${FILE_PATH} ps_6_0) endfunction() -compile_pixel_shader(blend_color_alpha_ps) -compile_vertex_shader(copy_vs) -compile_pixel_shader(copy_color_ps) +compile_pixel_shader(blend_color_alpha_ps) +compile_vertex_shader(copy_vs) +compile_pixel_shader(copy_color_ps) compile_pixel_shader(copy_depth_ps) compile_pixel_shader(csd_filter_ps) compile_vertex_shader(csd_no_tex_vs) @@ -433,7 +436,7 @@ compile_pixel_shader(gamma_correction_ps) compile_pixel_shader(imgui_ps) compile_vertex_shader(imgui_vs) compile_pixel_shader(movie_ps) -compile_vertex_shader(movie_vs) +compile_vertex_shader(movie_vs) compile_pixel_shader(resolve_msaa_color_2x) compile_pixel_shader(resolve_msaa_color_4x) compile_pixel_shader(resolve_msaa_color_8x) @@ -443,7 +446,7 @@ compile_pixel_shader(resolve_msaa_depth_8x) set(RESOURCES_SOURCE_PATH "${PROJECT_SOURCE_DIR}/../UnleashedRecompResources") set(RESOURCES_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/res") - + ## Miscellaneous ## BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/bc_diff/button_bc_diff.bin" DEST_FILE "${RESOURCES_OUTPUT_PATH}/bc_diff/button_bc_diff.bin" ARRAY_NAME "g_button_bc_diff" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/font/im_font_atlas.bin" DEST_FILE "${RESOURCES_OUTPUT_PATH}/font/im_font_atlas.bin" ARRAY_NAME "g_im_font_atlas" COMPRESSION_TYPE "zstd") @@ -455,7 +458,7 @@ BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/co BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/kbm.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/kbm.dds" ARRAY_NAME "g_kbm" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/select.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/select.dds" ARRAY_NAME "g_select" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/light.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/light.dds" ARRAY_NAME "g_light" COMPRESSION_TYPE "zstd") - + ## Installer ## BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/arrow_circle.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/arrow_circle.dds" ARRAY_NAME "g_arrow_circle" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_001.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_001.dds" ARRAY_NAME "g_install_001" COMPRESSION_TYPE "zstd") @@ -467,23 +470,23 @@ BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/in BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_007.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_007.dds" ARRAY_NAME "g_install_007" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_008.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_008.dds" ARRAY_NAME "g_install_008" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/miles_electric_icon.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/miles_electric_icon.dds" ARRAY_NAME "g_miles_electric_icon" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/pulse_install.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/pulse_install.dds" ARRAY_NAME "g_pulse_install" COMPRESSION_TYPE "zstd") - +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/pulse_install.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/pulse_install.dds" ARRAY_NAME "g_pulse_install" COMPRESSION_TYPE "zstd") + ## Options Menu ## BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/achievement_notifications.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/achievement_notifications.dds" ARRAY_NAME "g_achievement_notifications" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/allow_background_input_ps.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/allow_background_input_ps.dds" ARRAY_NAME "g_allow_background_input_ps" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/allow_background_input_xb.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/allow_background_input_xb.dds" ARRAY_NAME "g_allow_background_input_xb" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/antialiasing_none.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/antialiasing_none.dds" ARRAY_NAME "g_antialiasing_none" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/antialiasing_2x.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/antialiasing_2x.dds" ARRAY_NAME "g_antialiasing_2x" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/antialiasing_4x.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/antialiasing_4x.dds" ARRAY_NAME "g_antialiasing_4x" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/antialiasing_none.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/antialiasing_none.dds" ARRAY_NAME "g_antialiasing_none" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/antialiasing_2x.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/antialiasing_2x.dds" ARRAY_NAME "g_antialiasing_2x" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/antialiasing_4x.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/antialiasing_4x.dds" ARRAY_NAME "g_antialiasing_4x" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/antialiasing_8x.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/antialiasing_8x.dds" ARRAY_NAME "g_antialiasing_8x" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/aspect_ratio.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/aspect_ratio.dds" ARRAY_NAME "g_aspect_ratio" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/battle_theme.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/battle_theme.dds" ARRAY_NAME "g_battle_theme" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/brightness.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/brightness.dds" ARRAY_NAME "g_brightness" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/channel_stereo.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/channel_stereo.dds" ARRAY_NAME "g_channel_stereo" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/channel_surround.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/channel_surround.dds" ARRAY_NAME "g_channel_surround" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/brightness.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/brightness.dds" ARRAY_NAME "g_brightness" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/channel_stereo.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/channel_stereo.dds" ARRAY_NAME "g_channel_stereo" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/channel_surround.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/channel_surround.dds" ARRAY_NAME "g_channel_surround" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/control_tutorial_ps.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/control_tutorial_ps.dds" ARRAY_NAME "g_control_tutorial_ps" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/control_tutorial_xb.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/control_tutorial_xb.dds" ARRAY_NAME "g_control_tutorial_xb" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/control_tutorial_xb.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/control_tutorial_xb.dds" ARRAY_NAME "g_control_tutorial_xb" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/controller_icons.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/controller_icons.dds" ARRAY_NAME "g_controller_icons" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/default.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/default.dds" ARRAY_NAME "g_default" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/effects_volume.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/effects_volume.dds" ARRAY_NAME "g_effects_volume" COMPRESSION_TYPE "zstd") @@ -499,7 +502,7 @@ BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/op BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/motion_blur_off.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/motion_blur_off.dds" ARRAY_NAME "g_motion_blur_off" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/motion_blur_original.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/motion_blur_original.dds" ARRAY_NAME "g_motion_blur_original" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/motion_blur_enhanced.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/motion_blur_enhanced.dds" ARRAY_NAME "g_motion_blur_enhanced" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/movie_scale_fit.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/movie_scale_fit.dds" ARRAY_NAME "g_movie_scale_fit" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/movie_scale_fit.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/movie_scale_fit.dds" ARRAY_NAME "g_movie_scale_fit" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/movie_scale_fill.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/movie_scale_fill.dds" ARRAY_NAME "g_movie_scale_fill" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/music_attenuation.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/music_attenuation.dds" ARRAY_NAME "g_music_attenuation" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/music_volume.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/music_volume.dds" ARRAY_NAME "g_music_volume" COMPRESSION_TYPE "zstd") @@ -508,34 +511,34 @@ BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/op BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/shadow_resolution_x2048.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/shadow_resolution_x2048.dds" ARRAY_NAME "g_shadow_resolution_x2048" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/shadow_resolution_x4096.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/shadow_resolution_x4096.dds" ARRAY_NAME "g_shadow_resolution_x4096" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/shadow_resolution_x8192.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/shadow_resolution_x8192.dds" ARRAY_NAME "g_shadow_resolution_x8192" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/time_transition_ps.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/time_transition_ps.dds" ARRAY_NAME "g_time_of_day_transition_playstation" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/time_transition_ps.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/time_transition_ps.dds" ARRAY_NAME "g_time_of_day_transition_playstation" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/time_transition_xb.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/time_transition_xb.dds" ARRAY_NAME "g_time_of_day_transition_xbox" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/transparency_antialiasing_false.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/transparency_antialiasing_false.dds" ARRAY_NAME "g_transparency_antialiasing_false" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/transparency_antialiasing_true.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/transparency_antialiasing_true.dds" ARRAY_NAME "g_transparency_antialiasing_true" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/ui_alignment_centre.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/ui_alignment_centre.dds" ARRAY_NAME "g_ui_alignment_centre" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/ui_alignment_centre.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/ui_alignment_centre.dds" ARRAY_NAME "g_ui_alignment_centre" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/ui_alignment_edge.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/ui_alignment_edge.dds" ARRAY_NAME "g_ui_alignment_edge" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/vertical_camera.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/vertical_camera.dds" ARRAY_NAME "g_vertical_camera" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/voice_language.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/voice_language.dds" ARRAY_NAME "g_voice_language" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/vibration_ps.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/vibration_ps.dds" ARRAY_NAME "g_vibration_ps" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/vibration_xb.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/vibration_xb.dds" ARRAY_NAME "g_vibration_xb" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/vsync_on.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/vsync_on.dds" ARRAY_NAME "g_vsync_on" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/vibration_xb.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/vibration_xb.dds" ARRAY_NAME "g_vibration_xb" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/vsync_on.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/vsync_on.dds" ARRAY_NAME "g_vsync_on" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/vsync_off.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/vsync_off.dds" ARRAY_NAME "g_vsync_off" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/window_size.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/window_size.dds" ARRAY_NAME "g_window_size" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/xbox_color_correction.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/xbox_color_correction.dds" ARRAY_NAME "g_xbox_color_correction" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/miles_electric.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/miles_electric.dds" ARRAY_NAME "g_miles_electric" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/options_static.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/options_static.dds" ARRAY_NAME "g_options_static" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/miles_electric.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/miles_electric.dds" ARRAY_NAME "g_miles_electric" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/options_static.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/options_static.dds" ARRAY_NAME "g_options_static" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/options_static_flash.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/options_static_flash.dds" ARRAY_NAME "g_options_static_flash" COMPRESSION_TYPE "zstd") - + ## Game Icon ## BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/game_icon.bmp" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/game_icon.bmp" ARRAY_NAME "g_game_icon") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/game_icon_night.bmp" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/game_icon_night.bmp" ARRAY_NAME "g_game_icon_night") - +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/game_icon_night.bmp" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/game_icon_night.bmp" ARRAY_NAME "g_game_icon_night") + ## Audio ## -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/music/installer.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/music/installer.ogg" ARRAY_NAME "g_installer_music") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/music/installer.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/music/installer.ogg" ARRAY_NAME "g_installer_music") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_worldmap_cursor.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_worldmap_cursor.ogg" ARRAY_NAME "g_sys_worldmap_cursor") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_worldmap_finaldecide.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_worldmap_finaldecide.ogg" ARRAY_NAME "g_sys_worldmap_finaldecide") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_actstg_pausecansel.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_actstg_pausecansel.ogg" ARRAY_NAME "g_sys_actstg_pausecansel") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_actstg_pausecursor.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_actstg_pausecursor.ogg" ARRAY_NAME "g_sys_actstg_pausecursor") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_actstg_pausedecide.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_actstg_pausedecide.ogg" ARRAY_NAME "g_sys_actstg_pausedecide") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_actstg_pausewinclose.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_actstg_pausewinclose.ogg" ARRAY_NAME "g_sys_actstg_pausewinclose") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_actstg_pausewinopen.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_actstg_pausewinopen.ogg" ARRAY_NAME "g_sys_actstg_pausewinopen") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_actstg_pausewinopen.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_actstg_pausewinopen.ogg" ARRAY_NAME "g_sys_actstg_pausewinopen") diff --git a/UnleashedRecomp/os/linux/media_linux.cpp b/UnleashedRecomp/os/linux/media_linux.cpp index 9fc19b0..45a29ea 100644 --- a/UnleashedRecomp/os/linux/media_linux.cpp +++ b/UnleashedRecomp/os/linux/media_linux.cpp @@ -1,7 +1,389 @@ +#include +#include +#include +#include +#include +#include +#include +#include #include +#include + +enum class PlaybackStatus +{ + Stopped, + Playing, + Paused +}; + +static const char* DBusInterface = "org.freedesktop.DBus"; +static const char* DBusPropertiesInterface = "org.freedesktop.DBus.Properties"; +static const char* DBusPath = "/org/freedesktop/DBus"; +static const char* MPRIS2Interface = "org.mpris.MediaPlayer2"; +static const char* MPRIS2PlayerInterface = "org.mpris.MediaPlayer2.Player"; +static const char* MPRIS2Path = "/org/mpris/MediaPlayer2"; +static const char* MPRIS2PlayerctldBus = "org.mpris.MediaPlayer2.playerctld"; + +static std::optional g_dbusThread; +static std::string g_playerctldBus; +static std::unordered_map g_playerStatus; +static std::list g_activePlayers; +static std::atomic g_activeStatus; + +static PlaybackStatus PlaybackStatusFromString(const char* str) +{ + if (g_str_equal(str, "Playing")) + return PlaybackStatus::Playing; + else if (g_str_equal(str, "Paused")) + return PlaybackStatus::Paused; + else + return PlaybackStatus::Stopped; +} + +static bool ContainerHas(const auto& container, const auto& value) +{ + return std::find(container.begin(), container.end(), value) != container.end(); +} + +static void UpdateActiveStatus() +{ + if (!g_activePlayers.empty()) + g_activeStatus = g_playerStatus[g_activePlayers.front()]; + else + g_activeStatus = PlaybackStatus::Stopped; +} + +static void UpdateActivePlayers(const char* name, PlaybackStatus status) +{ + if (status == PlaybackStatus::Stopped) + { + // Don't remove playerctld from the queue, we want to prefer it for as long as it's available + if (!g_str_equal(name, g_playerctldBus.c_str())) + { + g_activePlayers.remove(name); + } + } + else if (!ContainerHas(g_activePlayers, name)) + { + // Prioritise playerctld if and when it appears + if (g_str_equal(name, g_playerctldBus.c_str())) + { + g_activePlayers.emplace_front(name); + } + else + { + g_activePlayers.emplace_back(name); + } + } + + g_playerStatus.insert_or_assign(name, status); + UpdateActiveStatus(); +} + +static PlaybackStatus MPRISGetPlaybackStatus(GDBusConnection* connection, const gchar* name) +{ + GError* error; + GVariant* response; + GVariant* tupleChild; + GVariant* value; + PlaybackStatus status; + + error = NULL; + + response = g_dbus_connection_call_sync( + connection, + name, + MPRIS2Path, + DBusPropertiesInterface, + "Get", + g_variant_new("(ss)", MPRIS2PlayerInterface, "PlaybackStatus"), + G_VARIANT_TYPE("(v)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error + ); + + if (!response) + { + LOGF_ERROR("Failed to process D-Bus Get: {}", error->message); + g_clear_error(&error); + return PlaybackStatus::Stopped; + } + + tupleChild = g_variant_get_child_value(response, 0); + value = g_variant_get_variant(tupleChild); + + if (!g_variant_is_of_type(value, G_VARIANT_TYPE_STRING)) + { + LOG_ERROR("Failed to process D-Bus Get"); + g_variant_unref(tupleChild); + return PlaybackStatus::Stopped; + } + + status = PlaybackStatusFromString(g_variant_get_string(value, NULL)); + + g_variant_unref(value); + g_variant_unref(tupleChild); + g_variant_unref(response); + + return status; +} + +// Something is very wrong with the system if this happens +static void DBusConnectionClosed(GDBusConnection* connection, + gboolean remotePeerVanished, + GError* error, + gpointer userData) +{ + LOG_ERROR("D-Bus connection closed"); + g_activeStatus = PlaybackStatus::Stopped; + g_main_loop_quit((GMainLoop*)userData); +} + +static void DBusNameOwnerChanged(GDBusConnection* connection, + const gchar* senderName, + const gchar* objectPath, + const gchar* interfaceName, + const gchar* signalName, + GVariant* parameters, + gpointer userData) +{ + const char* name; + const char* oldOwner; + const char* newOwner; + + g_variant_get(parameters, "(&s&s&s)", &name, &oldOwner, &newOwner); + + if (g_str_has_prefix(name, MPRIS2Interface)) + { + if (g_str_equal(name, MPRIS2PlayerctldBus)) + { + g_playerctldBus = newOwner; + } + + if (oldOwner[0]) + { + g_playerStatus.erase(oldOwner); + g_activePlayers.remove(oldOwner); + } + + UpdateActiveStatus(); + } +} + +static void MPRISPropertiesChanged(GDBusConnection* connection, + const gchar* senderName, + const gchar* objectPath, + const gchar* interfaceName, + const gchar* signalName, + GVariant* parameters, + gpointer userData) +{ + const char* interface; + GVariant* changed; + GVariantIter iter; + const char* key; + GVariant* value; + PlaybackStatus playbackStatus; + + g_variant_get_child(parameters, 0, "&s", &interface); + g_variant_get_child(parameters, 1, "@a{sv}", &changed); + + g_variant_iter_init(&iter, changed); + while (g_variant_iter_next(&iter, "{&sv}", &key, &value)) + { + if (g_str_equal(key, "PlaybackStatus")) + { + playbackStatus = PlaybackStatusFromString(g_variant_get_string(value, NULL)); + UpdateActivePlayers(senderName, playbackStatus); + g_variant_unref(value); + break; + } + g_variant_unref(value); + } + + g_variant_unref(changed); +} + +/* Called upon connect to discover already active MPRIS2 players by looking for + well-known bus names that begin with the MPRIS2 path. + We determine what their priority would be when pushed to the player queue + based on their current status. + The queue (and accompanying data structures) stores unique connection names, + not their well-known ones, as the PropertiesChanged signal only provides the + former. */ +static void DBusListNamesReceived(GObject* object, GAsyncResult* res, gpointer userData) +{ + GDBusConnection* connection; + GError* error; + GVariant* response; + GVariant* tupleChild; + GVariantIter iter; + const gchar* name; + bool foundPlayerctld; + std::list pausedPlayers; + + connection = G_DBUS_CONNECTION(object); + error = NULL; + response = g_dbus_connection_call_finish(connection, res, &error); + foundPlayerctld = false; + + if (!response) + { + LOGF_ERROR("Failed to process D-Bus ListNames: {}", error->message); + g_clear_error(&error); + return; + } + + tupleChild = g_variant_get_child_value(response, 0); + + g_variant_iter_init(&iter, tupleChild); + while (g_variant_iter_next(&iter, "&s", &name)) + { + GVariant* ownerResponse; + const gchar* ownerName; + PlaybackStatus status; + + if (!g_str_has_prefix(name, MPRIS2Interface)) + continue; + + ownerResponse = g_dbus_connection_call_sync( + connection, + DBusInterface, + DBusPath, + DBusInterface, + "GetNameOwner", + g_variant_new("(s)", name), + G_VARIANT_TYPE("(s)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error + ); + + if (!ownerResponse) + { + LOGF_ERROR("Failed to process D-Bus GetNameOwner: {}", error->message); + g_clear_error(&error); + g_variant_unref(tupleChild); + g_variant_unref(response); + return; + } + + g_variant_get(ownerResponse, "(&s)", &ownerName); + status = MPRISGetPlaybackStatus(connection, ownerName); + + // Prioritise playerctld, even if current playback status is stopped + if (!foundPlayerctld && g_str_equal(name, MPRIS2PlayerctldBus)) + { + foundPlayerctld = true; + g_playerctldBus = ownerName; + g_activePlayers.emplace_front(ownerName); + } + else if (status != PlaybackStatus::Stopped) + { + if (status == PlaybackStatus::Playing) + { + g_activePlayers.emplace_back(ownerName); + } + else + { + pausedPlayers.emplace_back(ownerName); + } + } + + g_playerStatus.insert_or_assign(ownerName, status); + g_variant_unref(ownerResponse); + } + + // Put all the paused players at the end of the queue + g_activePlayers.splice(g_activePlayers.end(), pausedPlayers); + UpdateActiveStatus(); + + g_variant_unref(tupleChild); + g_variant_unref(response); +} + +static void DBusThreadProc() +{ + GMainContext* mainContext; + GMainLoop* mainLoop; + GError* error; + GDBusConnection* connection; + + mainContext = g_main_context_new(); + g_main_context_push_thread_default(mainContext); + mainLoop = g_main_loop_new(mainContext, FALSE); + error = NULL; + + connection = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error); + if (!connection) + { + LOGF_ERROR("Failed to connect to D-Bus: {}", error->message); + g_clear_error(&error); + g_main_context_unref(mainContext); + g_main_loop_unref(mainLoop); + return; + } + + g_dbus_connection_set_exit_on_close(connection, FALSE); + g_signal_connect(connection, "closed", G_CALLBACK(DBusConnectionClosed), mainLoop); + + // Listen for player connection changes + g_dbus_connection_signal_subscribe( + connection, + DBusInterface, + DBusInterface, + "NameOwnerChanged", + DBusPath, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + DBusNameOwnerChanged, + NULL, + NULL + ); + + // Listen for player status changes + g_dbus_connection_signal_subscribe( + connection, + NULL, + DBusPropertiesInterface, + "PropertiesChanged", + MPRIS2Path, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + MPRISPropertiesChanged, + NULL, + NULL + ); + + // Request list of current players + g_dbus_connection_call( + connection, + DBusInterface, + DBusPath, + DBusInterface, + "ListNames", + NULL, + G_VARIANT_TYPE("(as)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + DBusListNamesReceived, + NULL + ); + + g_main_loop_run(mainLoop); +} bool os::media::IsExternalMediaPlaying() { - // This functionality is not supported in Linux. - return false; + if (!g_dbusThread) + { + g_dbusThread.emplace(DBusThreadProc); + g_dbusThread->detach(); + } + + return g_activeStatus == PlaybackStatus::Playing; } diff --git a/UnleashedRecomp/patches/audio_patches.cpp b/UnleashedRecomp/patches/audio_patches.cpp index 749d1c6..5f5603d 100644 --- a/UnleashedRecomp/patches/audio_patches.cpp +++ b/UnleashedRecomp/patches/audio_patches.cpp @@ -29,6 +29,8 @@ bool AudioPatches::CanAttenuate() m_isAttenuationSupported = version.Major >= 10 && version.Build >= 17763; return m_isAttenuationSupported; +#elif __linux__ + return true; #else return false; #endif