summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJaromil <jaromil@dyne.org>2011-04-19 14:21:32 (GMT)
committer Jaromil <jaromil@dyne.org>2011-04-19 14:21:32 (GMT)
commitcd55271dc7737d89c1a8a35ab19c0cf165565264 (patch)
tree4581c67e926e863495bed2a5a4bad0f7c4a550b3
parent09a913c6cd8f11302c5362b3d7f968cdb32528fb (diff)
now using avremote in hdsync
-rw-r--r--.gitignore2
-rw-r--r--scripts/S88hdsync10
-rwxr-xr-xscripts/assemble-app-bin.sh2
-rwxr-xr-xscripts/listen-sync.sh2
-rwxr-xr-xscripts/offer-sync.sh5
-rwxr-xr-xscripts/upnp-cmd296
-rwxr-xr-xscripts/upnp.sh2
-rw-r--r--scripts/utils-sync.sh28
-rw-r--r--src/Makefile.am2
-rw-r--r--src/avremote.c310
-rw-r--r--src/avremote.h111
-rw-r--r--src/cmdline.c256
-rw-r--r--src/discover.c93
-rw-r--r--src/discover.h25
-rw-r--r--src/parsers.c83
-rw-r--r--src/parsers.h31
16 files changed, 1050 insertions, 208 deletions
diff --git a/.gitignore b/.gitignore
index 4634a86..2535307 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,9 @@
src/broadcaster
src/netcat
+src/avremote
*~
*.o
+*.log
.deps
Makefile
Makefile.in
diff --git a/scripts/S88hdsync b/scripts/S88hdsync
index eb6b2cb..422791e 100644
--- a/scripts/S88hdsync
+++ b/scripts/S88hdsync
@@ -18,7 +18,7 @@
# wait the boot to complete
if [ "$1" = "stop" ]; then
- upnp.sh stop
+ $AV -p $UPNPPORT stop
rm -f /tmp/hdsync.reply
umount /apps/hdsync
exit 0
@@ -28,6 +28,9 @@ sleep 15
APPROOT=/apps/hdsync
+# kill dmaosd!
+killall dmaosd
+
touch /tmp/hdsync.log
# USBROOT=`mount | awk '/.tmp.mnt/ { print $3}'`
USBROOT=`cat /tmp/appsRoot | grep hdsync | sed 's/hdsync//`
@@ -66,8 +69,9 @@ get_ip
# retrieve application details
get_bins $APPROOT
-# retrieve upnp port
-$UP detect
+
+UPNPPORT="`lsof -a -i4 -sTCP:LISTEN -c DMARender -F n | awk -v FS=':' '/^n/ {print $2}'`"
+export UPNPPORT
# this exports UPNPPORT if a UPNP daemon is found listening
# wait that the usb is mounted
diff --git a/scripts/assemble-app-bin.sh b/scripts/assemble-app-bin.sh
index 2fb12d3..d403304 100755
--- a/scripts/assemble-app-bin.sh
+++ b/scripts/assemble-app-bin.sh
@@ -40,8 +40,8 @@ if [ -z $1 ]; then
cp -v src/netcat $appdir/bin &&
cp -v src/broadcaster $appdir/bin &&
+ cp -v src/avremote $appdir/bin &&
cp -v scripts/*-sync.sh $appdir/bin &&
- cp -v scripts/upnp.sh $appdir/bin &&
cp -v scripts/S88hdsync $appdir/etc/init.d &&
chmod a+x $appdir/etc/init.d/S88hdsync &&
cp -v README $appdir &&
diff --git a/scripts/listen-sync.sh b/scripts/listen-sync.sh
index 34b6745..3c955a9 100755
--- a/scripts/listen-sync.sh
+++ b/scripts/listen-sync.sh
@@ -57,7 +57,7 @@ $NC -c -u -l -p 3336 -e true
# "press play on tape"
-$UP play
+$AV -p $UPNPPORT play
echo "sync playback started on `date +%T`"
diff --git a/scripts/offer-sync.sh b/scripts/offer-sync.sh
index c0f6b2d..f3981e5 100755
--- a/scripts/offer-sync.sh
+++ b/scripts/offer-sync.sh
@@ -62,7 +62,10 @@ sync
# sync start!
$BC $bcast 3336 s
+# wait network latency
+sleep 0.1
+
# "press play on tape"
-$UP play
+$AV -p $UPNPPORT play
echo "sync playback started on `date +%T`"
diff --git a/scripts/upnp-cmd b/scripts/upnp-cmd
new file mode 100755
index 0000000..80dce5e
--- /dev/null
+++ b/scripts/upnp-cmd
@@ -0,0 +1,296 @@
+#!/usr/bin/php-cgi -q
+<?php
+
+# UPnP Command Line Tool for WDTV Live
+# Version: 0.1
+# Author: Zoster
+
+
+#### Command Line Functions ####
+
+if ( $argc < 2 ) {
+ die (_stringHelp());
+}
+
+$actionName = $argv[1];
+
+if ( function_exists('_action' . $actionName)) {
+ switch ($argc) {
+ case 2:
+ call_user_func('_action' . $actionName);
+ break;
+ case 3:
+ call_user_func('_action' . $actionName, $argv[2]);
+ break;
+ case 4:
+ call_user_func('_action' . $actionName, $argv[2], $argv[3]);
+ break;
+ default:
+ call_user_func('_action' . $actionName);
+ break;
+ }
+} else {
+ die (_stringHelp());
+}
+
+function _stringHelp() {
+ $hlp = 'UPnP command line tool for WDTV Live v0.1' . "\n";
+ $hlp .= 'Usage: upnp-cmd action argument' . "\n";
+ $hlp .= '' . "\n";
+ $hlp .= 'Available AVTransport actions:' . "\n";
+ $hlp .= '' . "\n";
+ $hlp .= 'GetCurrentTransportActions' . "\n";
+ $hlp .= 'GetDeviceCapabilities' . "\n";
+ $hlp .= 'GetMediaInfo' . "\n";
+ $hlp .= 'GetPositionInfo' . "\n";
+ $hlp .= 'GetTransportInfo' . "\n";
+ $hlp .= 'GetTransportSettings' . "\n";
+ $hlp .= 'Next' . "\n";
+ $hlp .= 'Pause' . "\n";
+ $hlp .= 'Play' . "\n";
+ $hlp .= 'Previous' . "\n";
+ $hlp .= 'Seek <SeekMode> <SeekTarget> (allowed SeekMode: "X_DLNA_REL_BYTE", "REL_TIME", "TRACK_NR")' . "\n";
+ $hlp .= 'SetAVTransportURI <URI> <URIMetaData> (allowed URI: "http://server/file", "file:///folder/file"' . "\n";
+ $hlp .= 'SetPlayMode <NewPlayMode> (allowed NewPlayMode = "NORMAL", "REPEAT_ONE", "REPEAT_ALL", "RANDOM")' . "\n";
+ $hlp .= 'Stop' . "\n";
+ $hlp .= 'X_DLNA_GetBytePositionInfo' . "\n";
+ $hlp .= '' . "\n";
+ $hlp .= 'Available RenderingControl actions:' . "\n";
+ $hlp .= '' . "\n";
+ $hlp .= 'GetMute' . "\n";
+ $hlp .= 'GetVolume' . "\n";
+ $hlp .= 'SetMute <DesiredMute> (allowed DesiredMute = 0 or 1)' . "\n";
+ $hlp .= 'SetVolume <DesiredVolume> (allowed DesiredVolume = 1 to 100)' . "\n";
+ return $hlp;
+}
+
+
+#### General UPnP Functions ####
+
+#### AVTransport Actions ####
+
+function _actionGetCurrentTransportActions() {
+ $action = 'GetCurrentTransportActions';
+ $args = '<InstanceID>0</InstanceID>' . "\r\n";
+ $result = _sendUPnPCommand($action, $args, 'AVTransport');
+}
+
+function _actionGetDeviceCapabilities() {
+ $action = 'GetDeviceCapabilities';
+ $args = '<InstanceID>0</InstanceID>' . "\r\n";
+ $result = _sendUPnPCommand($action, $args, 'AVTransport');
+}
+
+function _actionGetMediaInfo() {
+ $action = 'GetMediaInfo';
+ $args = '<InstanceID>0</InstanceID>' . "\r\n";
+ $result = _sendUPnPCommand($action, $args, 'AVTransport');
+}
+
+function _actionGetPositionInfo() {
+ $action = 'GetPositionInfo';
+ $args = '<InstanceID>0</InstanceID>' . "\r\n";
+ $result = _sendUPnPCommand($action, $args, 'AVTransport');
+}
+
+function _actionGetTransportInfo() {
+ $action = 'GetTransportInfo';
+ $args = '<InstanceID>0</InstanceID>' . "\r\n";
+ $result = _sendUPnPCommand($action, $args, 'AVTransport');
+}
+
+function _actionGetTransportSettings() {
+ $action = 'GetTransportSettings';
+ $args = '<InstanceID>0</InstanceID>' . "\r\n";
+ $result = _sendUPnPCommand($action, $args, 'AVTransport');
+}
+
+function _actionNext() {
+ $action = 'Next';
+ $args = '<InstanceID>0</InstanceID>' . "\r\n";
+ $result = _sendUPnPCommand($action, $args, 'AVTransport');
+}
+
+function _actionPause() {
+ $action = 'Pause';
+ $args = '<InstanceID>0</InstanceID>' . "\r\n";
+ $result = _sendUPnPCommand($action, $args, 'AVTransport');
+}
+
+function _actionPlay($prmSpeed = 1) {
+ $action = 'Play';
+ $args = '<InstanceID>0</InstanceID>' . "\r\n";
+ $args .= '<Speed>' . $prmSpeed . '</Speed>' . "\r\n";
+ $result = _sendUPnPCommand($action, $args, 'AVTransport');
+}
+
+function _actionPrevious() {
+ $action = 'Previous';
+ $args = '<InstanceID>0</InstanceID>' . "\r\n";
+ $result = _sendUPnPCommand($action, $args, 'AVTransport');
+}
+
+function _actionSeek($prmSeekMode, $prmSeekTarget) {
+ # SeekModes: "X_DLNA_REL_BYTE" = Bytes Integer, "REL_TIME" = hh:mm:ss, "TRACK_NR" = Track Integer
+ $action = 'Seek';
+ $args = '<InstanceID>0</InstanceID>' . "\r\n";
+ $args = '<SeekMode>' . $prmSeekMode . '</SeekMode>' . "\r\n";
+ $args = '<SeekTarget>' . $prmSeekTarget . '</SeekTarget>' . "\r\n";
+ $result = _sendUPnPCommand($action, $args, 'AVTransport');
+}
+
+function _actionSetAVTransportURI($prmURI = '', $prmMetaData = '') {
+ $action = 'SetAVTransportURI';
+ $args = '<InstanceID>0</InstanceID>' . "\r\n";
+ $args .= '<CurrentURI>' . $prmURI . '</CurrentURI>' . "\r\n";
+ #$args .= '<CurrentURIMetaData />'. "\r\n";
+ $args .= '<CurrentURIMetaData>' . htmlentities($prmMetaData) . '</CurrentURIMetaData>' . "\r\n";
+ $result = _sendUPnPCommand($action, $args, 'AVTransport');
+}
+
+function _actionSetPlayMode($prmNewPlayMode) {
+ #allowed NewPlayMode = "NORMAL", "REPEAT_ONE", "REPEAT_ALL", "RANDOM"
+ $action = 'SetPlayMode';
+ $args = '<InstanceID>0</InstanceID>' . "\r\n";
+ $args .= '<NewPlayMode>' . $prmNewPlayMode . '</NewPlayMode>' . "\r\n";
+ $result = _sendUPnPCommand($action, $args, 'AVTransport');
+}
+
+function _actionStop() {
+ $action = 'Stop';
+ $args = '<InstanceID>0</InstanceID>' . "\r\n";
+ $result = _sendUPnPCommand($action, $args, 'AVTransport');
+}
+
+function _actionX_DLNA_GetBytePositionInfo($prmTrackSize) {
+ $action = 'X_DLNA_GetBytePositionInfo';
+ $args = '<InstanceID>0</InstanceID>' . "\r\n";
+ $args = '<TrackSize>' . $prmTrackSize . '</TrackSize>' . "\r\n";
+ $result = _sendUPnPCommand($action, $args, 'AVTransport');
+}
+
+#### END AVTransport Actions ####
+
+
+#### RenderingControl Actions ####
+
+function _actionGetMute($prmChannel = 'Master') {
+ # allowed Channel = "Master", "LF", "RF"
+ $action = 'GetMute';
+ $args = '<InstanceID>0</InstanceID>' . "\r\n";
+ $args .= '<Channel>' . $prmChannel . '</Channel>' . "\r\n";
+ $result = _sendUPnPCommand($action, $args, 'RenderingControl');
+}
+
+function _actionGetVolume($prmChannel = 'Master') {
+ $action = 'GetVolume';
+ $args = '<InstanceID>0</InstanceID>' . "\r\n";
+ $args .= '<Channel>' . $prmChannel . '</Channel>' . "\r\n";
+ $result = _sendUPnPCommand($action, $args, 'RenderingControl');
+}
+
+function _actionSetMute($prmDesiredMute = 1, $prmChannel = 'Master' ) {
+ # allowed DesiredMute = 0 or 1
+ $action = 'SetMute';
+ $args = '<InstanceID>0</InstanceID>' . "\r\n";
+ $args .= '<Channel>' . $prmChannel . '</Channel>' . "\r\n";
+ $args .= '<DesiredMute>' . $prmDesiredMute . '</DesiredMute>' . "\r\n";
+ $result = _sendUPnPCommand($action, $args, 'RenderingControl');
+}
+
+function _actionSetVolume($prmDesiredVolume = 1, $prmChannel = 'Master' ) {
+ # allowed DesiredVolume = 1 to 100
+ $action = 'SetVolume';
+ $args = '<InstanceID>0</InstanceID>' . "\r\n";
+ $args .= '<Channel>' . $prmChannel . '</Channel>' . "\r\n";
+ $args .= '<DesiredVolume>' . $prmDesiredVolume . '</DesiredVolume>' . "\r\n";
+ $result = _sendUPnPCommand($action, $args, 'RenderingControl');
+}
+
+#### END RenderingControl Actions ####
+
+
+function _sendUPnPCommand($prmAction, $prmArguments, $prmService) {
+
+ $wdtvPort = _getPort();
+ $wdtvIP = _getIP();
+
+ $soap ='<?xml version="1.0" encoding="utf-8"?>' . "\r\n";
+ $soap .='<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">' . "\r\n";
+ $soap .=' <s:Body>' . "\r\n";
+ $soap .=' <u:' . $prmAction . ' xmlns:u="urn:schemas-upnp-org:service:' . $prmService . ':1">' . "\r\n";
+ $soap .= $prmArguments;
+ $soap .=' </u:' . $prmAction . '>' . "\r\n";
+ $soap .=' </s:Body>' . "\r\n";
+ $soap .='</s:Envelope>' . "\r\n";
+
+ $hdr ='POST /MediaRenderer_' . $prmService . '/control HTTP/1.0' . "\r\n";
+ $hdr .='SOAPACTION: "urn:schemas-upnp-org:service:' . $prmService . ':1#' . $prmAction . '"' . "\r\n";
+ $hdr .='CONTENT-TYPE: text/xml ; charset="utf-8"' . "\r\n";
+ $hdr .='HOST: 127.0.0.1:' . $wdtvPort . "\r\n";
+ $hdr .='Connection: close' . "\r\n";
+ $hdr .='Content-Length: ' . strlen($soap) . "\r\n";
+ $hdr .='' . "\r\n";
+
+ $msg = $hdr . $soap;
+
+ $sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
+ socket_bind($sock, $wdtvIP);
+ socket_connect($sock, '127.0.0.1', $wdtvPort);
+ socket_write($sock, $msg);
+ $reply = "";
+ do {
+ $recv = "";
+ $recv = socket_read($sock, '1400');
+ if($recv != "") {
+ $reply .= $recv;
+ }
+ } while($recv != "");
+ socket_close($sock);
+ $tmpArr = explode("\r\n\r\n", $reply, 2);
+ $result = _parseUPnPResponse($tmpArr[1]);
+ #print_r($result);
+ _printArray($result);
+}
+
+function _parseUPnPResponse($prmResponseXML) {
+ $doc = new DOMDocument();
+ $doc->preserveWhiteSpace = false;
+ $doc->formatOutput = true;
+ $doc->loadXML($prmResponseXML);
+ $respItems = $doc->getElementsByTagName('Body')->item(0)->childNodes->item(0)->childNodes;
+ $arrResponse = array();
+ foreach ($respItems as $item) {
+ $arrResponse[$item->nodeName] = $item->nodeValue;
+ }
+ return $arrResponse;
+}
+
+function _printArray($prmArray) {
+ foreach($prmArray as $key => $value) {
+ echo '[' . $key . '] => ' . $value . "\n";
+ }
+}
+
+function _getPort() {
+ # get the listening port of DMARender
+ $result = exec('lsof -a -i4 -sTCP:LISTEN -c DMARender -F n');
+ if ( preg_match('/n\*:(\d*)$/', $result, $matches) ) {
+ return (int)$matches[1];
+ } else {
+ return null;
+ }
+} # end function
+
+function _getIP() {
+ # get our LAN IP
+ $result = exec('ipaddr show dev eth0 | grep inet');
+ if ( preg_match('/inet ([^\/]*)\//', $result, $matches) ) {
+ return $matches[1];
+ } else {
+ return null;
+ }
+} # end function
+
+?>
+
diff --git a/scripts/upnp.sh b/scripts/upnp.sh
index 08ea15c..f4317bc 100755
--- a/scripts/upnp.sh
+++ b/scripts/upnp.sh
@@ -32,7 +32,7 @@ send_message() {
fi
action="$1"
message="$2"
- smsg="<?xml version=\"1.0\" encoding=\"utf-8\"?><s:Envelope s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"><s:Body><u:$action xmlns:u=\"urn:schemas-upnp-org:service:AVTransport:1\"><InstanceID>0</InstanceID>$message</s:Body></s:Envelope>"
+ smsg="<?xml version=\"1.0\" encoding=\"utf-8\"?><s:Envelope s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"><s:Body><u:$action xmlns:u=\"urn:schemas-upnp-org:service:AVTransport:1\"><InstanceID>0</InstanceID>$message</u:$action></s:Body></s:Envelope>"
wget -O - -q --post-data="$smsg" \
$HOST:$PORT/MediaRenderer_AVTransport/control \
--header="SOAPACTION: \"urn:schemas-upnp-org:service:AVTransport:1#$action\"" \
diff --git a/scripts/utils-sync.sh b/scripts/utils-sync.sh
index 1cf6424..5c3e1a7 100644
--- a/scripts/utils-sync.sh
+++ b/scripts/utils-sync.sh
@@ -44,17 +44,17 @@ get_bins() {
if [ -z $1 ]; then
NC="../src/netcat"
BC="../src/broadcaster"
- UP="../scripts/upnp.sh"
+ AV="../src/avremote"
else
NC="$APPROOT/bin/netcat"
BC="$APPROOT/bin/broadcaster"
- UP="$APPROOT/bin/upnp.sh"
+ AV="$APPROOT/bin/avremote"
fi
echo "hdsync binaries found:"
echo "$BC"
echo "$NC"
- echo "$UP"
- export BC NC UP
+ echo "$AV"
+ export BC NC AV
}
prepare_play() {
@@ -63,27 +63,15 @@ prepare_play() {
config_tool -c DMA_SCREENSAVER='0'
file=`ls $USBROOT/sync`
- state=`upnp-cmd GetTransportInfo | awk '/^.CurrentTransportState/ { print $3 }'`
- $UP load "$USBROOT/sync/$file"
- while [ "$state" = "NO_MEDIA_PRESENT" ]; do
- state=`upnp-cmd GetTransportInfo | awk '/^.CurrentTransportState/ { print $3 }'`
- done
+ $AV -p $UPNPPORT load "$USBROOT/sync/$file"
sync
- $UP play
- state=`upnp-cmd GetTransportInfo | awk '/^.CurrentTransportState/ { print $3 }'`
- while [ "$state" = "TRANSITIONING" ]; do
- state=`upnp-cmd GetTransportInfo | awk '/^.CurrentTransportState/ { print $3 }'`
- done
+ $AV -p $UPNPPORT play
- sync
+ sleep 0.2
- $UP pause
- state=`upnp-cmd GetTransportInfo | awk '/^.CurrentTransportStatus/ { print $3 }'`
- while [ "$state" = "PREBUFFING" ]; do
- state=`upnp-cmd GetTransportInfo | awk '/^.CurrentTransportStatus/ { print $3 }'`
- done
+ $AV -p $UPNPPORT pause
sync
diff --git a/src/Makefile.am b/src/Makefile.am
index e0d8cb9..2dab300 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -11,7 +11,7 @@ netcat_SOURCES = \
broadcaster_SOURCES = broadcaster.cpp
-avremote_SOURCES = avremote.c
+avremote_SOURCES = avremote.c avremote.h cmdline.c parsers.c parsers.h
netcat_nc = $(DESTDIR)$(bindir)/nc
diff --git a/src/avremote.c b/src/avremote.c
index 69a5c1e..808df8d 100644
--- a/src/avremote.c
+++ b/src/avremote.c
@@ -1,72 +1,76 @@
-/* avremote 0.1
- *
- * (c) 2011 Nederlands Instituut voor Mediakunst (NIMk)
- * 2011 Denis Roio <jaromil@dyne.org>
- *
- * This source code is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Public License as published
- * by the Free Software Foundation; either version 3 of the License,
- * or (at your option) any later version.
- *
- * This source code 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.
- * Please refer to the GNU Public License for more details.
- *
- * You should have received a copy of the GNU Public License along with
- * this source code; if not, write to:
- * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- *
- */
+/* AVRemote
+
+ (c) 2011 Nederlands Instituut voor Mediakunst (NIMk)
+ 2011 Denis Roio <jaromil@nimk.nl>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>
+
+
+
+
+
+ This code is written in C and is reentrant.
+ Our main goals are simplicity and speed.
+
+*/
+
+#define _GNU_SOURCE 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
+#include <sys/stat.h>
#include <sys/types.h>
+#include <sys/time.h>
#include <sys/socket.h>
+#include <sys/select.h>
+#include <sys/param.h>
+#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
-
-#include <errno.h>
-
-#include <libgen.h>
-// our exit codes are shell style: 1 is error, 0 is success
-#define ERR 1
+#include <poll.h>
-// uncomment to debug
-#define DEBUG 1
-
-char action [128];
-char message[1024];
-
-typedef struct {
- char *hostname;
- int port;
- int sockfd;
+#include <libgen.h>
+
+#include <errno.h>
- char *msg;
- char *hdr;
- char *res;
+#include <avremote.h>
- size_t size;
-} upnp_t;
+/* Buffer Boundaries
+ the following defines set the maximum size we allow for buffers used */
+#define MAX_HOSTNAME_SIZE 256
+#define MAX_MSG_SIZE 2048
+#define MAX_HDR_SIZE 512
+#define MAX_RES_SIZE 1401
+#define MAX_META_SIZE 2048
-upnp_t upnp = { NULL, -1, -1, NULL, NULL, 0 };
upnp_t *create_upnp() {
upnp_t *upnp;
upnp = calloc(1,sizeof(upnp_t));
- upnp->hostname = calloc(256,sizeof(char));
+ upnp->hostname = calloc(MAX_HOSTNAME_SIZE,sizeof(char));
upnp->port = -1;
upnp->sockfd = -1;
- upnp->msg = (char*) calloc(1024,sizeof(char));
- upnp->hdr = (char*) calloc(512,sizeof(char));
- upnp->res = (char*) calloc(1401,sizeof(char));
-
- upnp->size = -1;
+ upnp->msg = (char*) calloc(MAX_MSG_SIZE,sizeof(char));
+ upnp->msglen = 0;
+ upnp->hdr = (char*) calloc(MAX_HDR_SIZE,sizeof(char));
+ upnp->hdrlen = 0;
+ upnp->res = (char*) calloc(MAX_RES_SIZE,sizeof(char));
+ upnp->meta = (char*) calloc(MAX_META_SIZE,sizeof(char));
return(upnp);
}
@@ -81,26 +85,15 @@ void free_upnp(upnp_t *upnp) {
if(upnp->msg) free(upnp->msg);
if(upnp->hdr) free(upnp->hdr);
if(upnp->res) free(upnp->res);
+ if(upnp->meta) free(upnp->meta);
free(upnp);
}
-int check_upnp(upnp_t *upnp, const char *caller) {
- int res = 1;
- if(!upnp) {
- fprintf(stderr,"error: upnp object is NULL (%s)",caller);
- res = 0;
- }
- if(!upnp) {
- fprintf(stderr,"error: upnp is not connected (%s)",caller);
- res = 0;
- }
- return(res);
-}
-
int connect_upnp(upnp_t *upnp, char *hostname, int port) {
struct sockaddr_in serveraddr;
- struct hostent *server;
+ // const struct sockaddr *serveraddr;
+ struct hostent *host;
int sockfd;
if( upnp->sockfd > 0 ) {
@@ -111,27 +104,27 @@ int connect_upnp(upnp_t *upnp, char *hostname, int port) {
/* socket: create the socket */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
- error("error: can't open socket (%s)\n", strerror(errno));
+ fprintf(stderr,"error: can't open socket (%s)\n", strerror(errno));
return(-1);
}
/* gethostbyname: get the server's DNS entry */
- server = gethostbyname(hostname);
- if (server == NULL) {
- error("error: no such host as %s (%s)\n", hostname, strerror(errno));
+ host = gethostbyname(hostname);
+ if (host == NULL) {
+ fprintf(stderr,"error: no such host as %s (%s)\n", hostname, strerror(errno));
return(-1);
}
/* build the server's Internet address */
bzero((char *) &serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
- bcopy((char *)server->h_addr,
- (char *)&serveraddr.sin_addr.s_addr, server->h_length);
+ bcopy((char *)host->h_addr,
+ (char *)&serveraddr.sin_addr.s_addr, host->h_length);
serveraddr.sin_port = htons(port);
/* connect: create a connection with the server */
- if (connect(sockfd, &serveraddr, sizeof(serveraddr)) < 0) {
- error("error: can't connect (%s)\n",strerror(errno));
+ if (connect(sockfd, (const struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0) {
+ fprintf(stderr,"error: can't connect (%s)\n",strerror(errno));
return(-1);
}
@@ -142,135 +135,92 @@ int connect_upnp(upnp_t *upnp, char *hostname, int port) {
return(sockfd);
}
-void render_file_meta() {
- /*
- "<DIDL-Lite xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite\""
- "xmlns:dc=\"http://purl.org/dc/elements/1.1/\""
- "xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\">"
- "<item id=\"2$file\" parentID=\"2$parentDir\" restricted=\"0\">"
- "<dc:title>$fileName</dc:title><dc:date></dc:date><upnp:class>object.item.imageItem</upnp:class><dc:creator></dc:creator><upnp:genre></upnp:genre><upnp:artist></upnp:artist><upnp:album></upnp:album><res protocolInfo=\"file-get:*:*:*:DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=00000000001000000000000000000000\" protection=\"\" tokenType=\"0\" bitrate=\"0\" duration=\"\" size=\"$fileSize\" colorDepth=\"0\" ifoFileURI=\"\" resolution=\"\">$uri</res></item></DIDL-Lite>"
- */
+void render_uri_meta(upnp_t *upnp, char *path) {
+ char dir[1024], *pdir;
+ char file[1024], *pfile;
+ char url[1024];
+ size_t filesize;
+
+ // TODO: streams
+
+ struct stat fs;
+ if( stat(path,&fs) < 0 ) {
+ fprintf(stderr,"error: cannot load file %s (%s)\n", path, strerror(errno));
+ filesize = 0;
+ } else
+ filesize = fs.st_size;
+
+ strncpy(dir,path,1023);
+ pdir = dirname(dir);
+ strncpy(file,path,1023);
+ pfile = basename(file);
+ snprintf(url,1023,"file://%s",path);
+
+
+ snprintf(upnp->meta,MAX_META_SIZE-1,UPNP_META_FORMAT, url,
+ pfile, pdir, pfile, filesize, url);
+
+
}
void render_upnp(upnp_t *upnp, char *action, char *arg) {
// blank message first
- snprintf(upnp->msg,1023,"<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"
- "<s:Envelope s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" "
- "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">\r\n<s:Body>\r\n"
- "<u:%s xmlns:u=\"urn:schemas-upnp-org:service:AVTransport:1\">\r\n"
- "<InstanceID>0</InstanceID>\r\n%s\r\n</u:%s>\r\n</s:Body>\r\n</s:Envelope>\r\n",
+ snprintf(upnp->msg, MAX_MSG_SIZE-1,UPNP_MSG_FORMAT,
action, arg, action);
- upnp->size = strlen(upnp->msg);
+ upnp->msglen = strlen(upnp->msg);
+
+ snprintf(upnp->hdr, MAX_HDR_SIZE-1, UPNP_HDR_FORMAT,
+ action, upnp->hostname, upnp->port, upnp->msglen);
+
+ upnp->hdrlen = strlen(upnp->hdr);
- snprintf(upnp->hdr,1023,"POST /MediaRenderer_AVTransport/control HTTP/1.0\r\n"
- "SOAPACTION: \"urn:schemas-upnp-org:service:AVTransport:1#%s\"\r\n"
- "CONTENT-TYPE: text/xml ; charset=\"utf-8\"\r\n"
- "HOST: %s:%u\r\n"
- "Connection: close\r\n"
- "Content-Length: %u\r\n"
- "\r\n", action, upnp->hostname, upnp->port, upnp->size);
}
int send_upnp(upnp_t *upnp) {
int res;
- int hdrlen = strlen(upnp->hdr);
- res = write(upnp->sockfd,upnp->hdr,hdrlen);
- if(res != hdrlen)
- fprintf(stderr,"send upnp header wrote only %u of %u bytes",res, hdrlen);
+ res = write(upnp->sockfd,upnp->hdr,upnp->hdrlen);
+ if(res != upnp->hdrlen)
+ fprintf(stderr,"send upnp header wrote only %u of %u bytes",res, upnp->hdrlen);
// TODO: check success
- res = write(upnp->sockfd,upnp->msg,upnp->size);
- if(res != upnp->size)
- fprintf(stderr,"send upnp message wrote only %u of %u bytes",res, upnp->size);
-
-#ifdef DEBUG
- fprintf(stderr,"sent %u bytes header, %u bytes message\n",hdrlen, res);
- fprintf(stderr,"header:\n\n%s\n\n",upnp->hdr);
- fprintf(stderr,"message:\n\n%s\n\n",upnp->msg);
-#endif
+ res = write(upnp->sockfd,upnp->msg,upnp->msglen);
+ if(res != upnp->msglen)
+ fprintf(stderr,"send upnp message wrote only %u of %u bytes",res, upnp->msglen);
return(1);
}
-int recv_upnp(upnp_t *upnp) {
+int recv_upnp(upnp_t *upnp, int timeout) {
int res;
- res = read(upnp->sockfd, upnp->res,1400);
-#ifdef DEBUG
- fprintf(stderr,"response:\n\n%s\n",upnp->res);
-#endif
- return(1);
-}
-
-int load(upnp_t *upnp, char *file) {
- char meta[1024];
- if(!check_upnp(upnp, __PRETTY_FUNCTION__)) return(0);
- // render_file_meta(file, meta);
- // TODO
-}
-
-int play(upnp_t *upnp) {
- if(!check_upnp(upnp, __PRETTY_FUNCTION__)) return(0);
- render_upnp(upnp,"Play","<Speed>1</Speed>");
- send_upnp(upnp);
- return(1);
-}
+ struct pollfd fds[1]; /* for the poll */
+ fd_set socketSet;
+ struct timeval timeval;
+
+ fds[0].fd = upnp->sockfd;
+ fds[0].events = POLLRDHUP; // needs _GNU_SOURCE
+ res = poll(fds, 1, timeout);
+ if (res < 0) {
+ fprintf(stderr,"error polling reply: %s\n",strerror(errno));
+ return(0);
+ } else if (!res) return(0);
+
+ FD_ZERO(&socketSet);
+ FD_SET(upnp->sockfd, &socketSet);
+ timeval.tv_sec = timeout / 1000;
+ timeval.tv_usec = (timeout % 1000) * 1000;
+ res = select(FD_SETSIZE, &socketSet, NULL, NULL, &timeval);
+ if (res < 0) {
+ fprintf(stderr,"error on socket select: %s\n",strerror(errno));
+ return(0);
+ } else if (!res) return(0);
+
-int stop(upnp_t *upnp) {
- if(!check_upnp(upnp, __PRETTY_FUNCTION__)) return(0);
- render_upnp(upnp,"Stop","");
- send_upnp(upnp);
- return(1);
+ res = recv(upnp->sockfd, upnp->res, MAX_RES_SIZE-1, 0);
+ return(res);
}
-int get_trans_info(upnp_t *upnp) {
- if(!check_upnp(upnp, __PRETTY_FUNCTION__)) return(0);
- render_upnp(upnp,"GetTransportInfo","");
- send_upnp(upnp);
- recv_upnp(upnp);
- return(1);
+int print_upnp(upnp_t *upnp) {
+ fprintf(stderr,"header (%u bytes):\n\n%s\n\n",upnp->hdrlen, upnp->hdr);
+ fprintf(stderr,"message (%u bytes):\n\n%s\n\n",upnp->msglen, upnp->msg);
}
-int main(int argc, char **argv) {
- int sock, port, n;
- char hostname[512];
- char command[128];
-
- /* check command line arguments */
- if (argc < 4) {
- fprintf(stderr,"usage: %s <hostname> <port> <command> [filename]\n", argv[0]);
- exit(ERR);
- }
- snprintf(hostname,511,"%s",argv[1]);
- port = atoi(argv[2]);
-
- snprintf(command,127,"%s",argv[3]);
-
- upnp_t *upnp;
- upnp = create_upnp();
-
- if ( connect_upnp(upnp, hostname, port) < 0 ) {
- fprintf(stderr,"error: connection failed\n");
- exit(ERR);
- }
-
- fprintf(stderr,"socket: %u\n",upnp->sockfd);
-
- switch(command[0]) {
- case 'p': // play
- play(upnp);
- break;
- case 's':
- stop(upnp);
- break;
- case 'g':
- get_trans_info(upnp);
- break;
- default:
- fprintf(stderr,"error: command not understood.\n");
- break;
- }
-
- free_upnp(upnp);
-
- exit(0);
-}
diff --git a/src/avremote.h b/src/avremote.h
new file mode 100644
index 0000000..e1e80e6
--- /dev/null
+++ b/src/avremote.h
@@ -0,0 +1,111 @@
+/* AVRemote
+
+ (c) 2011 Nederlands Instituut voor Mediakunst (NIMk)
+ 2011 Denis Roio <jaromil@nimk.nl>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>
+
+*/
+
+#ifndef __AVREMOTE_H__
+#define __AVREMOTE_H__
+
+// messages get rendered in this structure
+// allocated and freed with create/free_upnp
+typedef struct {
+ char *hostname;
+ int port;
+ int sockfd;
+
+ char *hdr;
+ size_t hdrlen;
+
+ char *msg;
+ size_t msglen;
+
+ char *res;
+ size_t reslen;
+
+ char *meta;
+
+} upnp_t;
+
+// format (printf style) to be used when generating xml markup
+#define UPNP_MSG_FORMAT "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" \
+ "<s:Envelope s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"" \
+ "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">\r\n<s:Body>\r\n" \
+ "<u:%s xmlns:u=\"urn:schemas-upnp-org:service:AVTransport:1\">\r\n" \
+ "<InstanceID>0</InstanceID>\r\n%s\r\n</u:%s>\r\n</s:Body>\r\n</s:Envelope>\r\n"
+
+#define UPNP_HDR_FORMAT "POST /MediaRenderer_AVTransport/control HTTP/1.0\r\n" \
+ "SOAPACTION: \"urn:schemas-upnp-org:service:AVTransport:1#%s\"\r\n" \
+ "CONTENT-TYPE: text/xml ; charset=\"utf-8\"\r\n" \
+ "HOST: %s:%u\r\n" \
+ "Connection: close\r\n" \
+ "Content-Length: %u\r\n" \
+ "\r\n"
+
+#define UPNP_META_FORMAT "<CurrentURI>%s</CurrentURI>" \
+ "<CurrentURIMetaData><DIDL-Lite xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite\"" \
+ "xmlns:dc=\"http://purl.org/dc/elements/1.1/\"" \
+ "xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\">" \
+ "<item id=\"2%s\" parentID=\"2%s\" restricted=\"0\">" \
+ "<dc:title>%s</dc:title><dc:date></dc:date>" \
+ "<upnp:class>object.item.imageItem</upnp:class><dc:creator></dc:creator>" \
+ "<upnp:genre></upnp:genre><upnp:artist></upnp:artist><upnp:album></upnp:album>" \
+ "<res protocolInfo=\"file-get:*:*:*:DLNA.ORG_OP=01;DLNA.ORG_CI=0;" \
+ "DLNA.ORG_FLAGS=00000000001000000000000000000000\" protection=\"\" tokenType=\"0\" " \
+ "bitrate=\"0\" duration=\"\" size=\"%u\" colorDepth=\"0\" ifoFileURI=\"\" " \
+ "resolution=\"\">%s</res></item></DIDL-Lite></CurrentURIMetaData>"
+
+upnp_t *create_upnp();
+void free_upnp(upnp_t *upnp);
+
+int connect_upnp(upnp_t *upnp, char *hostname, int port);
+
+/*
+Available AVTransport actions:
+
+GetCurrentTransportActions
+GetDeviceCapabilities
+GetMediaInfo
+GetPositionInfo
+GetTransportInfo
+GetTransportSettings
+Next
+Pause
+Play
+Previous
+Seek <SeekMode> <SeekTarget> (allowed SeekMode: "X_DLNA_REL_BYTE", "REL_TIME", "TRACK_NR")
+SetAVTransportURI <URI> <URIMetaData> (allowed URI: "http://server/file", "file:///folder/file"
+SetPlayMode <NewPlayMode> (allowed NewPlayMode = "NORMAL", "REPEAT_ONE", "REPEAT_ALL", "RANDOM")
+Stop
+X_DLNA_GetBytePositionInfo
+
+Available RenderingControl actions:
+
+GetMute
+GetVolume
+SetMute <DesiredMute> (allowed DesiredMute = 0 or 1)
+SetVolume <DesiredVolume> (allowed DesiredVolume = 1 to 100)
+*/
+
+void render_upnp(upnp_t *upnp, char *action, char *arg);
+void render_uri_meta(upnp_t *upnp, char *path);
+
+int send_upnp(upnp_t *upnp);
+int recv_upnp(upnp_t *upnp, int timeout);
+int print_upnp(upnp_t *upnp);
+
+#endif
diff --git a/src/cmdline.c b/src/cmdline.c
new file mode 100644
index 0000000..73c40ef
--- /dev/null
+++ b/src/cmdline.c
@@ -0,0 +1,256 @@
+/* AVRemote
+
+ (c) 2011 Nederlands Instituut voor Mediakunst (NIMk)
+ 2011 Denis Roio <jaromil@nimk.nl>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>
+
+*/
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#include <errno.h>
+
+#include <avremote.h>
+#include <discover.h>
+#include <parsers.h>
+
+// our exit codes are shell style: 1 is error, 0 is success
+#define ERR 1
+
+// uncomment to debug
+#define DEBUG 1
+
+char filename[512];
+char command[64];
+char server[512];
+int port = 0;
+int dry_run = 0;
+int discover = 0;
+
+parser_f *parser = NULL;
+
+// we use only getopt, no _long
+static const char *short_options = "-hvs:p:t";
+
+void cmdline(int argc, char **argv) {
+ command[0] = 0;
+ filename[0] = 0;
+ server[0] = 0;
+
+ int res, optlen;
+ do {
+ res = getopt(argc, argv, short_options);
+ switch(res) {
+ case 'h':
+ fprintf(stderr,
+ "%s %s - send AVTransport commands to UPNP media services\n"
+ "\n"
+ " Copyright (C) 2011 Jaromil @ NIMk.nl Artlab , License GNU AGPL v3+\n"
+ " This is free software: you are free to change and redistribute it.\n"
+ " The latest AVTransport sourcecode is published on <%s>\n"
+ "\n"
+ "Syntax: avremote [options] command [file]\n"
+ "\n"
+ "Commands:\n"
+ "\n"
+#ifdef USE_UPNP
+ " discover search for upnp devices on the network\n"
+#endif
+ " load load a file and prepare it for playback\n"
+ " play start playing the selected file\n"
+ " pause pause currently running playback\n"
+ " stop stop playback and return to menu\n"
+ " get get the current status of the device\n"
+ "\n"
+ "Options:\n"
+ "\n"
+ " -s network address or hostname of the media server\n"
+ " -p port on which the UPNP AVTransport daemon is listening\n"
+ " -t dry run to test without a server (print out rendered xml)\n"
+ "\n"
+ " -h print this help\n"
+ " -v version information for this tool\n"
+ "\n"
+ "For more informations on AVRemote read the manual: man avremote\n"
+ "Please report bugs on <http://bugs.dyne.org>.\n",
+ PACKAGE, VERSION, PACKAGE_URL);
+ exit(0);
+
+ case 'v':
+ fprintf(stderr,"%s - simple commandline tool to send AVTransport commands over UPNP\n"
+ "version %s (Apr/2011) by Jaromil - Netherlands Media Art Institute\n"
+ "Copyright (C) 2011 NIMk Artlab, License GNU AGPL v3+\n"
+ "This is free software: you are free to change and redistribute it\n",
+ PACKAGE, VERSION);
+ exit(0);
+
+ case 's':
+ snprintf(server,511,"%s",optarg);
+ break;
+
+ case 'p':
+ sscanf (optarg, "%u", &port);
+ break;
+
+ case 't':
+ // test dry run
+ dry_run = 1;
+ fprintf(stderr,"dry run: printint out rendered upnp message without establishing connection\n");
+ break;
+
+ case '?':
+ fprintf(stderr,"unrecognized option: %s\n",optarg);
+ break;
+
+ case 1:
+ if(!command[0]) {
+ snprintf(command,63,"%s",optarg);
+ } else
+ snprintf(filename,511,"%s",optarg);
+ break;
+ default:
+ break;
+ }
+
+ } while(res != -1);
+
+ if(command[0] == 'd') { // discover
+ discover = 1;
+ } else if(!dry_run) {
+ // check requires args
+ if(!command[0]) {
+ fprintf(stderr,"command not specified, see %s -h for help\n",argv[0]);
+ exit(1);
+ }
+
+
+ // not in dry run nor discovery, check for necessary options
+ if(!port) {
+ fprintf(stderr,"port not specified, use -p\n");
+ exit(1);
+ }
+
+ if(!server[0]) {
+ fprintf(stderr,"server not specified, using localhost\n");
+ sprintf(server,"%s","localhost");
+ }
+ }
+}
+
+
+int main(int argc, char **argv) {
+ upnp_t *upnp;
+
+ cmdline(argc, argv);
+
+
+#ifdef USE_UPNP
+ if (discover)
+ {
+ fprintf(stderr,"Performing upnp autodiscovery...\n");
+ upnp_discover();
+ exit(0);
+ }
+#endif
+
+ upnp = create_upnp();
+
+ if(!dry_run)
+ {
+
+ if ( connect_upnp (upnp, server, port) < 0 )
+ {
+ fprintf(stderr,"can't connect to %s:%u: operation aborted.\n", server, port);
+ exit(ERR);
+ }
+
+ }
+ else
+ {
+
+ sprintf(upnp->hostname,"%s","dry run");
+ upnp->port = 0;
+
+ }
+
+ // command parsing is a cascade switch on single letters
+ // this is supposedly faster than strcmp
+ switch(command[0]) {
+
+ case 'l': // load url
+ render_uri_meta(upnp,filename);
+ render_upnp(upnp,"SetAVTransportURI", upnp->meta);
+ send_upnp(upnp);
+ break;
+
+ case 'p':
+
+ if(command[1]=='l')
+ { // 'pl*' is play
+
+ render_upnp(upnp,"Play","<Speed>1</Speed>");
+
+ }
+ else if (command[1]=='a')
+ { // 'pa*' is pause
+
+ render_upnp(upnp,"Pause","");
+
+ }
+ break;
+
+ case 's': // stop
+ render_upnp(upnp,"Stop","");
+ break;
+
+ case 'g': // dump a parsable full state of the device
+ render_upnp(upnp,"GetTransportInfo","");
+ parser = GetTransportInfo;
+
+ break;
+
+ default:
+ fprintf(stderr,"warning: command not recognized, sending anyway.\n");
+ render_upnp(upnp,command,"");
+
+ // free_upnp(upnp);
+ // exit(1);
+ }
+
+ if (dry_run)
+ print_upnp(upnp);
+ else
+ {
+ send_upnp(upnp);
+ recv_upnp(upnp, 1000);
+ if (parser)
+ (*parser)(upnp->res);
+ else
+ fprintf(stderr,"%s\n",upnp->res);
+ }
+
+ free_upnp(upnp);
+
+ exit(0);
+}
diff --git a/src/discover.c b/src/discover.c
new file mode 100644
index 0000000..2da2ffa
--- /dev/null
+++ b/src/discover.c
@@ -0,0 +1,93 @@
+/* AVRemote
+
+ (c) 2011 Nederlands Instituut voor Mediakunst (NIMk)
+ 2011 Denis Roio <jaromil@nimk.nl>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>
+
+*/
+
+#include <config.h>
+
+#include <stdio.h>
+#include <string.h>
+
+#ifdef USE_UPNP
+
+#include <miniwget.h>
+#include <miniupnpc.h>
+#include <upnpcommands.h>
+#include <upnperrors.h>
+
+
+int upnp_discover()
+{
+ const char * rootdescurl = 0;
+ const char * multicastif = 0;
+ const char * minissdpdpath = 0;
+ char lanaddr[64];
+ struct UPNPDev *devlist = 0;
+ struct UPNPDev *dev;
+ struct UPNPUrls urls;
+ struct IGDdatas data;
+ int r;
+
+ devlist = upnpDiscover(1000, multicastif, minissdpdpath, 0);
+
+ r = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr));
+ if (!r) {
+ fprintf(stderr,"no valid UPnP devices found\n");
+
+ } else if (r == 3) { // 3 = an UPnP root device has been found (not an IGD)
+
+ dev = devlist;
+ while(dev) {
+
+ // parse out ip and port from url
+ char ip[256];
+ char port[64];
+ char tmp[512];
+ char *p, *pp;
+
+ memcpy(tmp,dev->descURL,511);
+ p = tmp;
+
+ // ip
+ do p+=2; while(*p != '/'); p++;
+ pp = p; do pp++; while(*pp != ':'); *pp = 0;
+ snprintf(ip,255,"%s",p);
+
+ // port
+ p = pp+1; pp = p;
+ do pp++; while(*pp != '/'); *pp = 0;
+ snprintf(port,63,"%s",p);
+
+ fprintf(stderr,"%s\t%s\t%s\t%s\n", dev->st, dev->descURL, ip, port);
+ dev = dev->pNext;
+ }
+
+ /* fprintf(stderr,
+ " controlURL: %s\n"
+ " ipcondescURL: %s\n"
+ " controlURL_CIF: %s\n",
+ urls.controlURL, urls.ipcondescURL, urls.controlURL_CIF); */
+
+ FreeUPNPUrls(&urls);
+ }
+ freeUPNPDevlist(devlist); devlist = 0;
+
+ return(r);
+}
+
+#endif
diff --git a/src/discover.h b/src/discover.h
new file mode 100644
index 0000000..40bd95d
--- /dev/null
+++ b/src/discover.h
@@ -0,0 +1,25 @@
+/* AVRemote
+
+ (c) 2011 Nederlands Instituut voor Mediakunst (NIMk)
+ 2011 Denis Roio <jaromil@nimk.nl>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>
+
+*/
+
+#ifdef USE_UPNP
+
+int upnp_discover();
+
+#endif
diff --git a/src/parsers.c b/src/parsers.c
new file mode 100644
index 0000000..f37f0b0
--- /dev/null
+++ b/src/parsers.c
@@ -0,0 +1,83 @@
+/* AVRemote
+
+ (c) 2011 Nederlands Instituut voor Mediakunst (NIMk)
+ 2011 Denis Roio <jaromil@nimk.nl>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>
+
+*/
+
+#include <stdio.h>
+#include <string.h>
+
+
+#define MAX 256 // max XML string size
+
+// returns a pointer to the current position in haystack
+// for subsequent calls
+char *extract_xml(char *dest, char *haystack, const char *needle) {
+ char *p, *pp;
+
+ p = strstr(haystack, needle);
+ if (!p) {
+ fprintf(stderr, "parser cannot find \"%s\" in:\n%s\n",needle, haystack);
+ return NULL;
+ }
+ // as you can see, i had a lot of fun with pointers when i was a kid
+ pp = p; do pp+=1; while (*pp != '>'); pp++;
+ p = pp; do p+=1; while (*p != '<'); *p = 0;
+
+ snprintf(dest,MAX-1,"%s",pp);
+
+ return(p+1);
+
+}
+
+void GetCurrentTransportActions(char *res) {
+ char actions[MAX];
+ fprintf(stderr,"#\tactions\n");
+ extract_xml(actions, res, "Actions");
+ fprintf(stderr,"Act: %s\n");
+}
+
+void GetDeviceCapabilities(char *res) {
+ char play[MAX];
+ char rec[MAX];
+ char qual[MAX];
+ char *p;
+
+ fprintf(stderr,"#\tPlay\tRec\tQuality\n");
+ p = extract_xml(play, res, "PlayMedia");
+ p = extract_xml(rec, p, "RecMedia");
+ p = extract_xml(qual, p, "RecQualityModes");
+
+ fprintf(stderr,"Caps:\t%s\t%s\t%s\n",play,rec,qual);
+}
+
+void GetTransportInfo(char *res) {
+ char state[MAX];
+ char status[MAX];
+ char speed[MAX];
+ char *p;
+ fprintf(stderr,"#\tstate\tstatus\tspeed\n");
+
+ p = extract_xml(state, res, "CurrentTransportState");
+
+ p = extract_xml(status, p, "CurrentTransportStatus");
+
+ p = extract_xml(speed, p, "CurrentSpeed");
+
+ fprintf(stderr,"TInfo:\t%s\t%s\t%s\n", state, status, speed);
+}
+
diff --git a/src/parsers.h b/src/parsers.h
new file mode 100644
index 0000000..4983c59
--- /dev/null
+++ b/src/parsers.h
@@ -0,0 +1,31 @@
+/* AVRemote
+
+ (c) 2011 Nederlands Instituut voor Mediakunst (NIMk)
+ 2011 Denis Roio <jaromil@nimk.nl>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>
+
+*/
+
+#ifndef __PARSERS_H__
+#define __PARSERS_H__
+
+// function pointers to various parsers
+typedef void (parser_f)(char *res);
+
+// parsers implemented
+void GetTransportInfo(char *res);
+
+#endif
+