mirror of
				https://github.com/hedge-dev/UnleashedRecomp.git
				synced 2025-10-30 07:11:05 +00:00 
			
		
		
		
	Compare commits
	
		
			3 commits
		
	
	
		
			ccd8f900e8
			...
			1fa436c619
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 1fa436c619 | ||
|   | 3a22976fec | ||
|   | 8baa6d2a20 | 
					 3 changed files with 330 additions and 4 deletions
				
			
		|  | @ -423,9 +423,12 @@ target_include_directories(UnleashedRecomp PRIVATE | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| if (CMAKE_SYSTEM_NAME MATCHES "Linux") | if (CMAKE_SYSTEM_NAME MATCHES "Linux") | ||||||
|  |     find_package(PkgConfig REQUIRED) | ||||||
|     find_package(X11 REQUIRED) |     find_package(X11 REQUIRED) | ||||||
|     target_include_directories(UnleashedRecomp PRIVATE ${X11_INCLUDE_DIR}) |     pkg_search_module(GLIB REQUIRED glib-2.0) | ||||||
|     target_link_libraries(UnleashedRecomp PRIVATE ${X11_LIBRARIES}) |     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() | endif() | ||||||
| 
 | 
 | ||||||
| target_precompile_headers(UnleashedRecomp PUBLIC ${UNLEASHED_RECOMP_PRECOMPILED_HEADERS}) | target_precompile_headers(UnleashedRecomp PUBLIC ${UNLEASHED_RECOMP_PRECOMPILED_HEADERS}) | ||||||
|  |  | ||||||
|  | @ -1,7 +1,328 @@ | ||||||
|  | #include <algorithm> | ||||||
|  | #include <atomic> | ||||||
|  | #include <optional> | ||||||
|  | #include <string> | ||||||
|  | #include <thread> | ||||||
|  | #include <unordered_map> | ||||||
|  | #include <ranges> | ||||||
|  | #include <gio/gio.h> | ||||||
| #include <os/media.h> | #include <os/media.h> | ||||||
|  | #include <os/logger.h> | ||||||
|  | 
 | ||||||
|  | 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 std::optional<std::thread> g_dbusThread; | ||||||
|  | static std::unordered_map<std::string, PlaybackStatus> g_playerStatus; | ||||||
|  | static std::atomic<bool> g_isPlaying = false; | ||||||
|  | 
 | ||||||
|  | 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 void UpdateActiveStatus() | ||||||
|  | { | ||||||
|  |     g_isPlaying = std::ranges::any_of( | ||||||
|  |             g_playerStatus | std::views::values, | ||||||
|  |             [](PlaybackStatus status) { return status == PlaybackStatus::Playing; } | ||||||
|  |     ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void UpdateActivePlayers(const char* name, PlaybackStatus status) | ||||||
|  | { | ||||||
|  |     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_isPlaying = false; | ||||||
|  |     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 (oldOwner[0]) | ||||||
|  |         { | ||||||
|  |             g_playerStatus.erase(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. | ||||||
|  |    g_playerStatus 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; | ||||||
|  | 
 | ||||||
|  |     connection = G_DBUS_CONNECTION(object); | ||||||
|  |     error = NULL; | ||||||
|  |     response = g_dbus_connection_call_finish(connection, res, &error); | ||||||
|  | 
 | ||||||
|  |     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); | ||||||
|  | 
 | ||||||
|  |         g_playerStatus.insert_or_assign(ownerName, status); | ||||||
|  |         g_variant_unref(ownerResponse); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     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() | bool os::media::IsExternalMediaPlaying() | ||||||
| { | { | ||||||
|     // This functionality is not supported in Linux.
 |     if (!g_dbusThread) | ||||||
|     return false; |     { | ||||||
|  |         g_dbusThread.emplace(DBusThreadProc); | ||||||
|  |         g_dbusThread->detach(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return g_isPlaying; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -29,6 +29,8 @@ bool AudioPatches::CanAttenuate() | ||||||
|     m_isAttenuationSupported = version.Major >= 10 && version.Build >= 17763; |     m_isAttenuationSupported = version.Major >= 10 && version.Build >= 17763; | ||||||
| 
 | 
 | ||||||
|     return m_isAttenuationSupported; |     return m_isAttenuationSupported; | ||||||
|  | #elif __linux__ | ||||||
|  |     return true; | ||||||
| #else | #else | ||||||
|     return false; |     return false; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue