diff --git a/UnleashedRecomp/CMakeLists.txt b/UnleashedRecomp/CMakeLists.txt index 01e040ab..8ae7fa66 100644 --- a/UnleashedRecomp/CMakeLists.txt +++ b/UnleashedRecomp/CMakeLists.txt @@ -413,6 +413,11 @@ if (CMAKE_SYSTEM_NAME MATCHES "Linux") find_package(X11 REQUIRED) target_include_directories(UnleashedRecomp PRIVATE ${X11_INCLUDE_DIR}) target_link_libraries(UnleashedRecomp PRIVATE ${X11_LIBRARIES}) + + set(DBus_DIR "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/dbus") + find_package(DBus REQUIRED) + include_directories(UnleashedRecomp PRIVATE ${DBUS_INCLUDE_DIRS}) + target_link_libraries(${PROJECT_NAME} PRIVATE ${DBUS_LIBRARIES}) endif() target_precompile_headers(UnleashedRecomp PUBLIC ${UNLEASHED_RECOMP_PRECOMPILED_HEADERS}) diff --git a/UnleashedRecomp/os/linux/media_linux.cpp b/UnleashedRecomp/os/linux/media_linux.cpp index 9fc19b0a..0274508c 100644 --- a/UnleashedRecomp/os/linux/media_linux.cpp +++ b/UnleashedRecomp/os/linux/media_linux.cpp @@ -1,7 +1,206 @@ #include +#include +#include -bool os::media::IsExternalMediaPlaying() +#include "app.h" + +#define MEDIA_CHECK_RATE 1 + +static float g_mediaCheckTimer; +static bool g_mediaLastResult; + +static DBusConnection* CreateDBusConnection() +{ + DBusError dbusError; + dbus_error_init(&dbusError); + + DBusConnection* result = dbus_bus_get(DBUS_BUS_SESSION, &dbusError); + if (dbus_error_is_set(&dbusError)) + { + LOGF_ERROR("Failed to create DBus connection: {0}", dbusError.name); + return nullptr; + } + + return result; +} + +static void DestroyDBusConnection(DBusConnection* connection) +{ + assert(connection != nullptr); + dbus_connection_unref(connection); +} + +static std::vector GetMediaPlayerBusNames(DBusConnection* connection) { - // This functionality is not supported in Linux. - return false; + assert(connection != nullptr); + + auto result = std::vector(); + + DBusMessageIter rootIterator; + DBusMessageIter arrayIterator; + DBusPendingCall* pendingReturn; + + DBusMessage *message = dbus_message_new_method_call("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ListNames"); + if (!message) + { + LOG_ERROR("Failed to create D-Bus Message!"); + return result; + } + + dbus_message_iter_init_append(message, &rootIterator); + if (!dbus_connection_send_with_reply(connection, message, &pendingReturn, 40)) + { + LOG_ERROR("Failed to create D-Bus Message!"); + return result; + } + + if (!pendingReturn) + { + LOG_ERROR("D-Bus Pending call is null!"); + return result; + } + + dbus_connection_flush(connection); + dbus_message_unref(message); + dbus_pending_call_block(pendingReturn); + message = dbus_pending_call_steal_reply(pendingReturn); + if (!message) + { + LOG_ERROR("Failed to get D-Bus reply!"); + return result; + } + + dbus_pending_call_unref(pendingReturn); + + if (!dbus_message_iter_init(message, &rootIterator)) + { + LOG_ERROR("D-Bus message has no arguments!"); + return result; + } + + if (dbus_message_iter_get_arg_type(&rootIterator) != DBUS_TYPE_ARRAY) + { + LOG_ERROR("D-Bus message returned invalid type!"); + return result; + } + + dbus_message_iter_recurse(&rootIterator, &arrayIterator); + do + { + const char *rawName; + dbus_message_iter_get_basic(&arrayIterator, &rawName); + const std::string name = rawName; + if (name.starts_with("org.mpris.MediaPlayer2.")) + result.emplace_back(name); + } while (dbus_message_iter_next(&arrayIterator)); + + return result; } + +static bool IsMediaPlayerPlaying(DBusConnection* connection, const std::string& busName) +{ + assert(connection != nullptr); + + DBusMessageIter rootIterator; + DBusMessageIter arrayIterator; + DBusPendingCall* pendingReturn; + + DBusMessage* message = dbus_message_new_method_call(busName.c_str(), "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get"); + if (!message) + { + LOG_ERROR("Failed to create D-Bus Message!"); + return false; + } + + auto interfaceName = "org.mpris.MediaPlayer2.Player"; + auto propertyName = "PlaybackStatus"; + + dbus_message_iter_init_append(message, &rootIterator); + if (!dbus_message_iter_append_basic(&rootIterator, DBUS_TYPE_STRING, &interfaceName)) + { + LOG_ERROR("Failed to append interface name to D-Bus Message!"); + return false; + } + + if (!dbus_message_iter_append_basic(&rootIterator, DBUS_TYPE_STRING, &propertyName)) + { + LOG_ERROR("Failed to append property name to D-Bus Message!"); + return false; + } + + if (!dbus_connection_send_with_reply(connection, message, &pendingReturn, 40)) + { + LOG_ERROR("Failed to create D-Bus Message!"); + return false; + } + + if (!pendingReturn) + { + LOG_ERROR("D-Bus Pending call is null!"); + return false; + } + + dbus_connection_flush(connection); + dbus_message_unref(message); + dbus_pending_call_block(pendingReturn); + message = dbus_pending_call_steal_reply(pendingReturn); + if (!message) + { + LOG_ERROR("Failed to get D-Bus reply!"); + return false; + } + + dbus_pending_call_unref(pendingReturn); + + if (!dbus_message_iter_init(message, &rootIterator)) + { + LOG_ERROR("D-Bus message has no arguments!"); + return false; + } + + if (dbus_message_iter_get_arg_type(&rootIterator) == DBUS_TYPE_VARIANT) + dbus_message_iter_recurse(&rootIterator, &arrayIterator); + else + arrayIterator = rootIterator; + + if (dbus_message_iter_get_arg_type(&arrayIterator) != DBUS_TYPE_STRING) + { + LOG_ERROR("D-Bus message returned invalid type!"); + return false; + } + + const char *rawStatus; + dbus_message_iter_get_basic(&arrayIterator, &rawStatus); + const std::string status = rawStatus; + return status == "Playing"; +} + +bool os::media::IsExternalMediaPlaying() +{ + //Calling D-Bus functions too much seems cause D-Bus to stop working, so perhaps it should be run less often. + g_mediaCheckTimer -= App::s_deltaTime; + if (g_mediaCheckTimer > 0) + return g_mediaLastResult; + + g_mediaCheckTimer = MEDIA_CHECK_RATE; + + const auto dbusConnection = CreateDBusConnection(); + if (!dbusConnection) + return false; + + bool result = false; + + std::vector busNames = GetMediaPlayerBusNames(dbusConnection); + for (const auto& bus : busNames) + { + if (IsMediaPlayerPlaying(dbusConnection, bus)) + { + result = true; + break; + } + } + + DestroyDBusConnection(dbusConnection); + g_mediaLastResult = result; + return result; +} \ No newline at end of file diff --git a/UnleashedRecomp/patches/audio_patches.cpp b/UnleashedRecomp/patches/audio_patches.cpp index 749d1c69..94beecd8 100644 --- a/UnleashedRecomp/patches/audio_patches.cpp +++ b/UnleashedRecomp/patches/audio_patches.cpp @@ -20,13 +20,19 @@ static be* GetVolume(bool isMusic = true) bool AudioPatches::CanAttenuate() { -#if _WIN32 +#if defined(_WIN32) || defined(__linux__) if (m_isAttenuationSupported >= 0) return m_isAttenuationSupported; auto version = os::version::GetOSVersion(); +#if defined(_WIN32) m_isAttenuationSupported = version.Major >= 10 && version.Build >= 17763; +#endif + +#if defined(__linux__) + m_isAttenuationSupported = true; +#endif return m_isAttenuationSupported; #else diff --git a/thirdparty/dbus/DBusConfig.cmake b/thirdparty/dbus/DBusConfig.cmake new file mode 100644 index 00000000..d739df2c --- /dev/null +++ b/thirdparty/dbus/DBusConfig.cmake @@ -0,0 +1,59 @@ +# - Try to find DBus +# Once done, this will define +# +# DBUS_FOUND - system has DBus +# DBUS_INCLUDE_DIRS - the DBus include directories +# DBUS_LIBRARIES - link these to use DBus +# +# Copyright (C) 2012 Raphael Kubo da Costa +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND ITS CONTRIBUTORS ``AS +# IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR ITS +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +FIND_PACKAGE(PkgConfig) +PKG_CHECK_MODULES(PC_DBUS QUIET dbus-1) + +FIND_LIBRARY(DBUS_LIBRARIES + NAMES dbus-1 + HINTS ${PC_DBUS_LIBDIR} + ${PC_DBUS_LIBRARY_DIRS} +) + +FIND_PATH(DBUS_INCLUDE_DIR + NAMES dbus/dbus.h + HINTS ${PC_DBUS_INCLUDEDIR} + ${PC_DBUS_INCLUDE_DIRS} +) + +GET_FILENAME_COMPONENT(_DBUS_LIBRARY_DIR ${DBUS_LIBRARIES} PATH) +FIND_PATH(DBUS_ARCH_INCLUDE_DIR + NAMES dbus/dbus-arch-deps.h + HINTS ${PC_DBUS_INCLUDEDIR} + ${PC_DBUS_INCLUDE_DIRS} + ${_DBUS_LIBRARY_DIR} + ${DBUS_INCLUDE_DIR} + PATH_SUFFIXES include +) + +SET(DBUS_INCLUDE_DIRS ${DBUS_INCLUDE_DIR} ${DBUS_ARCH_INCLUDE_DIR}) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(DBUS REQUIRED_VARS DBUS_INCLUDE_DIRS DBUS_LIBRARIES)