diff --git a/build.gradle.kts b/build.gradle.kts index a2e5364..481ecf1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import java.util.Date plugins { - kotlin("jvm") version "1.2.30" + kotlin("jvm") version "1.2.51" id("com.jfrog.bintray") version "1.8.0" } @@ -16,6 +16,7 @@ allprojects { repositories { jcenter() + maven("https://jitpack.io") } } @@ -26,7 +27,7 @@ val kotlinVersion: String by extra { } dependencies { - compile("com.github.hypfvieh", "dbus-java", "2.7.+") + compile("com.github.Xerus2000", "dbus-java", "2.7-SNAPSHOT") compile("org.jetbrains.kotlin", "kotlin-runtime", kotlinVersion) } diff --git a/extensions/src/xerus/mpris/AbstractMPRISPlayer.kt b/extensions/src/xerus/mpris/AbstractMPRISPlayer.kt index a8669be..7e0611a 100644 --- a/extensions/src/xerus/mpris/AbstractMPRISPlayer.kt +++ b/extensions/src/xerus/mpris/AbstractMPRISPlayer.kt @@ -13,7 +13,7 @@ import org.mpris.MediaPlayer2.* * A val represents a Read-only field as declared by MPRIS, it is perfectly valid to implement it as var */ abstract class AbstractMPRISPlayer : MediaPlayerX, PlayerX, DefaultDBus { - val connection = DBusConnection.getConnection(DBusConnection.SESSION) + val connection: DBusConnection = DBusConnection.getConnection(DBusConnection.SESSION) val properties = HashMap>>() internal val propertyListeners = HashMap Unit>() @@ -109,4 +109,4 @@ interface PlaylistsX : Playlists { val playlistCount: Int val activePlaylist: MaybePlaylist -} \ No newline at end of file +} diff --git a/extensions/src/xerus/mpris/DBusPropertyDelegate.kt b/extensions/src/xerus/mpris/DBusPropertyDelegate.kt index cae768c..be2e349 100644 --- a/extensions/src/xerus/mpris/DBusPropertyDelegate.kt +++ b/extensions/src/xerus/mpris/DBusPropertyDelegate.kt @@ -6,7 +6,6 @@ import javafx.beans.value.ObservableValue import org.freedesktop.dbus.DBusInterfaceName import org.mpris.MediaPlayer2.PlaylistOrdering import org.slf4j.LoggerFactory -import kotlin.properties.ObservableProperty import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty @@ -21,7 +20,7 @@ private fun findInterface(clazz: Class<*>, name: String): Class<*>? = (clazz.interfaces + clazz.superclass).firstOrNull { if(it == null) return@firstOrNull false if (it.declaredMethods.any { it.name.contains(name) }) { - logger.debug("Found $it for property $name") + logger.trace("Found $it for property $name") return it.interfaces.first() } findInterface(it, name) != null @@ -40,8 +39,8 @@ class DBusProperty(private val initial: T, private val observable: Obse ?: throw RuntimeException("No interface found for Property $name") val interfaceName = (clazz.annotations.find { it is DBusInterfaceName } as? DBusInterfaceName)?.value ?: clazz.name - logger.debug("Registered Property ${prop.name} for $interfaceName") - thisRef.properties.getOrPut(interfaceName) { HashMap() }.put(name, initial.variant()) + logger.trace("Registered Property ${prop.name} for $interfaceName") + thisRef.properties.getOrPut(interfaceName) { HashMap() }[name] = initial.variant() val property = Property(interfaceName, name) if (onSet != null) thisRef.propertyListeners[name] = onSet as ((Any) -> Unit) @@ -49,6 +48,29 @@ class DBusProperty(private val initial: T, private val observable: Obse return property } +} +class DBusMapProperty(private val initial: Map = HashMap(), private val observable: ObservableValue>? = null, private val onSet: ((Map) -> Unit)? = null) { + + constructor(observable: ObservableValue>, onSet: ((Map) -> Unit)? = null) : this(observable.value, observable, onSet) + + operator fun provideDelegate( + thisRef: AbstractMPRISPlayer, + prop: KProperty<*> + ): ReadWriteProperty> { + val name = prop.name.capitalize() + val clazz = findInterface(thisRef::class.java, name) + ?: throw RuntimeException("No interface found for Property $name") + val interfaceName = (clazz.annotations.find { it is DBusInterfaceName } as? DBusInterfaceName)?.value + ?: clazz.name + logger.trace("Registered Property ${prop.name} for $interfaceName") + //thisRef.properties.getOrPut(interfaceName) { HashMap() }[name] = initial.variant() + val property = Property>(interfaceName, name) + if (onSet != null) + thisRef.propertyListeners[name] = onSet as ((Any) -> Unit) + observable?.addListener { _, _, new -> property.setValue(thisRef, prop, new) } + return property + } + } class DBusConstant(private val value: T) { @@ -62,7 +84,7 @@ class DBusConstant(private val value: T) { ?: throw RuntimeException("No interface found for Property $name") val interfaceName = (clazz.annotations.find { it is DBusInterfaceName } as? DBusInterfaceName)?.value ?: clazz.name - logger.debug("Registered Constant ${prop.name} for $interfaceName") + logger.trace("Registered Constant ${prop.name} for $interfaceName") thisRef.properties.getOrPut(interfaceName) { HashMap() }.put(name, value.variant()) return Constant(value) } diff --git a/extensions/src/xerus/mpris/DefaultDBus.kt b/extensions/src/xerus/mpris/DefaultDBus.kt index b3a3f20..85d10a4 100644 --- a/extensions/src/xerus/mpris/DefaultDBus.kt +++ b/extensions/src/xerus/mpris/DefaultDBus.kt @@ -3,33 +3,35 @@ package xerus.mpris import org.freedesktop.DBus.Properties import org.freedesktop.dbus.Variant import org.freedesktop.dbus.types.DBusMapType +import java.security.InvalidParameterException import java.util.* fun Any.variant(): Variant<*> { - if(this is Variant<*>) - return this - if(this is Map<*, *>) { - val params = this::class.java.typeParameters - return Variant(this, DBusMapType(params[0], params[1])) - } - return Variant(this) + if (this is Variant<*>) + return this + if (this is Map<*, *>) + throw InvalidParameterException("Use Map.variant for Maps!") + return Variant(this) } -interface DefaultDBus: Properties { - - override fun isRemote() = false - - @Suppress("UNCHECKED_CAST") - override fun Get(interface_name: String, property_name: String) = - GetAll(interface_name)[property_name]?.value as A - - override fun Set(interface_name: String, property_name: String, value: A) { - GetAll(interface_name).put(property_name, value.variant()) - propertyChanged(interface_name, property_name) - } - - /** returns a new [Properties.PropertiesChanged] signal */ - fun propertyChanged(interface_name: String, property_name: String) = - Properties.PropertiesChanged(objectPath, interface_name, Collections.singletonMap(property_name, Get(interface_name, property_name).variant()) as Map>, null) - +inline fun Map.variant() = + Variant(this, DBusMapType(K::class.java, V::class.java)) + +interface DefaultDBus : Properties { + + override fun isRemote() = false + + @Suppress("UNCHECKED_CAST") + override fun Get(interface_name: String, property_name: String) = + GetAll(interface_name)[property_name]?.value as A + + override fun Set(interface_name: String, property_name: String, value: A) { + GetAll(interface_name).put(property_name, value.variant()) + propertyChanged(interface_name, property_name) + } + + /** returns a new [Properties.PropertiesChanged] signal */ + fun propertyChanged(interface_name: String, property_name: String) = + Properties.PropertiesChanged(objectPath, interface_name, Collections.singletonMap(property_name, Get(interface_name, property_name).variant()) as Map>, null) + } diff --git a/extensions/src/xerus/mpris/PropertyMap.kt b/extensions/src/xerus/mpris/PropertyMap.kt index c44e229..d75493b 100644 --- a/extensions/src/xerus/mpris/PropertyMap.kt +++ b/extensions/src/xerus/mpris/PropertyMap.kt @@ -1,8 +1,7 @@ package xerus.mpris import org.freedesktop.dbus.Variant -import org.freedesktop.dbus.types.DBusMapType -import java.util.HashMap +import java.util.* class PropertyMap private constructor(initial: PropertyMap.() -> Unit, private val map: HashMap>): Map> by map { diff --git a/extensions/test/xerus/mpris/MPRISPlayer.kt b/extensions/test/xerus/mpris/MPRISPlayer.kt index 52867af..0caa059 100644 --- a/extensions/test/xerus/mpris/MPRISPlayer.kt +++ b/extensions/test/xerus/mpris/MPRISPlayer.kt @@ -1,178 +1,173 @@ package xerus.mpris import org.freedesktop.DBus.Properties.PropertiesChanged -import org.freedesktop.dbus.DBusConnection import org.freedesktop.dbus.Variant -import org.freedesktop.dbus.types.DBusMapType -import org.mpris.MediaPlayer2.* -import java.util.Collections -import java.util.HashMap - -fun println(obj: String) = System.out.println(obj) -operator fun HashMap.set(k: K, value: V) = put(k, value) +import org.mpris.MediaPlayer2.LoopStatus +import org.mpris.MediaPlayer2.PlaybackStatus +import java.util.* +operator fun HashMap.set(k: K, value: V) = put(k, value) fun main(args: Array) { - println("Connecting to DBus") - MPRISPlayer().exportAs("TestPlayer") + println("Connecting to DBus") + MPRISPlayer().exportAs("TestPlayer") } class MPRISPlayer : AbstractMPRISPlayer() { - - override var playbackStatus by DBusProperty(PlaybackStatus.Stopped) - override var loopStatus: LoopStatus? = null - override var rate by DBusProperty(1.0) - override var shuffle: Boolean? = null - override val metadata by DBusProperty(PropertyMap { - put("mpris:trackid", "/playerfx/songs/untiltheend") - put("mpris:length", 10_000000) - put("mpris:artUrl", "file:///home/janek/Daten/Musik/Monstercat/Aero Chord - Love & Hate EP/cover.jpeg") - put("xesam:artist", arrayOf("Aero Chord", "Fractal")) - put("xesam:title", "Until The End (feat. Q'AILA)") - }) - override var volume: Double - get() = TODO("not implemented") - set(value) {} - override val position: Long - get() = TODO("not implemented") - override val minimumRate: Double - get() = TODO("not implemented") - override val maximumRate: Double - get() = TODO("not implemented") - override val canGoNext: Boolean - get() = TODO("not implemented") - override val canGoPrevious: Boolean - get() = TODO("not implemented") - override val canPlay: Boolean - get() = TODO("not implemented") - override val canPause: Boolean - get() = TODO("not implemented") - override val canSeek: Boolean - get() = TODO("not implemented") - override val canControl: Boolean - get() = TODO("not implemented") - override val canQuit: Boolean - get() = TODO("not implemented") - override var fullscreen: Boolean? - get() = TODO("not implemented") - set(value) {} - override val canSetFullscreen: Boolean? - get() = TODO("not implemented") - override val canRaise: Boolean - get() = TODO("not implemented") - override val hasTrackList: Boolean - get() = TODO("not implemented") - override val identity: String - get() = TODO("not implemented") - override val desktopEntry: String? - get() = TODO("not implemented") - override val supportedUriSchemes: Array - get() = TODO("not implemented") - override val supportedMimeTypes: Array - get() = TODO("not implemented") - - var currentTrack = SimpleTrack { - } - - init { - println("Constructing MPRISPlayer") - // MediaPlayer2 - properties["org.mpris.MediaPlayer2"] = PropertyMap { - put("CanSeek", true) - put("CanQuit", true) - put("CanRaise", false) - put("HasTrackList", true) - put("Identity", "PlayerFX") - put("SupportedUriSchemes", arrayOf("file")) - put("SupportedMimeTypes", arrayOf("audio/mpeg", "audio/mp4")) - //put("DesktopEntry") - } - - // Player - properties["org.mpris.MediaPlayer2.Player"] = PropertyMap { - put("PlaybackStatus", PlaybackStatus.Stopped) - put("LoopStatus", LoopStatus.None) - put("Rate", 1.0) - put("Shuffle", false) - put("Metadata", Variant(currentTrack.metadata, DBusMapType(String::class.java, Variant::class.java))) - put("Volume", 1.0) - put("Position", 0) - put("MinimumRate", 1.0) - put("MaximumRate", 1.0) - put("CanGoNext", true) - put("CanGoPrevious", true) - put("CanPlay", true) - put("CanPause", true) - put("CanSeek", true) - put("CanControl", true) - } - println("MPRISPlayer constructed") - - } - - override fun GetAll(interface_name: String) = properties[interface_name] - - fun updateProperty(interface_name: String, name: String, value: A) { - println("Updating $name of $interface_name to $value") - val new = Variant(value) - properties[interface_name]!![name] = new - try { - connection.sendSignal(PropertiesChanged("/org/mpris/MediaPlayer2", interface_name, Collections.singletonMap(name, new) as Map>, Collections.emptyList())) - } catch (e: Throwable) { - e.printStackTrace() - } - } - - fun updateStatus(status: PlaybackStatus) { - playbackStatus = status - updateProperty("org.mpris.MediaPlayer2.Player", "PlaybackStatus", status.name) - } - - override fun PlayPause() { - println("PlayPause called") - updateStatus(playbackStatus.playPause()) - } - - override fun Play() { - println("Play called") - updateStatus(PlaybackStatus.Playing) - } - - override fun Pause() { - println("Pause called") - updateStatus(PlaybackStatus.Paused) - } - - override fun Stop() { - println("Stop called") - updateStatus(PlaybackStatus.Stopped) - } - - override fun Seek(x: Long) { - println("Seeking by $x") - } - - override fun Previous() { - println("Previous called") - } - - override fun Next() { - println("Next called") - } - - override fun Raise() { - println("open me!") - } - - override fun Quit() { - println("I'm quitting!") - connection.disconnect() - } - - override fun OpenUri(uri: String) { - println("OpenUri called") - } - - override fun getObjectPath() = "/org/mpris/MediaPlayer2" - + + override var playbackStatus by DBusProperty(PlaybackStatus.Stopped) + override var loopStatus: LoopStatus = LoopStatus.None + override var rate by DBusProperty(1.0) + override var shuffle: Boolean = false + override val metadata by DBusMapProperty(PropertyMap { + put("mpris:trackid", "/playerfx/songs/untiltheend") + put("mpris:length", 10_000000) + put("mpris:artUrl", "file:///home/janek/daten/musik/Monstercat/Aero Chord - Love & Hate EP/cover.jpeg") + put("xesam:artist", arrayOf("Aero Chord", "Fractal")) + put("xesam:title", "Until The End (feat. Q'AILA)") + }) + override var volume: Double + get() = TODO("not implemented") + set(value) {} + override val position: Long + get() = TODO("not implemented") + override val minimumRate: Double + get() = TODO("not implemented") + override val maximumRate: Double + get() = TODO("not implemented") + override val canGoNext: Boolean + get() = TODO("not implemented") + override val canGoPrevious: Boolean + get() = TODO("not implemented") + override val canPlay: Boolean + get() = TODO("not implemented") + override val canPause: Boolean + get() = TODO("not implemented") + override val canSeek: Boolean + get() = TODO("not implemented") + override val canControl: Boolean + get() = TODO("not implemented") + override val canQuit: Boolean + get() = TODO("not implemented") + override var fullscreen: Boolean + get() = TODO("not implemented") + set(value) {} + override val canSetFullscreen: Boolean + get() = TODO("not implemented") + override val canRaise: Boolean + get() = TODO("not implemented") + override val hasTrackList: Boolean + get() = TODO("not implemented") + override val identity: String + get() = TODO("not implemented") + override val desktopEntry: String + get() = TODO("not implemented") + override val supportedUriSchemes: Array + get() = TODO("not implemented") + override val supportedMimeTypes: Array + get() = TODO("not implemented") + + var currentTrack = SimpleTrack(metadata) + + init { + println("Constructing MPRISPlayer") + /* + properties["org.mpris.MediaPlayer2"] = PropertyMap { + put("CanSeek", true) + put("CanQuit", true) + put("CanRaise", false) + put("HasTrackList", true) + put("Identity", "PlayerFX") + put("SupportedUriSchemes", arrayOf("file")) + put("SupportedMimeTypes", arrayOf("audio/mpeg", "audio/mp4")) + //put("DesktopEntry") + } + + properties["org.mpris.MediaPlayer2.Player"] = PropertyMap { + put("PlaybackStatus", PlaybackStatus.Stopped) + put("LoopStatus", LoopStatus.None) + put("Rate", 1.0) + put("Shuffle", false) + put("Metadata", Variant(currentTrack.metadata, DBusMapType(String::class.java, Variant::class.java))) + put("Volume", 1.0) + put("Position", 0) + put("MinimumRate", 1.0) + put("MaximumRate", 1.0) + put("CanGoNext", true) + put("CanGoPrevious", true) + put("CanPlay", true) + put("CanPause", true) + put("CanSeek", true) + put("CanControl", true) + } + */ + println("MPRISPlayer constructed") + + } + + override fun GetAll(interface_name: String) = properties[interface_name] + + fun updateProperty(interface_name: String, name: String, value: A) { + println("Updating $name of $interface_name to $value") + val new = Variant(value) + properties[interface_name]!![name] = new + try { + connection.sendSignal(PropertiesChanged("/org/mpris/MediaPlayer2", interface_name, Collections.singletonMap(name, new) as Map>, Collections.emptyList())) + } catch (e: Throwable) { + e.printStackTrace() + } + } + + fun updateStatus(status: PlaybackStatus) { + playbackStatus = status + updateProperty("org.mpris.MediaPlayer2.Player", "PlaybackStatus", status.name) + } + + override fun PlayPause() { + println("PlayPause called") + updateStatus(playbackStatus.playPause()) + } + + override fun Play() { + println("Play called") + updateStatus(PlaybackStatus.Playing) + } + + override fun Pause() { + println("Pause called") + updateStatus(PlaybackStatus.Paused) + } + + override fun Stop() { + println("Stop called") + updateStatus(PlaybackStatus.Stopped) + } + + override fun Seek(x: Long) { + println("Seeking by $x") + } + + override fun Previous() { + println("Previous called") + } + + override fun Next() { + println("Next called") + } + + override fun Raise() { + println("open me!") + } + + override fun Quit() { + println("I'm quitting!") + connection.disconnect() + } + + override fun OpenUri(uri: String) { + println("OpenUri called") + } + + override fun getObjectPath() = "/org/mpris/MediaPlayer2" + } diff --git a/settings.gradle b/settings.gradle index 48c35d2..d2dc7e0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,4 @@ - rootProject.name = "mpris" -rootProject.buildFileName = "build.gradle.kts" include ":mpris-extensions" project(":mpris-extensions").projectDir = file("extensions")