inLimbo
TUI Music Player that keeps you in Limbo.
 
Loading...
Searching...
No Matches
mpris-service.hpp
Go to the documentation of this file.
1
13#pragma once
14
15#include <gio/gio.h>
16#include <glib.h>
17#include <iostream>
18#include <string>
19#include <utility>
20
29
31{
32public:
41 MPRISService(std::string applicationName) : applicationName_(std::move(applicationName))
42 {
43
44 const char* introspection_xml = R"XML(
45<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
46 "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
47<node>
48 <interface name="org.mpris.MediaPlayer2">
49 <property name="Identity" type="s" access="read"/>
50 <property name="DesktopEntry" type="s" access="read"/>
51 <property name="SupportedUriSchemes" type="as" access="read"/>
52 <property name="SupportedMimeTypes" type="as" access="read"/>
53 <property name="CanQuit" type="b" access="read"/>
54 <property name="CanRaise" type="b" access="read"/>
55 <property name="HasTrackList" type="b" access="read"/>
56 </interface>
57 <interface name="org.mpris.MediaPlayer2.Player">
58 <property name="PlaybackStatus" type="s" access="read"/>
59 <property name="LoopStatus" type="s" access="readwrite"/>
60 <property name="Rate" type="d" access="readwrite"/>
61 <property name="Shuffle" type="b" access="readwrite"/>
62 <property name="Metadata" type="a{sv}" access="read"/>
63 <property name="Volume" type="d" access="readwrite"/>
64 <property name="Position" type="x" access="read"/>
65 <property name="MinimumRate" type="d" access="read"/>
66 <property name="MaximumRate" type="d" access="read"/>
67 <property name="CanGoNext" type="b" access="read"/>
68 <property name="CanGoPrevious" type="b" access="read"/>
69 <property name="CanPlay" type="b" access="read"/>
70 <property name="CanPause" type="b" access="read"/>
71 <property name="CanSeek" type="b" access="read"/>
72 <property name="CanControl" type="b" access="read"/>
73 <method name="Next"/>
74 <method name="Previous"/>
75 <method name="Pause"/>
76 <method name="PlayPause"/>
77 <method name="Stop"/>
78 <method name="Play"/>
79 <method name="Seek">
80 <arg name="Offset" type="x" direction="in"/>
81 </method>
82 <method name="SetPosition">
83 <arg name="TrackId" type="o" direction="in"/>
84 <arg name="Position" type="x" direction="in"/>
85 </method>
86 <method name="OpenUri">
87 <arg name="Uri" type="s" direction="in"/>
88 </method>
89 </interface>
90</node>)XML";
91
92 GError* error = nullptr;
93 introspection_data_ = g_dbus_node_info_new_for_xml(introspection_xml, &error);
94 if (error != nullptr)
95 {
96 std::cerr << "Error creating introspection data: " << error->message << std::endl;
97 g_error_free(error);
98 return;
99 }
100
101 connection_ = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, &error);
102 if (error != nullptr)
103 {
104 std::cerr << "Error connecting to D-Bus: " << error->message << std::endl;
105 g_error_free(error);
106 return;
107 }
108
109 // Register both interfaces
110 const GDBusInterfaceVTable root_vtable = {
111 handle_root_method_call, handle_root_get_property, nullptr, {nullptr}};
112
113 const GDBusInterfaceVTable player_vtable = {
114 handle_player_method_call, handle_player_get_property, handle_player_set_property, {nullptr}};
115
116 std::string busName = "org.mpris.MediaPlayer2." + applicationName_;
117 guint owner_id =
118 g_bus_own_name(G_BUS_TYPE_SESSION, busName.c_str(), G_BUS_NAME_OWNER_FLAGS_REPLACE,
119 on_bus_acquired, nullptr, nullptr, this, nullptr);
120
121 // Register both interfaces
122 g_dbus_connection_register_object(connection_, "/org/mpris/MediaPlayer2",
123 introspection_data_->interfaces[0], // Root interface
124 &root_vtable, this, nullptr, &error);
125
126 g_dbus_connection_register_object(connection_, "/org/mpris/MediaPlayer2",
127 introspection_data_->interfaces[1], // Player interface
128 &player_vtable, this, nullptr, &error);
129
130 if (error != nullptr)
131 {
132 std::cerr << "Error registering object: " << error->message << std::endl;
133 g_error_free(error);
134 }
135
136 // Initialize default metadata
137 updateMetadata("Unknown Song", "Unknown Artist", "Unknown Album", 0, "No Comment", "No genre",
138 0, 0);
139 }
145
147 {
148 if (connection_)
149 {
150 g_object_unref(connection_);
151 connection_ = nullptr;
152 }
153 if (introspection_data_)
154 {
155 g_dbus_node_info_unref(introspection_data_);
156 introspection_data_ = nullptr;
157 }
158 if (current_metadata_)
159 {
160 g_variant_unref(current_metadata_);
161 current_metadata_ = nullptr;
162 }
163 std::cout << "-- MPRISService cleaned up." << std::endl;
164 }
165
182
183 void updateMetadata(const std::string& title, const std::string& artist, const std::string& album,
184 int64_t length, const std::string& comment = "",
185 const std::string& genre = "", int trackNumber = 0, int discNumber = 0)
186 {
187 GVariantBuilder builder;
188 g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
189
190 // Required field
191 std::string trackid = "/org/mpris/MediaPlayer2/Track/" + std::to_string(rand());
192 g_variant_builder_add(&builder, "{sv}", "mpris:trackid",
193 g_variant_new_object_path(trackid.c_str()));
194
195 g_variant_builder_add(&builder, "{sv}", "xesam:title", g_variant_new_string(title.c_str()));
196
197 GVariantBuilder artist_builder;
198 g_variant_builder_init(&artist_builder, G_VARIANT_TYPE("as"));
199 g_variant_builder_add(&artist_builder, "s", artist.c_str());
200 g_variant_builder_add(&builder, "{sv}", "xesam:artist", g_variant_builder_end(&artist_builder));
201
202 g_variant_builder_add(&builder, "{sv}", "xesam:album", g_variant_new_string(album.c_str()));
203
204 g_variant_builder_add(&builder, "{sv}", "mpris:length", g_variant_new_int64(length * 1000000));
205
206 // Add optional fields
207 if (!comment.empty())
208 {
209 g_variant_builder_add(&builder, "{sv}", "xesam:comment",
210 g_variant_new_string(comment.c_str()));
211 }
212 if (!genre.empty())
213 {
214 GVariantBuilder genre_builder;
215 g_variant_builder_init(&genre_builder, G_VARIANT_TYPE("as"));
216 g_variant_builder_add(&genre_builder, "s", genre.c_str());
217 g_variant_builder_add(&builder, "{sv}", "xesam:genre", g_variant_builder_end(&genre_builder));
218 }
219 if (trackNumber > 0)
220 {
221 g_variant_builder_add(&builder, "{sv}", "xesam:trackNumber",
222 g_variant_new_int32(trackNumber));
223 }
224 if (discNumber > 0)
225 {
226 g_variant_builder_add(&builder, "{sv}", "xesam:discNumber", g_variant_new_int32(discNumber));
227 }
228
229 // Save current metadata
230 if (current_metadata_)
231 g_variant_unref(current_metadata_);
232 current_metadata_ = g_variant_builder_end(&builder);
233 g_variant_ref(current_metadata_);
234
235 // Emit property changed
236 GVariantBuilder props_builder;
237 g_variant_builder_init(&props_builder, G_VARIANT_TYPE_ARRAY);
238 g_variant_builder_add(&props_builder, "{sv}", "Metadata", current_metadata_);
239
240 g_dbus_connection_emit_signal(
241 connection_, nullptr, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties",
242 "PropertiesChanged",
243 g_variant_new("(sa{sv}as)", "org.mpris.MediaPlayer2.Player", &props_builder, nullptr),
244 nullptr);
245 }
246
247private:
258 static void on_bus_acquired(GDBusConnection* connection, const gchar* name, gpointer user_data)
259 {
260 std::cout << "Acquired bus name: " << name << std::endl;
261 }
262
278 static void handle_root_method_call(GDBusConnection*, const char*, const char*, const char*,
279 const char*, GVariant*, GDBusMethodInvocation* invocation,
280 void*)
281 {
282 g_dbus_method_invocation_return_value(invocation, nullptr);
283 }
284
299 static void handle_player_method_call(GDBusConnection*, const char*, const char*, const char*,
300 const char* method_name, GVariant*,
301 GDBusMethodInvocation* invocation, void* user_data)
302 {
303 auto* service = static_cast<MPRISService*>(user_data);
304 g_dbus_method_invocation_return_value(invocation, nullptr);
305 }
306
321 static auto handle_root_get_property(GDBusConnection* connection, const char* sender,
322 const char* object_path, const char* interface_name,
323 const char* property_name, GError** error,
324 void* user_data) -> GVariant*
325 {
326 auto* service = static_cast<MPRISService*>(user_data);
327
328 if (g_strcmp0(property_name, "Identity") == 0)
329 return g_variant_new_string(service->applicationName_.c_str());
330 if (g_strcmp0(property_name, "CanQuit") == 0)
331 return g_variant_new_boolean(TRUE);
332 if (g_strcmp0(property_name, "CanRaise") == 0)
333 return g_variant_new_boolean(TRUE);
334 if (g_strcmp0(property_name, "HasTrackList") == 0)
335 return g_variant_new_boolean(FALSE);
336
337 // Property not recognized: set error and return nullptr
338 g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
339 "Property '%s' is not recognized for interface '%s'.", property_name,
340 interface_name);
341 return nullptr;
342 }
343
359 static auto handle_player_get_property(GDBusConnection* connection, const char* sender,
360 const char* object_path, const char* interface_name,
361 const char* property_name, GError** error,
362 void* user_data) -> GVariant*
363 {
364 auto* service = static_cast<MPRISService*>(user_data);
365
366 if (g_strcmp0(property_name, "PlaybackStatus") == 0)
367 return g_variant_new_string("Playing");
368 if (g_strcmp0(property_name, "LoopStatus") == 0)
369 return g_variant_new_string("None");
370 if (g_strcmp0(property_name, "Rate") == 0)
371 return g_variant_new_double(1.0);
372 if (g_strcmp0(property_name, "Shuffle") == 0)
373 return g_variant_new_boolean(FALSE);
374 if (g_strcmp0(property_name, "Metadata") == 0)
375 return service->current_metadata_ ? g_variant_ref(service->current_metadata_)
376 : g_variant_new("a{sv}", nullptr);
377 if (g_strcmp0(property_name, "Volume") == 0)
378 return g_variant_new_double(1.0);
379 if (g_strcmp0(property_name, "Position") == 0)
380 return g_variant_new_int64(0);
381 if (g_strcmp0(property_name, "MinimumRate") == 0)
382 return g_variant_new_double(1.0);
383 if (g_strcmp0(property_name, "MaximumRate") == 0)
384 return g_variant_new_double(1.0);
385 if (g_strcmp0(property_name, "CanGoNext") == 0)
386 return g_variant_new_boolean(TRUE);
387 if (g_strcmp0(property_name, "CanGoPrevious") == 0)
388 return g_variant_new_boolean(TRUE);
389 if (g_strcmp0(property_name, "CanPlay") == 0)
390 return g_variant_new_boolean(TRUE);
391 if (g_strcmp0(property_name, "CanPause") == 0)
392 return g_variant_new_boolean(TRUE);
393 if (g_strcmp0(property_name, "CanSeek") == 0)
394 return g_variant_new_boolean(FALSE);
395 if (g_strcmp0(property_name, "CanControl") == 0)
396 return g_variant_new_boolean(TRUE);
397
398 // Property not recognized: set error and return nullptr
399 g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
400 "Property '%s' is not recognized for interface '%s'.", property_name,
401 interface_name);
402 return nullptr;
403 }
418 static auto handle_player_set_property(GDBusConnection*, const char*, const char*, const char*,
419 const char*, GVariant*, GError**, void*) -> gboolean
420 {
421 return TRUE;
422 }
423
424 std::string applicationName_;
425 GDBusNodeInfo* introspection_data_ = nullptr;
426 GDBusConnection* connection_ = nullptr;
427 GVariant* current_metadata_ = nullptr;
428};
A class that implements an MPRIS service over D-Bus.
Definition mpris-service.hpp:31
MPRISService(std::string applicationName)
Constructs a new MPRISService object.
Definition mpris-service.hpp:41