Tizen 3.0: Fix issues in the merged code 85/36885/6
authorSudha Bheemanna <b.sudha@samsung.com>
Mon, 16 Mar 2015 14:04:55 +0000 (19:34 +0530)
committerSudha Bheemanna <b.sudha@samsung.com>
Thu, 26 Mar 2015 12:08:51 +0000 (05:08 -0700)
Fixed issues in the merged code found during testing.
Removed unwanted files.

Change-Id: I978bd815bd5f52b4e1431c85750b502dcb31bd96
Signed-off-by: Sudha Bheemanna <b.sudha@samsung.com>
21 files changed:
Makefile.am [changed mode: 0644->0755]
Makefile.tools [changed mode: 0644->0755]
configure.ac [changed mode: 0644->0755]
packaging/baselibs.conf [moved from packaging/bluez-5.19-0/baselibs.conf with 100% similarity, mode: 0755]
packaging/bluetooth.modprobe [moved from packaging/bluez-5.19-0/bluetooth.modprobe with 100% similarity, mode: 0755]
packaging/bluetooth.sh [moved from packaging/bluez-5.19-0/bluetooth.sh with 100% similarity]
packaging/bluetooth.sysconfig [moved from packaging/bluez-5.19-0/bluetooth.sysconfig with 100% similarity, mode: 0755]
packaging/bluez-5.19-0/bluez-5.19.tar.gz [deleted file]
packaging/bluez-5.19-0/bluez.spec [deleted file]
packaging/bluez-5.19-0/create-symlinks [deleted file]
packaging/bluez-5.19-0/obex-root-setup [deleted file]
packaging/bluez-coldplug.init [moved from packaging/bluez-5.19-0/bluez-coldplug.init with 100% similarity, mode: 0755]
packaging/bluez.changes [moved from packaging/bluez-5.19-0/bluez.changes with 100% similarity, mode: 0755]
packaging/bluez.manifest [moved from packaging/bluez-5.19-0/bluez.manifest with 100% similarity, mode: 0755]
packaging/bluez.spec [changed mode: 0644->0755]
packaging/obex.sh [moved from packaging/bluez-5.19-0/obex.sh with 100% similarity]
src/bluetooth.conf [changed mode: 0644->0755]
src/bluetooth.service.in [changed mode: 0644->0755]
src/main.conf [new file with mode: 0755]
src/org.bluez.service [changed mode: 0644->0755]
tools/mpris-player.c [new file with mode: 0755]

old mode 100644 (file)
new mode 100755 (executable)
index 030531f..9dcc13a
@@ -43,13 +43,21 @@ endif
 
 if SYSTEMD
 systemdsystemunitdir = @SYSTEMD_SYSTEMUNITDIR@
+#if USBBT
+#systemdsystemunit_DATA = src/common/bluetooth.service
+#else
 systemdsystemunit_DATA = src/bluetooth.service
+#endif
 
 dbussystembusdir = @DBUS_SYSTEMBUSDIR@
 dbussystembus_DATA = src/org.bluez.service
 endif
 
+if USBBT
+#EXTRA_DIST += src/common/bluetooth.service.in src/org.bluez.service
+else
 EXTRA_DIST += src/bluetooth.service.in src/org.bluez.service
+endif
 
 plugindir = $(libdir)/bluetooth/plugins
 
@@ -192,10 +200,17 @@ src_bluetoothd_LDADD = lib/libbluetooth-internal.la \
 src_bluetoothd_LDFLAGS = $(AM_LDFLAGS) -Wl,--export-dynamic \
                                -Wl,--version-script=$(srcdir)/src/bluetooth.ver
 
+if USBBT
+src_bluetoothd_DEPENDENCIES = lib/libbluetooth-internal.la \
+                               gdbus/libgdbus-internal.la \
+                               src/libshared-glib.la \
+                               src/common/bluetooth.service
+else
 src_bluetoothd_DEPENDENCIES = lib/libbluetooth-internal.la \
                                gdbus/libgdbus-internal.la \
                                src/libshared-glib.la \
                                src/bluetooth.service
+endif
 
 src_bluetoothd_CFLAGS = $(AM_CFLAGS) -DBLUETOOTH_PLUGIN_BUILTIN \
                                        -DPLUGINDIR=\""$(build_plugindir)"\"
old mode 100644 (file)
new mode 100755 (executable)
index f01d3f9..5bce254
@@ -117,11 +117,13 @@ tools_hci_tester_SOURCES = tools/hci-tester.c monitor/bt.h
 tools_hci_tester_LDADD = src/libshared-glib.la @GLIB_LIBS@
 endif
 
+#tools/mpris-proxy tools/mcaptest
+
 if TOOLS
 bin_PROGRAMS += tools/hciattach tools/hciconfig tools/hcitool tools/hcidump \
                        tools/rfcomm tools/rctest tools/l2test tools/l2ping \
                        tools/sdptool tools/ciptool tools/bccmd \
-                       tools/bluemoon tools/mpris-proxy tools/mcaptest
+                       tools/bluemoon
 
 tools_hciattach_SOURCES = tools/hciattach.c tools/hciattach.h \
                                                tools/hciattach_st.c \
@@ -131,7 +133,7 @@ tools_hciattach_SOURCES = tools/hciattach.c tools/hciattach.h \
                                                tools/hciattach_qualcomm.c \
                                                tools/hciattach_intel.c \
                                                tools/hciattach_bcm43xx.c
-#                                              src/log.c 
+#                                              src/log.c
 #                                              tools/hciattach_bcm43xx.c
 tools_hciattach_LDADD = lib/libbluetooth-internal.la
 
@@ -190,14 +192,14 @@ tools_bccmd_LDADD = lib/libbluetooth-internal.la
 tools_bluemoon_SOURCES = tools/bluemoon.c monitor/bt.h
 tools_bluemoon_LDADD = src/libshared-mainloop.la
 
-tools_mpris_proxy_SOURCES = tools/mpris-proxy.c
-tools_mpris_proxy_LDADD = gdbus/libgdbus-internal.la @GLIB_LIBS@ @DBUS_LIBS@
+#tools_mpris_proxy_SOURCES = tools/mpris-proxy.c
+#tools_mpris_proxy_LDADD = gdbus/libgdbus-internal.la @GLIB_LIBS@ @DBUS_LIBS@
 
-tools_mcaptest_SOURCES = tools/mcaptest.c \
-                               btio/btio.h btio/btio.c \
-                               src/log.c src/log.h \
-                               profiles/health/mcap.h profiles/health/mcap.c
-tools_mcaptest_LDADD = lib/libbluetooth-internal.la @GLIB_LIBS@ -lrt
+#tools_mcaptest_SOURCES = tools/mcaptest.c \
+#                              btio/btio.h btio/btio.c \
+#                              src/log.c src/log.h \
+#                              profiles/health/mcap.h profiles/health/mcap.c
+#tools_mcaptest_LDADD = lib/libbluetooth-internal.la @GLIB_LIBS@ -lrt
 
 dist_man_MANS += tools/hciattach.1 tools/hciconfig.1 \
                        tools/hcitool.1 tools/hcidump.1 \
@@ -230,7 +232,7 @@ noinst_PROGRAMS += tools/bdaddr tools/avinfo tools/avtest \
                        tools/btsnoop tools/btproxy tools/btiotest \
                        tools/cltest tools/seq2bseq tools/hex2hcd \
                        tools/ibeacon tools/btgatt-client \
-                       tools/btgatt-server
+                       tools/btgatt-server tools/mpris-player
 
 tools_bdaddr_SOURCES = tools/bdaddr.c src/oui.h src/oui.c
 tools_bdaddr_LDADD = lib/libbluetooth-internal.la @UDEV_LIBS@
@@ -266,6 +268,8 @@ tools_btiotest_SOURCES = tools/btiotest.c btio/btio.h btio/btio.c
 tools_btiotest_LDADD = lib/libbluetooth-internal.la @GLIB_LIBS@
 
 tools_cltest_SOURCES = tools/cltest.c
+tools_mpris_player_SOURCES = tools/mpris-player.c
+tools_mpris_player_LDADD = gdbus/libgdbus-internal.la @GLIB_LIBS@ @DBUS_LIBS@
 tools_cltest_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la
 
 tools_seq2bseq_SOURCES = tools/seq2bseq.c
old mode 100644 (file)
new mode 100755 (executable)
index 18e4ddb..5078c98
@@ -130,8 +130,8 @@ AM_CONDITIONAL(MONITOR, test "${enable_monitor}" != "no")
 AC_ARG_ENABLE(udev, AC_HELP_STRING([--disable-udev],
                [disable udev device support]), [enable_udev=${enableval}])
 if (test "${enable_tools}" != "no" && test "${enable_udev}" != "no"); then
