summaryrefslogtreecommitdiff
path: root/doc
diff options
context:
space:
mode:
authorJens Georg <mail@jensge.org>2022-01-03 11:07:59 +0100
committerJens Georg <mail@jensge.org>2022-01-03 19:10:51 +0100
commitd2ad83d588759d005c7a3bae87f7523666e9a733 (patch)
tree35de8ceeb48fe817dfc03c5026150b7c35af1c8f /doc
parent79a2cb24cd0079e9a025809d11dce13edce68bdd (diff)
downloadgupnp-d2ad83d588759d005c7a3bae87f7523666e9a733.tar.gz
docs: Use gi-docgen instead of gtk-doc
Diffstat (limited to 'doc')
-rw-r--r--doc/choosing-a-context-manager.md20
-rw-r--r--doc/client-tutorial.md215
-rw-r--r--doc/glossary.md115
-rw-r--r--doc/gupnp.toml.in54
-rw-r--r--doc/images/gupnp-logo-short.svg126
-rw-r--r--doc/meson.build67
-rw-r--r--doc/server-tutorial.md373
-rw-r--r--doc/urlmap.js4
8 files changed, 944 insertions, 30 deletions
diff --git a/doc/choosing-a-context-manager.md b/doc/choosing-a-context-manager.md
new file mode 100644
index 0000000..bc98595
--- /dev/null
+++ b/doc/choosing-a-context-manager.md
@@ -0,0 +1,20 @@
+-----
+Title: Choosing a Context Manager Implementation
+-----
+
+# Choosing a Context Manager Implementation
+
+Ususally it is fine to trust the auto-detection. If the operating system is not Linux,
+there is only one choice anyway.
+
+For Linux, four different implementations exist:
+
+ - A basic polling implementation, the fall-back if nothing else works.
+ - A Netlink-based implementation
+ - Using NetworkManager to identify available network interfaces
+ - Using Connman to identify the available interfaces
+ - An Android-specific implementation
+
+With the exception of Android, It is generally recommended to use the Netlink-based implementation.
+It should co-exist with any other network management implementation.
+
diff --git a/doc/client-tutorial.md b/doc/client-tutorial.md
new file mode 100644
index 0000000..5d0b71e
--- /dev/null
+++ b/doc/client-tutorial.md
@@ -0,0 +1,215 @@
+---
+Title: Interacting with remote UPnP devices
+---
+
+# UPnP Client Tutorial
+
+This chapter explains how to write an application which fetches the external IP address
+from an UPnP-compliant modem. To do this, a Control Point is created, which searches for
+services of the type `urn:schemas-upnp-org:service:WANIPConnection:1` which is part of
+the Internet Gateway Devce specification.
+
+As services are discovered, ServiceProxy objects are created by GUPnP to allow interaction
+with the service, on which we can invoke the action `GetExternalIPAddress` to fetch the
+external IP address.
+
+## Finding Services
+
+First, we initialize GUPnP and create a control point targeting the service type.
+Then we connect a signal handler so that we are notified when services we are interested in
+are found.
+
+
+```c
+#include <ibgupnp/gupnp-control-point.h>
+
+static GMainLoop *main_loop;
+
+static void
+service_proxy_available_cb (GUPnPControlPoint *cp,
+ GUPnPServiceProxy *proxy,
+ gpointer userdata)
+{
+ /* ... */
+}
+
+int
+main (int argc, char **argv)
+{
+ GUPnPContext *context;
+ GUPnPControlPoint *cp;
+
+ /* Create a new GUPnP Context. By here we are using the default GLib main
+ context, and connecting to the current machine&apos;s default IP on an
+ automatically generated port. */
+ context = gupnp_context_new (NULL, 0, NULL);
+
+ /* Create a Control Point targeting WAN IP Connection services */
+ cp = gupnp_control_point_new
+ (context, "urn:schemas-upnp-org:service:WANIPConnection:1");
+
+ /* The service-proxy-available signal is emitted when any services which match
+ our target are found, so connect to it */
+ g_signal_connect (cp,
+ "service-proxy-available2,
+ G_CALLBACK (service_proxy_available_cb),
+ NULL);
+
+ /* Tell the Control Point to start searching */
+ gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (cp), TRUE);
+
+ /* Enter the main loop. This will start the search and result in callbacks to
+ service_proxy_available_cb. */
+ main_loop = g_main_loop_new (NULL, FALSE);
+ g_main_loop_run (main_loop);
+
+ /* Clean up */
+ g_main_loop_unref (main_loop);
+ g_object_unref (cp);
+ g_object_unref (context);
+
+ return 0;
+}
+```
+
+## Invoking Actions
+Now we have an application which searches for the service we specified and
+calls `service_proxy_available_cb` for each one it
+found. To get the external IP address we need to invoke the
+`GetExternalIPAddress` action. This action takes no in
+arguments, and has a single out argument called "NewExternalIPAddress".
+GUPnP has a set of methods to invoke actions where you pass a
+`NULL`-terminated varargs list of (name, GType, value)
+tuples for the in arguments, then a `NULL`-terminated
+varargs list of (name, GType, return location) tuples for the out
+arguments.
+
+```c
+static void
+service_proxy_available_cb (GUPnPControlPoint *cp,
+ GUPnPServiceProxy *proxy,
+ gpointer userdata)
+{
+ GError *error = NULL;
+ char *ip = NULL;
+ GUPnPServiceProxyAction *action = NULL;
+
+ action = gupnp_service_proxy_action_new (
+ /* Action name */
+ "GetExternalIPAddress",
+ /* IN args */
+ NULL);
+ gupnp_service_proxy_call_action (proxy,
+ action,
+ NULL,
+ &error);
+ if (error != NULL) {
+ goto out;
+ }
+
+ gupnp_service_proxy_action_get_result (action,
+ /* Error location */
+ &error,
+ /* OUT args */
+ "NewExternalIPAddress",
+ G_TYPE_STRING, &ip,
+ NULL);
+
+ if (error == NULL) {
+ g_print ("External IP address is %s\n", ip);
+ g_free (ip);
+ }
+
+out:
+ if (error != NULL) {
+ g_printerr ("Error: %s\n", error->message);
+ g_error_free (error);
+ }
+
+ gupnp_service_proxy_action_unref (action);
+ g_main_loop_quit (main_loop);
+}
+```
+
+Note that `gupnp_service_proxy_call_action()` blocks until the service has
+replied. If you need to make non-blocking calls then use
+`gupnp_service_proxy_call_action_async()`, which takes a callback that will be
+called from the mainloop when the reply is received.
+
+
+## Subscribing to state variable change notifications
+It is possible to get change notifications for the service state variables
+that have attribute `sendEvents="yes"`. We'll demonstrate
+this by modifying `service_proxy_available_cb` and using
+`gupnp_service_proxy_add_notify()` to setup a notification callback:
+
+
+```c
+static void
+external_ip_address_changed (GUPnPServiceProxy *proxy,
+ const char *variable,
+ GValue *value,
+ gpointer userdata)
+{
+ g_print ("External IP address changed: %s\n", g_value_get_string (value));
+}
+
+static void
+service_proxy_available_cb (GUPnPControlPoint *cp,
+ GUPnPServiceProxy *proxy,
+ gpointer userdata)
+{
+ g_print ("Found a WAN IP Connection service\n");
+
+ gupnp_service_proxy_set_subscribed (proxy, TRUE);
+ if (!gupnp_service_proxy_add_notify (proxy,
+ "ExternalIPAddress",
+ G_TYPE_STRING,
+ external_ip_address_changed,
+ NULL)) {
+ g_printerr ("Failed to add notify");
+ }
+}
+```
+
+## Generating wrappers
+
+Using `gupnp_service_proxy_call_action()` and `gupnp_service_proxy_add_notify()`
+can become tedious, because of the requirement to specify the types and deal
+with GValues. An
+alternative is to use `gupnp-binding-tool`, which
+generates wrappers that hide the boilerplate code from you. Using a
+wrapper generated with prefix "ipconn" would replace
+`gupnp_service_proxy_call_action()` with this code:
+
+```c
+ipconn_get_external_ip_address (proxy, &ip, &error);
+```
+
+State variable change notifications are friendlier with wrappers as well:
+
+```c
+static void
+external_ip_address_changed (GUPnPServiceProxy *proxy,
+ const gchar *external_ip_address,
+ gpointer userdata)
+{
+ g_print ("External IP address changed: &apos;%s&apos;\n", external_ip_address);
+}
+
+static void
+service_proxy_available_cb (GUPnPControlPoint *cp,
+ GUPnPServiceProxy *proxy
+ gpointer userdata)
+{
+ g_print ("Found a WAN IP Connection service\n");
+
+ gupnp_service_proxy_set_subscribed (proxy, TRUE);
+ if (!ipconn_external_ip_address_add_notify (proxy,
+ external_ip_address_changed,
+ NULL)) {
+ g_printerr ("Failed to add notify");
+ }
+}
+```
+
diff --git a/doc/glossary.md b/doc/glossary.md
new file mode 100644
index 0000000..0b9dbb2
--- /dev/null
+++ b/doc/glossary.md
@@ -0,0 +1,115 @@
+---
+Title: UPnP Glossary
+---
+
+# Action
+
+> An Action is a method call on a Service, which encapsulated a single piece of
+functionality. Actions can have multiple input and output arguments, and
+can return error codes. UPnP allows one of the output arguments to be
+marked as the return value, but GUPnP doesn't treat return values specially.
+
+> Every action argument has a related State Variable,
+which determines the type of the argument. Note that even if the argument
+wouldn't need a state variable it is still required, due to historical
+reasons.
+
+# Control Point
+
+> A Control Point is an entity on the network which
+communicates with other Devices and
+Services. In the client/server model the control
+point is a client and the Service is a server,
+although it is common for devices to also be a control point because
+whilst a single control point/service connection is client/server, the
+UPnP network as whole is peer-to-peer.
+
+# Device
+> A Device is an entity on the network which
+communicates using the UPnP standards. This can be a dedicated physical
+device such as a router or printer, or a PC which is running software
+implementing the UPnP standards.
+
+> A Device can contain sub-devices, for example a combination
+printer/scanner could appear as a general device with a printer
+sub-device and a scanner sub-device.
+
+> Every device has zero or more Services. UPnP defines many standard
+device types, which specify services which are required to be implemented.
+Alternatively, a non-standard device type could be used. Examples of
+standard device types are `MediaRenderer` or
+`InternetGatewayDevice`.
+
+# DIDL-Lite
+
+> Digital Item Declaration Language - Lite
+
+> An XML schema used to represent digital content metadata. Defined by
+the UPnP Forum.
+
+# Service
+
+> A Service is a collection of related methods
+(called Actions) and public variables (called
+State Variables) which together form a logical interface.
+> UPnP defines standard services that define actions and variables which
+must be present and their semantics. Examples of these are
+`AVTransport` and `WANIPConnection`.
+
+See also:
+
+- [Action](#action)
+- [Device](#device)
+- [State Variable](#state-variable)
+
+# SCDP
+> Service Control Protocol Document
+
+
+> An XML document which defines the set of <glossterm>Actions</glossterm>
+and <glossterm>State Variables</glossterm> that a
+<glossterm>Service</glossterm> implements.
+
+See also:
+
+- [Action](#action)
+- [Device](#device)
+- [State Variable](#state-variable)
+
+# SSDP
+> <glossterm>Simple Service Discovery Protocol</glossterm>
+
+> UPnP device discovery protocol. Specifies how <glossterm>Devices</glossterm>
+advertise their <glossterm>Services</glossterm> in the network and also how
+<glossterm>Control Points</glossterm> search for
+services and devices respond.
+
+See also:
+
+- [Device](#device)
+- [Action](#controlpoint)
+- [Service](#service)
+
+# State Variable
+
+> A <firstterm>State Variable</firstterm> is a public variable exposing some
+aspect of the service's state. State variables are typed and optionally
+are <firstterm>evented</firstterm>, which means that any changes will be
+notified. Control points are said to <firstterm>subscribe</firstterm> to
+a state variable to receive change notifications.
+
+
+# UDN
+> Unique Device Name
+
+> An unique identifier which is <emphasis>unique</emphasis> for every
+device but <emphasis>never changes</emphasis> for each particular
+device.
+
+> A common practise is to generate a unique UDN on first boot from a
+random seed, or use some unique and persistent property such as the
+device's MAC address to create the UDN.
+
+See also:
+
+- [Device](#device)
diff --git a/doc/gupnp.toml.in b/doc/gupnp.toml.in
new file mode 100644
index 0000000..e416445
--- /dev/null
+++ b/doc/gupnp.toml.in
@@ -0,0 +1,54 @@
+[library]
+namespace = "GUPnP"
+version = "@VERSION@"
+browse_url = "https://gitlab.gnome.org/GNOME/gssdp/"
+repository_url = "https://gitlab.gnome.org/GNOME/gssdp.git"
+website_url = "https://gupnp.org"
+logo_url = "gupnp-logo-short.svg"
+license = "LGPL-2.1-or-later"
+description = "UPnP implementation using GObject"
+dependencies = [ "GObject-2.0", "GSSDP-1.2", "Soup-2.4", "libxml2-2.0" ]
+devhelp = true
+search_index = true
+authors = "The GUPnP developers"
+
+[theme]
+name="basic"
+show_index_summary = true
+
+[source-location]
+base_url = "https://gitlab.gnome.org/GNOME/gupnp/-/blob/master"
+
+[dependencies."GObject-2.0"]
+name = "GObject"
+description = "The base type system library"
+docs_url = "https://developer.gnome.org/gobject/stable"
+
+[dependencies."GSSDP-1.2"]
+name = "GSSDP"
+description = "SSDP implementation using GObject"
+docs_url = "https://gnome.pages.gitlab.gnome.org/gssdp/docs/"
+
+[dependencies."Soup-2.4"]
+name = "Soup"
+description = "A HTTP handling library"
+docs_url = "https://developer.gnome.org/libsoup/stable"
+
+[dependencies."libxml2-2.0"]
+name = "LibXML2"
+description = "A XML handling library"
+docs_url = "http://www.xmlsoft.org/html/index.html"
+
+[extra]
+content_files = [
+ "client-tutorial.md",
+ "server-tutorial.md",
+ "choosing-a-context-manager.md",
+ "glossary.md"
+]
+
+content_images = [
+ "images/gupnp-logo-short.svg"
+]
+
+urlmap_file = "urlmap.js"
diff --git a/doc/images/gupnp-logo-short.svg b/doc/images/gupnp-logo-short.svg
new file mode 100644
index 0000000..6386c74
--- /dev/null
+++ b/doc/images/gupnp-logo-short.svg
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ id="svg4194"
+ version="1.1"
+ inkscape:version="1.1 (c68e22c387, 2021-05-23)"
+ width="89.339256mm"
+ height="37.64888mm"
+ viewBox="0 0 316.55642 133.40154"
+ sodipodi:docname="gupnp-logo-short.svg"
+ inkscape:export-filename="/home/jgeorg/gupnp-logo-v1.svg.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <metadata
+ id="metadata4200">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <cc:license
+ rdf:resource="http://creativecommons.org/publicdomain/zero/1.0/" />
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/publicdomain/zero/1.0/">
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#Reproduction" />
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#Distribution" />
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs4198" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1011"
+ id="namedview4196"
+ showgrid="false"
+ showguides="true"
+ inkscape:snap-page="false"
+ inkscape:zoom="1.6897519"
+ inkscape:cx="106.82042"
+ inkscape:cy="36.691777"
+ inkscape:window-x="0"
+ inkscape:window-y="32"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg4194"
+ inkscape:guide-bbox="true"
+ inkscape:pagecheckerboard="0"
+ units="mm"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:document-units="mm">
+ <inkscape:grid
+ type="xygrid"
+ id="grid5354"
+ originx="-2.7774772"
+ originy="-2.2013303"
+ spacingx="1"
+ spacingy="1" />
+ <sodipodi:guide
+ position="311.44403,45.237576"
+ orientation="0,1"
+ id="guide4237"
+ inkscape:locked="false" />
+ <sodipodi:guide
+ position="316.24124,64.031713"
+ orientation="1,0"
+ id="guide14" />
+ </sodipodi:namedview>
+ <path
+ inkscape:connector-curvature="0"
+ id="path4222"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:111.914px;line-height:125%;font-family:moderna;-inkscape-font-specification:moderna;letter-spacing:0px;word-spacing:0px;fill:#204a87;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 59.874011,60.321668 q 0,12.7582 -9.176951,21.487495 9.288865,8.841209 9.288865,21.711327 0,16.11562 -13.205856,24.84491 -7.722069,5.03614 -16.675192,5.03614 -16.003708,0 -24.8449171,-13.31778 -5.1480458,-7.61015 -5.1480458,-16.56327 0,-5.819533 2.2382807,-11.191407 L 16.33945,97.92479 q -1.11914,2.68593 -1.11914,5.5957 0,6.15527 4.364647,10.408 4.476562,4.36465 10.51992,4.36465 6.043358,0 10.408006,-4.36465 4.364647,-4.36464 4.364647,-10.408 0,-9.848438 -9.288865,-13.76543 -2.797851,0.55957 -5.595702,0.55957 -12.534373,0 -21.2636682,-8.729295 Q 0,72.85604 0,60.321668 0,47.899209 8.7292948,39.169914 17.570504,30.440619 29.992963,30.440619 l 8.729295,-20.7040974 11.862888,5.0361324 -7.833983,18.57773 q 7.833983,3.805078 12.422459,11.07949 4.700389,7.274413 4.700389,15.891794 z m -15.108395,0 q 0,-6.043358 -4.364647,-10.408006 -4.364648,-4.364647 -10.408006,-4.364647 -6.155272,0 -10.51992,4.364647 -4.364647,4.252734 -4.364647,10.408006 0,6.155272 4.364647,10.519919 4.364648,4.364648 10.51992,4.364648 6.155272,0 10.408006,-4.364648 4.364647,-4.364647 4.364647,-10.519919 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4224"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:111.914px;line-height:125%;font-family:moderna;-inkscape-font-specification:moderna;letter-spacing:0px;word-spacing:0px;fill:#204a87;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 97.753413,73.527524 q -6.15527,0 -10.519918,-4.252733 -4.252733,-4.364648 -4.252733,-10.51992 L 82.868848,0 H 67.760452 v 58.754871 q 0,12.422459 8.729296,21.151754 8.841209,8.729295 21.263665,8.729295 12.422457,0 21.151757,-8.729295 8.8412,-8.729295 8.8412,-21.151754 V 0 h -15.10839 v 58.754871 q 0,6.155272 -4.36465,10.51992 -4.36465,4.252733 -10.519917,4.252733 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4226"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:111.914px;line-height:125%;font-family:moderna;-inkscape-font-specification:moderna;letter-spacing:0px;word-spacing:0px;fill:#204a87;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 190.13495,30.216791 Q 190.24687,17.682419 181.51757,9.0650376 172.78828,0.3357421 160.2539,0.2238281 L 135.5209,0.111914 V 88.63592 h 15.22031 V 59.985926 h 9.40078 q 12.42246,0 21.15175,-8.617381 8.7293,-8.729296 8.84121,-21.151754 z m -15.10839,-0.111914 q -0.11192,6.267186 -4.36465,10.51992 -4.25273,4.252733 -10.51992,4.252733 H 150.6293 V 15.22031 l 9.6246,0.111914 q 6.26719,0.111914 10.51992,4.364647 4.36465,4.252734 4.25274,10.408006 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4228"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:111.914px;line-height:125%;font-family:moderna;-inkscape-font-specification:moderna;letter-spacing:0px;word-spacing:0px;fill:#204a87;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 221.80976,45.549015 q 6.04336,0 10.40801,4.364647 4.36465,4.364648 4.36465,10.408006 l 0.11191,28.314252 h 15.1084 V 60.321668 q 0,-12.422459 -8.84121,-21.151754 -8.7293,-8.729295 -21.15176,-8.729295 -12.42246,0 -21.26366,8.729295 -8.7293,8.729295 -8.7293,21.151754 V 88.63592 h 15.1084 V 60.321668 q 0,-6.155272 4.36464,-10.408006 4.36465,-4.364647 10.51992,-4.364647 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4230"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:111.914px;line-height:125%;font-family:moderna;-inkscape-font-specification:moderna;letter-spacing:0px;word-spacing:0px;fill:#204a87;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 314.19132,30.216791 Q 314.30323,17.682419 305.57394,9.0650376 296.84464,0.3357421 284.31027,0.2238281 L 259.57727,0.111914 V 88.63592 h 15.22031 V 59.985926 h 9.40078 q 12.42245,0 21.15175,-8.617381 8.7293,-8.729296 8.84121,-21.151754 z m -15.1084,-0.111914 q -0.11191,6.267186 -4.36464,10.51992 -4.25274,4.252733 -10.51992,4.252733 h -9.5127 V 15.22031 l 9.62461,0.111914 q 6.26719,0.111914 10.51992,4.364647 4.36465,4.252734 4.25273,10.408006 z" />
+ <rect
+ style="fill:#204a87;fill-opacity:1;stroke:#204a87;stroke-width:0.630386;stroke-linejoin:miter;stroke-miterlimit:0;stroke-dasharray:none;stroke-opacity:1"
+ id="rect5352"
+ width="248.92432"
+ height="15.959336"
+ x="67.316925"
+ y="104.33861"
+ ry="0" />
+</svg>
diff --git a/doc/meson.build b/doc/meson.build
index c3b8fb3..c5ebbc0 100644
--- a/doc/meson.build
+++ b/doc/meson.build
@@ -1,5 +1,6 @@
entities = configuration_data()
entities.set('VERSION', meson.project_version())
+
version_xml = configure_file(input: 'version.xml.in',
output: 'version.xml', configuration:
entities)
@@ -12,36 +13,42 @@ docbook_man_page = configure_file(
)
if get_option('gtk_doc')
- gnome.gtkdoc('gupnp',
- content_files : files(
- 'client-tutorial.xml',
- 'fdl-1.1.xml',
- 'glossary.xml',
- 'gupnp-docs.xml',
- 'overview.xml',
- 'server-tutorial.xml'
- ),
- main_xml : 'gupnp-docs.xml',
- src_dir : ['libgupnp'],
- dependencies : [libgupnp, version_xml, docbook_man_page],
- scan_args : ['--ignore-decorators', 'G_DEPRECATED|G_GNUC_DEPRECATED,G_DEPRECATED_FOR'],
- ignore_headers : [
- 'gena-protocol.h',
- 'xml-util.h',
- 'gvalue-util.h',
- 'http-headers.h',
- 'gupnp-context-private.h',
- 'gupnp-linux-context-manager.h',
- 'gupnp-network-manager.h',
- 'gupnp-unix-context-manager.h',
- 'gupnp-device-info-private.h',
- 'gupnp-error-private.h',
- 'gupnp-resource-factory-private.h',
- 'gupnp-service-introspection-private.h',
- 'gupnp-service-proxy-action-private.h',
- 'gupnp-types-private.h'
- ],
- install : true)
+ gidocgen = find_program('gi-docgen', required: true)
+
+ gupnp_toml = configure_file (
+ input: 'gupnp.toml.in',
+ output: 'gupnp.toml',
+ configuration: entities
+ )
+
+ docs_dir = join_paths(get_option('prefix'), get_option('datadir')) / 'doc/gupnp-1.2/reference'
+
+ custom_target(
+ 'gupnp-doc',
+ input: [ gupnp_toml, gir[0] ],
+ output: 'GUPnP',
+ command : [
+ gidocgen,
+ 'generate',
+ '--quiet',
+ '--add-include-path=@0@'.format(meson.current_build_dir() / '../libgupnp'),
+ '--config', gupnp_toml,
+ '--output-dir=@OUTPUT@',
+ '--no-namespace-dir',
+ '--content-dir=@0@'.format(meson.current_source_dir()),
+ '@INPUT1@',
+ ],
+ depend_files : [
+ gupnp_toml,
+ 'client-tutorial.md',
+ 'server-tutorial.md',
+ 'choosing-a-context-manager.md',
+ 'glossary.md',
+ ],
+ build_by_default: true,
+ install: true,
+ install_dir : docs_dir,
+ )
endif
xsltproc = find_program('xsltproc', required: false)
diff --git a/doc/server-tutorial.md b/doc/server-tutorial.md
new file mode 100644
index 0000000..d0c0ce2
--- /dev/null
+++ b/doc/server-tutorial.md
@@ -0,0 +1,373 @@
+---
+Title: Implementing UPnP devices
+---
+
+# UPnP Server Tutorial
+
+This chapter explains how to implement an UPnP service using GUPnP. For
+this example we will create a virtual UPnP-enabled light bulb.
+
+Before any code can be written, the device and services that it implement
+need to be described in XML. Although this can be frustrating, if you are
+implementing a standardised service then the service description is
+already written for you and the device description is trivial. UPnP has
+standardised Lighting Controls, so we'll be using the device and service types defined
+there.
+
+## Defining the Device
+
+The first step is to write the _device description_
+file. This is a short XML document which describes the device and what
+services it provides (for more details see the [UPnP Device Architecture specification](http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.0.pdf), section 2.1). We'll be using
+the `BinaryLight1` device type, but if none of the
+existing device types are suitable a custom device type can be created.
+
+```xml
+<?xml version="1.0" encoding="utf-8"?>
+<root xmlns="urn:schemas-upnp-org:device-1-0">
+ <specVersion>
+ <major>1</major>
+ <minor>0</minor>
+ </specVersion>
+
+ <device>
+ <deviceType>urn:schemas-upnp-org:device:BinaryLight:1</deviceType>
+ <friendlyName>Kitchen Lights</friendlyName>
+ <manufacturer>OpenedHand</manufacturer>
+ <modelName>Virtual Light</modelName>
+ <UDN>uuid:cc93d8e6-6b8b-4f60-87ca-228c36b5b0e8</UDN>
+
+ <serviceList>
+ <service>
+ <serviceType>urn:schemas-upnp-org:service:SwitchPower:1</serviceType>
+ <serviceId>urn:upnp-org:serviceId:SwitchPower:1</serviceId>
+ <SCPDURL>/SwitchPower1.xml</SCPDURL>
+ <controlURL>/SwitchPower/Control</controlURL>
+ <eventSubURL>/SwitchPower/Event</eventSubURL>
+ </service>
+ </serviceList>
+ </device>
+</root>
+```
+The `<specVersion>` tag defines what version of the UPnP
+Device Architecture the document conforms to. At the time of writing the
+only version is 1.0.
+
+Next there is the root `<device>` tag. This contains
+metadata about the device, lists the services it provides and any
+sub-devices present (there are none in this example). The
+`<deviceType>` tag specifies the type of the device.
+
+Next we have `<friendlyName>`, `<manufacturer>` and `<modelName>`. The
+friendly name is a human-readable name for the device, the manufacturer
+and model name are self-explanatory.
+
+Next there is the UDN, or _Unique Device Name_. This
+is an identifier which is unique for each device but persistent for each
+particular device. Although it has to start with `uuid:`
+note that it doesn't have to be an UUID. There are several alternatives
+here: for example it could be computed at built-time if the software will
+only be used on a single machine, or it could be calculated using the
+device's serial number or MAC address.
+
+Finally we have the `<serviceList>` which describes the
+services this device provides. Each service has a service type (again
+there are types defined for standardised services or you can create your
+own), service identifier, and three URLs. As a service type we're using
+the standard `SwitchPower1` service. The
+`<SCPDURL>` field specifies where the _Service
+Control Protocol Document_ can be found, this describes the
+service in more detail and will be covered next. Finally there are the
+control and event URLs, which need to be unique on the device and will be
+managed by GUPnP.
+
+## Defining Services
+
+Because we are using a standard service we can use the service description
+from the specification. This is the `SwitchPower1`
+service description file:
+
+```xml
+<?xml version="1.0" encoding="utf-8"?>
+<scpd xmlns="urn:schemas-upnp-org:service-1-0">
+ <specVersion>
+ <major>1</major>
+ <minor>0</minor>
+ </specVersion>
+ <actionList>
+ <action>
+ <name>SetTarget</name>
+ <argumentList>
+ <argument>
+ <name>newTargetValue</name>
+ <relatedStateVariable>Target</relatedStateVariable>
+ <direction>in</direction>
+ </argument>
+ </argumentList>
+ </action>
+ <action>
+ <name>GetTarget</name>
+ <argumentList>
+ <argument>
+ <name>RetTargetValue</name>
+ <relatedStateVariable>Target</relatedStateVariable>
+ <direction>out</direction>
+ </argument>
+ </argumentList>
+ </action>
+ <action>
+ <name>GetStatus</name>
+ <argumentList>
+ <argument>
+ <name>ResultStatus</name>
+ <relatedStateVariable>Status</relatedStateVariable>
+ <direction>out</direction>
+ </argument>
+ </argumentList>
+ </action>
+ </actionList>
+ <serviceStateTable>
+ <stateVariable sendEvents="no">
+ <name>Target</name>
+ <dataType>boolean</dataType>
+ <defaultValue>0</defaultValue>
+ </stateVariable>
+ <stateVariable sendEvents="yes">
+ <name>Status</name>
+ <dataType>boolean</dataType>
+ <defaultValue>0</defaultValue>
+ </stateVariable>
+ </serviceStateTable>
+</scpd>
+```
+
+Again, the `<specVersion>` tag defines the UPnP version
+that is being used. The rest of the document consists of an
+`<actionList>` defining the actions available and a
+`<serviceStateTable>` defining the state variables.
+
+Every `<action>` has a `<name>` and a list
+of `<argument>`s. Arguments also have a name, a direction
+(`in` or `out` for input or output
+ arguments) and a related state variable. The state variable is used to
+determine the type of the argument, and as such is a required element.
+This can lead to the creation of otherwise unused state variables to
+define the type for an argument (the `WANIPConnection`
+service is a good example of this), thanks to the legacy behind UPnP.
+
+`<stateVariable>`s need to specify their
+`<name>` and `<dataType>`. State variables
+by default send notifications when they change, to specify that a variable
+doesn't do this set the `<sendEvents>` attribute to
+`no`. Finally there are optional
+`<defaultValue>`, `<allowedValueList>` and
+`<allowedValueRange>` elements which specify what the
+default and valid values for the variable.
+
+For the full specification of the service definition file, including a
+complete list of valid `<dataType>`s, see section 2.3 of
+the [UPnP Device Architecture](http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.0.pdf)
+
+## Implementing the Device
+
+Before starting to implement the device, some boilerplate code is needed
+to initialise GUPnP. A GUPnP context can be created using `gupnp_context_new()`.
+
+```c
+GUPnPContext *context;
+/* Create the GUPnP context with default host and port */
+context = gupnp_context_new (NULL, 0, NULL);
+```
+Next the root device can be created. The name of the device description
+file can be passed as an absolute file path or a relative path to the
+second parameter of `gupnp_root_device_new()`. The service description
+files referenced in the device description are expected to be at the path
+given there as well.
+
+```c
+GUPnPRootDevice *dev;
+/* Create the root device object */
+dev = gupnp_root_device_new (context, "BinaryLight1.xml", ".");
+/* Activate the root device, so that it announces itself */
+gupnp_root_device_set_available (dev, TRUE);
+```
+
+GUPnP scans the device description and any service description files it
+refers to, so if the main loop was entered now the device and service
+would be available on the network, albeit with no functionality. The
+remaining task is to implement the services.
+
+## Implementing a Service
+
+To implement a service we first fetch the #GUPnPService from the root
+device using gupnp_device_info_get_service() (#GUPnPRootDevice is a
+subclass of #GUPnPDevice, which implements #GUPnPDeviceInfo). This
+returns a #GUPnPServiceInfo which again is an interface, implemented by
+#GUPnPService (on the server) and #GUPnPServiceProxy (on the client).
+
+```c
+GUPnPServiceInfo *service;
+service = gupnp_device_info_get_service (GUPNP_DEVICE_INFO (dev), "urn:schemas-upnp-org:service:SwitchPower:1");
+```
+
+GUPnPService handles interacting with the network itself, leaving the
+implementation of the service itself to signal handlers that we need to
+connect. There are two signals: #GUPnPService::action-invoked and
+#GUPnPService::query-variable. #GUPnPService::action-invoked is emitted
+when a client invokes an action: the handler is passed a
+#GUPnPServiceAction object that identifies which action was invoked, and
+is used to return values using `gupnp_service_action_set()`.
+#GUPnPService::query-variable is emitted for evented variables when a
+control point subscribes to the service (to announce the initial value),
+or whenever a client queries the value of a state variable (note that this
+is now deprecated behaviour for UPnP control points): the handler is
+passed the variable name and a #GValue which should be set to the current
+value of the variable.
+
+Handlers should be targetted at specific actions or variables by using
+the signal detail when connecting. For example,
+this causes `on_get_status_action` to be called when
+the `GetStatus` action is invoked:
+
+```c
+static void on_get_status_action (GUPnPService *service, GUPnPServiceAction *action, gpointer user_data);
+// ...
+g_signal_connect (service, "action-invoked::GetStatus", G_CALLBACK (on_get_status_action), NULL);
+```
+
+The implementation of action handlers is quite simple. The handler is
+passed a #GUPnPServiceAction object which represents the in-progress
+action. If required it can be queried using
+gupnp_service_action_get_name() to identify the action (this isn't
+required if detailed signals were connected). Any
+in arguments can be retrieving using
+`gupnp_service_action_get()`, and then return values can be set using
+`gupnp_service_action_set()`. Once the action has been performed, either
+`gupnp_service_action_return()` or `gupnp_service_action_return_error()`
+should be called to either return successfully or return an error code.
+
+If any evented state variables were modified during the action then a
+notification should be emitted using `gupnp_service_notify()`. This is an
+example implementation of `GetStatus` and `SetTarget`
+
+```c
+static gboolean status;
+
+static void
+get_status_cb (GUPnPService *service, GUPnPServiceAction *action, gpointer user_data)
+{
+ gupnp_service_action_set (action,
+ "ResultStatus", G_TYPE_BOOLEAN, status,
+ NULL);
+ gupnp_service_action_return (action);
+}
+
+void
+set_target_cb (GUPnPService *service, GUPnPServiceAction *action, gpointer user_data)
+{
+ gupnp_service_action_get (action,
+ "NewTargetValue", G_TYPE_BOOLEAN, &status,
+ NULL);
+ gupnp_service_action_return (action);
+ gupnp_service_notify (service, "Status", G_TYPE_STRING, status, NULL);
+}
+
+//...
+
+g_signal_connect (service, "action-invoked::GetStatus", G_CALLBACK (get_status_cb), NULL);
+g_signal_connect (service, "action-invoked::SetTarget", G_CALLBACK (set_target_cb), NULL);
+```
+
+State variable query handlers are called with the name of the variable and
+a #GValue. This value should be initialized with the relevant type and
+then set to the current value. Again signal detail can be used to connect
+handlers to specific state variable callbacks.
+
+```c
+static gboolean status;
+
+static void
+query_status_cb (GUPnPService *service, char *variable, GValue *value, gpointer user_data)
+{
+ g_value_init (value, G_TYPE_BOOLEAN);
+ g_value_set_boolean (value, status);
+}
+
+// ...
+
+g_signal_connect (service, "query-variable::Status", G_CALLBACK (query_status_cb), NULL);
+```
+
+The service is now fully implemented. To complete it, enter a GLib main
+loop and wait for a client to connect. The complete source code for this
+example is available as [examples/light-server.c](https://gitlab.gnome.org/GNOME/gupnp/-/blob/master/examples/light-server.c) in
+the GUPnP sources.
+
+For services which have many actions and variables there is a convenience
+method [method@GUPnP.Service.signals_autoconnect] which will automatically
+connect specially named handlers to signals. See the documentation for
+full details on how it works.
+
+## Generating Service-specific Wrappers
+
+Using service-specific wrappers can simplify the implementation of a service.
+Wrappers can be generated with gupnp-binding-tool
+using the option `--mode server`.
+
+In the following examples the wrapper has been created with
+ `--mode server --prefix switch`. Please note that the callback handlers
+ (`get_status_cb` and `set_target_cb`) are not automatically
+ generated by gupnp-binding-tool for you.
+
+```c
+static gboolean status;
+
+static void
+get_status_cb (GUPnPService *service,
+ GUPnPServiceAction *action,
+ gpointer user_data)
+{
+ switch_get_status_action_set (action, status);
+
+ gupnp_service_action_return (action);
+}
+
+static void
+set_target_cb (GUPnPService *service,
+ GUPnPServiceAction *action,
+ gpointer user_data)
+{
+ switch_set_target_action_get (action, &status);
+ switch_status_variable_notify (service, status);
+
+ gupnp_service_action_return (action);
+}
+
+// ...
+
+switch_get_status_action_connect (service, G_CALLBACK(get_status_cb), NULL);
+switch_set_target_action_connect (service, G_CALLBACK(set_target_cb), NULL);
+```
+
+Note how many possible problem situations that were run-time errors are
+actually compile-time errors when wrappers are used: Action names,
+argument names and argument types are easier to get correct (and available
+in editor autocompletion).
+
+State variable query handlers are implemented in a similar manner, but
+they are even simpler as the return value of the handler is the state
+variable value.
+
+```c
+static gboolean
+query_status_cb (GUPnPService *service,
+ gpointer user_data)
+{
+ return status;
+}
+
+// ...
+
+
+switch_status_query_connect (service, query_status_cb, NULL);
+```
diff --git a/doc/urlmap.js b/doc/urlmap.js
new file mode 100644
index 0000000..4e19607
--- /dev/null
+++ b/doc/urlmap.js
@@ -0,0 +1,4 @@
+// A map between namespaces and base URLs for their online documentation
+baseURLs = [
+ [ 'GSSDP', 'https://gnome.pages.gitlab.gnome.org/gssdp/docs/' ],
+]