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