--- a
+++ b/src/rdcvolume.cpp
@@ -0,0 +1,226 @@
+// This libupnpp sample control program connects to a media renderer,
+// designated by its friendly name or uuid, and lets you adjust the
+// volume from the keyboard. It uses the MediaRenderer and
+// RenderingControl device and service classes from libupnpp.
+
+#include <termios.h>
+
+#include <string>
+#include <iostream>
+#include <mutex>
+#include <condition_variable>
+
+#include "libupnpp/upnpplib.hxx"
+#include "libupnpp/control/discovery.hxx"
+#include "libupnpp/control/mediarenderer.hxx"
+
+using namespace std;
+using namespace UPnPClient;
+using namespace UPnPP;
+
+// Event reporter. We are not really using it here, apart from printing
+// stuff to the console.
+class MyReporter : public VarEventReporter {
+public:
+    MyReporter(const string& nm, RDCH rdc)
+        : m_nm(nm), m_srv(rdc)
+    {
+        m_srv->installReporter(this);
+    }
+    virtual ~MyReporter() {
+        m_srv->installReporter(0);
+    }
+        
+    virtual void changed(const char *nm, int value)
+    {
+        cerr << m_nm << " : Changed: " << nm << " (int): " << value << endl;
+    }
+
+    virtual void changed(const char *nm, const char *value)
+    {
+        cerr << m_nm << " : Changed: " << nm << " (str): " << value << endl;
+    }
+
+private:
+    string m_nm;
+    RDCH m_srv;
+};
+
+
+//
+// Device discovery part. We can't just connect to the device, UPnP
+// does not work like this. The lib is going to broadcast a request
+// for devices to signal their presence, withing a fixed time window
+// (a few seconds). We could just wait for the full window and then
+// connect, but here, we are doing the fancy thing, setting up a
+// callback which will be called as each new device manifests itself,
+// so that we can connect asap. The callback is called from a
+// different thread, so we need locking.
+std::mutex discolock;
+std::condition_variable discocv;
+
+// Using shared variables, but the callback is an std::function, so
+// there are other possibilities.
+UPnPDeviceDesc o_devicedesc;
+string o_name;
+
+static bool discoCB(const UPnPDeviceDesc& device, const UPnPServiceDesc&)
+{
+    std::unique_lock<std::mutex> lock(discolock);
+    //cerr << "discoCB: got " << device.friendlyName << endl;
+    if (!device.UDN.compare(o_name) || !device.friendlyName.compare(o_name)) {
+        //cerr << "discoCB: FOUND\n";
+        o_devicedesc = device;
+        discocv.notify_all();
+    }
+    return true;
+}
+
+MRDH getRenderer(const string& name)
+{
+    // Add a discovery callback, and remember about it in case we're
+    // called several times (not the case in this program).
+    o_name = name;
+    static int cbindex = -1;
+    if (cbindex == -1) {
+        cbindex = UPnPDeviceDirectory::addCallback(discoCB);
+    }
+
+    // Initialize and get a discovery directory handle. This must be
+    // done *after* the callback is set up, else we may miss devices.
+    static UPnPDeviceDirectory *superdir;
+    if (superdir == 0) {
+        superdir = UPnPDeviceDirectory::getTheDir();
+        if (superdir == 0) {
+            cerr << "Discovery init failed\n";
+            return MRDH();
+        }
+    }
+
+    // Until the initial delay is through, use the reporter to test
+    // devices as they come, so that we may respond asap
+    for (;;) {
+        std::unique_lock<std::mutex> lock(discolock);
+#if FUTURE
+        // Older versions of the lib don't have this.
+        int ms = superdir->getRemainingDelayMs();
+#else
+        int ms = superdir->getRemainingDelay() * 1000;
+#endif
+        if (ms > 0) {
+            discocv.wait_for(lock, std::chrono::milliseconds(ms));
+            if (!o_devicedesc.UDN.compare(name) ||
+                !o_devicedesc.friendlyName.compare(name)) {
+                //cerr << "getRenderer: early wakeup\n";
+                return MRDH(new MediaRenderer(o_devicedesc));
+            }
+        } else {
+            // Initial delay done. We'll try one last time to ask the
+            // directory about our device
+            break;
+        }
+    }
+
+    // Try one last time just in case.
+    if (superdir->getDevByUDN(name, o_devicedesc)) {
+        return MRDH(new MediaRenderer(o_devicedesc));
+    } else if (superdir->getDevByFName(name, o_devicedesc)) {
+        return MRDH(new MediaRenderer(o_devicedesc));
+    }
+
+    cerr << "Can't connect to " << name << endl;
+    return MRDH();
+}
+
+// nothing to see here: character reading, one at a time.
+int mygetch()
+{
+    struct termios oldt, newt;
+    int ch;
+    tcgetattr(STDIN_FILENO, &oldt);
+    newt = oldt;
+    newt.c_lflag &= ~(ICANON | ECHO);
+    tcsetattr(STDIN_FILENO, TCSANOW, &newt);
+    ch = getchar();
+    tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
+    return ch;
+}
+
+int main(int argc, char **argv)
+{
+    argv++;argc--;
+    if (argc != 1) {
+        cerr << "Usage: rdcvolume rendererNameOrUid\n";
+        return 1;
+    }
+    string devname(*argv++); 
+    argc--;
+    
+    // Initialize libupnpp logging
+    Logger::getTheLog("")->setLogLevel(Logger::LLERR);
+
+    // Explicitely initialize libupnpp so that we can display a
+    // possible error
+    LibUPnP *mylib = LibUPnP::getLibUPnP();
+    if (!mylib) {
+        cerr << "Can't get LibUPnP" << endl;
+        return 1;
+    }
+    if (!mylib->ok()) {
+        cerr << "Lib init failed: " <<
+            mylib->errAsString("main", mylib->getInitError()) << endl;
+        return 1;
+    }
+
+
+    // Connect to the device
+    MRDH rdr = getRenderer(devname);
+    if (!rdr) {
+        cerr << "Renderer " << devname << " not found" << endl;
+        return 1;
+    }
+
+    // The MediaRender class has magic to create the well-know service
+    // class instances, here RenderingControl
+    RDCH rdc = rdr->rdc();
+    if (!rdc) {
+        cerr << "Device " << devname << 
+            " has no RenderingControl service" << endl;
+        return 1;
+    }
+
+    // Create the event-reporting object. Not used here actually, but
+    // it will print volume change events.
+    new MyReporter(devname, rdc);
+
+    cout << "q = quit, 'u' = up, 'd' = down\n";
+    for (;;) {
+        int vol = rdc->getVolume();
+        cout << "Volume now " << vol << endl;
+        int key = mygetch();
+
+        if (key == 'q') {
+            cout << "QUIT\n";
+            break;
+        } else if (key == 'u') {
+            vol += 5;
+            if (vol > 100) {
+                vol = 100;
+            }
+        } else if (key == 'd') {
+            vol -= 5;
+            if (vol < 0) {
+                vol = 0;
+            }
+        } else {
+            cout << "Bad key: " << (char)key << endl;
+            continue;
+        }
+        if (rdc->setVolume(vol)) {
+            cerr << "setVolume(" << vol << ") failed\n";
+        }
+    }
+
+    return 0;
+}
+