-       PKG_CHECK_MODULES(UDEV, libudev >= 172, dummy=yes,
-                               AC_MSG_ERROR(libudev >= 172 is required))
+       PKG_CHECK_MODULES(UDEV, libudev >= 143, dummy=yes,
+                               AC_MSG_ERROR(libudev >= 143 is required))
        AC_SUBST(UDEV_CFLAGS)
        AC_SUBST(UDEV_LIBS)
        AC_CHECK_LIB(udev, udev_hwdb_new,
@@ -227,6 +227,10 @@ AC_ARG_ENABLE(wearable, AC_HELP_STRING([--enable-wearable],
                        [enable wearable profile]), [enable_wearable=${enableval}])
 AM_CONDITIONAL(WEARABLE, test "${enable_wearable}" = "yes")
 
+AC_ARG_ENABLE(usbbt, AC_HELP_STRING([--enable-usbbt],
+                [enable usb bluetooth]), [enable_usbbt=${enableval}])
+AM_CONDITIONAL(USBBT, test "${enable_usbbt}" = "yes")
+
 AC_ARG_ENABLE(autopair, AC_HELP_STRING([--enable-autopair],
                        [Enable Autopair Plugin]), [enable_autopair=${enableval}])
 AM_CONDITIONAL(AUTOPAIR, test "${enable_autopair}" = "yes")
old mode 100644 (file)
new mode 100755 (executable)
similarity index 100%
rename from packaging/bluez-5.19-0/baselibs.conf
rename to packaging/baselibs.conf
old mode 100644 (file)
new mode 100755 (executable)
similarity index 100%
rename from packaging/bluez-5.19-0/bluetooth.modprobe
rename to packaging/bluetooth.modprobe
old mode 100644 (file)
new mode 100755 (executable)
similarity index 100%
rename from packaging/bluez-5.19-0/bluetooth.sysconfig
rename to packaging/bluetooth.sysconfig
diff --git a/packaging/bluez-5.19-0/bluez-5.19.tar.gz b/packaging/bluez-5.19-0/bluez-5.19.tar.gz
deleted file mode 100644 (file)
index 80b4f7c..0000000
Binary files a/packaging/bluez-5.19-0/bluez-5.19.tar.gz and /dev/null differ
diff --git a/packaging/bluez-5.19-0/bluez.spec b/packaging/bluez-5.19-0/bluez.spec
deleted file mode 100644 (file)
index 9fd3f0c..0000000
+++ /dev/null
@@ -1,242 +0,0 @@
-%define with_libcapng --enable-capng
-
-Name:           bluez
-VCS:            platform/upstream/bluez#ccb69ca94573c9863fe6324d77dae73ef7c01393
-BuildRequires:  pkgconfig(dbus-1)
-BuildRequires:  flex
-BuildRequires:  libcap-ng-devel
-BuildRequires:  systemd
-%{?systemd_requires}
-BuildRequires:  pkgconfig(alsa)
-BuildRequires:  automake
-BuildRequires:  check-devel
-BuildRequires:  glib2-devel >= 2.16
-BuildRequires:  libsndfile-devel
-BuildRequires:  libtool
-BuildRequires:  libudev-devel
-BuildRequires:  libusb-devel
-BuildRequires:  pkg-config
-BuildRequires:  readline-devel
-BuildRequires:  udev
-BuildRequires:  pkgconfig(libnl-1)
-BuildRequires:  libical-devel
-BuildRequires:  pkgconfig(libtzplatform-config)
-Url:            http://www.bluez.org
-Version:        5.19
-Release:        0
-Summary:        Bluetooth Stack for Linux
-License:        GPL-2.0+
-Group:          Network & Connectivity/Bluetooth
-Source:         bluez-%{version}.tar.gz
-Source2:        bluez-coldplug.init
-Source3:        bluetooth.sysconfig
-Source4:        bluetooth.sh
-Source5:        baselibs.conf
-Source7:        bluetooth.modprobe
-Source101:      obex-root-setup
-Source102:      create-symlinks
-Source103:      obex.sh
-Source1001:     bluez.manifest
-
-%define cups_lib_dir %{_prefix}/lib/cups
-
-%description
-The Bluetooth stack for Linux.
-
-%package devel
-Summary:        Files needed for BlueZ development
-License:        GPL-2.0+
-Group:          Development/Libraries
-Requires:       libbluetooth = %{version}
-
-%description devel
-Files needed to develop applications for the BlueZ Bluetooth protocol
-stack.
-
-%package -n libbluetooth
-Summary:        Bluetooth Libraries
-License:        GPL-2.0+
-Group:          Network & Connectivity/Bluetooth
-
-%description -n libbluetooth
-Bluetooth protocol stack libraries.
-
-%package -n libbluetooth-plugins-service
-Summary:        Bluetooth Plugins
-License:        GPL-2.0+
-Group:          Network & Connectivity/Bluetooth
-
-%description -n libbluetooth-plugins-service
-Bluetooth protocol stack plugins.
-
-%package cups
-Summary:        CUPS Driver for Bluetooth Printers
-License:        GPL-2.0+
-Group:          Network & Connectivity/Bluetooth
-Requires:       libbluetooth = %{version}
-
-%description cups
-Contains the files required by CUPS for printing to Bluetooth-connected
-printers.
-
-%package -n obexd
-Summary:        OBEX Server A basic OBEX server implementation
-Group:          Network & Connectivity/Bluetooth
-Requires:       tizen-platform-config-tools
-
-%description -n obexd
-OBEX Server A basic OBEX server implementation.
-
-%package test
-Summary:        Tools for testing of various Bluetooth-functions
-License:        GPL-2.0+ and MIT
-Group:          Development/Tools
-Requires:       dbus-python
-Requires:       libbluetooth = %{version}
-Requires:       python-gobject
-
-%description test
-Contains a few tools for testing various bluetooth functions. The
-BLUETOOTH trademarks are owned by Bluetooth SIG, Inc., U.S.A.
-
-%prep
-%setup -q
-cp %{SOURCE1001} .
-
-%build
-autoreconf -fiv
-
-export CFLAGS="${CFLAGS} -D__TIZEN_PATCH__ -D__BROADCOM_PATCH__"
-%configure  --with-pic \
-            --libexecdir=/lib \
-            --disable-usb      \
-            --enable-test      \
-            --enable-library   \
-            --enable-experimental      \
-            --enable-readline  \
-            --enable-service \
-            --with-systemdunitdir=%{_unitdir}  \
-            %{?with_libcapng}
-make %{?_smp_mflags} all V=1
-
-%check
-make check
-
-%install
-%make_install
-
-# bluez-test
-rm -rvf $RPM_BUILD_ROOT/%{_libdir}/gstreamer-*
-install --mode=0755 -D %{S:4} $RPM_BUILD_ROOT/usr/lib/udev/bluetooth.sh
-install --mode=0644 -D %{S:7} $RPM_BUILD_ROOT/%{_sysconfdir}/modprobe.d/50-bluetooth.conf
-if ! test -e %{buildroot}%{cups_lib_dir}/backend/bluetooth
-then if test -e %{buildroot}%{_libdir}/cups/backend/bluetooth
-     then mkdir -p %{buildroot}%{cups_lib_dir}/backend
-          mv %{buildroot}%{_libdir}/cups/backend/bluetooth %{buildroot}%{cups_lib_dir}/backend/bluetooth
-     fi
-fi
-# no idea why this is suddenly necessary...
-install --mode 0755 -d $RPM_BUILD_ROOT/var/lib/bluetooth
-
-install -D -m 0755 %SOURCE101 %{buildroot}%{_bindir}/obex-root-setup
-install -D -m 0755 %SOURCE102 %{buildroot}%{_sysconfdir}/obex/root-setup.d/000_create-symlinks
-install -D -m 0755 %SOURCE103 %{buildroot}%{_bindir}/obex.sh
-install -D -m 0755 tools/btiotest $RPM_BUILD_ROOT/%{_bindir}/
-install -D -m 0755 tools/bluetooth-player $RPM_BUILD_ROOT/%{_bindir}/
-install -D -m 0755 tools/mpris-player $RPM_BUILD_ROOT/%{_bindir}/
-install -D -m 0755 tools/btmgmt $RPM_BUILD_ROOT/%{_bindir}/
-install -D -m 0755 tools/scotest $RPM_BUILD_ROOT/%{_bindir}/
-install -D -m 0755 tools/bluemoon $RPM_BUILD_ROOT/%{_bindir}/
-install -D -m 0755 attrib/gatttool $RPM_BUILD_ROOT/%{_bindir}/
-
-install -D -m 0755 tools/obexctl %{buildroot}%{_bindir}/obexctl
-
-%post -n libbluetooth -p /sbin/ldconfig
-
-%postun -n libbluetooth -p /sbin/ldconfig
-
-%post -n libbluetooth-plugins-service -p /sbin/ldconfig
-
-%postun -n libbluetooth-plugins-service -p /sbin/ldconfig
-
-%files
-%manifest %{name}.manifest
-%defattr(-, root, root)
-%license COPYING 
-%{_bindir}/hcitool
-%{_bindir}/l2ping
-%{_bindir}/obexctl
-%{_bindir}/rfcomm
-%{_bindir}/sdptool
-%{_bindir}/ciptool
-#%{_bindir}/dfutool
-%{_bindir}/hciattach
-%{_bindir}/hciconfig
-/lib/bluetooth/bluetoothd
-%{_bindir}/bccmd
-#%{_sbindir}/hid2hci
-%dir /usr/lib/udev
-/usr/lib/udev/*
-%{_datadir}/dbus-1/system-services/org.bluez.service
-%config %{_sysconfdir}/dbus-1/system.d/bluetooth.conf
-%dir /var/lib/bluetooth
-%dir %{_sysconfdir}/modprobe.d
-%config(noreplace) %{_sysconfdir}/modprobe.d/50-bluetooth.conf
-%{_unitdir}/bluetooth.service
-
-%files devel
-%manifest %{name}.manifest
-%defattr(-, root, root)
-/usr/include/bluetooth
-%{_libdir}/libbluetooth.so
-%{_libdir}/pkgconfig/bluez.pc
-
-%files -n libbluetooth
-%manifest %{name}.manifest
-%defattr(-, root, root)
-%{_libdir}/libbluetooth.so.*
-%{_libdir}/bluetooth/plugins/*.so
-%license COPYING
-
-%files -n libbluetooth-plugins-service
-%manifest %{name}.manifest
-%defattr(-, root, root)
-%{_libdir}/bluetooth/plugins/*.so
-%license COPYING
-
-%files cups
-%manifest %{name}.manifest
-%defattr(-,root,root)
-%dir %{cups_lib_dir}
-%dir %{cups_lib_dir}/backend
-%{cups_lib_dir}/backend/bluetooth
-
-%files -n obexd
-%defattr(-,root,root,-)
-/lib/bluetooth/obexd
-%{_unitdir_user}/obex.service
-%{_datadir}/dbus-1/services/org.bluez.obex.service
-%{_sysconfdir}/obex/root-setup.d/000_create-symlinks
-%{_bindir}/obex-root-setup
-%{_bindir}/obex.sh
-
-%files test
-%manifest %{name}.manifest
-%defattr(-,root,root)
-%{_libdir}/bluez/test/*
-%{_bindir}/l2test
-%{_bindir}/rctest
-%{_bindir}/bluetoothctl
-%{_bindir}/btiotest
-%{_bindir}/mpris-player
-%{_bindir}/bluetooth-player
-%{_bindir}/btmon
-%{_bindir}/hcidump
-%{_bindir}/btmgmt
-%{_bindir}/scotest
-%{_bindir}/bluemoon
-%{_bindir}/gatttool
-
-%docs_package
-
-%changelog
diff --git a/packaging/bluez-5.19-0/create-symlinks b/packaging/bluez-5.19-0/create-symlinks
deleted file mode 100755 (executable)
index 31aa6ab..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/sh
-
-OBEX_ROOT="$1"
-
-cd "$OBEX_ROOT"
-
-ln -sf ../MyDocs/.documents Documents
-ln -sf ../MyDocs/.images Images
-ln -sf ../MyDocs/.sounds "Audio clips"
-ln -sf ../MyDocs/.camera Camera
-ln -sf ../MyDocs/.videos "Video clips"
-ln -sf ../MyDocs Data
diff --git a/packaging/bluez-5.19-0/obex-root-setup b/packaging/bluez-5.19-0/obex-root-setup
deleted file mode 100755 (executable)
index fc0864f..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/sh
-
-set -e
-
-ROOT_SETUP=/etc/obex/root-setup.d
-
-OBEX_ROOT="$1"
-
-mkdir -p "$OBEX_ROOT"
-
-if [ -d "$ROOT_SETUP" ]; then
-       run-parts -a "$OBEX_ROOT" "$ROOT_SETUP"
-fi
-
-chmod 0550 "$OBEX_ROOT"
old mode 100644 (file)
new mode 100755 (executable)
similarity index 100%
rename from packaging/bluez-5.19-0/bluez-coldplug.init
rename to packaging/bluez-coldplug.init
old mode 100644 (file)
new mode 100755 (executable)
similarity index 100%
rename from packaging/bluez-5.19-0/bluez.changes
rename to packaging/bluez.changes
old mode 100644 (file)
new mode 100755 (executable)
similarity index 100%
rename from packaging/bluez-5.19-0/bluez.manifest
rename to packaging/bluez.manifest
old mode 100644 (file)
new mode 100755 (executable)
index e46f2b1..22488e5
-Name:       bluez
-Summary:    Bluetooth utilities
-Version:    5.27
-Release:    1
-VCS:        framework/connectivity/bluez#bluez_4.101-slp2+22-132-g5d15421a5bd7101225aecd7c25b592b9a9ca218b
-Group:      Applications/System
-License:    GPLv2+
-URL:        http://www.bluez.org/
-Source0:    http://www.kernel.org/pub/linux/bluetooth/%{name}-%{version}.tar.gz
-Source101:    obex-root-setup
-Source102:    create-symlinks
-Patch1 :    bluez-ncurses.patch
-Patch2 :    disable-eir-unittest.patch
-Requires:   dbus >= 0.60
-BuildRequires:  pkgconfig(libudev)
+%define with_libcapng --enable-capng
+Name:          bluez
+Summary:       Bluetooth Stack for Linux
+Version:       5.27
+Release:       0
+Group:         Network & Connectivity/Bluetooth
+License:       GPL-2.0+
+URL:           http://www.bluez.org/
+Source:         bluez-%{version}.tar.gz
+Source2:        bluez-coldplug.init
+Source3:        bluetooth.sysconfig
+Source4:        bluetooth.sh
+Source5:        baselibs.conf
+Source7:        bluetooth.modprobe
+Source101:     obex-root-setup
+Source102:     create-symlinks
+Source103:      obex.sh
+Source1001:     bluez.manifest
+#Patch1 :    bluez-ncurses.patch
+#Patch2 :    disable-eir-unittest.patch
+#Requires:   dbus >= 0.60
+#BuildRequires:  pkgconfig(libudev)
 BuildRequires:  pkgconfig(dbus-1)
-BuildRequires:  pkgconfig(glib-2.0)
-BuildRequires:  pkgconfig(ncurses)
+#BuildRequires:  pkgconfig(glib-2.0)
+#BuildRequires:  pkgconfig(ncurses)
+#BuildRequires:  flex
+#BuildRequires:  bison
+#BuildRequires:  readline-devel
+#BuildRequires:  openssl-devel
 BuildRequires:  flex
-BuildRequires:  bison
+BuildRequires:  libcap-ng-devel
+BuildRequires:  systemd
+%{?systemd_requires}
+BuildRequires:  pkgconfig(alsa)
+BuildRequires:  automake
+BuildRequires:  check-devel
+BuildRequires:  glib2-devel >= 2.16
+BuildRequires:  libsndfile-devel
+BuildRequires:  libtool
+BuildRequires:  libudev-devel
+BuildRequires:  libusb-devel
+BuildRequires:  pkg-config
 BuildRequires:  readline-devel
-BuildRequires:  openssl-devel
+BuildRequires:  udev
+BuildRequires:  pkgconfig(libnl-1)
+BuildRequires:  libical-devel
+BuildRequires:  pkgconfig(libtzplatform-config)
 
-%ifarch %{arm}
-BuildRequires:  kernel-headers
-#BuildRequires:  kernel-headers-tizen-dev
-%endif
+%define cups_lib_dir %{_prefix}/lib/cups
 
 %description
-Utilities for use in Bluetooth applications:
-       --dfutool
-       --hcitool
-       --l2ping
-       --rfcomm
-       --sdptool
-       --hciattach
-       --hciconfig
-       --hid2hci
-
-The BLUETOOTH trademarks are owned by Bluetooth SIG, Inc., U.S.A.
-
-%package -n libbluetooth3
-Summary:    Libraries for use in Bluetooth applications
-Group:      System/Libraries
-#Requires:   %{name} = %{version}-%{release}
-#Requires(post): eglibc
-#Requires(postun): eglibc
-
-%description -n libbluetooth3
-Libraries for use in Bluetooth applications.
-
-%package -n libbluetooth-devel
-Summary:    Development libraries for Bluetooth applications
-Group:      Development/Libraries
-Requires:   %{name} = %{version}-%{release}
-Requires:   libbluetooth3 = %{version}
-
-%description -n libbluetooth-devel
-bluez-libs-devel contains development libraries and headers for
-use in Bluetooth applications.
+The Bluetooth stack for Linux.
+
+%package devel
+Summary:        Files needed for BlueZ development
+License:        GPL-2.0+
+Group:          Development/Libraries
+Requires:       libbluetooth = %{version}
+
+%description devel
+Files needed to develop applications for the BlueZ Bluetooth protocol
+stack.
+
+%package -n libbluetooth
+Summary:        Bluetooth Libraries
+License:        GPL-2.0+
+Group:          Network & Connectivity/Bluetooth
+
+%description -n libbluetooth
+Bluetooth protocol stack libraries.
+
+%package -n libbluetooth-plugins-service
+Summary:        Bluetooth Plugins
+License:        GPL-2.0+
+Group:          Network & Connectivity/Bluetooth
+
+%description -n libbluetooth-plugins-service
+Bluetooth protocol stack plugins.
+
+%package cups
+Summary:        CUPS Driver for Bluetooth Printers
+License:        GPL-2.0+
+Group:          Network & Connectivity/Bluetooth
+Requires:       libbluetooth = %{version}
+
+%description cups
+Contains the files required by CUPS for printing to Bluetooth-connected
+printers.
 
 %package -n obexd
-Summary: OBEX Server A basic OBEX server implementation
-Group: Applications/System
+Summary:        OBEX Server A basic OBEX server implementation
+Group:          Network & Connectivity/Bluetooth
+Requires:       tizen-platform-config-tools
 
 %description -n obexd
 OBEX Server A basic OBEX server implementation.
 
-%package -n bluez-test
-Summary:    Test utilities for BlueZ
-Group:      Test Utilities
+%package test
+Summary:        Tools for testing of various Bluetooth-functions
+License:        GPL-2.0+ and MIT
+Group:          Development/Tools
+Requires:       dbus-python
+Requires:       libbluetooth = %{version}
+Requires:       python-gobject
 
-%description -n bluez-test
-bluez-test contains test utilities for BlueZ testing.
+%description test
+Contains a few tools for testing various bluetooth functions. The
+BLUETOOTH trademarks are owned by Bluetooth SIG, Inc., U.S.A.
 
 %prep
 %setup -q
-%patch1 -p1
+cp %{SOURCE1001} .
 
 %build
+autoreconf -fiv
+
 export CFLAGS="${CFLAGS} -D__TIZEN_PATCH__ -D__BROADCOM_PATCH__"
-%if "%{?tizen_profile_name}" == "wearable"
-export CFLAGS="${CFLAGS} -D__TIZEN_PATCH__ -D__BROADCOM_PATCH__ -D__BT_SCMST_FEATURE__ -DSUPPORT_SMS_ONLY -D__BROADCOM_QOS_PATCH__ -DTIZEN_WEARABLE"
-%else
-%if "%{?tizen_profile_name}" == "mobile"
-export CFLAGS="${CFLAGS} -D__TIZEN_PATCH__ -DSUPPORT_SMS_ONLY"
-export CFLAGS="${CFLAGS} -D__BROADCOM_PATCH__"
-%endif
-%endif
 
 export LDFLAGS=" -lncurses -Wl,--as-needed "
 export CFLAGS+=" -DPBAP_SIM_ENABLE"
-%reconfigure --disable-static \
-                       --sysconfdir=%{_sysconfdir} \
-                       --localstatedir=%{_localstatedir} \
-                       --disable-systemd \
-                       --enable-debug \
-                       --enable-pie \
-%if "%{?tizen_profile_name}" == "mobile"
-                       --enable-network \
-%endif
-                       --enable-serial \
-                       --enable-input \
-                       --enable-usb=no \
-                       --enable-tools \
-                       --disable-bccmd \
-                       --enable-pcmcia=no \
-                       --enable-hid2hci=no \
-                       --enable-alsa=no \
-                       --enable-gstreamer=no \
-                       --disable-dfutool \
-                       --disable-cups \
-                       --enable-health \
-                       --enable-dbusoob \
-                       --enable-test \
-                       --with-telephony=tizen \
-                       --enable-obex \
+#%reconfigure --with-pic \
+%reconfigure --with-pic \
+                       --libexecdir=/lib \
+                         --disable-usb \
+                        --enable-test  \
                        --enable-library \
-%if "%{?tizen_profile_name}" == "wearable"
-                       --enable-gatt \
-                       --enable-wearable \
-%endif
-                       --enable-experimental \
-                       --enable-autopair=no \
-                       --enable-tizenunusedplugin=no
+                           --enable-experimental       \
+                           --enable-readline   \
+                           --enable-service \
+                           --with-systemdunitdir=%{_unitdir}   \
+                           %{?with_libcapng}
 
-make %{?jobs:-j%jobs}
+#                      --disable-static \
+#                      --sysconfdir=%{_sysconfdir} \
+#                      --localstatedir=%{_localstatedir} \
+#                      --disable-systemd \
+#                      --enable-debug \
+#                      --enable-pie \
+#%if "%{?tizen_profile_name}" == "mobile"
+#                      --enable-network \
+#%endif
+#                      --enable-serial \
+#                      --enable-input \
+#                      --disable-usb \
+#                      --enable-tools \
+#                      --disable-bccmd \
+#                      --disable-pcmcia \
+#                      --disable-hid2hci \
+#                      --disable-alsa \
+#                      --enable-gstreamer=no \
+#                      --disable-dfutool \
+#                      --disable-cups \
+#                      --enable-health \
+#                      --enable-dbusoob \
+#                      --enable-test \
+#                      --with-telephony=tizen \
+#                      --enable-obex \
+#                      --enable-library \
+#%if "%{?tizen_profile_name}" == "wearable"
+#                      --enable-gatt \
+#                      --enable-wearable \
+#%endif
+#%if "%{?tizen_profile_name}" == "common"
+#                      --enable-usbbt \
+#%endif
+#                      --enable-experimental \
+#                      --enable-readline       \
+#3                     --enable-service \
+#                      --with-systemdunitdir=%{_unitdir} \
+#                      %{?with_libcapng}
+#                      --disable-autopair \
+#                      --disable-tizenunusedplugin
+make %{?_smp_mflags} all V=1
+
+%check
+make check
 
 %install
-rm -rf %{buildroot}
 %make_install
 
-%if "%{?tizen_profile_name}" == "wearable"
-install -D -m 0644 src/main_w.conf %{buildroot}%{_sysconfdir}/bluetooth/main.conf
-%else
-%if "%{?tizen_profile_name}" == "mobile"
-install -D -m 0644 src/main_m.conf %{buildroot}%{_sysconfdir}/bluetooth/main.conf
-%endif
-%endif
-install -D -m 0644 src/bluetooth.conf %{buildroot}%{_sysconfdir}/dbus-1/system.d/bluetooth.conf
+# bluez-test
+rm -rvf $RPM_BUILD_ROOT/%{_libdir}/gstreamer-*
+install --mode=0755 -D %{S:4} $RPM_BUILD_ROOT/usr/lib/udev/bluetooth.sh
+install --mode=0644 -D %{S:7} $RPM_BUILD_ROOT/%{_sysconfdir}/modprobe.d/50-bluetooth.conf
+if ! test -e %{buildroot}%{cups_lib_dir}/backend/bluetooth
+then if test -e %{buildroot}%{_libdir}/cups/backend/bluetooth
+     then mkdir -p %{buildroot}%{cups_lib_dir}/backend
+          mv %{buildroot}%{_libdir}/cups/backend/bluetooth %{buildroot}%{cups_lib_dir}/backend/bluetooth
+     fi
+fi
+# no idea why this is suddenly necessary...
+install --mode 0755 -d $RPM_BUILD_ROOT/var/lib/bluetooth
+
+
+#install -D -m 0644 src/bluetooth.conf %{buildroot}%{_sysconfdir}/dbus-1/system.d/bluetooth.conf
 #install -D -m 0644 profiles/audio/audio.conf %{buildroot}%{_sysconfdir}/bluetooth/audio.conf
-install -D -m 0644 profiles/network/network.conf %{buildroot}%{_sysconfdir}/bluetooth/network.conf
+#install -D -m 0644 profiles/network/network.conf %{buildroot}%{_sysconfdir}/bluetooth/network.conf
 
-install -D -m 0644 COPYING %{buildroot}%{_datadir}/license/bluez
-install -D -m 0644 COPYING %{buildroot}%{_datadir}/license/libbluetooth3
-install -D -m 0644 COPYING %{buildroot}%{_datadir}/license/libbluetooth-devel
+#install -D -m 0644 COPYING %{buildroot}%{_datadir}/license/bluez
+#install -D -m 0644 COPYING %{buildroot}%{_datadir}/license/libbluetooth3
+#install -D -m 0644 COPYING %{buildroot}%{_datadir}/license/libbluetooth-devel
 
 install -D -m 0755 %SOURCE101 %{buildroot}%{_bindir}/obex-root-setup
 install -D -m 0755 %SOURCE102 %{buildroot}%{_sysconfdir}/obex/root-setup.d/000_create-symlinks
+install -D -m 0755 %SOURCE103 %{buildroot}%{_bindir}/obex.sh
+install -D -m 0755 tools/btiotest $RPM_BUILD_ROOT/%{_bindir}/
+install -D -m 0755 tools/bluetooth-player $RPM_BUILD_ROOT/%{_bindir}/
+install -D -m 0755 tools/mpris-player $RPM_BUILD_ROOT/%{_bindir}/
+install -D -m 0755 tools/btmgmt $RPM_BUILD_ROOT/%{_bindir}/
+install -D -m 0755 tools/scotest $RPM_BUILD_ROOT/%{_bindir}/
+install -D -m 0755 tools/bluemoon $RPM_BUILD_ROOT/%{_bindir}/
+install -D -m 0755 attrib/gatttool $RPM_BUILD_ROOT/%{_bindir}/
 
-%post -n libbluetooth3 -p /sbin/ldconfig
 
-%postun -n libbluetooth3 -p /sbin/ldconfig
+install -D -m 0755 tools/obexctl %{buildroot}%{_bindir}/obexctl
 
+#test
+ln -sf bluetooth.service %{buildroot}%{_libdir}/systemd/system/dbus-org.bluez.service
+
+%post -n libbluetooth -p /sbin/ldconfig
+
+%postun -n libbluetooth -p /sbin/ldconfig
+
+%post -n libbluetooth-plugins-service -p /sbin/ldconfig
+
+%postun -n libbluetooth-plugins-service -p /sbin/ldconfig
 
 %files
-%manifest bluez.manifest
-%defattr(-,root,root,-)
+%manifest %{name}.manifest
+%defattr(-, root, root)
+%license COPYING
 #%{_sysconfdir}/bluetooth/audio.conf
 #%{_sysconfdir}/bluetooth/main.conf
-%{_sysconfdir}/bluetooth/network.conf
+#%{_sysconfdir}/bluetooth/network.conf
 #%{_sysconfdir}/bluetooth/rfcomm.conf
-%{_sysconfdir}/dbus-1/system.d/bluetooth.conf
-%{_datadir}/man/*/*
-%{_libexecdir}/bluetooth/bluetoothd
-%{_bindir}/hciconfig
-%{_bindir}/hciattach
-%{_bindir}/ciptool
+#%{_sysconfdir}/dbus-1/system.d/bluetooth.conf
+#%{_datadir}/man/*/*
+%{_bindir}/hcitool
 %{_bindir}/l2ping
-%{_bindir}/sdptool
-#%{_bindir}/gatttool
-#%{_bindir}/btgatt-client
-%{_bindir}/mcaptest
-%{_bindir}/mpris-proxy
+%{_bindir}/obexctl
 %{_bindir}/rfcomm
-%{_bindir}/hcitool
-#%dir %{_libdir}/bluetooth/plugins
-#%dir %{_localstatedir}/lib/bluetooth
-%dir %{_libexecdir}/bluetooth
-%{_datadir}/license/bluez
-%{_libdir}/udev/hid2hci
-%{_libdir}/udev/rules.d/97-hid2hci.rules
-
-%files -n libbluetooth3
-%defattr(-,root,root,-)
-%{_libdir}/libbluetooth.so.*
-%{_datadir}/license/libbluetooth3
+%{_bindir}/sdptool
+%{_bindir}/ciptool
+#%{_bindir}/dfutool
+%{_bindir}/hciattach
+%{_bindir}/hciconfig
+/lib/bluetooth/bluetoothd
+%{_bindir}/bccmd
+#%{_sbindir}/hid2hci
+%dir /usr/lib/udev
+/usr/lib/udev/*
+
+#test -2
+%{_libdir}/systemd/system/bluetooth.service
+%{_libdir}/systemd/system/dbus-org.bluez.service
+
+%{_datadir}/dbus-1/system-services/org.bluez.service
+%config %{_sysconfdir}/dbus-1/system.d/bluetooth.conf
+%dir /var/lib/bluetooth
+%dir %{_sysconfdir}/modprobe.d
+%config(noreplace) %{_sysconfdir}/modprobe.d/50-bluetooth.conf
+%{_unitdir}/bluetooth.service
+
+
+
 
-%files -n libbluetooth-devel
+%files devel
+%manifest %{name}.manifest
 %defattr(-, root, root)
-%{_includedir}/bluetooth/*
+/usr/include/bluetooth
 %{_libdir}/libbluetooth.so
 %{_libdir}/pkgconfig/bluez.pc
-%{_datadir}/license/libbluetooth-devel
+
+%files -n libbluetooth
+%manifest %{name}.manifest
+%defattr(-, root, root)
+%{_libdir}/libbluetooth.so.*
+%{_libdir}/bluetooth/plugins/*.so
+%license COPYING
+
+%files -n libbluetooth-plugins-service
+%manifest %{name}.manifest
+%defattr(-, root, root)
+%{_libdir}/bluetooth/plugins/*.so
+%license COPYING
+
+%files cups
+%manifest %{name}.manifest
+%defattr(-,root,root)
+%dir %{cups_lib_dir}
+%dir %{cups_lib_dir}/backend
+%{cups_lib_dir}/backend/bluetooth
 
 %files -n obexd
-#%dir %{_libdir}/obex/plugins
-%manifest obexd.manifest
 %defattr(-,root,root,-)
-%{_libexecdir}/bluetooth/obexd
+/lib/bluetooth/obexd
+%{_unitdir_user}/obex.service
 %{_datadir}/dbus-1/services/org.bluez.obex.service
 %{_sysconfdir}/obex/root-setup.d/000_create-symlinks
 %{_bindir}/obex-root-setup
+%{_bindir}/obex.sh
 
-%files -n bluez-test
-%defattr(-,root,root,-)
+
+%files test
+%manifest %{name}.manifest
+%defattr(-,root,root)
 %{_libdir}/bluez/test/*
 %{_bindir}/l2test
 %{_bindir}/rctest
-%{_bindir}/bccmd
 %{_bindir}/bluetoothctl
+%{_bindir}/btiotest
+%{_bindir}/mpris-player
+%{_bindir}/bluetooth-player
 %{_bindir}/btmon
 %{_bindir}/hcidump
+%{_bindir}/btmgmt
+%{_bindir}/scotest
 %{_bindir}/bluemoon
+%{_bindir}/gatttool
+
+
+%docs_package
+
+%changelog
old mode 100644 (file)
new mode 100755 (executable)
index b575f78..3caebdc
@@ -13,6 +13,8 @@
     <allow own="org.projectx.bluetooth"/>
     <allow send_interface="org.projectx.bluetooth"/>
     <allow send_destination="org.projectx.bluetooth"/>
+    <allow send_interface="org.projectx.bt_event"/>
+    <allow send_destination="org.projectx.bt_event"/>
     <allow own="org.bluez.frwk_agent"/>
     <allow send_interface="org.bluez.frwk_agent"/>
     <allow send_destination="org.bluez.frwk_agent"/>
   <!-- allow users of bt_use group (Tizen BT group) to
        communicate with bluetoothd -->
   <policy group="bt_use">
+    <allow send_interface="org.freedesktop.DBus.ObjectManager"/>
+    <allow send_destination="org.bluez"/>
     <allow send_interface="org.projectx.bluetooth"/>
     <allow send_destination="org.projectx.bluetooth"/>
+    <allow send_interface="org.projectx.bt_event"/>
+    <allow send_destination="org.projectx.bt_event"/>
     <allow send_interface="org.bluez.frwk_agent"/>
     <allow send_destination="org.bluez.frwk_agent"/>
     <allow send_interface="org.bluez.Agent1"/>
old mode 100644 (file)
new mode 100755 (executable)
index 35e9457..ec56003
@@ -5,12 +5,13 @@ Documentation=man:bluetoothd(8)
 [Service]
 Type=dbus
 BusName=org.bluez
-ExecStart=@libexecdir@/bluetoothd
+ExecStart=@libexecdir@/bluetoothd -E
 NotifyAccess=main
 #WatchdogSec=10
 #Restart=on-failure
 CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
 LimitNPROC=1
+SmackExecLabel=User
 
 [Install]
 WantedBy=bluetooth.target
diff --git a/src/main.conf b/src/main.conf
new file mode 100755 (executable)
index 0000000..3ebadde
--- /dev/null
@@ -0,0 +1,57 @@
+[General]
+
+# Default adaper name
+# %h - substituted for hostname
+# %d - substituted for adapter id
+# Defaults to 'BlueZ'
+#Name = %h-%d
+
+# Default device class. Only the major and minor device class bits are
+# considered. Defaults to '0x000000'.
+#Class = 0x000100
+
+# How long to stay in discoverable mode before going back to non-discoverable
+# The value is in seconds. Default is 180, i.e. 3 minutes.
+# 0 = disable timer, i.e. stay discoverable forever
+#DiscoverableTimeout = 0
+
+# How long to stay in pairable mode before going back to non-discoverable
+# The value is in seconds. Default is 0.
+# 0 = disable timer, i.e. stay pairable forever
+#PairableTimeout = 0
+
+# Automatic connection for bonded devices driven by platform/user events.
+# If a platform plugin uses this mechanism, automatic connections will be
+# enabled during the interval defined below. Initially, this feature
+# intends to be used to establish connections to ATT channels. Default is 60.
+#AutoConnectTimeout = 60
+
+# Use vendor id source (assigner), vendor, product and version information for
+# DID profile support. The values are separated by ":" and assigner, VID, PID
+# and version.
+# Possible vendor id source values: bluetooth, usb (defaults to usb)
+#DeviceID = bluetooth:1234:5678:abcd
+
+# Do reverse service discovery for previously unknown devices that connect to
+# us. This option is really only needed for qualification since the BITE tester
+# doesn't like us doing reverse SDP for some test cases (though there could in
+# theory be other useful purposes for this too). Defaults to 'true'.
+#ReverseServiceDiscovery = true
+
+# Enable name resolving after inquiry. Set it to 'false' if you don't need
+# remote devices name and want shorter discovery cycle. Defaults to 'true'.
+#NameResolving = true
+
+# Enable runtime persistency of debug link keys. Default is false which
+# makes debug link keys valid only for the duration of the connection
+# that they were created for.
+#DebugKeys = false
+
+#[Policy]
+#
+# The ReconnectUUIDs defines the set of remote services that should try
+# to be reconnected to in case of a link loss (link supervision
+# timeout). The policy plugin should contain a sane set of values by
+# default, but this list can be overridden here. By setting the list to
+# empty the reconnection feature gets disabled.
+#ReconnectUUIDs=
old mode 100644 (file)
new mode 100755 (executable)
index 2a3b057..ff2408b
@@ -2,4 +2,7 @@
 Name=org.bluez
 Exec=/bin/false
 User=root
-SystemdService=bluetooth.service
+SystemdService=dbus-org.bluez.service
+#test
+#SystemdService=bluetooth.service
+
diff --git a/tools/mpris-player.c b/tools/mpris-player.c
new file mode 100755 (executable)
index 0000000..c94330c
--- /dev/null
@@ -0,0 +1,2593 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <getopt.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include <dbus/dbus.h>
+#include <glib.h>
+#include <gdbus/gdbus.h>
+
+#define BLUEZ_BUS_NAME "org.bluez"
+#define BLUEZ_PATH "/org/bluez"
+#define BLUEZ_ADAPTER_INTERFACE "org.bluez.Adapter1"
+#define BLUEZ_MEDIA_INTERFACE "org.bluez.Media1"
+#define BLUEZ_MEDIA_PLAYER_INTERFACE "org.bluez.MediaPlayer1"
+#define BLUEZ_MEDIA_FOLDER_INTERFACE "org.bluez.MediaFolder1"
+#define BLUEZ_MEDIA_ITEM_INTERFACE "org.bluez.MediaItem1"
+#define BLUEZ_MEDIA_TRANSPORT_INTERFACE "org.bluez.MediaTransport1"
+#define MPRIS_BUS_NAME "org.mpris.MediaPlayer2."
+#define MPRIS_INTERFACE "org.mpris.MediaPlayer2"
+#define MPRIS_PLAYER_INTERFACE "org.mpris.MediaPlayer2.Player"
+#define MPRIS_TRACKLIST_INTERFACE "org.mpris.MediaPlayer2.TrackList"
+#define MPRIS_PLAYLISTS_INTERFACE "org.mpris.MediaPlayer2.Playlists"
+#define MPRIS_PLAYER_PATH "/org/mpris/MediaPlayer2"
+#define ERROR_INTERFACE "org.mpris.MediaPlayer2.Error"
+
+static GMainLoop *main_loop;
+static GDBusProxy *adapter = NULL;
+static DBusConnection *sys = NULL;
+static DBusConnection *session = NULL;
+static GDBusClient *client = NULL;
+static GSList *players = NULL;
+static GSList *transports = NULL;
+
+static gboolean option_version = FALSE;
+static gboolean option_export = FALSE;
+
+struct tracklist {
+       GDBusProxy *proxy;
+       GSList *items;
+};
+
+struct player {
+       char *bus_name;
+       DBusConnection *conn;
+       GDBusProxy *proxy;
+       GDBusProxy *folder;
+       GDBusProxy *device;
+       GDBusProxy *transport;
+       GDBusProxy *playlist;
+       struct tracklist *tracklist;
+};
+
+typedef int (* parse_metadata_func) (DBusMessageIter *iter, const char *key,
+                                               DBusMessageIter *metadata);
+
+static void dict_append_entry(DBusMessageIter *dict, const char *key, int type,
+                                                               void *val);
+
+static void sig_term(int sig)
+{
+       g_main_loop_quit(main_loop);
+}
+
+static DBusMessage *get_all(DBusConnection *conn, const char *name)
+{
+       DBusMessage *msg, *reply;
+       DBusError err;
+       const char *iface = MPRIS_PLAYER_INTERFACE;
+
+       msg = dbus_message_new_method_call(name, MPRIS_PLAYER_PATH,
+                                       DBUS_INTERFACE_PROPERTIES, "GetAll");
+       if (!msg) {
+               fprintf(stderr, "Can't allocate new method call\n");
+               return NULL;
+       }
+
+       dbus_message_append_args(msg, DBUS_TYPE_STRING, &iface,
+                                       DBUS_TYPE_INVALID);
+
+       dbus_error_init(&err);
+
+       reply = dbus_connection_send_with_reply_and_block(conn, msg, -1, &err);
+
+       dbus_message_unref(msg);
+
+       if (!reply) {
+               if (dbus_error_is_set(&err)) {
+                       fprintf(stderr, "%s\n", err.message);
+                       dbus_error_free(&err);
+               }
+               return NULL;
+       }
+
+       return reply;
+}
+
+static void append_variant(DBusMessageIter *iter, int type, void *val)
+{
+       DBusMessageIter value;
+       char sig[2] = { type, '\0' };
+
+       dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, sig, &value);
+
+       dbus_message_iter_append_basic(&value, type, val);
+
+       dbus_message_iter_close_container(iter, &value);
+}
+
+static void append_array_variant(DBusMessageIter *iter, int type, void *val,
+                                                       int n_elements)
+{
+       DBusMessageIter variant, array;
+       char type_sig[2] = { type, '\0' };
+       char array_sig[3] = { DBUS_TYPE_ARRAY, type, '\0' };
+
+       dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT,
+                                               array_sig, &variant);
+
+       dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY,
+                                               type_sig, &array);
+
+       if (dbus_type_is_fixed(type) == TRUE) {
+               dbus_message_iter_append_fixed_array(&array, type, val,
+                                                       n_elements);
+       } else if (type == DBUS_TYPE_STRING || type == DBUS_TYPE_OBJECT_PATH) {
+               const char ***str_array = val;
+               int i;
+
+               for (i = 0; i < n_elements; i++)
+                       dbus_message_iter_append_basic(&array, type,
+                                                       &((*str_array)[i]));
+       }
+
+       dbus_message_iter_close_container(&variant, &array);
+
+       dbus_message_iter_close_container(iter, &variant);
+}
+
+static void dict_append_array(DBusMessageIter *dict, const char *key, int type,
+                       void *val, int n_elements)
+{
+       DBusMessageIter entry;
+
+       dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY,
+                                               NULL, &entry);
+
+       dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key);
+
+       append_array_variant(&entry, type, val, n_elements);
+
+       dbus_message_iter_close_container(dict, &entry);
+}
+
+static void append_basic(DBusMessageIter *base, DBusMessageIter *iter,
+                                                               int type)
+{
+       const void *value;
+
+       dbus_message_iter_get_basic(iter, &value);
+       dbus_message_iter_append_basic(base, type, &value);
+}
+
+static void append_iter(DBusMessageIter *base, DBusMessageIter *iter);
+static void append_container(DBusMessageIter *base, DBusMessageIter *iter,
+                                                               int type)
+{
+       DBusMessageIter iter_sub, base_sub;
+       char *sig;
+
+       dbus_message_iter_recurse(iter, &iter_sub);
+
+       switch (type) {
+       case DBUS_TYPE_ARRAY:
+       case DBUS_TYPE_VARIANT:
+               sig = dbus_message_iter_get_signature(&iter_sub);
+               break;
+       default:
+               sig = NULL;
+               break;
+       }
+
+       dbus_message_iter_open_container(base, type, sig, &base_sub);
+
+       if (sig != NULL)
+               dbus_free(sig);
+
+       append_iter(&base_sub, &iter_sub);
+
+       dbus_message_iter_close_container(base, &base_sub);
+}
+
+static void append_iter(DBusMessageIter *base, DBusMessageIter *iter)
+{
+       int type;
+
+       while ((type = dbus_message_iter_get_arg_type(iter)) !=
+                                                       DBUS_TYPE_INVALID) {
+               if (dbus_type_is_basic(type))
+                       append_basic(base, iter, type);
+               else if (dbus_type_is_container(type))
+                       append_container(base, iter, type);
+
+               dbus_message_iter_next(iter);
+       }
+}
+
+static void dict_append_iter(DBusMessageIter *dict, const char *key,
+                                               DBusMessageIter *iter)
+{
+       DBusMessageIter entry;
+
+       dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY,
+                                               NULL, &entry);
+
+       dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key);
+
+       append_iter(&entry, iter);
+
+       dbus_message_iter_close_container(dict, &entry);
+}
+
+static int parse_metadata_entry(DBusMessageIter *entry, const char *key,
+                                               DBusMessageIter *metadata)
+{
+       if (dbus_message_iter_get_arg_type(entry) != DBUS_TYPE_VARIANT)
+               return -EINVAL;
+
+       dict_append_iter(metadata, key, entry);
+
+       return 0;
+}
+
+static int parse_metadata(DBusMessageIter *args, DBusMessageIter *metadata,
+                                               parse_metadata_func func)
+{
+       DBusMessageIter dict;
+       int ctype;
+
+       ctype = dbus_message_iter_get_arg_type(args);
+       if (ctype != DBUS_TYPE_ARRAY)
+               return -EINVAL;
+
+       dbus_message_iter_recurse(args, &dict);
+
+       while ((ctype = dbus_message_iter_get_arg_type(&dict)) !=
+                                                       DBUS_TYPE_INVALID) {
+               DBusMessageIter entry;
+               const char *key;
+
+               if (ctype != DBUS_TYPE_DICT_ENTRY)
+                       return -EINVAL;
+
+               dbus_message_iter_recurse(&dict, &entry);
+               if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING)
+                       return -EINVAL;
+
+               dbus_message_iter_get_basic(&entry, &key);
+               dbus_message_iter_next(&entry);
+
+               if (func(&entry, key, metadata) < 0)
+                       return -EINVAL;
+
+               dbus_message_iter_next(&dict);
+       }
+
+       return 0;
+}
+
+static void append_metadata(DBusMessageIter *iter, DBusMessageIter *dict,
+                                               parse_metadata_func func)
+{
+       DBusMessageIter value, metadata;
+
+       dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "a{sv}",
+                                                               &value);
+
+       dbus_message_iter_open_container(&value, DBUS_TYPE_ARRAY,
+                       DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+                       DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+                       DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &metadata);
+
+       parse_metadata(dict, &metadata, func);
+
+       dbus_message_iter_close_container(&value, &metadata);
+       dbus_message_iter_close_container(iter, &value);
+}
+
+static void dict_append_entry(DBusMessageIter *dict, const char *key, int type,
+                                                               void *val)
+{
+       DBusMessageIter entry;
+
+       if (type == DBUS_TYPE_STRING) {
+               const char *str = *((const char **) val);
+               if (str == NULL)
+                       return;
+       }
+
+       dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY,
+                                                       NULL, &entry);
+
+       dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key);
+
+       if (strcasecmp(key, "Metadata") == 0)
+               append_metadata(&entry, val, parse_metadata_entry);
+       else
+               append_variant(&entry, type, val);
+
+       dbus_message_iter_close_container(dict, &entry);
+}
+
+static char *sender2path(const char *sender)
+{
+       char *path;
+
+       path = g_strconcat("/", sender, NULL);
+       return g_strdelimit(path, ":.", '_');
+}
+
+static void copy_reply(DBusPendingCall *call, void *user_data)
+{
+       DBusMessage *msg = user_data;
+       DBusMessage *reply = dbus_pending_call_steal_reply(call);
+       DBusMessage *copy;
+       DBusMessageIter args, iter;
+
+       copy = dbus_message_new_method_return(msg);
+       if (copy == NULL) {
+               dbus_message_unref(reply);
+               return;
+       }
+
+       dbus_message_iter_init_append(copy, &iter);
+
+       if (!dbus_message_iter_init(reply, &args))
+               goto done;
+
+       append_iter(&iter, &args);
+
+       dbus_connection_send(sys, copy, NULL);
+
+done:
+       dbus_message_unref(copy);
+       dbus_message_unref(reply);
+}
+
+static DBusHandlerResult player_message(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       char *owner = data;
+       DBusMessage *copy;
+       DBusMessageIter args, iter;
+       DBusPendingCall *call;
+
+       dbus_message_iter_init(msg, &args);
+
+       copy = dbus_message_new_method_call(owner,
+                                       MPRIS_PLAYER_PATH,
+                                       dbus_message_get_interface(msg),
+                                       dbus_message_get_member(msg));
+       if (copy == NULL)
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+       dbus_message_iter_init_append(copy, &iter);
+       append_iter(&iter, &args);
+
+       if (!dbus_connection_send_with_reply(session, copy, &call, -1))
+               goto done;
+
+       dbus_message_ref(msg);
+       dbus_pending_call_set_notify(call, copy_reply, msg, NULL);
+       dbus_pending_call_unref(call);
+
+done:
+       dbus_message_unref(copy);
+
+       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static struct player *find_player_by_bus_name(const char *name)
+{
+       GSList *l;
+
+       for (l = players; l; l = l->next) {
+               struct player *player = l->data;
+
+               if (strcmp(player->bus_name, name) == 0)
+                       return player;
+       }
+
+       return NULL;
+}
+
+static const DBusObjectPathVTable player_table = {
+       .message_function = player_message,
+};
+
+static void add_player(DBusConnection *conn, const char *name,
+                                                       const char *sender)
+{
+       DBusMessage *reply = NULL;
+       DBusMessage *msg;
+       DBusMessageIter iter, args;
+       DBusError err;
+       char *path, *owner;
+       struct player *player;
+
+       if (!adapter)
+               return;
+
+       player = find_player_by_bus_name(name);
+       if (player == NULL) {
+               reply = get_all(conn, name);
+               if (reply == NULL)
+                       return;
+               dbus_message_iter_init(reply, &args);
+       }
+
+       msg = dbus_message_new_method_call(BLUEZ_BUS_NAME,
+                                       g_dbus_proxy_get_path(adapter),
+                                       BLUEZ_MEDIA_INTERFACE,
+                                       "RegisterPlayer");
+       if (!msg) {
+               fprintf(stderr, "Can't allocate new method call\n");
+               return;
+       }
+
+       path = sender2path(sender);
+       dbus_connection_get_object_path_data(sys, path, (void **) &owner);
+
+       if (owner != NULL)
+               goto done;
+
+       dbus_message_iter_init_append(msg, &iter);
+
+       dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path);
+
+       if (player != NULL) {
+               if (!g_dbus_get_properties(player->conn,
+                                               MPRIS_PLAYER_PATH,
+                                               MPRIS_PLAYER_INTERFACE,
+                                               &iter))
+                       goto done;
+       } else {
+               append_iter(&iter, &args);
+               dbus_message_unref(reply);
+       }
+
+       dbus_error_init(&err);
+
+       owner = strdup(sender);
+
+       if (!dbus_connection_register_object_path(sys, path, &player_table,
+                                                               owner)) {
+               fprintf(stderr, "Can't register object path for player\n");
+               free(owner);
+               goto done;
+       }
+
+       reply = dbus_connection_send_with_reply_and_block(sys, msg, -1, &err);
+       if (!reply) {
+               fprintf(stderr, "Can't register player\n");
+               free(owner);
+               if (dbus_error_is_set(&err)) {
+                       fprintf(stderr, "%s\n", err.message);
+                       dbus_error_free(&err);
+               }
+       }
+
+done:
+       if (reply)
+               dbus_message_unref(reply);
+       dbus_message_unref(msg);
+       g_free(path);
+}
+
+static void remove_player(DBusConnection *conn, const char *sender)
+{
+       DBusMessage *msg;
+       char *path, *owner;
+
+       if (!adapter)
+               return;
+
+       path = sender2path(sender);
+       dbus_connection_get_object_path_data(sys, path, (void **) &owner);
+
+       if (owner == NULL) {
+               g_free(path);
+               return;
+       }
+
+       msg = dbus_message_new_method_call(BLUEZ_BUS_NAME,
+                                       g_dbus_proxy_get_path(adapter),
+                                       BLUEZ_MEDIA_INTERFACE,
+                                       "UnregisterPlayer");
+       if (!msg) {
+               fprintf(stderr, "Can't allocate new method call\n");
+               g_free(path);
+               return;
+       }
+
+       dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &path,
+                                       DBUS_TYPE_INVALID);
+
+       dbus_connection_send(sys, msg, NULL);
+
+       dbus_connection_unregister_object_path(sys, path);
+
+       dbus_message_unref(msg);
+       g_free(path);
+       g_free(owner);
+}
+
+static gboolean player_signal(DBusConnection *conn, DBusMessage *msg,
+                                                               void *user_data)
+{
+       DBusMessage *signal;
+       DBusMessageIter iter, args;
+       char *path, *owner;
+
+       dbus_message_iter_init(msg, &iter);
+
+       path = sender2path(dbus_message_get_sender(msg));
+       dbus_connection_get_object_path_data(sys, path, (void **) &owner);
+
+       if (owner == NULL)
+               goto done;
+
+       signal = dbus_message_new_signal(path, dbus_message_get_interface(msg),
+                                               dbus_message_get_member(msg));
+       if (signal == NULL) {
+               fprintf(stderr, "Unable to allocate new %s.%s signal",
+                                               dbus_message_get_interface(msg),
+                                               dbus_message_get_member(msg));
+               goto done;
+       }
+
+       dbus_message_iter_init_append(signal, &args);
+
+       append_iter(&args, &iter);
+
+       dbus_connection_send(sys, signal, NULL);
+       dbus_message_unref(signal);
+
+done:
+       g_free(path);
+
+       return TRUE;
+}
+
+static gboolean name_owner_changed(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       const char *name, *old, *new;
+
+       if (!dbus_message_get_args(msg, NULL,
+                                       DBUS_TYPE_STRING, &name,
+                                       DBUS_TYPE_STRING, &old,
+                                       DBUS_TYPE_STRING, &new,
+                                       DBUS_TYPE_INVALID)) {
+               fprintf(stderr, "Invalid arguments for NameOwnerChanged signal");
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+       }
+
+       if (!g_str_has_prefix(name, "org.mpris"))
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+       if (*new == '\0') {
+               printf("player %s at %s disappear\n", name, old);
+               remove_player(conn, old);
+       } else if (option_export || find_player_by_bus_name(name) == NULL) {
+               printf("player %s at %s found\n", name, new);
+               add_player(conn, name, new);
+       }
+
+       return TRUE;
+}
+
+static char *get_name_owner(DBusConnection *conn, const char *name)
+{
+       DBusMessage *msg, *reply;
+       DBusError err;
+       char *owner;
+
+       msg = dbus_message_new_method_call(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS,
+                                       DBUS_INTERFACE_DBUS, "GetNameOwner");
+
+       if (!msg) {
+               fprintf(stderr, "Can't allocate new method call\n");
+               return NULL;
+       }
+
+       dbus_message_append_args(msg, DBUS_TYPE_STRING, &name,
+                                                       DBUS_TYPE_INVALID);
+
+       dbus_error_init(&err);
+
+       reply = dbus_connection_send_with_reply_and_block(conn, msg, -1, &err);
+
+       dbus_message_unref(msg);
+
+       if (!reply) {
+               if (dbus_error_is_set(&err)) {
+                       fprintf(stderr, "%s\n", err.message);
+                       dbus_error_free(&err);
+               }
+               return NULL;
+       }
+
+       if (!dbus_message_get_args(reply, NULL,
+                                       DBUS_TYPE_STRING, &owner,
+                                       DBUS_TYPE_INVALID)) {
+               dbus_message_unref(reply);
+               return NULL;
+       }
+
+       owner = g_strdup(owner);
+
+       dbus_message_unref(reply);
+
+       dbus_connection_flush(conn);
+
+       return owner;
+}
+
+static void parse_list_names(DBusConnection *conn, DBusMessageIter *args)
+{
+       DBusMessageIter array;
+       int ctype;
+
+       ctype = dbus_message_iter_get_arg_type(args);
+       if (ctype != DBUS_TYPE_ARRAY)
+               return;
+
+       dbus_message_iter_recurse(args, &array);
+
+       while ((ctype = dbus_message_iter_get_arg_type(&array)) !=
+                                                       DBUS_TYPE_INVALID) {
+               const char *name;
+               char *owner;
+
+               if (ctype != DBUS_TYPE_STRING)
+                       goto next;
+
+               dbus_message_iter_get_basic(&array, &name);
+
+               if (!g_str_has_prefix(name, "org.mpris"))
+                       goto next;
+
+               owner = get_name_owner(conn, name);
+
+               if (owner == NULL)
+                       goto next;
+
+               printf("player %s at %s found\n", name, owner);
+
+               add_player(conn, name, owner);
+
+               g_free(owner);
+next:
+               dbus_message_iter_next(&array);
+       }
+}
+
+static void list_names(DBusConnection *conn)
+{
+       DBusMessage *msg, *reply;
+       DBusMessageIter iter;
+       DBusError err;
+
+       msg = dbus_message_new_method_call(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS,
+                                       DBUS_INTERFACE_DBUS, "ListNames");
+
+       if (!msg) {
+               fprintf(stderr, "Can't allocate new method call\n");
+               return;
+       }
+
+       dbus_error_init(&err);
+
+       reply = dbus_connection_send_with_reply_and_block(conn, msg, -1, &err);
+
+       dbus_message_unref(msg);
+
+       if (!reply) {
+               if (dbus_error_is_set(&err)) {
+                       fprintf(stderr, "%s\n", err.message);
+                       dbus_error_free(&err);
+               }
+               return;
+       }
+
+       dbus_message_iter_init(reply, &iter);
+
+       parse_list_names(conn, &iter);
+
+       dbus_message_unref(reply);
+
+       dbus_connection_flush(conn);
+}
+
+static void usage(void)
+{
+       printf("Bluetooth mpris-player ver %s\n\n", VERSION);
+
+       printf("Usage:\n");
+}
+
+static GOptionEntry options[] = {
+       { "version", 'v', 0, G_OPTION_ARG_NONE, &option_version,
+                               "Show version information and exit" },
+       { "export", 'e', 0, G_OPTION_ARG_NONE, &option_export,
+                               "Export remote players" },
+       { NULL },
+};
+
+static void connect_handler(DBusConnection *connection, void *user_data)
+{
+       printf("org.bluez appeared\n");
+}
+
+static void disconnect_handler(DBusConnection *connection, void *user_data)
+{
+       printf("org.bluez disappeared\n");
+}
+
+static void unregister_tracklist(struct player *player)
+{
+       struct tracklist *tracklist = player->tracklist;
+
+       g_slist_free(tracklist->items);
+       g_dbus_proxy_unref(tracklist->proxy);
+       g_free(tracklist);
+       player->tracklist = NULL;
+}
+
+static void player_free(void *data)
+{
+       struct player *player = data;
+
+       if (player->tracklist != NULL)
+               unregister_tracklist(player);
+
+       if (player->conn) {
+               dbus_connection_close(player->conn);
+               dbus_connection_unref(player->conn);
+       }
+
+       g_dbus_proxy_unref(player->device);
+       g_dbus_proxy_unref(player->proxy);
+
+       if (player->transport)
+               g_dbus_proxy_unref(player->transport);
+
+       if (player->playlist)
+               g_dbus_proxy_unref(player->playlist);
+
+       g_free(player->bus_name);
+       g_free(player);
+}
+
+struct pending_call {
+       struct player *player;
+       DBusMessage *msg;
+};
+
+static void pending_call_free(void *data)
+{
+       struct pending_call *p = data;
+
+       if (p->msg)
+               dbus_message_unref(p->msg);
+
+       g_free(p);
+}
+
+static void player_reply(DBusMessage *message, void *user_data)
+{
+       struct pending_call *p = user_data;
+       struct player *player = p->player;
+       DBusMessage *msg = p->msg;
+       DBusMessage *reply;
+       DBusError err;
+
+       dbus_error_init(&err);
+       if (dbus_set_error_from_message(&err, message)) {
+               fprintf(stderr, "error: %s", err.name);
+               reply = g_dbus_create_error(msg, err.name, "%s", err.message);
+               dbus_error_free(&err);
+       } else
+               reply = g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+
+       g_dbus_send_message(player->conn, reply);
+}
+
+static void player_control(struct player *player, DBusMessage *msg,
+                                                       const char *name)
+{
+       struct pending_call *p;
+
+       p = g_new0(struct pending_call, 1);
+       p->player = player;
+       p->msg = dbus_message_ref(msg);
+
+       g_dbus_proxy_method_call(player->proxy, name, NULL, player_reply,
+                                               p, pending_call_free);
+}
+
+static const char *status_to_playback(const char *status)
+{
+       if (strcasecmp(status, "playing") == 0)
+               return "Playing";
+       else if (strcasecmp(status, "paused") == 0)
+               return "Paused";
+       else
+               return "Stopped";
+}
+
+static const char *player_get_status(struct player *player)
+{
+       const char *status;
+       DBusMessageIter value;
+
+       if (g_dbus_proxy_get_property(player->proxy, "Status", &value)) {
+               dbus_message_iter_get_basic(&value, &status);
+               return status_to_playback(status);
+       }
+
+       if (player->transport == NULL)
+               goto done;
+
+       if (!g_dbus_proxy_get_property(player->transport, "State", &value))
+               goto done;
+
+       dbus_message_iter_get_basic(&value, &status);
+
+       if (strcasecmp(status, "active") == 0)
+               return "Playing";
+
+done:
+       return "Stopped";
+}
+
+static DBusMessage *player_toggle(DBusConnection *conn, DBusMessage *msg,
+                                                               void *data)
+{
+       struct player *player = data;
+       const char *status;
+
+       status = player_get_status(player);
+
+       if (strcasecmp(status, "Playing") == 0)
+               player_control(player, msg, "Pause");
+       else
+               player_control(player, msg, "Play");
+
+       return NULL;
+}
+
+static DBusMessage *player_play(DBusConnection *conn, DBusMessage *msg,
+                                                               void *data)
+{
+       struct player *player = data;
+
+       player_control(player, msg, "Play");
+
+       return NULL;
+}
+
+static DBusMessage *player_pause(DBusConnection *conn, DBusMessage *msg,
+                                                               void *data)
+{
+       struct player *player = data;
+
+       player_control(player, msg, "Pause");
+
+       return NULL;
+}
+
+static DBusMessage *player_stop(DBusConnection *conn, DBusMessage *msg,
+                                                               void *data)
+{
+       struct player *player = data;
+
+       player_control(player, msg, "Stop");
+
+       return NULL;
+}
+
+static DBusMessage *player_next(DBusConnection *conn, DBusMessage *msg,
+                                                               void *data)
+{
+       struct player *player = data;
+
+       player_control(player, msg, "Next");
+
+       return NULL;
+}
+
+static DBusMessage *player_previous(DBusConnection *conn, DBusMessage *msg,
+                                                               void *data)
+{
+       struct player *player = data;
+
+       player_control(player, msg, "Previous");
+
+       return NULL;
+}
+
+static gboolean status_exists(const GDBusPropertyTable *property, void *data)
+{
+       struct player *player = data;
+
+       return player_get_status(player) != NULL;
+}
+
+static gboolean get_status(const GDBusPropertyTable *property,
+                                       DBusMessageIter *iter, void *data)
+{
+       struct player *player = data;
+       const char *status;
+
+       status = player_get_status(player);
+
+       dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &status);
+
+       return TRUE;
+}
+
+static gboolean repeat_exists(const GDBusPropertyTable *property, void *data)
+{
+       DBusMessageIter iter;
+       struct player *player = data;
+
+       return g_dbus_proxy_get_property(player->proxy, "Repeat", &iter);
+}
+
+static const char *repeat_to_loopstatus(const char *value)
+{
+       if (strcasecmp(value, "off") == 0)
+               return "None";
+       else if (strcasecmp(value, "singletrack") == 0)
+               return "Track";
+       else if (strcasecmp(value, "alltracks") == 0)
+               return "Playlist";
+
+       return NULL;
+}
+
+static gboolean get_repeat(const GDBusPropertyTable *property,
+                                       DBusMessageIter *iter, void *data)
+{
+       struct player *player = data;
+       DBusMessageIter value;
+       const char *status;
+
+       if (!g_dbus_proxy_get_property(player->proxy, "Repeat", &value))
+               return FALSE;
+
+       dbus_message_iter_get_basic(&value, &status);
+
+       status = repeat_to_loopstatus(status);
+       if (status == NULL)
+               return FALSE;
+
+       dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &status);
+
+       return TRUE;
+}
+
+static const char *loopstatus_to_repeat(const char *value)
+{
+       if (strcasecmp(value, "None") == 0)
+               return "off";
+       else if (strcasecmp(value, "Track") == 0)
+               return "singletrack";
+       else if (strcasecmp(value, "Playlist") == 0)
+               return "alltracks";
+
+       return NULL;
+}
+
+static void property_result(const DBusError *err, void *user_data)
+{
+       GDBusPendingPropertySet id = GPOINTER_TO_UINT(user_data);
+
+       if (!dbus_error_is_set(err))
+               return g_dbus_pending_property_success(id);
+
+       g_dbus_pending_property_error(id, err->name, err->message);
+}
+
+static void set_repeat(const GDBusPropertyTable *property,
+                       DBusMessageIter *iter, GDBusPendingPropertySet id,
+                       void *data)
+{
+       struct player *player = data;
+       const char *value;
+
+       if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) {
+               g_dbus_pending_property_error(id,
+                                       ERROR_INTERFACE ".InvalidArguments",
+                                       "Invalid arguments in method call");
+               return;
+       }
+
+       dbus_message_iter_get_basic(iter, &value);
+
+       value = loopstatus_to_repeat(value);
+       if (value == NULL) {
+               g_dbus_pending_property_error(id,
+                                       ERROR_INTERFACE ".InvalidArguments",
+                                       "Invalid arguments in method call");
+               return;
+       }
+
+       g_dbus_proxy_set_property_basic(player->proxy, "Repeat",
+                                       DBUS_TYPE_STRING, &value,
+                                       property_result, GUINT_TO_POINTER(id),
+                                       NULL);
+}
+
+static gboolean get_double(const GDBusPropertyTable *property,
+                                       DBusMessageIter *iter, void *data)
+{
+       double value = 1.0;
+
+       dbus_message_iter_append_basic(iter, DBUS_TYPE_DOUBLE, &value);
+
+       return TRUE;
+}
+
+static gboolean shuffle_exists(const GDBusPropertyTable *property, void *data)
+{
+       DBusMessageIter iter;
+       struct player *player = data;
+
+       return g_dbus_proxy_get_property(player->proxy, "Shuffle", &iter);
+}
+
+static gboolean get_shuffle(const GDBusPropertyTable *property,
+                                       DBusMessageIter *iter, void *data)
+{
+       struct player *player = data;
+       DBusMessageIter value;
+       const char *string;
+       dbus_bool_t shuffle;
+
+       if (!g_dbus_proxy_get_property(player->proxy, "Shuffle", &value))
+               return FALSE;
+
+       dbus_message_iter_get_basic(&value, &string);
+
+       shuffle = strcmp(string, "off") != 0;
+
+       dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &shuffle);
+
+       return TRUE;
+}
+
+static void set_shuffle(const GDBusPropertyTable *property,
+                       DBusMessageIter *iter, GDBusPendingPropertySet id,
+                       void *data)
+{
+       struct player *player = data;
+       dbus_bool_t shuffle;
+       const char *value;
+
+       if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) {
+               g_dbus_pending_property_error(id,
+                                       ERROR_INTERFACE ".InvalidArguments",
+                                       "Invalid arguments in method call");
+               return;
+       }
+
+       dbus_message_iter_get_basic(iter, &shuffle);
+       value = shuffle ? "alltracks" : "off";
+
+       g_dbus_proxy_set_property_basic(player->proxy, "Shuffle",
+                                       DBUS_TYPE_STRING, &value,
+                                       property_result, GUINT_TO_POINTER(id),
+                                       NULL);
+}
+
+static gboolean position_exists(const GDBusPropertyTable *property, void *data)
+{
+       DBusMessageIter iter;
+       struct player *player = data;
+
+       return g_dbus_proxy_get_property(player->proxy, "Position", &iter);
+}
+
+static gboolean get_position(const GDBusPropertyTable *property,
+                                       DBusMessageIter *iter, void *data)
+{
+       struct player *player = data;
+       DBusMessageIter var;
+       uint32_t position;
+       int64_t value;
+
+       if (!g_dbus_proxy_get_property(player->proxy, "Position", &var))
+               return FALSE;
+
+       dbus_message_iter_get_basic(&var, &position);
+
+       value = position * 1000;
+
+       dbus_message_iter_append_basic(iter, DBUS_TYPE_INT64, &value);
+
+       return TRUE;
+}
+
+static gboolean track_exists(const GDBusPropertyTable *property, void *data)
+{
+       DBusMessageIter iter;
+       struct player *player = data;
+
+       return g_dbus_proxy_get_property(player->proxy, "Track", &iter);
+}
+
+static gboolean parse_string_metadata(DBusMessageIter *iter, const char *key,
+                                               DBusMessageIter *metadata)
+{
+       const char *value;
+
+       if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING)
+               return FALSE;
+
+       dbus_message_iter_get_basic(iter, &value);
+
+       dict_append_entry(metadata, key, DBUS_TYPE_STRING, &value);
+
+       return TRUE;
+}
+
+static gboolean parse_array_metadata(DBusMessageIter *iter, const char *key,
+                                               DBusMessageIter *metadata)
+{
+       char **value;
+
+       if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING)
+               return FALSE;
+
+       value = dbus_malloc0(sizeof(char *));
+
+       dbus_message_iter_get_basic(iter, &(value[0]));
+
+       dict_append_array(metadata, key, DBUS_TYPE_STRING, &value, 1);
+
+       dbus_free(value);
+
+       return TRUE;
+}
+
+static gboolean parse_int64_metadata(DBusMessageIter *iter, const char *key,
+                                               DBusMessageIter *metadata)
+{
+       uint32_t duration;
+       int64_t value;
+
+       if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT32)
+               return FALSE;
+
+       dbus_message_iter_get_basic(iter, &duration);
+
+       value = duration * 1000;
+
+       dict_append_entry(metadata, key, DBUS_TYPE_INT64, &value);
+
+       return TRUE;
+}
+
+static gboolean parse_int32_metadata(DBusMessageIter *iter, const char *key,
+                                               DBusMessageIter *metadata)
+{
+       uint32_t value;
+
+       if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT32)
+               return FALSE;
+
+       dbus_message_iter_get_basic(iter, &value);
+
+       dict_append_entry(metadata, key, DBUS_TYPE_INT32, &value);
+
+       return TRUE;
+}
+
+static gboolean parse_path_metadata(DBusMessageIter *iter, const char *key,
+                                               DBusMessageIter *metadata)
+{
+       const char *value;
+
+       if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_OBJECT_PATH)
+               return FALSE;
+
+       dbus_message_iter_get_basic(iter, &value);
+
+       dict_append_entry(metadata, key, DBUS_TYPE_OBJECT_PATH, &value);
+
+       return TRUE;
+}
+
+static int parse_track_entry(DBusMessageIter *entry, const char *key,
+                                               DBusMessageIter *metadata)
+{
+       DBusMessageIter var;
+
+       if (dbus_message_iter_get_arg_type(entry) != DBUS_TYPE_VARIANT)
+               return -EINVAL;
+
+       dbus_message_iter_recurse(entry, &var);
+
+       if (strcasecmp(key, "Title") == 0) {
+               if (!parse_string_metadata(&var, "xesam:title", metadata))
+                       return -EINVAL;
+       } else if (strcasecmp(key, "Artist") == 0) {
+               if (!parse_array_metadata(&var, "xesam:artist", metadata))
+                       return -EINVAL;
+       } else if (strcasecmp(key, "Album") == 0) {
+               if (!parse_string_metadata(&var, "xesam:album", metadata))
+                       return -EINVAL;
+       } else if (strcasecmp(key, "Genre") == 0) {
+               if (!parse_array_metadata(&var, "xesam:genre", metadata))
+                       return -EINVAL;
+       } else if (strcasecmp(key, "Duration") == 0) {
+               if (!parse_int64_metadata(&var, "mpris:length", metadata))
+                       return -EINVAL;
+       } else if (strcasecmp(key, "TrackNumber") == 0) {
+               if (!parse_int32_metadata(&var, "xesam:trackNumber", metadata))
+                       return -EINVAL;
+       } else if (strcasecmp(key, "Item") == 0) {
+               if (!parse_path_metadata(&var, "mpris:trackid", metadata))
+                       return -EINVAL;
+       }
+
+       return 0;
+}
+
+static gboolean get_track(const GDBusPropertyTable *property,
+                                       DBusMessageIter *iter, void *data)
+{
+       struct player *player = data;
+       DBusMessageIter var, metadata;
+
+       if (!g_dbus_proxy_get_property(player->proxy, "Track", &var))
+               return FALSE;
+
+       dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
+                       DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+                       DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+                       DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &metadata);
+
+       parse_metadata(&var, &metadata, parse_track_entry);
+
+       dbus_message_iter_close_container(iter, &metadata);
+
+       return TRUE;
+}
+
+static gboolean get_enable(const GDBusPropertyTable *property,
+                                       DBusMessageIter *iter, void *data)
+{
+       dbus_bool_t value = TRUE;
+
+       dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value);
+
+       return TRUE;
+}
+
+
+static gboolean get_volume(const GDBusPropertyTable *property,
+                                       DBusMessageIter *iter, void *data)
+{
+       struct player *player = data;
+       double value = 0.0;
+       uint16_t volume;
+       DBusMessageIter var;
+
+       if (player->transport == NULL)
+               goto done;
+
+       if (!g_dbus_proxy_get_property(player->transport, "Volume", &var))
+               goto done;
+
+       dbus_message_iter_get_basic(&var, &volume);
+
+       value = (double) volume / 127;
+
+done:
+       dbus_message_iter_append_basic(iter, DBUS_TYPE_DOUBLE, &value);
+
+       return TRUE;
+}
+
+static const GDBusMethodTable player_methods[] = {
+       { GDBUS_ASYNC_METHOD("PlayPause", NULL, NULL, player_toggle) },
+       { GDBUS_ASYNC_METHOD("Play", NULL, NULL, player_play) },
+       { GDBUS_ASYNC_METHOD("Pause", NULL, NULL, player_pause) },
+       { GDBUS_ASYNC_METHOD("Stop", NULL, NULL, player_stop) },
+       { GDBUS_ASYNC_METHOD("Next", NULL, NULL, player_next) },
+       { GDBUS_ASYNC_METHOD("Previous", NULL, NULL, player_previous) },
+       { }
+};
+
+static const GDBusSignalTable player_signals[] = {
+       { GDBUS_SIGNAL("Seeked", GDBUS_ARGS({"Position", "x"})) },
+       { }
+};
+
+static const GDBusPropertyTable player_properties[] = {
+       { "PlaybackStatus", "s", get_status, NULL, status_exists },
+       { "LoopStatus", "s", get_repeat, set_repeat, repeat_exists },
+       { "Rate", "d", get_double, NULL, NULL },
+       { "MinimumRate", "d", get_double, NULL, NULL },
+       { "MaximumRate", "d", get_double, NULL, NULL },
+       { "Shuffle", "b", get_shuffle, set_shuffle, shuffle_exists },
+       { "Position", "x", get_position, NULL, position_exists },
+       { "Metadata", "a{sv}", get_track, NULL, track_exists },
+       { "Volume", "d", get_volume, NULL, NULL },
+       { "CanGoNext", "b", get_enable, NULL, NULL },
+       { "CanGoPrevious", "b", get_enable, NULL, NULL },
+       { "CanPlay", "b", get_enable, NULL, NULL },
+       { "CanPause", "b", get_enable, NULL, NULL },
+       { "CanSeek", "b", get_enable, NULL, NULL },
+       { "CanControl", "b", get_enable, NULL, NULL },
+       { }
+};
+
+static gboolean get_disable(const GDBusPropertyTable *property,
+                                       DBusMessageIter *iter, void *data)
+{
+       dbus_bool_t value = FALSE;
+
+       dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value);
+
+       return TRUE;
+}
+
+static gboolean get_name(const GDBusPropertyTable *property,
+                                       DBusMessageIter *iter, void *data)
+{
+       struct player *player = data;
+       DBusMessageIter var;
+       const char *alias;
+       char *name;
+
+       if (!g_dbus_proxy_get_property(player->device, "Alias", &var))
+               return FALSE;
+
+       dbus_message_iter_get_basic(&var, &alias);
+
+       if (g_dbus_proxy_get_property(player->proxy, "Name", &var)) {
+               dbus_message_iter_get_basic(&var, &name);
+               name = g_strconcat(alias, " ", name, NULL);
+       } else
+               name = g_strdup(alias);
+
+       dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &name);
+
+       g_free(name);
+
+       return TRUE;
+}
+
+static const GDBusMethodTable mpris_methods[] = {
+       { }
+};
+
+static gboolean get_tracklist(const GDBusPropertyTable *property,
+                                       DBusMessageIter *iter, void *data)
+{
+       struct player *player = data;
+       dbus_bool_t value;
+
+       value = player->tracklist != NULL ? TRUE : FALSE;
+
+       dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value);
+
+       return TRUE;
+}
+
+static const GDBusPropertyTable mpris_properties[] = {
+       { "CanQuit", "b", get_disable, NULL, NULL },
+       { "Fullscreen", "b", get_disable, NULL, NULL },
+       { "CanSetFullscreen", "b", get_disable, NULL, NULL },
+       { "CanRaise", "b", get_disable, NULL, NULL },
+       { "HasTrackList", "b", get_tracklist, NULL, NULL },
+       { "Identity", "s", get_name, NULL, NULL },
+       { }
+};
+
+static GDBusProxy *find_item(struct player *player, const char *path)
+{
+       struct tracklist *tracklist = player->tracklist;
+       GSList *l;
+
+       for (l = tracklist->items; l; l = l->next) {
+               GDBusProxy *proxy = l->data;
+               const char *p = g_dbus_proxy_get_path(proxy);
+
+               if (g_str_equal(path, p))
+                       return proxy;
+       }
+
+       return NULL;
+}
+
+static void append_item_metadata(void *data, void *user_data)
+{
+       GDBusProxy *item = data;
+       DBusMessageIter *iter = user_data;
+       DBusMessageIter var, metadata;
+       const char *path = g_dbus_proxy_get_path(item);
+
+       dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
+                       DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+                       DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+                       DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &metadata);
+
+       dict_append_entry(&metadata, "mpris:trackid", DBUS_TYPE_OBJECT_PATH,
+                                                                       &path);
+
+       if (g_dbus_proxy_get_property(item, "Metadata", &var))
+               parse_metadata(&var, &metadata, parse_track_entry);
+
+       dbus_message_iter_close_container(iter, &metadata);
+
+       return;
+}
+
+static DBusMessage *tracklist_get_metadata(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       struct player *player = data;
+       DBusMessage *reply;
+       DBusMessageIter args, array;
+       GSList *l = NULL;
+
+       dbus_message_iter_init(msg, &args);
+
+       if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY)
+               return g_dbus_create_error(msg,
+                                       ERROR_INTERFACE ".InvalidArguments",
+                                       "Invalid Arguments");
+
+       dbus_message_iter_recurse(&args, &array);
+
+       while (dbus_message_iter_get_arg_type(&array) ==
+                                               DBUS_TYPE_OBJECT_PATH) {
+               const char *path;
+               GDBusProxy *item;
+
+               dbus_message_iter_get_basic(&array, &path);
+
+               item = find_item(player, path);
+               if (item == NULL)
+                       return g_dbus_create_error(msg,
+                                       ERROR_INTERFACE ".InvalidArguments",
+                                       "Invalid Arguments");
+
+               l = g_slist_append(l, item);
+
+               dbus_message_iter_next(&array);
+       }
+
+       reply = dbus_message_new_method_return(msg);
+
+       dbus_message_iter_init_append(reply, &args);
+
+       dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY,
+                                       DBUS_TYPE_ARRAY_AS_STRING
+                                       DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+                                       DBUS_TYPE_STRING_AS_STRING
+                                       DBUS_TYPE_VARIANT_AS_STRING
+                                       DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
+                                       &array);
+
+       g_slist_foreach(l, append_item_metadata, &array);
+
+       dbus_message_iter_close_container(&args, &array);
+
+       return reply;
+}
+
+static void item_play_reply(DBusMessage *message, void *user_data)
+{
+       struct pending_call *p = user_data;
+       struct player *player = p->player;
+       DBusMessage *msg = p->msg;
+       DBusMessage *reply;
+       DBusError err;
+
+       dbus_error_init(&err);
+       if (dbus_set_error_from_message(&err, message)) {
+               fprintf(stderr, "error: %s", err.name);
+               reply = g_dbus_create_error(msg, err.name, "%s", err.message);
+               dbus_error_free(&err);
+       } else
+               reply = g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+
+       g_dbus_send_message(player->conn, reply);
+}
+
+static void item_play(struct player *player, DBusMessage *msg,
+                                                       GDBusProxy *item)
+{
+       struct pending_call *p;
+
+       p = g_new0(struct pending_call, 1);
+       p->player = player;
+       p->msg = dbus_message_ref(msg);
+
+       g_dbus_proxy_method_call(item, "Play", NULL, item_play_reply,
+                                               p, pending_call_free);
+}
+
+static DBusMessage *tracklist_goto(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       struct player *player = data;
+       GDBusProxy *item;
+       const char *path;
+
+       if (!dbus_message_get_args(msg, NULL,
+                                       DBUS_TYPE_OBJECT_PATH, &path,
+                                       DBUS_TYPE_INVALID))
+               return g_dbus_create_error(msg,
+                                       ERROR_INTERFACE ".InvalidArguments",
+                                       "Invalid arguments");
+
+       item = find_item(player, path);
+       if (item == NULL)
+               return g_dbus_create_error(msg,
+                                       ERROR_INTERFACE ".InvalidArguments",
+                                       "Invalid arguments");
+
+       item_play(player, msg, item);
+
+       return NULL;
+}
+
+static DBusMessage *tracklist_add_track(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       return g_dbus_create_error(msg, ERROR_INTERFACE ".NotImplemented",
+                                       "Not implemented");
+}
+
+static DBusMessage *tracklist_remove_track(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       return g_dbus_create_error(msg, ERROR_INTERFACE ".NotImplemented",
+                                       "Not implemented");
+}
+
+static const GDBusMethodTable tracklist_methods[] = {
+       { GDBUS_METHOD("GetTracksMetadata",
+                       GDBUS_ARGS({ "tracks", "ao" }),
+                       GDBUS_ARGS({ "metadata", "aa{sv}" }),
+                       tracklist_get_metadata) },
+       { GDBUS_METHOD("AddTrack",
+                       GDBUS_ARGS({ "uri", "s" }, { "after", "o" },
+                                               { "current", "b" }),
+                       NULL,
+                       tracklist_add_track) },
+       { GDBUS_METHOD("RemoveTrack",
+                       GDBUS_ARGS({ "track", "o" }), NULL,
+                       tracklist_remove_track) },
+       { GDBUS_ASYNC_METHOD("GoTo",
+                       GDBUS_ARGS({ "track", "o" }), NULL,
+                       tracklist_goto) },
+       { },
+};
+
+static const GDBusSignalTable tracklist_signals[] = {
+       { GDBUS_SIGNAL("TrackAdded", GDBUS_ARGS({"metadata", "a{sv}"},
+                                               {"after", "o"})) },
+       { GDBUS_SIGNAL("TrackRemoved", GDBUS_ARGS({"track", "o"})) },
+       { GDBUS_SIGNAL("TrackMetadataChanged", GDBUS_ARGS({"track", "o"},
+                                               {"metadata", "a{sv}"})) },
+       { }
+};
+
+static gboolean tracklist_exists(const GDBusPropertyTable *property, void *data)
+{
+       struct player *player = data;
+
+       return player->tracklist != NULL;
+}
+
+static void append_path(gpointer data, gpointer user_data)
+{
+       GDBusProxy *proxy = data;
+       DBusMessageIter *iter = user_data;
+       const char *path = g_dbus_proxy_get_path(proxy);
+
+       dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path);
+}
+
+static gboolean get_tracks(const GDBusPropertyTable *property,
+                                       DBusMessageIter *iter, void *data)
+{
+       struct player *player = data;
+       struct tracklist *tracklist = player->tracklist;
+       DBusMessageIter value;
+
+       if (tracklist == NULL)
+               return FALSE;
+
+       dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
+                                       DBUS_TYPE_OBJECT_PATH_AS_STRING,
+                                       &value);
+       g_slist_foreach(player->tracklist->items, append_path, &value);
+       dbus_message_iter_close_container(iter, &value);
+
+       return TRUE;
+}
+
+static const GDBusPropertyTable tracklist_properties[] = {
+       { "Tracks", "ao", get_tracks, NULL, tracklist_exists },
+       { "CanEditTracks", "b", get_disable, NULL, NULL },
+       { }
+};
+
+static void list_items_setup(DBusMessageIter *iter, void *user_data)
+{
+       DBusMessageIter dict;
+
+       dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
+                                       DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+                                       DBUS_TYPE_STRING_AS_STRING
+                                       DBUS_TYPE_VARIANT_AS_STRING
+                                       DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
+                                       &dict);
+       dbus_message_iter_close_container(iter, &dict);
+}
+
+static void change_folder_reply(DBusMessage *message, void *user_data)
+{
+       struct player *player = user_data;
+       struct tracklist *tracklist = player->tracklist;
+       DBusError err;
+
+       dbus_error_init(&err);
+       if (dbus_set_error_from_message(&err, message)) {
+               fprintf(stderr, "error: %s", err.name);
+               return;
+       }
+
+       g_dbus_emit_property_changed(player->conn, MPRIS_PLAYER_PATH,
+                                               MPRIS_PLAYLISTS_INTERFACE,
+                                               "ActivePlaylist");
+
+       g_dbus_proxy_method_call(tracklist->proxy, "ListItems",
+                                       list_items_setup, NULL, NULL, NULL);
+}
+
+static void change_folder_setup(DBusMessageIter *iter, void *user_data)
+{
+       struct player *player = user_data;
+       const char *path;
+
+       path = g_dbus_proxy_get_path(player->playlist);
+
+       dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path);
+}
+
+static DBusMessage *playlist_activate(DBusConnection *conn,
+                                               DBusMessage *msg, void *data)
+{
+       struct player *player = data;
+       struct tracklist *tracklist = player->tracklist;
+       const char *path;
+
+       if (player->playlist == NULL || tracklist == NULL)
+               return g_dbus_create_error(msg,
+                                       ERROR_INTERFACE ".InvalidArguments",
+                                       "Invalid Arguments");
+
+       if (!dbus_message_get_args(msg, NULL,
+                                       DBUS_TYPE_OBJECT_PATH, &path,
+                                       DBUS_TYPE_INVALID))
+               return g_dbus_create_error(msg,
+                                       ERROR_INTERFACE ".InvalidArguments",
+                                       "Invalid Arguments");
+
+       if (!g_str_equal(path, g_dbus_proxy_get_path(player->playlist)))
+               return g_dbus_create_error(msg,
+                                       ERROR_INTERFACE ".InvalidArguments",
+                                       "Invalid Arguments");
+
+       g_dbus_proxy_method_call(tracklist->proxy, "ChangeFolder",
+                               change_folder_setup, change_folder_reply,
+                               player, NULL);
+
+       return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static DBusMessage *playlist_get(DBusConnection *conn, DBusMessage *msg,
+                                                               void *data)
+{
+       struct player *player = data;
+       uint32_t index, count;
+       const char *order;
+       dbus_bool_t reverse;
+       DBusMessage *reply;
+       DBusMessageIter iter, entry, value, name;
+       const char *string, *path;
+       const char *empty = "";
+
+       if (player->playlist == NULL)
+               return g_dbus_create_error(msg,
+                                       ERROR_INTERFACE ".InvalidArguments",
+                                       "Invalid Arguments");
+
+       if (!dbus_message_get_args(msg, NULL,
+                                       DBUS_TYPE_UINT32, &index,
+                                       DBUS_TYPE_UINT32, &count,
+                                       DBUS_TYPE_STRING, &order,
+                                       DBUS_TYPE_BOOLEAN, &reverse,
+                                       DBUS_TYPE_INVALID))
+               return g_dbus_create_error(msg,
+                                       ERROR_INTERFACE ".InvalidArguments",
+                                       "Invalid Arguments");
+
+       path = g_dbus_proxy_get_path(player->playlist);
+
+       reply = dbus_message_new_method_return(msg);
+
+       dbus_message_iter_init_append(reply, &iter);
+
+       dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(oss)",
+                                                               &entry);
+       dbus_message_iter_open_container(&entry, DBUS_TYPE_STRUCT, NULL,
+                                                               &value);
+       dbus_message_iter_append_basic(&value, DBUS_TYPE_OBJECT_PATH, &path);
+       if (g_dbus_proxy_get_property(player->playlist, "Name", &name)) {
+               dbus_message_iter_get_basic(&name, &string);
+               dbus_message_iter_append_basic(&value, DBUS_TYPE_STRING,
+                                                               &string);
+       } else {
+               dbus_message_iter_append_basic(&value, DBUS_TYPE_STRING,
+                                                               &path);
+       }
+       dbus_message_iter_append_basic(&value, DBUS_TYPE_STRING, &empty);
+       dbus_message_iter_close_container(&entry, &value);
+       dbus_message_iter_close_container(&iter, &entry);
+
+       return reply;
+}
+
+static const GDBusMethodTable playlist_methods[] = {
+       { GDBUS_METHOD("ActivatePlaylist",
+                       GDBUS_ARGS({ "playlist", "o" }), NULL,
+                       playlist_activate) },
+       { GDBUS_METHOD("GetPlaylists",
+                       GDBUS_ARGS({ "index", "u" }, { "maxcount", "u"},
+                                       { "order", "s" }, { "reverse", "b" }),
+                       GDBUS_ARGS({ "playlists", "a(oss)"}),
+                       playlist_get) },
+       { },
+};
+
+static gboolean playlist_exists(const GDBusPropertyTable *property, void *data)
+{
+       struct player *player = data;
+
+       return player->playlist != NULL;
+}
+
+static gboolean get_playlist_count(const GDBusPropertyTable *property,
+                                       DBusMessageIter *iter, void *data)
+{
+       struct player *player = data;
+       uint32_t count = 1;
+
+       if (player->playlist == NULL)
+               return FALSE;
+
+       dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &count);
+
+       return TRUE;
+}
+
+static gboolean get_orderings(const GDBusPropertyTable *property,
+                                       DBusMessageIter *iter, void *data)
+{
+       DBusMessageIter value;
+       const char *order = "User";
+
+       dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
+                                       DBUS_TYPE_OBJECT_PATH_AS_STRING,
+                                       &value);
+       dbus_message_iter_append_basic(&value, DBUS_TYPE_STRING, &order);
+       dbus_message_iter_close_container(iter, &value);
+
+       return TRUE;
+}
+
+static gboolean get_active_playlist(const GDBusPropertyTable *property,
+                                       DBusMessageIter *iter, void *data)
+{
+       struct player *player = data;
+       DBusMessageIter value, entry;
+       dbus_bool_t enabled = TRUE;
+       const char *path, *empty = "";
+
+       if (player->playlist == NULL)
+               return FALSE;
+
+       path = g_dbus_proxy_get_path(player->playlist);
+
+       dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT,
+                                                       NULL, &value);
+       dbus_message_iter_append_basic(&value, DBUS_TYPE_BOOLEAN, &enabled);
+       dbus_message_iter_open_container(&value, DBUS_TYPE_STRUCT, NULL,
+                                                               &entry);
+       dbus_message_iter_append_basic(&entry, DBUS_TYPE_OBJECT_PATH, &path);
+       dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &path);
+       dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &empty);
+       dbus_message_iter_close_container(&value, &entry);
+       dbus_message_iter_close_container(iter, &value);
+
+       return TRUE;
+}
+
+static const GDBusPropertyTable playlist_properties[] = {
+       { "PlaylistCount", "u", get_playlist_count, NULL, playlist_exists },
+       { "Orderings", "as", get_orderings, NULL, NULL },
+       { "ActivePlaylist", "(b(oss))", get_active_playlist, NULL,
+                                                       playlist_exists },
+       { }
+};
+
+#define a_z "abcdefghijklmnopqrstuvwxyz"
+#define A_Z "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+#define _0_9 "_0123456789"
+
+static char *mpris_busname(char *name)
+{
+       if (g_ascii_isdigit(name[0]))
+               return g_strconcat(MPRIS_BUS_NAME, "bt_",
+                               g_strcanon(name, A_Z a_z _0_9, '_'), NULL);
+       else
+               return g_strconcat(MPRIS_BUS_NAME,
+                               g_strcanon(name, A_Z a_z _0_9, '_'), NULL);
+}
+
+static GDBusProxy *find_transport_by_path(const char *path)
+{
+       GSList *l;
+
+       for (l = transports; l; l = l->next) {
+               GDBusProxy *transport = l->data;
+               DBusMessageIter iter;
+               const char *value;
+
+               if (!g_dbus_proxy_get_property(transport, "Device", &iter))
+                       continue;
+
+               dbus_message_iter_get_basic(&iter, &value);
+
+               if (strcmp(path, value) == 0)
+                       return transport;
+       }
+
+       return NULL;
+}
+
+static struct player *find_player(GDBusProxy *proxy)
+{
+       GSList *l;
+
+       for (l = players; l; l = l->next) {
+               struct player *player = l->data;
+               const char *path, *p;
+
+               if (player->proxy == proxy)
+                       return player;
+
+               path = g_dbus_proxy_get_path(proxy);
+               p = g_dbus_proxy_get_path(player->proxy);
+               if (g_str_equal(path, p))
+                       return player;
+       }
+
+       return NULL;
+}
+
+static void register_tracklist(GDBusProxy *proxy)
+{
+       struct player *player;
+       struct tracklist *tracklist;
+
+       player = find_player(proxy);
+       if (player == NULL)
+               return;
+
+       if (player->tracklist != NULL)
+               return;
+
+       tracklist = g_new0(struct tracklist, 1);
+       tracklist->proxy = g_dbus_proxy_ref(proxy);
+
+       player->tracklist = tracklist;
+
+       g_dbus_emit_property_changed(player->conn, MPRIS_PLAYER_PATH,
+                                               MPRIS_INTERFACE,
+                                               "HasTrackList");
+
+       if (player->playlist == NULL)
+               return;
+
+       g_dbus_proxy_method_call(player->tracklist->proxy, "ChangeFolder",
+                               change_folder_setup, change_folder_reply,
+                               player, NULL);
+}
+
+static void register_player(GDBusProxy *proxy)
+{
+       struct player *player;
+       DBusMessageIter iter;
+       const char *path, *alias, *name;
+       char *busname;
+       GDBusProxy *device, *transport;
+
+       if (!g_dbus_proxy_get_property(proxy, "Device", &iter))
+               return;
+
+       dbus_message_iter_get_basic(&iter, &path);
+
+       device = g_dbus_proxy_new(client, path, "org.bluez.Device1");
+       if (device == NULL)
+               return;
+
+       if (!g_dbus_proxy_get_property(device, "Alias", &iter))
+               return;
+
+       dbus_message_iter_get_basic(&iter, &alias);
+
+       if (g_dbus_proxy_get_property(proxy, "Name", &iter)) {
+               dbus_message_iter_get_basic(&iter, &name);
+               busname = g_strconcat(alias, " ", name, NULL);
+       } else
+               busname = g_strdup(alias);
+
+       player = g_new0(struct player, 1);
+       player->bus_name = mpris_busname(busname);
+       player->proxy = g_dbus_proxy_ref(proxy);
+       player->device = device;
+
+       g_free(busname);
+
+       players = g_slist_prepend(players, player);
+
+       printf("Player %s created\n", player->bus_name);
+
+       player->conn = g_dbus_setup_private(DBUS_BUS_SESSION, player->bus_name,
+                                                                       NULL);
+       if (!session) {
+               fprintf(stderr, "Could not register bus name %s",
+                                                       player->bus_name);
+               goto fail;
+       }
+
+       if (!g_dbus_register_interface(player->conn, MPRIS_PLAYER_PATH,
+                                               MPRIS_INTERFACE,
+                                               mpris_methods,
+                                               NULL,
+                                               mpris_properties,
+                                               player, NULL)) {
+               fprintf(stderr, "Could not register interface %s",
+                                               MPRIS_INTERFACE);
+               goto fail;
+       }
+
+       if (!g_dbus_register_interface(player->conn, MPRIS_PLAYER_PATH,
+                                               MPRIS_PLAYER_INTERFACE,
+                                               player_methods,
+                                               player_signals,
+                                               player_properties,
+                                               player, player_free)) {
+               fprintf(stderr, "Could not register interface %s",
+                                               MPRIS_PLAYER_INTERFACE);
+               goto fail;
+       }
+
+       if (!g_dbus_register_interface(player->conn, MPRIS_PLAYER_PATH,
+                                               MPRIS_TRACKLIST_INTERFACE,
+                                               tracklist_methods,
+                                               tracklist_signals,
+                                               tracklist_properties,
+                                               player, NULL)) {
+               fprintf(stderr, "Could not register interface %s",
+                                               MPRIS_TRACKLIST_INTERFACE);
+               goto fail;
+       }
+
+       if (!g_dbus_register_interface(player->conn, MPRIS_PLAYER_PATH,
+                                               MPRIS_PLAYLISTS_INTERFACE,
+                                               playlist_methods,
+                                               NULL,
+                                               playlist_properties,
+                                               player, NULL)) {
+               fprintf(stderr, "Could not register interface %s",
+                                               MPRIS_PLAYLISTS_INTERFACE);
+               goto fail;
+       }
+
+       transport = find_transport_by_path(path);
+       if (transport)
+               player->transport = g_dbus_proxy_ref(transport);
+
+       return;
+
+fail:
+       players = g_slist_remove(players, player);
+       player_free(player);
+}
+
+static struct player *find_player_by_device(const char *device)
+{
+       GSList *l;
+
+       for (l = players; l; l = l->next) {
+               struct player *player = l->data;
+               const char *path = g_dbus_proxy_get_path(player->device);
+
+               if (g_strcmp0(device, path) == 0)
+                       return player;
+       }
+
+       return NULL;
+}
+
+static void register_transport(GDBusProxy *proxy)
+{
+       struct player *player;
+       DBusMessageIter iter;
+       const char *path;
+
+       if (g_slist_find(transports, proxy) != NULL)
+               return;
+
+       if (!g_dbus_proxy_get_property(proxy, "Volume", &iter))
+               return;
+
+       if (!g_dbus_proxy_get_property(proxy, "Device", &iter))
+               return;
+
+       dbus_message_iter_get_basic(&iter, &path);
+
+       transports = g_slist_append(transports, proxy);
+
+       player = find_player_by_device(path);
+       if (player == NULL || player->transport != NULL)
+               return;
+
+       player->transport = g_dbus_proxy_ref(proxy);
+}
+
+static struct player *find_player_by_item(const char *item)
+{
+       GSList *l;
+
+       for (l = players; l; l = l->next) {
+               struct player *player = l->data;
+               const char *path = g_dbus_proxy_get_path(player->proxy);
+
+               if (g_str_has_prefix(item, path))
+                       return player;
+       }
+
+       return NULL;
+}
+
+static void register_playlist(struct player *player, GDBusProxy *proxy)
+{
+       const char *path;
+       DBusMessageIter iter;
+
+       if (!g_dbus_proxy_get_property(player->proxy, "Playlist", &iter))
+               return;
+
+       dbus_message_iter_get_basic(&iter, &path);
+
+       if (!g_str_equal(path, g_dbus_proxy_get_path(proxy)))
+               return;
+
+       player->playlist = g_dbus_proxy_ref(proxy);
+
+       g_dbus_emit_property_changed(player->conn, MPRIS_PLAYER_PATH,
+                                               MPRIS_PLAYLISTS_INTERFACE,
+                                               "PlaylistCount");
+
+       if (player->tracklist == NULL)
+               return;
+
+       g_dbus_proxy_method_call(player->tracklist->proxy, "ChangeFolder",
+                               change_folder_setup, change_folder_reply,
+                               player, NULL);
+}
+
+static void register_item(struct player *player, GDBusProxy *proxy)
+{
+       struct tracklist *tracklist;
+       const char *path, *playlist;
+       DBusMessage *signal;
+       DBusMessageIter iter, args, metadata;
+       GSList *l;
+       GDBusProxy *after;
+
+       if (player->playlist == NULL) {
+               register_playlist(player, proxy);
+               return;
+       }
+
+       tracklist = player->tracklist;
+       if (tracklist == NULL)
+               return;
+
+       path = g_dbus_proxy_get_path(proxy);
+       playlist = g_dbus_proxy_get_path(player->playlist);
+       if (!g_str_has_prefix(path, playlist))
+               return;
+
+       l = g_slist_last(tracklist->items);
+       tracklist->items = g_slist_append(tracklist->items, proxy);
+
+       g_dbus_emit_property_changed(player->conn, MPRIS_PLAYER_PATH,
+                                               MPRIS_TRACKLIST_INTERFACE,
+                                               "Tracks");
+
+       if (l == NULL)
+               return;
+
+       signal = dbus_message_new_signal(MPRIS_PLAYER_PATH,
+                                       MPRIS_TRACKLIST_INTERFACE,
+                                       "TrackAdded");
+       if (!signal) {
+               fprintf(stderr, "Unable to allocate new %s.TrackAdded signal",
+                                               MPRIS_TRACKLIST_INTERFACE);
+               return;
+       }
+
+       dbus_message_iter_init_append(signal, &args);
+
+       if (!g_dbus_proxy_get_property(proxy, "Metadata", &iter)) {
+               dbus_message_unref(signal);
+               return;
+       }
+
+       dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY,
+                       DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+                       DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+                       DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &metadata);
+
+       parse_metadata(&iter, &metadata, parse_track_entry);
+
+       dbus_message_iter_close_container(&args, &metadata);
+
+       after = l->data;
+       path = g_dbus_proxy_get_path(after);
+       dbus_message_iter_append_basic(&args, DBUS_TYPE_OBJECT_PATH, &path);
+
+       g_dbus_send_message(player->conn, signal);
+}
+
+static void proxy_added(GDBusProxy *proxy, void *user_data)
+{
+       const char *interface;
+       const char *path;
+
+       interface = g_dbus_proxy_get_interface(proxy);
+       path = g_dbus_proxy_get_path(proxy);
+
+       if (!strcmp(interface, BLUEZ_ADAPTER_INTERFACE)) {
+               if (adapter != NULL)
+                       return;
+
+               printf("Bluetooth Adapter %s found\n", path);
+               adapter = proxy;
+               list_names(session);
+       } else if (!strcmp(interface, BLUEZ_MEDIA_PLAYER_INTERFACE)) {
+               printf("Bluetooth Player %s found\n", path);
+               register_player(proxy);
+       } else if (!strcmp(interface, BLUEZ_MEDIA_TRANSPORT_INTERFACE)) {
+               printf("Bluetooth Transport %s found\n", path);
+               register_transport(proxy);
+       } else if (!strcmp(interface, BLUEZ_MEDIA_FOLDER_INTERFACE)) {
+               printf("Bluetooth Folder %s found\n", path);
+               register_tracklist(proxy);
+       } else if (!strcmp(interface, BLUEZ_MEDIA_ITEM_INTERFACE)) {
+               struct player *player;
+
+               player = find_player_by_item(path);
+               if (player == NULL)
+                       return;
+
+               printf("Bluetooth Item %s found\n", path);
+               register_item(player, proxy);
+       }
+}
+
+static void unregister_player(struct player *player)
+{
+       players = g_slist_remove(players, player);
+
+       if (player->tracklist != NULL) {
+               g_dbus_unregister_interface(player->conn, MPRIS_PLAYER_PATH,
+                                               MPRIS_PLAYLISTS_INTERFACE);
+               g_dbus_unregister_interface(player->conn, MPRIS_PLAYER_PATH,
+                                               MPRIS_TRACKLIST_INTERFACE);
+       }
+
+       g_dbus_unregister_interface(player->conn, MPRIS_PLAYER_PATH,
+                                               MPRIS_INTERFACE);
+
+       g_dbus_unregister_interface(player->conn, MPRIS_PLAYER_PATH,
+                                               MPRIS_PLAYER_INTERFACE);
+}
+
+static struct player *find_player_by_transport(GDBusProxy *proxy)
+{
+       GSList *l;
+
+       for (l = players; l; l = l->next) {
+               struct player *player = l->data;
+
+               if (player->transport == proxy)
+                       return player;
+       }
+
+       return NULL;
+}
+
+static void unregister_transport(GDBusProxy *proxy)
+{
+       struct player *player;
+
+       if (g_slist_find(transports, proxy) == NULL)
+               return;
+
+       transports = g_slist_remove(transports, proxy);
+
+       player = find_player_by_transport(proxy);
+       if (player == NULL)
+               return;
+
+       g_dbus_proxy_unref(player->transport);
+       player->transport = NULL;
+}
+
+static void unregister_item(struct player *player, GDBusProxy *proxy)
+{
+       struct tracklist *tracklist = player->tracklist;
+       const char *path;
+
+       if (tracklist == NULL)
+               return;
+
+       if (g_slist_find(tracklist->items, proxy) == NULL)
+               return;
+
+       path = g_dbus_proxy_get_path(proxy);
+
+       tracklist->items = g_slist_remove(tracklist->items, proxy);
+
+       g_dbus_emit_property_changed(player->conn, MPRIS_PLAYER_PATH,
+                                               MPRIS_TRACKLIST_INTERFACE,
+                                               "Tracks");
+
+       g_dbus_emit_signal(player->conn, MPRIS_PLAYER_PATH,
+                               MPRIS_TRACKLIST_INTERFACE, "TrackRemoved",
+                               DBUS_TYPE_OBJECT_PATH, &path,
+                               DBUS_TYPE_INVALID);
+}
+
+static void remove_players(DBusConnection *conn)
+{
+       char **paths;
+       int i;
+
+       dbus_connection_list_registered(conn, "/", &paths);
+
+       for (i = 0; paths[i]; i++) {
+               char *path;
+               void *data;
+
+               path = g_strdup_printf("/%s", paths[i]);
+               dbus_connection_get_object_path_data(sys, path, &data);
+               dbus_connection_unregister_object_path(sys, path);
+
+               g_free(path);
+               g_free(data);
+       }
+
+       dbus_free_string_array(paths);
+}
+
+static void proxy_removed(GDBusProxy *proxy, void *user_data)
+{
+       const char *interface;
+       const char *path;
+
+       if (adapter == NULL)
+               return;
+
+       interface = g_dbus_proxy_get_interface(proxy);
+       path = g_dbus_proxy_get_path(proxy);
+
+       if (strcmp(interface, BLUEZ_ADAPTER_INTERFACE) == 0) {
+               if (adapter != proxy)
+                       return;
+               printf("Bluetooth Adapter %s removed\n", path);
+               adapter = NULL;
+               remove_players(sys);
+       } else if (strcmp(interface, BLUEZ_MEDIA_PLAYER_INTERFACE) == 0) {
+               struct player *player;
+
+               player = find_player(proxy);
+               if (player == NULL)
+                       return;
+
+               printf("Bluetooth Player %s removed\n", path);
+               unregister_player(player);
+       } else if (strcmp(interface, BLUEZ_MEDIA_TRANSPORT_INTERFACE) == 0) {
+               printf("Bluetooth Transport %s removed\n", path);
+               unregister_transport(proxy);
+       } else if (strcmp(interface, BLUEZ_MEDIA_ITEM_INTERFACE) == 0) {
+               struct player *player;
+
+               player = find_player_by_item(path);
+               if (player == NULL)
+                       return;
+
+               printf("Bluetooth Item %s removed\n", path);
+               unregister_item(player, proxy);
+       }
+}
+
+static const char *property_to_mpris(const char *property)
+{
+       if (strcasecmp(property, "Repeat") == 0)
+               return "LoopStatus";
+       else if (strcasecmp(property, "Shuffle") == 0)
+               return "Shuffle";
+       else if (strcasecmp(property, "Status") == 0)
+               return "PlaybackStatus";
+       else if (strcasecmp(property, "Position") == 0)
+               return "Position";
+       else if (strcasecmp(property, "Track") == 0)
+               return "Metadata";
+
+       return NULL;
+}
+
+static void player_property_changed(GDBusProxy *proxy, const char *name,
+                                       DBusMessageIter *iter, void *user_data)
+{
+       struct player *player;
+       const char *property;
+       uint32_t position;
+       uint64_t value;
+
+       player = find_player(proxy);
+       if (player == NULL)
+               return;
+
+       property = property_to_mpris(name);
+       if (property == NULL)
+               return;
+
+       g_dbus_emit_property_changed(player->conn, MPRIS_PLAYER_PATH,
+                                               MPRIS_PLAYER_INTERFACE,
+                                               property);
+
+       if (strcasecmp(name, "Position") != 0)
+               return;
+
+       dbus_message_iter_get_basic(iter, &position);
+
+       value = position * 1000;
+
+       g_dbus_emit_signal(player->conn, MPRIS_PLAYER_PATH,
+                                       MPRIS_PLAYER_INTERFACE, "Seeked",
+                                       DBUS_TYPE_INT64, &value,
+                                       DBUS_TYPE_INVALID);
+}
+
+static void transport_property_changed(GDBusProxy *proxy, const char *name,
+                                       DBusMessageIter *iter, void *user_data)
+{
+       struct player *player;
+       DBusMessageIter var;
+       const char *path;
+
+       if (strcasecmp(name, "Volume") != 0 && strcasecmp(name, "State") != 0)
+               return;
+
+       if (!g_dbus_proxy_get_property(proxy, "Device", &var))
+               return;
+
+       dbus_message_iter_get_basic(&var, &path);
+
+       player = find_player_by_device(path);
+       if (player == NULL)
+               return;
+
+       if (strcasecmp(name, "State") == 0) {
+               if (!g_dbus_proxy_get_property(player->proxy, "Status", &var))
+                       g_dbus_emit_property_changed(player->conn,
+                                               MPRIS_PLAYER_PATH,
+                                               MPRIS_PLAYER_INTERFACE,
+                                               "PlaybackStatus");
+               return;
+       }
+
+       g_dbus_emit_property_changed(player->conn, MPRIS_PLAYER_PATH,
+                                               MPRIS_PLAYER_INTERFACE,
+                                               name);
+}
+
+static void item_property_changed(GDBusProxy *proxy, const char *name,
+                                       DBusMessageIter *iter, void *user_data)
+{
+       struct player *player;
+       DBusMessage *signal;
+       DBusMessageIter args;
+       const char *path;
+
+       path = g_dbus_proxy_get_path(proxy);
+
+       player = find_player_by_item(path);
+       if (player == NULL)
+               return;
+
+       if (strcasecmp(name, "Metadata") != 0)
+               return;
+
+       signal = dbus_message_new_signal(MPRIS_PLAYER_PATH,
+                                       MPRIS_TRACKLIST_INTERFACE,
+                                       "TrackMetadataChanged");
+       if (!signal) {
+               fprintf(stderr, "Unable to allocate new %s.TrackAdded signal",
+                                               MPRIS_TRACKLIST_INTERFACE);
+               return;
+       }
+
+       dbus_message_iter_init_append(signal, &args);
+
+       dbus_message_iter_append_basic(&args, DBUS_TYPE_OBJECT_PATH, &path);
+
+       append_iter(&args, iter);
+
+       g_dbus_send_message(player->conn, signal);
+}
+
+static void property_changed(GDBusProxy *proxy, const char *name,
+                                       DBusMessageIter *iter, void *user_data)
+{
+       const char *interface;
+
+       interface = g_dbus_proxy_get_interface(proxy);
+
+       if (strcmp(interface, BLUEZ_MEDIA_PLAYER_INTERFACE) == 0)
+               return player_property_changed(proxy, name, iter, user_data);
+
+       if (strcmp(interface, BLUEZ_MEDIA_TRANSPORT_INTERFACE) == 0)
+               return transport_property_changed(proxy, name, iter,
+                                                               user_data);
+
+       if (strcmp(interface, BLUEZ_MEDIA_ITEM_INTERFACE) == 0)
+               return item_property_changed(proxy, name, iter, user_data);
+}
+
+int main(int argc, char *argv[])
+{
+       GOptionContext *context;
+       GError *error = NULL;
+       guint owner_watch, properties_watch, signal_watch;
+       struct sigaction sa;
+
+       context = g_option_context_new(NULL);
+       g_option_context_add_main_entries(context, options, NULL);
+
+       if (g_option_context_parse(context, &argc, &argv, &error) == FALSE) {
+               if (error != NULL) {
+                       g_printerr("%s\n", error->message);
+                       g_error_free(error);
+               } else
+                       g_printerr("An unknown error occurred\n");
+               exit(1);
+       }
+
+       g_option_context_free(context);
+
+       if (option_version == TRUE) {
+               usage();
+               exit(0);
+       }
+
+       main_loop = g_main_loop_new(NULL, FALSE);
+
+       sys = g_dbus_setup_bus(DBUS_BUS_SYSTEM, NULL, NULL);
+       if (!sys) {
+               fprintf(stderr, "Can't get on system bus");
+               exit(1);
+       }
+
+       session = g_dbus_setup_bus(DBUS_BUS_SESSION, NULL, NULL);
+       if (!session) {
+               fprintf(stderr, "Can't get on session bus");
+               exit(1);
+       }
+
+       owner_watch = g_dbus_add_signal_watch(session, NULL, NULL,
+                                               DBUS_INTERFACE_DBUS,
+                                               "NameOwnerChanged",
+                                               name_owner_changed,
+                                               NULL, NULL);
+
+       properties_watch = g_dbus_add_properties_watch(session, NULL, NULL,
+                                                       MPRIS_PLAYER_INTERFACE,
+                                                       player_signal,
+                                                       NULL, NULL);
+
+       signal_watch = g_dbus_add_signal_watch(session, NULL, NULL,
+                                                       MPRIS_PLAYER_INTERFACE,
+                                                       NULL, player_signal,
+                                                       NULL, NULL);
+
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_flags   = SA_NOCLDSTOP;
+       sa.sa_handler = sig_term;
+       sigaction(SIGTERM, &sa, NULL);
+       sigaction(SIGINT,  &sa, NULL);
+
+       client = g_dbus_client_new(sys, BLUEZ_BUS_NAME, BLUEZ_PATH);
+
+       g_dbus_client_set_connect_watch(client, connect_handler, NULL);
+       g_dbus_client_set_disconnect_watch(client, disconnect_handler, NULL);
+
+       g_dbus_client_set_proxy_handlers(client, proxy_added, proxy_removed,
+                                               property_changed, NULL);
+
+       g_main_loop_run(main_loop);
+
+       g_dbus_remove_watch(session, owner_watch);
+       g_dbus_remove_watch(session, properties_watch);
+       g_dbus_remove_watch(session, signal_watch);
+
+       g_dbus_client_unref(client);
+
+       dbus_connection_unref(session);
+       dbus_connection_unref(sys);
+
+       g_main_loop_unref(main_loop);
+
+       return 0;
+}