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") | ||||
|     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}) | ||||
|  |  | |||
|  | @ -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/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() | ||||
| { | ||||
|     // This functionality is not supported in Linux.
 | ||||
|     return false; | ||||
|     if (!g_dbusThread) | ||||
|     { | ||||
|         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; | ||||
| 
 | ||||
|     return m_isAttenuationSupported; | ||||
| #elif __linux__ | ||||
|     return true; | ||||
| #else | ||||
|     return false; | ||||
| #endif | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue