Commits

Mathias Panzenböck committed 47d6d84

refactorings and added ui files (not used yet)

Comments (0)

Files changed (8)

+UI_PY=contents/code/commands_ui.py contents/code/edit_ui.py
+PY=$(UI_PY) contents/code/main.py contents/code/mediaplayer.py \
+	contents/code/playericon.py contents/code/scrollwidget.py \
+	contents/code/lbls.py
+
+.PHONY: all clean install uninstall pkg ui
+
+all: pkg
+
+pkg: playctrl.plasmoid
+
+ui: $(UI)
+
+playctrl.plasmoid: $(PY)
+	zip -r9 $@ $(PY) metadata.desktop COPYING
+
+$(UI_PY): contents/code/%_ui.py: contents/ui/%.ui
+	pyuic4 -o $@ $<
+
+clean:
+	rm -f playctrl.plasmoid $(UI_PY)
+
+install: pkg
+	plasmapkg -i playctrl.plasmoid
+
+uninstall:
+	plasmapkg -r playctrl

contents/code/lbls.py

+# -*- coding: utf-8 -*-
+##################################################################################
+# Play Control
+# Copyright (C) 2011-2013 Mathias Panzenböck <grosser.meister.morti@gmx.net>
+#
+# 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 Street, Fifth Floor, Boston, MA  02110-1301, USA.
+#
+##################################################################################
+
+# TODO: proper internationalization/localization
+
+prev_lbl = 'Previous Song'
+play_pause_lbl = 'Toggle Play / Pause'
+play_lbl = 'Play'
+pause_lbl = 'Pause'
+next_lbl = 'Next Song'
+stop_lbl = 'Stop'
+forward_lbl = 'Seek Forward'
+backward_lbl = 'Seek Backward'
+quit_lbl = 'Quit Media Player'

contents/code/main.py

 # -*- coding: utf-8 -*-
 ##################################################################################
 # Play Control
-# Copyright (C) 2011 Mathias Panzenböck <grosser.meister.morti@gmx.net>
+# Copyright (C) 2011-2013 Mathias Panzenböck <grosser.meister.morti@gmx.net>
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
 #
 ##################################################################################
 
-import sys, subprocess, re, weakref
+import sys
 from PyQt4.QtCore import Qt, QString, QSizeF
 from PyQt4.QtGui import QGraphicsGridLayout, QSizePolicy, \
-	QGraphicsLinearLayout, QGraphicsWidget, QMenu, QKeySequence
+	QGraphicsLinearLayout, QGraphicsWidget
 from PyKDE4.plasma import Plasma
 from PyKDE4.kdeui import KIcon, KShortcutsEditor, KActionCollection, \
 	KAction, KShortcut
-from PyKDE4.kdecore import KService
 from PyKDE4 import plasmascript
 
 import dbus
 import dbus.mainloop.qt
 
-PLAYER_NAME = re.compile('^org\\.mpris\\.(?:MediaPlayer2\\.)?(.+?)(?:-[^-]*)?$')
-
-MEDIA_PLAYER = 'org.freedesktop.MediaPlayer'
-MEDIA_PLAYER2 = 'org.mpris.MediaPlayer2'
-MEDIA_PLAYER2_PLAYER = 'org.mpris.MediaPlayer2.Player'
-UNKNOWN_METHOD_ERROR = 'org.freedesktop.DBus.Error.UnknownMethod'
-NAME_HAS_NO_OWNER_ERROR = 'org.freedesktop.DBus.Error.NameHasNoOwner'
-
-CAN_GO_NEXT = 1 << 0
-CAN_GO_PREV = 1 << 1
-CAN_PAUSE = 1 << 2
-CAN_PLAY = 1 << 3
-CAN_SEEK = 1 << 4
-CAN_PROVIDE_METADATA = 1 << 5
-CAN_HAS_TRACKLIST = 1 << 6
-
-STATUS_PLAYING = 0
-STATUS_PAUSED = 1
-STATUS_STOPPED = 2
-
-STATUS_MAP = {
-	'playing': STATUS_PLAYING,
-	'paused':  STATUS_PAUSED,
-	'stopped': STATUS_STOPPED
-}
-
-prev_lbl = 'Previous Song'
-play_pause_lbl = 'Toggle Play / Pause'
-play_lbl = 'Play'
-pause_lbl = 'Pause'
-next_lbl = 'Next Song'
-stop_lbl = 'Stop'
-forward_lbl = 'Seek Forward'
-backward_lbl = 'Seek Backward'
-quit_lbl = 'Quit Media Player'
-
-ScrollWidget_resizeEvent = plasmascript.Plasma.ScrollWidget.resizeEvent
-class ScrollWidget(plasmascript.Plasma.ScrollWidget):
-	def __init__(self,parent=None):
-		plasmascript.Plasma.ScrollWidget.__init__(self,parent)
-		self.setOverShoot(False)
-		self.setMinimumSize(QSizeF(0,0))
-		self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
-		self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
-		self.setOverflowBordersVisible(True)
-
-	def resizeEvent(self,event):
-		height = event.newSize().height()
-		policy = Qt.ScrollBarAsNeeded if height > 50 else Qt.ScrollBarAlwaysOff
-		if policy != self.verticalScrollBarPolicy():
-			self.setVerticalScrollBarPolicy(policy)
-		visible = height > 5
-		if visible != self.isVisible():
-			self.setVisible(visible)
-		ScrollWidget_resizeEvent(self,event)
-
-IconWidget_paint = plasmascript.Plasma.IconWidget.paint
-class PlayerIcon(plasmascript.Plasma.IconWidget):
-	__slots__ = 'applet', 'player', 'frame'
-	def __init__(self,icon,text,applet,player,parent=None):
-		plasmascript.Plasma.IconWidget.__init__(self,icon,text,parent)
-		self.applet = weakref.ref(applet)
-		self.player = player
-		self.setOrientation(Qt.Horizontal)
-		self.setDrawBackground(True)
-		self.setSizePolicy(QSizePolicy.Preferred,QSizePolicy.Preferred)
-		self.frame = plasmascript.Plasma.FrameSvg(self)
-		self.frame.setImagePath("widgets/viewitem")
-		self.frame.setCacheAllRenderedFrames(True)
-		self.frame.setElementPrefix("selected")
-	
-	def paint(self,painter,option,widget):
-		applet = self.applet()
-		if applet and applet._selected is self:
-			self.frame.resizeFrame(self.size())
-			self.frame.paintFrame(painter)
-		IconWidget_paint(self,painter,option,widget)
-	
-	def contextMenuEvent(self,event):
-		menu = QMenu()
-		
-		prev_icon = KIcon('media-skip-backward')
-		play_pause_icon = KIcon('media-playback-start')
-		play_icon = KIcon('media-playback-start')
-		pause_icon = KIcon('media-playback-pause')
-		stop_icon = KIcon('media-playback-stop')
-		next_icon = KIcon('media-skip-forward')
-		quit_icon = KIcon('application-exit')
-		backward_icon = KIcon('media-seek-backward')
-		forward_icon = KIcon('media-seek-forward')
-
-		menu.addAction(prev_icon,prev_lbl).triggered.connect(self.player.prev)
-		menu.addAction(backward_icon,backward_lbl).triggered.connect(self.player.backward)
-		menu.addAction(play_pause_icon,play_pause_lbl).triggered.connect(self.player.playPause)
-		menu.addAction(play_icon,play_lbl).triggered.connect(self.player.play)
-		menu.addAction(pause_icon,pause_lbl).triggered.connect(self.player.pause)
-		menu.addAction(stop_icon,stop_lbl).triggered.connect(self.player.stop)
-		menu.addAction(forward_icon,forward_lbl).triggered.connect(self.player.forward)
-		menu.addAction(next_icon,next_lbl).triggered.connect(self.player.next)
-		menu.addAction(quit_icon,quit_lbl).triggered.connect(self.player.quit)
-
-		menu.exec_(event.screenPos())
-
-class MediaPlayerBase(object):
-	__slots__ = 'bus', 'name', 'owner'
-	def __init__(self,bus,name,owner):
-		self.bus   = bus
-		self.name  = name
-		self.owner = owner
-	
-	def _call(self,object,interface,method,*args):
-		return self.bus.get_object(self.name,object,introspect=False).get_dbus_method(
-			method,interface)(*args)
-	
-	def _get(self,object,interface,property):
-		return self.bus.get_object(self.name,object,introspect=False).get_dbus_method(
-			'Get',dbus_interface='org.freedesktop.DBus.Properties')(interface,property)
-
-	def _set(self,object,interface,property,value):
-		return self.bus.get_object(self.name,object,introspect=False).get_dbus_method(
-			'Set',dbus_interface='org.freedesktop.DBus.Properties')(interface,property,value)
-	
-	def icon(self,identity=None):
-		if not identity:
-			identity = self.identity()
-		lower_name = unicode(identity).lower()
-		lower_id = PLAYER_NAME.match(unicode(self.name)).group(1).lower()
-		for service in KService.allServices():
-			service_name = unicode(service.name())
-			if service_name and service_name == lower_id or \
-					service.desktopEntryName().toLower() == lower_id or (
-					lower_name.startswith(service_name.lower()) and
-					(len(identity) == len(service_name) or
-					identity[len(service_name)].isspace())):
-				icon = service.icon()
-				if icon:
-					return icon
-		return None
-
-class MediaPlayer(MediaPlayerBase):
-	__slots__ = ()
-	def _mpris_call(self,method,*args):
-		return self._call('/Player',MEDIA_PLAYER,method,*args)
-
-	def quit(self):
-		return self._call('/',MEDIA_PLAYER,'Quit')
-
-	def identity(self):
-		return self._call('/',MEDIA_PLAYER,'Identity')
-
-	def capabilities(self):
-		return self._mpris_call('GetCaps')
-
-	def position(self):
-		return self._mpris_call('PositionGet')
-	
-	def set_position(self,position):
-		self._mpris_call('PositionSet',position)
-	
-	def forward(self):
-		self.set_position(self.position()+10000)
-	
-	def backward(self):
-		self.set_position(max(self.position()-10000,0))
-
-	def play(self):
-		self._mpris_call('Play')
-	
-	def pause(self):
-		self._mpris_call('Pause')
-		
-	def _nativePlayPause(self):
-		self._mpris_call('PlayPause')
-	
-	def _playPauseViaCaps(self):
-		caps = self.capabilities()
-		if caps & CAN_PAUSE:
-			self.pause()
-		elif caps & CAN_PLAY:
-			self.play()
-
-	def status(self):
-		return self._mpris_call('GetStatus')
-
-	def _playPauseViaStatus(self):
-		if self.status()[0] == STATUS_PLAYING:
-			self.pause()
-		else:
-			self.play()
-	
-	def _playPauseViaPosition(self):
-		pos1 = self.position()
-		pos2 = self.position()
-		if pos1 == pos2:
-			self.play()
-		else:
-			self.pause()
-	
-	def playPause(self):
-		try:
-			self._nativePlayPause()
-		except dbus.DBusException as e:
-			if e.get_dbus_name() == UNKNOWN_METHOD_ERROR:
-				try:
-					self._playPauseViaCaps()
-				except dbus.DBusException as e:
-					if e.get_dbus_name() == UNKNOWN_METHOD_ERROR:
-						try:
-							self._playPauseViaStatus()
-						except dbus.DBusException as e:
-							if e.get_dbus_name() == UNKNOWN_METHOD_ERROR:
-								self._playPauseViaPosition()
-								self.playPause = self._playPauseViaPosition
-							else:
-								raise
-						else:
-							self.playPause = self._playPauseViaStatus
-					else:
-						raise
-				else:
-					self.playPause = self._playPauseViaCaps
-			else:
-				raise
-	
-	def _playingViaStatus(self):
-		return self.status()[0] == STATUS_PLAYING
-
-	def _playingViaPosition(self):
-		return self.position() != self.position()
-
-	def playing(self):
-		try:
-			return self._playingViaStatus()
-		except dbus.DBusException as e:
-			if e.get_dbus_name() == UNKNOWN_METHOD_ERROR:
-				playing = self._playingViaPosition()
-				self.playing = self._playingViaPosition
-				return playing
-			else:
-				raise
-		else:
-			self.playing = self._playingViaStatus
-
-	def stop(self):
-		self._mpris_call('Stop')
-
-	def next(self):
-		self._mpris_call('Next')
-		
-	def prev(self):
-		self._mpris_call('Prev')
-
-class Audacious(MediaPlayer):
-	def playPause(self):
-		self._call('/org/atheme/audacious','org.atheme.audacious','PlayPause')
-
-class VLC(MediaPlayer):
-	def playPause(self):
-		pos1 = self.position()
-		if pos1 == 0 and self.position() == pos1:
-			self.play()
-		else:
-			self.pause()
-
-class MediaPlayer2(MediaPlayerBase):
-	__slots__ = ()
-	def _player_call(self,method,*args):
-		return self._call('/org/mpris/MediaPlayer2',MEDIA_PLAYER2_PLAYER,method,*args)
-
-	def _player_get(self,property):
-		return self._get('/org/mpris/MediaPlayer2',MEDIA_PLAYER2_PLAYER,property)
-
-	def _general_call(self,method,*args):
-		return self._call('/org/mpris/MediaPlayer2',MEDIA_PLAYER2,method,*args)
-
-	def _general_get(self,property):
-		return self._get('/org/mpris/MediaPlayer2',MEDIA_PLAYER2,property)
-
-	def quit(self):
-		self._general_call('Quit')
-
-	def identity(self):
-		return self._general_get('Identity')
-
-	def seek(self,offset):
-		self._player_call('Seek',long(offset))
-
-	def forward(self):
-		self.seek(10000000L)
-
-	def backward(self):
-		self.seek(-10000000L)
-
-	def play(self):
-		self._player_call('Play')
-	
-	def pause(self):
-		self._player_call('Pause')
-
-	def playPause(self):
-		self._player_call('PlayPause')
-
-	def playbackStatus(self):
-		return self._player_get('PlaybackStatus')
-
-	def playing(self):
-		return self.playbackStatus() == 'Playing'
-
-	def stop(self):
-		self._player_call('Stop')
-
-	def next(self):
-		self._player_call('Next')
-
-	def prev(self):
-		self._player_call('Previous')
-
-class CommandMediaPlayer(MediaPlayerBase):
-	__slots__ = 'config',
-	def __init__(self,name,identity,config,icon=None):
-		MediaPlayerBase.__init__(self,None,name,self)
-		self.config = config
-	
-	def icon(self,identity=None):
-		return self.config.get('icon',None)
-
-	def identity(self):
-		return self.config.get('identity',None)
-	
-	def capabilities(self):
-		caps = 0
-
-		if config.get('next',None):
-			caps |= CAN_GO_NEXT
-
-		if config.get('prev',None):
-			caps |= CAN_GO_PREV
-
-		if config.get('pause',None):
-			caps |= CAN_PAUSE
-
-		if config.get('play',None):
-			caps |= CAN_PLAY
-
-		return caps
-	
-	def _call(self,cmdname):
-		cmd = self.config.get(cmdname,None)
-		if cmd:
-			subprocess.call(["sh","-c",cmd,cmdname])
-	
-	def _call_output(self,cmdname):
-		cmd = self.config.get(cmdname,None)
-		if cmd:
-			return subprocess.check_output(["sh","-c",cmd,cmdname])
-		else:
-			return ''
-
-	def playPause(self):
-		cmd = self.config.get('playPause',None)
-		if cmd:
-			subprocess.call(["sh","-c",cmd,'playPause'])
-			return
-
-		if self.config.get('status',None):
-			if self.playing():
-				self.pause()
-			else:
-				self.play()
-	
-	def playing(self):
-		return self.status() == STATUS_PLAYING
-
-	play     = lambda self: self._call('play')
-	pause    = lambda self: self._call('pause')
-	stop     = lambda self: self._call('stop')
-	quit     = lambda self: self._call('quit')
-	next     = lambda self: self._call('next')
-	prev     = lambda self: self._call('prev')
-	backward = lambda self: self._call('backward')
-	forward  = lambda self: self._call('forward')
-	status   = lambda self: STATUS_MAP.get(self._call_output('status').strip().lower(),None)
-
-PLAYERS = {
-	"org.mpris.audacious": Audacious,
-	"org.mpris.vlc": VLC
-}
+from mediaplayer import *
+from scrollwidget import ScrollWidget
+from playericon import PlayerIcon
+from lbls import *
 
 class PlayControl(plasmascript.Applet):
 	def init(self):
 				else:
 					default_cls = MediaPlayer2 if dbus_name.startswith("org.mpris.MediaPlayer2.") else MediaPlayer
 					if owner not in players or default_cls is MediaPlayer2:
-						player = PLAYERS.get(dbus_name,default_cls)(self._bus,dbus_name,owner)
+						player = PLAYER_MAP.get(dbus_name,default_cls)(self._bus,dbus_name,owner)
 						try:
 							name = player.identity()
 						except dbus.DBusException as e:
 						else:
 							players[owner] = (name, player)
 		players = players.values()
+
+		# TODO: GUI
+		cmdplayer = CommandMediaPlayer('cmd:greattune-player',dict(
+			identity = 'Greattune Player',
+			icon = '/home/panzi/src/python/plasma/playctrl/magnatune.png',
+			playPause = '/home/panzi/src/shell/gtp-remote.sh playpause',
+			play = '/home/panzi/src/shell/gtp-remote.sh play',
+			pause = '/home/panzi/src/shell/gtp-remote.sh pause',
+			stop = '/home/panzi/src/shell/gtp-remote.sh stop',
+			next = '/home/panzi/src/shell/gtp-remote.sh next',
+			prev = '/home/panzi/src/shell/gtp-remote.sh prev',
+			status = '/home/panzi/src/shell/gtp-remote.sh status',
+			quit = '/home/panzi/src/shell/gtp-remote.sh quit'
+		))
+		players.append((cmdplayer.identity(), cmdplayer))
+
 		players.sort()
 		current = self._player()
 		if current:

contents/code/mediaplayer.py

+# -*- coding: utf-8 -*-
+##################################################################################
+# Play Control
+# Copyright (C) 2011-2013 Mathias Panzenböck <grosser.meister.morti@gmx.net>
+#
+# 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 Street, Fifth Floor, Boston, MA  02110-1301, USA.
+#
+##################################################################################
+
+import re, subprocess
+from PyKDE4.kdecore import KService
+
+PLAYER_NAME = re.compile('^org\\.mpris\\.(?:MediaPlayer2\\.)?(.+?)(?:-[^-]*)?$')
+
+MEDIA_PLAYER = 'org.freedesktop.MediaPlayer'
+MEDIA_PLAYER2 = 'org.mpris.MediaPlayer2'
+MEDIA_PLAYER2_PLAYER = 'org.mpris.MediaPlayer2.Player'
+UNKNOWN_METHOD_ERROR = 'org.freedesktop.DBus.Error.UnknownMethod'
+NAME_HAS_NO_OWNER_ERROR = 'org.freedesktop.DBus.Error.NameHasNoOwner'
+
+CAN_GO_NEXT = 1 << 0
+CAN_GO_PREV = 1 << 1
+CAN_PAUSE = 1 << 2
+CAN_PLAY = 1 << 3
+CAN_SEEK = 1 << 4
+CAN_PROVIDE_METADATA = 1 << 5
+CAN_HAS_TRACKLIST = 1 << 6
+
+STATUS_PLAYING = 0
+STATUS_PAUSED = 1
+STATUS_STOPPED = 2
+
+STATUS_MAP = {
+	'playing': STATUS_PLAYING,
+	'paused':  STATUS_PAUSED,
+	'stopped': STATUS_STOPPED
+}
+
+class MediaPlayerBase(object):
+	__slots__ = 'bus', 'name', 'owner'
+	def __init__(self,bus,name,owner):
+		self.bus   = bus
+		self.name  = name
+		self.owner = owner
+	
+	def _call(self,object,interface,method,*args):
+		return self.bus.get_object(self.name,object,introspect=False).get_dbus_method(
+			method,interface)(*args)
+	
+	def _get(self,object,interface,property):
+		return self.bus.get_object(self.name,object,introspect=False).get_dbus_method(
+			'Get',dbus_interface='org.freedesktop.DBus.Properties')(interface,property)
+
+	def _set(self,object,interface,property,value):
+		return self.bus.get_object(self.name,object,introspect=False).get_dbus_method(
+			'Set',dbus_interface='org.freedesktop.DBus.Properties')(interface,property,value)
+	
+	def icon(self,identity=None):
+		if not identity:
+			identity = self.identity()
+		lower_name = unicode(identity).lower()
+		lower_id = PLAYER_NAME.match(unicode(self.name)).group(1).lower()
+		for service in KService.allServices():
+			service_name = unicode(service.name())
+			if service_name and service_name == lower_id or \
+					service.desktopEntryName().toLower() == lower_id or (
+					lower_name.startswith(service_name.lower()) and
+					(len(identity) == len(service_name) or
+					identity[len(service_name)].isspace())):
+				icon = service.icon()
+				if icon:
+					return icon
+		return None
+
+class MediaPlayer(MediaPlayerBase):
+	__slots__ = ()
+	def _mpris_call(self,method,*args):
+		return self._call('/Player',MEDIA_PLAYER,method,*args)
+
+	def quit(self):
+		return self._call('/',MEDIA_PLAYER,'Quit')
+
+	def identity(self):
+		return self._call('/',MEDIA_PLAYER,'Identity')
+
+	def capabilities(self):
+		return self._mpris_call('GetCaps')
+
+	def position(self):
+		return self._mpris_call('PositionGet')
+	
+	def set_position(self,position):
+		self._mpris_call('PositionSet',position)
+	
+	def forward(self):
+		self.set_position(self.position()+10000)
+	
+	def backward(self):
+		self.set_position(max(self.position()-10000,0))
+
+	def play(self):
+		self._mpris_call('Play')
+	
+	def pause(self):
+		self._mpris_call('Pause')
+		
+	def _nativePlayPause(self):
+		self._mpris_call('PlayPause')
+	
+	def _playPauseViaCaps(self):
+		caps = self.capabilities()
+		if caps & CAN_PAUSE:
+			self.pause()
+		elif caps & CAN_PLAY:
+			self.play()
+
+	def status(self):
+		return self._mpris_call('GetStatus')
+
+	def _playPauseViaStatus(self):
+		if self.status()[0] == STATUS_PLAYING:
+			self.pause()
+		else:
+			self.play()
+	
+	def _playPauseViaPosition(self):
+		pos1 = self.position()
+		pos2 = self.position()
+		if pos1 == pos2:
+			self.play()
+		else:
+			self.pause()
+	
+	def playPause(self):
+		try:
+			self._nativePlayPause()
+		except dbus.DBusException as e:
+			if e.get_dbus_name() == UNKNOWN_METHOD_ERROR:
+				try:
+					self._playPauseViaCaps()
+				except dbus.DBusException as e:
+					if e.get_dbus_name() == UNKNOWN_METHOD_ERROR:
+						try:
+							self._playPauseViaStatus()
+						except dbus.DBusException as e:
+							if e.get_dbus_name() == UNKNOWN_METHOD_ERROR:
+								self._playPauseViaPosition()
+								self.playPause = self._playPauseViaPosition
+							else:
+								raise
+						else:
+							self.playPause = self._playPauseViaStatus
+					else:
+						raise
+				else:
+					self.playPause = self._playPauseViaCaps
+			else:
+				raise
+	
+	def _playingViaStatus(self):
+		return self.status()[0] == STATUS_PLAYING
+
+	def _playingViaPosition(self):
+		return self.position() != self.position()
+
+	def playing(self):
+		try:
+			return self._playingViaStatus()
+		except dbus.DBusException as e:
+			if e.get_dbus_name() == UNKNOWN_METHOD_ERROR:
+				playing = self._playingViaPosition()
+				self.playing = self._playingViaPosition
+				return playing
+			else:
+				raise
+		else:
+			self.playing = self._playingViaStatus
+
+	def stop(self):
+		self._mpris_call('Stop')
+
+	def next(self):
+		self._mpris_call('Next')
+		
+	def prev(self):
+		self._mpris_call('Prev')
+
+class Audacious(MediaPlayer):
+	def playPause(self):
+		self._call('/org/atheme/audacious','org.atheme.audacious','PlayPause')
+
+class VLC(MediaPlayer):
+	def playPause(self):
+		pos1 = self.position()
+		if pos1 == 0 and self.position() == pos1:
+			self.play()
+		else:
+			self.pause()
+
+class MediaPlayer2(MediaPlayerBase):
+	__slots__ = ()
+	def _player_call(self,method,*args):
+		return self._call('/org/mpris/MediaPlayer2',MEDIA_PLAYER2_PLAYER,method,*args)
+
+	def _player_get(self,property):
+		return self._get('/org/mpris/MediaPlayer2',MEDIA_PLAYER2_PLAYER,property)
+
+	def _general_call(self,method,*args):
+		return self._call('/org/mpris/MediaPlayer2',MEDIA_PLAYER2,method,*args)
+
+	def _general_get(self,property):
+		return self._get('/org/mpris/MediaPlayer2',MEDIA_PLAYER2,property)
+
+	def quit(self):
+		self._general_call('Quit')
+
+	def identity(self):
+		return self._general_get('Identity')
+
+	def seek(self,offset):
+		self._player_call('Seek',long(offset))
+
+	def forward(self):
+		self.seek(10000000L)
+
+	def backward(self):
+		self.seek(-10000000L)
+
+	def play(self):
+		self._player_call('Play')
+	
+	def pause(self):
+		self._player_call('Pause')
+
+	def playPause(self):
+		self._player_call('PlayPause')
+
+	def playbackStatus(self):
+		return self._player_get('PlaybackStatus')
+
+	def playing(self):
+		return self.playbackStatus() == 'Playing'
+
+	def stop(self):
+		self._player_call('Stop')
+
+	def next(self):
+		self._player_call('Next')
+
+	def prev(self):
+		self._player_call('Previous')
+
+class CommandMediaPlayer(MediaPlayerBase):
+	__slots__ = 'config',
+	def __init__(self,name,config):
+		MediaPlayerBase.__init__(self,None,name,self)
+		self.config = config
+	
+	def icon(self,identity=None):
+		return self.config.get('icon',None)
+
+	def identity(self):
+		return self.config.get('identity',None)
+	
+	def capabilities(self):
+		caps = 0
+
+		if config.get('next',None):
+			caps |= CAN_GO_NEXT
+
+		if config.get('prev',None):
+			caps |= CAN_GO_PREV
+
+		if config.get('pause',None):
+			caps |= CAN_PAUSE
+
+		if config.get('play',None):
+			caps |= CAN_PLAY
+
+		return caps
+	
+	def _call(self,cmdname):
+		cmd = self.config.get(cmdname,None)
+		if cmd:
+			subprocess.call(["sh","-c",cmd,cmdname])
+	
+	def _call_output(self,cmdname):
+		cmd = self.config.get(cmdname,None)
+		if cmd:
+			return subprocess.check_output(["sh","-c",cmd,cmdname])
+		else:
+			return ''
+
+	def playPause(self):
+		cmd = self.config.get('playPause',None)
+		if cmd:
+			subprocess.call(["sh","-c",cmd,'playPause'])
+			return
+
+		if self.config.get('status',None):
+			if self.playing():
+				self.pause()
+			else:
+				self.play()
+	
+	def playing(self):
+		return self.status() == STATUS_PLAYING
+
+	play     = lambda self: self._call('play')
+	pause    = lambda self: self._call('pause')
+	stop     = lambda self: self._call('stop')
+	quit     = lambda self: self._call('quit')
+	next     = lambda self: self._call('next')
+	prev     = lambda self: self._call('prev')
+	backward = lambda self: self._call('backward')
+	forward  = lambda self: self._call('forward')
+	status   = lambda self: STATUS_MAP.get(self._call_output('status').strip().lower(),None)
+
+PLAYER_MAP = {
+	"org.mpris.audacious": Audacious,
+	"org.mpris.vlc": VLC
+}

contents/code/playericon.py

+# -*- coding: utf-8 -*-
+##################################################################################
+# Play Control
+# Copyright (C) 2011-2013 Mathias Panzenböck <grosser.meister.morti@gmx.net>
+#
+# 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 Street, Fifth Floor, Boston, MA  02110-1301, USA.
+#
+##################################################################################
+
+import weakref
+from PyQt4.QtCore import Qt, QSizeF
+from PyQt4.QtGui import QSizePolicy, QMenu
+from PyKDE4 import plasmascript
+from PyKDE4.kdeui import KIcon
+
+from lbls import *
+
+IconWidget_paint = plasmascript.Plasma.IconWidget.paint
+class PlayerIcon(plasmascript.Plasma.IconWidget):
+	__slots__ = 'applet', 'player', 'frame'
+	def __init__(self,icon,text,applet,player,parent=None):
+		plasmascript.Plasma.IconWidget.__init__(self,icon,text,parent)
+		self.applet = weakref.ref(applet)
+		self.player = player
+		self.setOrientation(Qt.Horizontal)
+		self.setDrawBackground(True)
+		self.setSizePolicy(QSizePolicy.Preferred,QSizePolicy.Preferred)
+		self.frame = plasmascript.Plasma.FrameSvg(self)
+		self.frame.setImagePath("widgets/viewitem")
+		self.frame.setCacheAllRenderedFrames(True)
+		self.frame.setElementPrefix("selected")
+	
+	def paint(self,painter,option,widget):
+		applet = self.applet()
+		if applet and applet._selected is self:
+			self.frame.resizeFrame(self.size())
+			self.frame.paintFrame(painter)
+		IconWidget_paint(self,painter,option,widget)
+	
+	def contextMenuEvent(self,event):
+		menu = QMenu()
+		
+		prev_icon = KIcon('media-skip-backward')
+		play_pause_icon = KIcon('media-playback-start')
+		play_icon = KIcon('media-playback-start')
+		pause_icon = KIcon('media-playback-pause')
+		stop_icon = KIcon('media-playback-stop')
+		next_icon = KIcon('media-skip-forward')
+		quit_icon = KIcon('application-exit')
+		backward_icon = KIcon('media-seek-backward')
+		forward_icon = KIcon('media-seek-forward')
+
+		menu.addAction(prev_icon,prev_lbl).triggered.connect(self.player.prev)
+		menu.addAction(backward_icon,backward_lbl).triggered.connect(self.player.backward)
+		menu.addAction(play_pause_icon,play_pause_lbl).triggered.connect(self.player.playPause)
+		menu.addAction(play_icon,play_lbl).triggered.connect(self.player.play)
+		menu.addAction(pause_icon,pause_lbl).triggered.connect(self.player.pause)
+		menu.addAction(stop_icon,stop_lbl).triggered.connect(self.player.stop)
+		menu.addAction(forward_icon,forward_lbl).triggered.connect(self.player.forward)
+		menu.addAction(next_icon,next_lbl).triggered.connect(self.player.next)
+		menu.addAction(quit_icon,quit_lbl).triggered.connect(self.player.quit)
+
+		menu.exec_(event.screenPos())

contents/code/scrollwidget.py

+# -*- coding: utf-8 -*-
+##################################################################################
+# Play Control
+# Copyright (C) 2011-2013 Mathias Panzenböck <grosser.meister.morti@gmx.net>
+#
+# 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 Street, Fifth Floor, Boston, MA  02110-1301, USA.
+#
+##################################################################################
+
+from PyQt4.QtCore import Qt, QSizeF
+from PyKDE4 import plasmascript
+
+ScrollWidget_resizeEvent = plasmascript.Plasma.ScrollWidget.resizeEvent
+class ScrollWidget(plasmascript.Plasma.ScrollWidget):
+	def __init__(self,parent=None):
+		plasmascript.Plasma.ScrollWidget.__init__(self,parent)
+		self.setOverShoot(False)
+		self.setMinimumSize(QSizeF(0,0))
+		self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+		self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
+		self.setOverflowBordersVisible(True)
+
+	def resizeEvent(self,event):
+		height = event.newSize().height()
+		policy = Qt.ScrollBarAsNeeded if height > 50 else Qt.ScrollBarAlwaysOff
+		if policy != self.verticalScrollBarPolicy():
+			self.setVerticalScrollBarPolicy(policy)
+		visible = height > 5
+		if visible != self.isVisible():
+			self.setVisible(visible)
+		ScrollWidget_resizeEvent(self,event)

contents/ui/commands.ui

+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Commands</class>
+ <widget class="QWidget" name="Commands">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>755</width>
+    <height>476</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="0">
+    <widget class="KLineEdit" name="txtPlayerName"/>
+   </item>
+   <item row="0" column="1" rowspan="2">
+    <layout class="QVBoxLayout" name="verticalLayout">
+     <item>
+      <widget class="QPushButton" name="btnAdd">
+       <property name="text">
+        <string>&amp;Add</string>
+       </property>
+       <property name="checkable">
+        <bool>false</bool>
+       </property>
+       <property name="flat">
+        <bool>false</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="btnRemove">
+       <property name="text">
+        <string>&amp;Remove</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="btnEdit">
+       <property name="text">
+        <string>&amp;Edit...</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="verticalSpacer">
+       <property name="orientation">
+        <enum>Qt::Vertical</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>20</width>
+         <height>40</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+    </layout>
+   </item>
+   <item row="1" column="0">
+    <widget class="QListView" name="listPlayers"/>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>KLineEdit</class>
+   <extends>QLineEdit</extends>
+   <header>klineedit.h</header>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>

contents/ui/edit.ui

+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Edit</class>
+ <widget class="QWidget" name="Edit">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>471</width>
+    <height>459</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <layout class="QFormLayout" name="formLayout">
+     <property name="fieldGrowthPolicy">
+      <enum>QFormLayout::ExpandingFieldsGrow</enum>
+     </property>
+     <item row="3" column="0">
+      <widget class="QLabel" name="lblIcon">
+       <property name="text">
+        <string>&amp;Icon</string>
+       </property>
+       <property name="buddy">
+        <cstring>txtIcon</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="5" column="0">
+      <widget class="QLabel" name="lblPlay">
+       <property name="text">
+        <string>&amp;Play</string>
+       </property>
+       <property name="buddy">
+        <cstring>txtPlay</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="6" column="0">
+      <widget class="QLabel" name="lblPause">
+       <property name="text">
+        <string>P&amp;ause</string>
+       </property>
+       <property name="buddy">
+        <cstring>txtPause</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="7" column="0">
+      <widget class="QLabel" name="lblPlayPause">
+       <property name="text">
+        <string>Pla&amp;y/Pause</string>
+       </property>
+       <property name="buddy">
+        <cstring>txtPlayPause</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="3" column="1">
+      <widget class="KLineEdit" name="txtIcon"/>
+     </item>
+     <item row="7" column="1">
+      <widget class="KLineEdit" name="txtPlayPause"/>
+     </item>
+     <item row="6" column="1">
+      <widget class="KLineEdit" name="txtPause"/>
+     </item>
+     <item row="5" column="1">
+      <widget class="KLineEdit" name="txtPlay"/>
+     </item>
+     <item row="2" column="1">
+      <widget class="KLineEdit" name="txtName"/>
+     </item>
+     <item row="2" column="0">
+      <widget class="QLabel" name="lblName">
+       <property name="text">
+        <string>&amp;Name</string>
+       </property>
+       <property name="buddy">
+        <cstring>txtName</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="8" column="0">
+      <widget class="QLabel" name="lblStop">
+       <property name="text">
+        <string>&amp;Stop</string>
+       </property>
+       <property name="buddy">
+        <cstring>txtStop</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="9" column="0">
+      <widget class="QLabel" name="lblNext">
+       <property name="text">
+        <string>&amp;Next</string>
+       </property>
+       <property name="buddy">
+        <cstring>txtNext</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="10" column="0">
+      <widget class="QLabel" name="lblPrev">
+       <property name="text">
+        <string>P&amp;revious</string>
+       </property>
+       <property name="buddy">
+        <cstring>txtPrev</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="11" column="0">
+      <widget class="QLabel" name="lblForward">
+       <property name="text">
+        <string>Seek &amp;Forward</string>
+       </property>
+       <property name="buddy">
+        <cstring>txtForward</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="8" column="1">
+      <widget class="KLineEdit" name="txtStop"/>
+     </item>
+     <item row="9" column="1">
+      <widget class="KLineEdit" name="txtNext"/>
+     </item>
+     <item row="10" column="1">
+      <widget class="KLineEdit" name="txtPrev"/>
+     </item>
+     <item row="11" column="1">
+      <widget class="KLineEdit" name="txtForward"/>
+     </item>
+     <item row="12" column="0">
+      <widget class="QLabel" name="lblBackward">
+       <property name="text">
+        <string>Seek &amp;Backward</string>
+       </property>
+       <property name="buddy">
+        <cstring>txtBackward</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="13" column="0">
+      <widget class="QLabel" name="lblStatus">
+       <property name="text">
+        <string>Stat&amp;us</string>
+       </property>
+       <property name="buddy">
+        <cstring>txtStatus</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="12" column="1">
+      <widget class="KLineEdit" name="txtBackward"/>
+     </item>
+     <item row="13" column="1">
+      <widget class="KLineEdit" name="txtStatus">
+       <property name="toolTip">
+        <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This command has to print &amp;quot;playing&amp;quot;, &amp;quot;paused&amp;quot; or &amp;quot;stopped&amp;quot;. It will only be used if Play/Pause is not defined and Play and Pause are defined.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+       </property>
+      </widget>
+     </item>
+     <item row="14" column="0">
+      <widget class="QLabel" name="lblQuit">
+       <property name="text">
+        <string>&amp;Quit</string>
+       </property>
+       <property name="buddy">
+        <cstring>txtQuit</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="14" column="1">
+      <widget class="KLineEdit" name="txtQuit"/>
+     </item>
+     <item row="4" column="0" colspan="2">
+      <widget class="QLabel" name="label">
+       <property name="text">
+        <string>Commands</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>KLineEdit</class>
+   <extends>QLineEdit</extends>
+   <header>klineedit.h</header>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>