diff options
| -rw-r--r-- | CMakeLists.txt | 34 | ||||
| -rw-r--r-- | README.md | 22 | ||||
| -rw-r--r-- | cmake/ConfigurePaths.cmake | 90 | ||||
| -rw-r--r-- | cmake/PkgConfigVars.cmake | 30 | ||||
| -rw-r--r-- | uca-net-camera.c | 296 | ||||
| -rw-r--r-- | uca-net-camera.h | 76 | ||||
| -rw-r--r-- | uca-net-client.c | 207 | ||||
| -rw-r--r-- | uca-net-protocol.h | 85 | ||||
| -rw-r--r-- | uca-net-server.c | 174 | ||||
| -rw-r--r-- | ucad.c | 223 | 
10 files changed, 1237 insertions, 0 deletions
| diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..bfd96d6 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 2.6) +project(ucanet C) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") + +include(FindPackageHandleStandardArgs) +include(PkgConfigVars) +include(ConfigurePaths) + +find_package(PkgConfig) + +add_definitions("-std=c99 -Wall") +add_definitions(-DG_LOG_DOMAIN="Uca-Net") + +pkg_check_modules(GIO gio-2.0>=2.22 REQUIRED) +pkg_check_modules(UCA libuca>=2.1.0 REQUIRED) +pkg_check_variable(libuca plugindir) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${UCA_INCLUDE_DIRS} ${GIO_INCLUDE_DIRS} ${MSGPACK_INCLUDE_DIRS}) +link_directories(${UCA_LIBRARY_DIRS} ${GIO_LIBRARY_DIRS}) + +# uca-net client camera +add_library(ucanet SHARED uca-net-camera.c uca-net-client.c) +target_link_libraries(ucanet ${UCA_LIBRARIES} ${MSGPACK_LIBRARIES}) + +install(TARGETS ucanet LIBRARY DESTINATION ${LIBUCA_PLUGINDIR}) + +# uca-net server +configure_paths(UCAD) +add_executable(ucad ucad.c uca-net-server.c) + +target_link_libraries(ucad ${UCA_LIBRARIES} ${GIO_LIBRARIES}) + +install(TARGETS ucad RUNTIME DESTINATION ${UCAD_BINDIR} COMPONENT executables) diff --git a/README.md b/README.md new file mode 100644 index 0000000..2352ac1 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +## uca-net + +A transparent TCP-based network bridge camera for remote access of libuca +cameras. + +### Installation and usage + +The only dependency is libuca itself and any camera you wish to access. Build +the server and client with + +    $ mkdir build && cd build +    $ cmake .. +    $ make && (sudo) make install + +Now, you can start a server on a remote machine with + +    $ ucad mock + +and connect to it from any other machine, e.g. + +    $ uca-grab -n 10 net            # grab ten frames +    $ uca-camera-control -c net     # control graphically diff --git a/cmake/ConfigurePaths.cmake b/cmake/ConfigurePaths.cmake new file mode 100644 index 0000000..df9c03c --- /dev/null +++ b/cmake/ConfigurePaths.cmake @@ -0,0 +1,90 @@ +# - pre-configured paths for CMake +# +# Usage: +#   configure_paths(<PREFIX>) +# +# Checks if configure-like prefix and installation paths were passed by the user +# and sets up corresponding variables for use in install() commands and to fill +# out .pc files: +# +#   PREFIX_PREFIX       defaults to ...     CMAKE_INSTALL_PREFIX +#   PREFIX_EPREFIX                          PREFIX_PREFIX +#   PREFIX_SBINDIR                          PREFIX_EPREFIX/sbin +#   PREFIX_SYSCONFDIR                       PREFIX_PREFIX/etc +#   PREFIX_LOCALSTATEDIR                    PREFIX_PREFIX/var +#   PREFIX_BINDIR                           PREFIX_EPREFIX/bin +#   PREFIX_LIBDIR                           PREFIX_EPREFIX/lib +#   PREFIX_INCLUDEDIR                       PREFIX_PREFIX/include +#   PREFIX_PKGCONFIGDIR                     PREFIX_LIBDIR/pkgconfig +#   PREFIX_TYPELIBDIR                       PREFIX_LIBDIR/girepository-1.0 +#   PREFIX_DATAROOTDIR                      PREFIX_PREFIX/share +#   PREFIX_DATADIR                          PREFIX_DATAROOTDIR +#   PREFIX_INFODIR                          PREFIX_DATAROOTDIR/info +#   PREFIX_MANDIR                           PREFIX_DATAROOTDIR/man +#   PREFIX_LOCALEDIR                        PREFIX_DATAROOTDIR/locale +#   PREFIX_GIRDIR                           PREFIX_DATAROOTDIR/gir-1.0 + +# Copyright (C) 2013 Matthias Vogelgesang <matthias.vogelgesang@gmail.com> +# +# Redistribution and use, with or without modification, are permitted +# provided that the following conditions are met: +#  +#    1. Redistributions must retain the above copyright notice, this +#       list of conditions and the following disclaimer. +#    2. The name of the author may not be used to endorse or promote +#       products derived from this software without specific prior +#       written permission. +#  +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +if(__configure_paths) +    return() +endif() + +set(__configure_paths YES) + +macro(_set_var _prefix _var _user _override _description) +    set(_name "${_prefix}_${_var}") + +    set("${_name}" "${_user}") + +    if("${_name}" STREQUAL "") +        set("${_name}" "${_override}") +    endif() + +    set(${_name} "${${_name}}" CACHE PATH "${_description}") +    mark_as_advanced(${_name}) +endmacro() + +function(configure_paths _prefix) +    _set_var("${_prefix}" "PREFIX"          "${PREFIX}"         "${CMAKE_INSTALL_PREFIX}"               "install architecture-independent files in PREFIX") +    _set_var("${_prefix}" "EPREFIX"         "${EXEC_PREFIX}"    "${${_prefix}_PREFIX}"                  "install architecture-dependent files in EPREFIX") + +    _set_var("${_prefix}" "SBINDIR"         "${SBINDIR}"        "${${_prefix}_EPREFIX}/sbin"            "system admin executabls") +    _set_var("${_prefix}" "SYSCONFDIR"      "${SYSCONFDIR}"     "${${_prefix}_PREFIX}/etc"              "read-only single-machine data") +    _set_var("${_prefix}" "LOCALSTATEDIR"   "${LOCALSTATEDIR}"  "${${_prefix}_PREFIX}/var"              "modifiable single-machine data") +    _set_var("${_prefix}" "BINDIR"          "${BINDIR}"         "${${_prefix}_EPREFIX}/bin"             "user executables") +    _set_var("${_prefix}" "LIBDIR"          "${LIBDIR}"         "${${_prefix}_EPREFIX}/lib"             "object code libraries") +    _set_var("${_prefix}" "INCLUDEDIR"      "${INCLUDEDIR}"     "${${_prefix}_PREFIX}/include"          "C header files") +    _set_var("${_prefix}" "PKGCONFIGDIR"    "${PKGCONFIGDIR}"   "${${_prefix}_LIBDIR}/pkgconfig"        "pkg-config files") +    _set_var("${_prefix}" "TYPELIBDIR"      "${TYPELIBDIR}"     "${${_prefix}_LIBDIR}/girepository-1.0" "GObject run-time introspection data") +    _set_var("${_prefix}" "DATAROOTDIR"     "${DATAROOTDIR}"    "${${_prefix}_PREFIX}/share"            "read-only arch.-independent data root") +    _set_var("${_prefix}" "DATADIR"         "${DATADIR}"        "${${_prefix}_DATAROOTDIR}"             "read-only architecture-independent data") +    _set_var("${_prefix}" "INFODIR"         "${INFODIR}"        "${${_prefix}_DATAROOTDIR}/info"        "info documentation") +    _set_var("${_prefix}" "MANDIR"          "${MANDIR}"         "${${_prefix}_DATAROOTDIR}/man"         "man documentation") +    _set_var("${_prefix}" "LOCALEDIR"       "${LOCALEDIR}"      "${${_prefix}_DATAROOTDIR}/locale"      "locale-dependent data") +    _set_var("${_prefix}" "GIRDIR"          "${GIRDIR}"         "${${_prefix}_DATAROOTDIR}/gir-1.0"     "GObject introspection data") +endfunction() + +# vim: tw=0: diff --git a/cmake/PkgConfigVars.cmake b/cmake/PkgConfigVars.cmake new file mode 100644 index 0000000..f295457 --- /dev/null +++ b/cmake/PkgConfigVars.cmake @@ -0,0 +1,30 @@ +# - determine variables defined in pkg-config files +# +# Usage: +#   pkg_check_variable(<PKG_NAME> <VARIABLE_NAME>) +# +# Checks for a variable in the given package and translates to a call such as +# `pkg-config --variable=<VARIABLE_NAME> <PKG_NAME>`. The output is a cached +# variable named +# +#   <PKG_NAME>_<VARIABLE_NAME> +# +# Note that both names are uppercased and any dashes replaced by underscores. +# + +find_package(PkgConfig REQUIRED) + +function(pkg_check_variable _pkg _name) +    string(TOUPPER ${_pkg} _pkg_upper) +    string(TOUPPER ${_name} _name_upper) +    string(REPLACE "-" "_" _pkg_upper ${_pkg_upper}) +    string(REPLACE "-" "_" _name_upper ${_name_upper}) +    set(_output_name "${_pkg_upper}_${_name_upper}") + +    execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} --variable=${_name} ${_pkg} +                    OUTPUT_VARIABLE _pkg_result +                    OUTPUT_STRIP_TRAILING_WHITESPACE) + +    set("${_output_name}" "${_pkg_result}" CACHE STRING "pkg-config variable +    ${_name} of ${_pkg}") +endfunction() diff --git a/uca-net-camera.c b/uca-net-camera.c new file mode 100644 index 0000000..6088ef3 --- /dev/null +++ b/uca-net-camera.c @@ -0,0 +1,296 @@ +/* Copyright (C) 2011-2013 Matthias Vogelgesang <matthias.vogelgesang@kit.edu> +   (Karlsruhe Institute of Technology) + +   This library is free software; you can redistribute it and/or modify it +   under the terms of the GNU Lesser General Public License as published by the +   Free Software Foundation; either version 2.1 of the License, or (at your +   option) any later version. + +   This library 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 Lesser General Public License for more +   details. + +   You should have received a copy of the GNU Lesser General Public License along +   with this library; if not, write to the Free Software Foundation, Inc., 51 +   Franklin St, Fifth Floor, Boston, MA 02110, USA */ + +#include <stdlib.h> +#include <gio/gio.h> +#include <gmodule.h> +#include <string.h> + +#include <uca/uca-camera.h> +#include "uca-net-camera.h" +#include "uca-net-protocol.h" + + +#define UCA_NET_CAMERA_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), UCA_TYPE_NET_CAMERA, UcaNetCameraPrivate)) + +static void uca_net_camera_initable_iface_init (GInitableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (UcaNetCamera, uca_net_camera, UCA_TYPE_CAMERA, +                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, +                                                uca_net_camera_initable_iface_init)) + +GQuark uca_net_camera_error_quark () +{ +    return g_quark_from_static_string("uca-net-camera-error-quark"); +} + +enum { +    PROP_HOST = N_BASE_PROPERTIES, +    N_PROPERTIES +}; + +static GParamSpec *net_properties[N_PROPERTIES] = { NULL, }; + +struct _UcaNetCameraPrivate { +    GError              *construct_error; +    gchar               *host; +    GSocketConnection   *connection; +    GSocketClient       *client; +    gsize                size; +}; + +static void +uca_net_camera_start_recording (UcaCamera *camera, +                                GError **error) +{ +    UcaNetCameraPrivate *priv; +    guint width; +    guint height; +    guint bits; + +    g_return_if_fail (UCA_IS_NET_CAMERA (camera)); + +    g_object_get (G_OBJECT (camera), +                  "roi-width", &width, +                  "roi-height", &height, +                  "sensor-bitdepth", &bits, +                  NULL); + +    priv = UCA_NET_CAMERA_GET_PRIVATE (camera); +    priv->size = width * height * (bits > 8 ? 2 : 1); + +    uca_net_client_start_recording (priv->connection, error); +} + +static void +uca_net_camera_stop_recording (UcaCamera *camera, +                               GError **error) +{ +    g_return_if_fail (UCA_IS_NET_CAMERA (camera)); +    uca_net_client_stop_recording (UCA_NET_CAMERA_GET_PRIVATE (camera)->connection, error); +} + +static void +uca_net_camera_start_readout (UcaCamera *camera, +                              GError **error) +{ +    g_return_if_fail (UCA_IS_NET_CAMERA (camera)); +} + +static void +uca_net_camera_stop_readout (UcaCamera *camera, +                             GError **error) +{ +    g_return_if_fail (UCA_IS_NET_CAMERA (camera)); +} + +static void +uca_net_camera_write (UcaCamera *camera, +                      const gchar *name, +                      gpointer data, +                      gsize size, +                      GError **error) +{ +    g_return_if_fail (UCA_IS_NET_CAMERA (camera)); +} + +static gboolean +uca_net_camera_grab (UcaCamera *camera, +                     gpointer data, +                     GError **error) +{ +    UcaNetCameraPrivate *priv; + +    g_return_val_if_fail (UCA_IS_NET_CAMERA (camera), FALSE); +    priv = UCA_NET_CAMERA_GET_PRIVATE (camera); +    return uca_net_client_grab (priv->connection, data, priv->size, error); +} + +static void +uca_net_camera_trigger (UcaCamera *camera, +                         GError **error) +{ +    g_return_if_fail (UCA_IS_NET_CAMERA (camera)); +} + +static void +uca_net_camera_set_property (GObject *object, +                             guint property_id, +                             const GValue *value, +                             GParamSpec *pspec) +{ +    UcaNetCameraPrivate *priv; +    const gchar *name; +    GError *error = NULL; + +    priv = UCA_NET_CAMERA_GET_PRIVATE (object); + +    if (property_id == PROP_HOST) { +        g_free (priv->host); +        priv->host = g_value_dup_string (value); +        return; +    } + +    name = g_param_spec_get_name (pspec); + +    if (!uca_net_client_set_property (priv->connection, name, value, &error)) +        g_warning ("Could not set property: %s", error->message); +} + +static void +uca_net_camera_get_property (GObject *object, +                             guint property_id, +                             GValue *value, +                             GParamSpec *pspec) +{ +    UcaNetCameraPrivate *priv; +    const gchar *name; +    GError *error = NULL; + +    priv = UCA_NET_CAMERA_GET_PRIVATE (object); + +    if (property_id == PROP_HOST) { +        g_value_set_string (value, priv->host); +        return; +    } + +    name = g_param_spec_get_name (pspec); + +    if (!uca_net_client_get_property (priv->connection, name, value, &error)) +        g_warning ("Could not get property: %s", error->message); +} + +static void +uca_net_camera_dispose (GObject *object) +{ +    UcaNetCameraPrivate *priv; + +    priv = UCA_NET_CAMERA_GET_PRIVATE (object); + +    if (priv->connection != NULL) { +        uca_net_client_close (priv->connection, NULL); +        g_object_unref (priv->connection); +    } + +    g_object_unref (priv->client); +    G_OBJECT_CLASS (uca_net_camera_parent_class)->dispose (object); +} + +static void +uca_net_camera_finalize (GObject *object) +{ +    UcaNetCameraPrivate *priv; + +    priv = UCA_NET_CAMERA_GET_PRIVATE (object); +    g_clear_error (&priv->construct_error); + +    g_free (priv->host); + +    G_OBJECT_CLASS (uca_net_camera_parent_class)->finalize (object); +} + +static gboolean +ufo_net_camera_initable_init (GInitable *initable, +                               GCancellable *cancellable, +                               GError **error) +{ +    UcaNetCamera *camera; +    UcaNetCameraPrivate *priv; + +    g_return_val_if_fail (UCA_IS_NET_CAMERA (initable), FALSE); + +    if (cancellable != NULL) { +        g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, +                             "Cancellable initialization not supported"); +        return FALSE; +    } + +    camera = UCA_NET_CAMERA (initable); +    priv = camera->priv; + +    if (priv->construct_error != NULL) { +        if (error) +            *error = g_error_copy (priv->construct_error); + +        return FALSE; +    } + +    return TRUE; +} + +static void +uca_net_camera_initable_iface_init (GInitableIface *iface) +{ +    iface->init = ufo_net_camera_initable_init; +} + +static void +uca_net_camera_class_init (UcaNetCameraClass *klass) +{ +    GObjectClass *oclass = G_OBJECT_CLASS (klass); +    UcaCameraClass *camera_class = UCA_CAMERA_CLASS (klass); + +    oclass->set_property = uca_net_camera_set_property; +    oclass->get_property = uca_net_camera_get_property; +    oclass->dispose = uca_net_camera_dispose; +    oclass->finalize = uca_net_camera_finalize; + +    camera_class->start_recording = uca_net_camera_start_recording; +    camera_class->stop_recording = uca_net_camera_stop_recording; +    camera_class->start_readout = uca_net_camera_start_readout; +    camera_class->stop_readout = uca_net_camera_stop_readout; +    camera_class->write = uca_net_camera_write; +    camera_class->grab = uca_net_camera_grab; +    camera_class->trigger = uca_net_camera_trigger; + +    net_properties[PROP_HOST] = +        g_param_spec_string ("host", +                             "Host name and optional port", +                             "Host name and optional port", +                             "localhost", +                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT); + +    for (guint i = PROP_0 + 1; i < N_BASE_PROPERTIES; i++) +        g_object_class_override_property (oclass, i, uca_camera_props[i]); + +    for (guint id = N_BASE_PROPERTIES; id < N_PROPERTIES; id++) +        g_object_class_install_property (oclass, id, net_properties[id]); + +    g_type_class_add_private (klass, sizeof(UcaNetCameraPrivate)); +} + +static void +uca_net_camera_init (UcaNetCamera *self) +{ +    UcaNetCameraPrivate *priv; + +    self->priv = priv = UCA_NET_CAMERA_GET_PRIVATE (self); + +    priv->construct_error = NULL; +    priv->client = g_socket_client_new (); + +    if (priv->host == NULL) +        priv->host = g_strdup ("localhost"); + +    priv->connection = g_socket_client_connect_to_host (priv->client, priv->host, 8989, NULL, &priv->construct_error); +} + +G_MODULE_EXPORT GType +uca_camera_get_type (void) +{ +    return UCA_TYPE_NET_CAMERA; +} diff --git a/uca-net-camera.h b/uca-net-camera.h new file mode 100644 index 0000000..865fb4b --- /dev/null +++ b/uca-net-camera.h @@ -0,0 +1,76 @@ +/* Copyright (C) 2011, 2012 Matthias Vogelgesang <matthias.vogelgesang@kit.edu> +   (Karlsruhe Institute of Technology) + +   This library is free software; you can redistribute it and/or modify it +   under the terms of the GNU Lesser General Public License as published by the +   Free Software Foundation; either version 2.1 of the License, or (at your +   option) any later version. + +   This library 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 Lesser General Public License for more +   details. + +   You should have received a copy of the GNU Lesser General Public License along +   with this library; if not, write to the Free Software Foundation, Inc., 51 +   Franklin St, Fifth Floor, Boston, MA 02110, USA */ + +#ifndef __UCA_NET_CAMERA_H +#define __UCA_NET_CAMERA_H + +#include <glib-object.h> +#include <uca/uca-camera.h> + +G_BEGIN_DECLS + +#define UCA_TYPE_NET_CAMERA             (uca_net_camera_get_type()) +#define UCA_NET_CAMERA(obj)             (G_TYPE_CHECK_INSTANCE_CAST((obj), UCA_TYPE_NET_CAMERA, UcaNetCamera)) +#define UCA_IS_NET_CAMERA(obj)          (G_TYPE_CHECK_INSTANCE_TYPE((obj), UCA_TYPE_NET_CAMERA)) +#define UCA_NET_CAMERA_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST((klass), UCA_TYPE_NET_CAMERA, UcaNetCameraClass)) +#define UCA_IS_NET_CAMERA_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE((klass), UCA_TYPE_NET_CAMERA)) +#define UCA_NET_CAMERA_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS((obj), UCA_TYPE_NET_CAMERA, UcaNetCameraClass)) + +#define UCA_NET_CAMERA_ERROR uca_net_camera_error_quark() +typedef enum { +    UCA_NET_CAMERA_ERROR_INIT, +    UCA_NET_CAMERA_ERROR_START_RECORDING, +    UCA_NET_CAMERA_ERROR_STOP_RECORDING, +    UCA_NET_CAMERA_ERROR_TRIGGER, +    UCA_NET_CAMERA_ERROR_NEXT_EVENT, +    UCA_NET_CAMERA_ERROR_NO_DATA, +    UCA_NET_CAMERA_ERROR_MAYBE_CORRUPTED +} UcaNetCameraError; + +typedef struct _UcaNetCamera           UcaNetCamera; +typedef struct _UcaNetCameraClass      UcaNetCameraClass; +typedef struct _UcaNetCameraPrivate    UcaNetCameraPrivate; + +/** + * UcaNetCamera: + * + * Creates #UcaNetCamera instances by loading corresponding shared objects. The + * contents of the #UcaNetCamera structure are private and should only be + * accessed via the provided API. + */ +struct _UcaNetCamera { +    /*< private >*/ +    UcaCamera parent; + +    UcaNetCameraPrivate *priv; +}; + +/** + * UcaNetCameraClass: + * + * #UcaNetCamera class + */ +struct _UcaNetCameraClass { +    /*< private >*/ +    UcaCameraClass parent; +}; + +GType uca_net_camera_get_type(void); + +G_END_DECLS + +#endif diff --git a/uca-net-client.c b/uca-net-client.c new file mode 100644 index 0000000..c92eedd --- /dev/null +++ b/uca-net-client.c @@ -0,0 +1,207 @@ +#include <string.h> +#include <stdlib.h> +#include "uca-net-protocol.h" + +static gboolean +send_default_message (GSocketConnection *connection, UcaNetMessageType type, GError **error) +{ +    GOutputStream *output; +    UcaNetMessageDefault request; + +    output = g_io_stream_get_output_stream (G_IO_STREAM (connection)); +    request.type = type; + +    if (!g_output_stream_write_all (output, &request, sizeof (request), NULL, NULL, error)) +        return FALSE; + +    if (!g_output_stream_flush (output, NULL, error)) +        return FALSE; + +    return TRUE; +} + +static gboolean +handle_default_reply (GSocketConnection *connection, UcaNetMessageType type, GError **error) +{ +    GInputStream *input; +    UcaNetDefaultReply reply; + +    input = g_io_stream_get_input_stream (G_IO_STREAM (connection)); + +    if (g_input_stream_read_all (input, &reply, sizeof (reply), NULL, NULL, error)) { +        g_assert (reply.type == type); + +        if (reply.error.occurred) { +            g_set_error_literal (error, g_quark_from_string (reply.error.domain), reply.error.code, reply.error.message); +            return FALSE; +        } + +        return TRUE; +    } + +    return FALSE; +} + +gboolean +uca_net_client_get_property (GSocketConnection *connection, const gchar *name, GValue *value, GError **error) +{ +    UcaNetMessageGetPropertyRequest request; +    UcaNetMessageGetPropertyReply reply; +    GInputStream *input; +    GOutputStream *output; + +    input = g_io_stream_get_input_stream (G_IO_STREAM (connection)); +    output = g_io_stream_get_output_stream (G_IO_STREAM (connection)); + +    if (g_input_stream_has_pending (input)) +        g_input_stream_clear_pending (input); + +    /* request */ +    request.type = UCA_NET_MESSAGE_GET_PROPERTY; +    strncpy (request.property_name, name, sizeof (request.property_name)); + +    if (!g_output_stream_write_all (output, &request, sizeof (request), NULL, NULL, error)) +        return FALSE; + +    /* reply */ +    if (g_input_stream_read (input, &reply, sizeof (reply), NULL, error) < 0) +        return FALSE; + +    if (reply.type != request.type) { +        if (*error != NULL) +            /* FIXME: replace with correct error codes */ +            *error = g_error_new_literal (G_FILE_ERROR, G_FILE_ERROR_NOENT, "Reply does not match request"); +        return FALSE; +    } + +    if (g_type_is_a (G_VALUE_TYPE (value), G_TYPE_ENUM)) { +        g_value_set_enum (value, atoi (reply.property_value)); +    } +    else { +        /* XXX: I'd like to avoid this and rather use g_value_transform(), however +         * that call fails with Python and uca-camera-control but succeeds with +         * uca-grab ... */ +        switch (G_VALUE_TYPE (value)) { +            case G_TYPE_UINT: +                g_value_set_uint (value, atol (reply.property_value)); +                break; +            case G_TYPE_DOUBLE: +                g_value_set_double (value, atof (reply.property_value)); +                break; +            case G_TYPE_BOOLEAN: +                g_value_set_boolean (value, g_strcmp0 (reply.property_value, "TRUE")); +                break; +            case G_TYPE_STRING: +                g_value_set_string (value, reply.property_value); +                break; +            default: +                g_warning ("Unsupported property type %s", G_VALUE_TYPE_NAME (value)); +        } +    } +     +    return TRUE; +} + +gboolean +uca_net_client_set_property (GSocketConnection *connection, const gchar *name, const GValue *value, GError **error) +{ +    GOutputStream *output; +    const gchar *str; +    GValue str_value = {0}; +    UcaNetMessageSetPropertyRequest request = { .type = UCA_NET_MESSAGE_SET_PROPERTY }; + +    output = g_io_stream_get_output_stream (G_IO_STREAM (connection)); +    g_value_init (&str_value, G_TYPE_STRING); + +    if (g_type_is_a (G_VALUE_TYPE (value), G_TYPE_ENUM)) { +        GValue int_value = {0}; + +        g_value_init (&int_value, G_TYPE_INT); +        g_value_transform (value, &int_value); +        g_value_transform (&int_value, &str_value); +    } +    else { +        g_value_transform (value, &str_value); +    } + +    str = g_value_get_string (&str_value); +    strncpy (request.property_name, name, sizeof (request.property_name)); +    strncpy (request.property_value, str, sizeof (request.property_value)); + +    if (!g_output_stream_write_all (output, &request, sizeof (request), NULL, NULL, error)) +        return FALSE; + +    return handle_default_reply (connection, UCA_NET_MESSAGE_SET_PROPERTY, error); +} + +void +uca_net_client_start_recording (GSocketConnection *connection, GError **error) +{ +    if (!send_default_message (connection, UCA_NET_MESSAGE_START_RECORDING, error)) +        return; + +    handle_default_reply (connection, UCA_NET_MESSAGE_START_RECORDING, error); +} + +void +uca_net_client_stop_recording (GSocketConnection *connection, GError **error) +{ +    if (!send_default_message (connection, UCA_NET_MESSAGE_STOP_RECORDING, error)) +        return; + +    handle_default_reply (connection, UCA_NET_MESSAGE_STOP_RECORDING, error); +} + +gboolean +uca_net_client_grab (GSocketConnection *connection, gpointer data, gsize size, GError **error) +{ +    GInputStream *input; +    GOutputStream *output; +    gsize transmitted; +    gsize bytes_left; +    UcaNetMessageGrabRequest request = { .type = UCA_NET_MESSAGE_GRAB, .size = size }; + +    input = g_io_stream_get_input_stream (G_IO_STREAM (connection)); +    output = g_io_stream_get_output_stream (G_IO_STREAM (connection)); + +    /* request */ +    if (!g_output_stream_write_all (output, &request, sizeof (request), &transmitted, NULL, error)) { +        return FALSE; +    } + +    /* error reply */ +    if (handle_default_reply (connection, UCA_NET_MESSAGE_GRAB, error)) { +        bytes_left = size; + +        while (bytes_left > 0) { +            gssize read; +            gchar *buffer; + +            buffer = (gchar *) data; +            read = g_input_stream_read (input, &buffer[size - bytes_left], bytes_left, NULL, error); + +            if (read < 0) +                return FALSE; + +            bytes_left -= read; +        } + +        return TRUE; +    } + +    return FALSE; +} + +gboolean +uca_net_client_close (GSocketConnection *connection, GError **error) +{ +    GOutputStream *output; +    UcaNetMessageDefault request = { .type = UCA_NET_MESSAGE_CLOSE_CONNECTION }; + +    output = g_io_stream_get_output_stream (G_IO_STREAM (connection)); + +    if (!g_output_stream_write_all (output, &request, sizeof (request), NULL, NULL, error)) +        return FALSE; + +    return TRUE; +} diff --git a/uca-net-protocol.h b/uca-net-protocol.h new file mode 100644 index 0000000..3df8c37 --- /dev/null +++ b/uca-net-protocol.h @@ -0,0 +1,85 @@ +#ifndef PROTOCOL_H +#define PROTOCOL_H + +#include <gio/gio.h> + +typedef enum { +    UCA_NET_MESSAGE_GET_PROPERTY = 0, +    UCA_NET_MESSAGE_SET_PROPERTY, +    UCA_NET_MESSAGE_START_RECORDING, +    UCA_NET_MESSAGE_STOP_RECORDING, +    UCA_NET_MESSAGE_GRAB, +    UCA_NET_MESSAGE_CLOSE_CONNECTION, +} UcaNetMessageType; + +typedef struct { +    gboolean occurred; +    gchar domain[64]; +    gint code; +    gchar message[512]; +} UcaNetErrorReply; + +typedef struct { +    UcaNetMessageType type; +    UcaNetErrorReply error; +} UcaNetDefaultReply; + +typedef struct { +    UcaNetMessageType type; +} UcaNetMessageDefault; + +typedef struct { +    UcaNetMessageType type; +    gchar property_name[128]; +} UcaNetMessageGetPropertyRequest; + +typedef struct { +    UcaNetMessageType type; +    gchar property_value[128]; +} UcaNetMessageGetPropertyReply; + +typedef struct { +    UcaNetMessageType type; +    gchar property_name[128]; +    gchar property_value[128]; +} UcaNetMessageSetPropertyRequest; + +typedef struct { +    UcaNetMessageType type; +    gsize size; +} UcaNetMessageGrabRequest; + + +typedef struct { +    gpointer user_data; + +    void     (*get_property)    (gpointer user_data, const gchar *name, gchar *value); +    void     (*set_property)    (gpointer user_data, const gchar *name, const gchar *value, GError **error); +    void     (*start_recording) (gpointer user_data, GError **error); +    void     (*stop_recording)  (gpointer user_data, GError **error); +    gboolean (*grab)            (gpointer data, gpointer user_data, GError **error); +} UcaNetHandlers; + +gboolean    uca_net_client_get_property    (GSocketConnection   *connection, +                                            const gchar         *name, +                                            GValue              *value, +                                            GError             **error); +gboolean    uca_net_client_set_property    (GSocketConnection   *connection, +                                            const gchar         *name, +                                            const GValue        *value, +                                            GError             **error); +void        uca_net_client_start_recording (GSocketConnection   *connection, +                                            GError             **error); +void        uca_net_client_stop_recording  (GSocketConnection   *connection, +                                            GError             **error); +gboolean    uca_net_client_grab            (GSocketConnection   *connection, +                                            gpointer             data, +                                            gsize                size, +                                            GError             **error); +gboolean    uca_net_client_close           (GSocketConnection  *connection, +                                            GError             **error); + +void        uca_net_server_register_handlers (UcaNetHandlers    *handlers); +void        uca_net_server_handle           (GSocketConnection  *connection); + +#endif diff --git a/uca-net-server.c b/uca-net-server.c new file mode 100644 index 0000000..f038cd6 --- /dev/null +++ b/uca-net-server.c @@ -0,0 +1,174 @@ +#include <string.h> +#include "uca-net-protocol.h" + +static UcaNetHandlers handlers; + +static void +send_reply (GOutputStream *output, gpointer data, gsize size, GError **error) +{ +    gsize written; + +    if (!g_output_stream_write_all (output, data, size, &written, NULL, error)) +        return; + +    if (!g_output_stream_flush (output, NULL, error)) +        return; +} + +static void +prepare_error_reply (GError *error, UcaNetErrorReply *reply) +{ +    if (error != NULL) { +        reply->occurred = TRUE; +        reply->code = error->code; +        strncpy (reply->domain, g_quark_to_string (error->domain), sizeof (error->domain)); +        strncpy (reply->message, error->message, sizeof (reply->message)); +        g_error_free (error); +    } +    else { +        reply->occurred = FALSE; +    } +} + +static void +uca_net_server_handle_get_property (GOutputStream *output, UcaNetMessageGetPropertyRequest *request, GError **error) +{ +    UcaNetMessageGetPropertyReply reply = { .type = UCA_NET_MESSAGE_GET_PROPERTY }; + +    handlers.get_property (handlers.user_data, request->property_name, reply.property_value); +    send_reply (output, &reply, sizeof (reply), error); +} + +static void +uca_net_server_handle_set_property (GOutputStream *output, UcaNetMessageSetPropertyRequest *request, GError **stream_error) +{ +    UcaNetDefaultReply reply = { .type = UCA_NET_MESSAGE_SET_PROPERTY }; +    GError *error = NULL; + +    handlers.set_property (handlers.user_data, request->property_name, request->property_value, &error); +    prepare_error_reply (error, &reply.error); +    send_reply (output, &reply, sizeof (reply), stream_error); +} + +static void +uca_net_server_handle_start_recording (GOutputStream *output, GError **stream_error) +{ +    UcaNetDefaultReply reply = { .type = UCA_NET_MESSAGE_START_RECORDING }; +    GError *error = NULL; + +    handlers.start_recording (handlers.user_data, &error); +    prepare_error_reply (error, &reply.error); +    send_reply (output, &reply, sizeof (reply), stream_error); +} + +static void +uca_net_server_handle_stop_recording (GOutputStream *output, GError **stream_error) +{ +    UcaNetDefaultReply reply = { .type = UCA_NET_MESSAGE_STOP_RECORDING }; +    GError *error = NULL; + +    handlers.stop_recording (handlers.user_data, &error); +    prepare_error_reply (error, &reply.error); +    send_reply (output, &reply, sizeof (reply), stream_error); +} + +static void +uca_net_server_handle_grab (GOutputStream *output, UcaNetMessageGrabRequest *request, GError **stream_error) +{ +    UcaNetDefaultReply reply = { .type = UCA_NET_MESSAGE_GRAB }; +    gsize bytes_left; +    GError *error = NULL; +    static gsize size = 0; +    static gchar *buffer = NULL; + +    if (buffer == NULL || size != request->size) { +        buffer = g_realloc (buffer, request->size); +        size = request->size; +    } + +    handlers.grab (buffer, handlers.user_data, &error); +    prepare_error_reply (error, &reply.error); +    send_reply (output, &reply, sizeof (reply), stream_error); + +    /* send data if no error occured during grab */ +    if (!reply.error.occurred) { +        bytes_left = size; + +        while (bytes_left > 0) { +            gssize written; + +            written = g_output_stream_write (output, &buffer[size - bytes_left], bytes_left, NULL, stream_error); + +            if (written < 0) +                return; + +            bytes_left -= written; +        } +    } +} + +void +uca_net_server_register_handlers (UcaNetHandlers *new_handlers) +{ +    memcpy (&handlers, new_handlers, sizeof (UcaNetHandlers)); +} + +void +uca_net_server_handle (GSocketConnection *connection) +{ +    GInputStream *input; +    GOutputStream *output; +    gchar *buffer; +    gboolean active; + +    buffer = g_malloc0 (4096); +    input = g_io_stream_get_input_stream (G_IO_STREAM (connection)); +    output = g_io_stream_get_output_stream (G_IO_STREAM (connection)); +    active = TRUE; + +    while (active) { +        UcaNetMessageDefault *message; +        GError *error = NULL; + +        /* looks dangerous */ +        g_input_stream_read (input, buffer, 4096, NULL, &error); +        message = (UcaNetMessageDefault *) buffer; + +        if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE)) { +            g_error_free (error); +            active = FALSE; +            break; +        } + +        switch (message->type) { +            case UCA_NET_MESSAGE_GET_PROPERTY: +                uca_net_server_handle_get_property (output, (UcaNetMessageGetPropertyRequest *) buffer, &error); +                break; +            case UCA_NET_MESSAGE_SET_PROPERTY: +                uca_net_server_handle_set_property (output, (UcaNetMessageSetPropertyRequest *) buffer, &error); +                break; +            case UCA_NET_MESSAGE_START_RECORDING: +                uca_net_server_handle_start_recording (output, &error); +                break; +            case UCA_NET_MESSAGE_STOP_RECORDING: +                uca_net_server_handle_stop_recording (output, &error); +                break; +            case UCA_NET_MESSAGE_GRAB: +                uca_net_server_handle_grab (output, (UcaNetMessageGrabRequest *) buffer, &error); +                break; +            case UCA_NET_MESSAGE_CLOSE_CONNECTION: +                active = FALSE; +                break; +            default: +                g_warning ("Message type not known"); +        } + +        if (error != NULL) { +            g_warning ("Error handling requests: %s", error->message); +            g_error_free (error); +            active = FALSE; +        } +    } + +    g_free (buffer); +} @@ -0,0 +1,223 @@ +/* Copyright (C) 2011-2016 Matthias Vogelgesang <matthias.vogelgesang@kit.edu> +   (Karlsruhe Institute of Technology) + +   This library is free software; you can redistribute it and/or modify it +   under the terms of the GNU Lesser General Public License as published by the +   Free Software Foundation; either version 2.1 of the License, or (at your +   option) any later version. + +   This library 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 Lesser General Public License for more +   details. + +   You should have received a copy of the GNU Lesser General Public License along +   with this library; if not, write to the Free Software Foundation, Inc., 51 +   Franklin St, Fifth Floor, Boston, MA 02110, USA */ + +#include <glib-object.h> +#include <gio/gio.h> +#include <string.h> +#include <uca/uca-camera.h> +#include <uca/uca-plugin-manager.h> +#include "uca-net-protocol.h" + +static gchar * +get_camera_list (UcaPluginManager *manager) +{ +    GList *types; +    GString *str; + +    manager = uca_plugin_manager_new (); +    types = uca_plugin_manager_get_available_cameras (manager); +    str = g_string_new ("[ "); + +    if (types != NULL) { +        for (GList *it = g_list_first (types); it != NULL; it = g_list_next (it)) { +            gchar *name = (gchar *) it->data; + +            if (g_list_next (it) == NULL) +                g_string_append_printf (str, "%s ]", name); +            else +                g_string_append_printf (str, "%s, ", name); +        } +    } +    else { +        g_string_append (str, "]"); +    } + +    g_list_free_full (types, g_free); +    g_object_unref (manager); +    return g_string_free (str, FALSE); +} + +static GOptionContext * +uca_option_context_new (UcaPluginManager *manager) +{ +    GOptionContext *context; +    gchar *camera_list; + +    camera_list = get_camera_list (manager); +    context = g_option_context_new (camera_list); +    g_free (camera_list); +    return context; +} + +static void +handle_get_property_request (gpointer user_data, const gchar *name, gchar *value) +{ +    UcaCamera *camera; +    GParamSpec *pspec; +    GValue prop_value = {0}; +    GValue str_value = {0}; + +    camera = user_data; +    pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (camera), name); + +    g_value_init (&prop_value, g_type_is_a (pspec->value_type, G_TYPE_ENUM) ? G_TYPE_INT : pspec->value_type); +    g_object_get_property (G_OBJECT (camera), name, &prop_value); + +    g_value_init (&str_value, G_TYPE_STRING); +    g_value_transform (&prop_value, &str_value); + +    strncpy (value, g_value_get_string (&str_value), sizeof (g_value_get_string (&str_value))); +} + +static void +handle_set_property_request (gpointer user_data, const gchar *name, const gchar *value, GError **error) +{ +    UcaCamera *camera; +    GParamSpec *pspec; +    GValue prop_value = {0}; +    GValue str_value = {0}; + +    camera = user_data; +    pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (camera), name); + +    g_value_init (&prop_value, g_type_is_a (pspec->value_type, G_TYPE_ENUM) ? G_TYPE_INT : pspec->value_type); +    g_value_init (&str_value, G_TYPE_STRING); +    g_value_set_string (&str_value, value); +    g_value_transform (&str_value, &prop_value); + +    g_object_set_property (G_OBJECT (camera), name, &prop_value); +} + +static void +handle_start_recording_request (gpointer user_data, GError **error) +{ +    uca_camera_start_recording (UCA_CAMERA (user_data), error); +} + +static void +handle_stop_recording_request (gpointer user_data, GError **error) +{ +    uca_camera_stop_recording (UCA_CAMERA (user_data), error); +} + +static gboolean +handle_grab_request (gpointer data, gpointer user_data, GError **error) +{ +    return uca_camera_grab (UCA_CAMERA (user_data), data, error); +} + +static gboolean +run_callback (GSocketService *service, GSocketConnection *connection, GObject *source, gpointer user_data) +{ +    GInetSocketAddress *sock_address; +    GInetAddress *address; +    gchar *address_string; + +    UcaNetHandlers handlers = { +        .user_data = user_data, +        .get_property = handle_get_property_request, +        .set_property = handle_set_property_request, +        .start_recording = handle_start_recording_request, +        .stop_recording = handle_stop_recording_request, +        .grab = handle_grab_request, +    }; + +    sock_address = G_INET_SOCKET_ADDRESS (g_socket_connection_get_remote_address (connection, NULL)); +    address = g_inet_socket_address_get_address (sock_address); +    address_string = g_inet_address_to_string (address); +    g_message ("Connection accepted from %s:%u", address_string, g_inet_socket_address_get_port (sock_address)); + +    g_free (address_string); +    g_object_unref (sock_address); + +    uca_net_server_register_handlers (&handlers); +    uca_net_server_handle (connection); +     +    return FALSE; +} + +static void +serve (UcaCamera *camera, GError **error) +{ +    GMainLoop *loop; +    GSocketService *service; + +    service = g_threaded_socket_service_new (1); + +    if (!g_socket_listener_add_inet_port (G_SOCKET_LISTENER (service), 8989, NULL, error)) +        return; + +    g_signal_connect (service, "run", G_CALLBACK (run_callback), camera); + +    loop = g_main_loop_new (NULL, TRUE); +    g_main_loop_run (loop); +} + +int +main (int argc, char **argv) +{ +    GOptionContext *context; +    UcaPluginManager *manager; +    UcaCamera *camera; +    GError *error = NULL; + +    static GOptionEntry entries[] = { { NULL } }; + +#if !(GLIB_CHECK_VERSION (2, 36, 0)) +    g_type_init(); +#endif + +    manager = uca_plugin_manager_new (); +    context = uca_option_context_new (manager); +    g_option_context_add_main_entries (context, entries, NULL); + +    if (!g_option_context_parse (context, &argc, &argv, &error)) { +        g_print ("Failed parsing arguments: %s\n", error->message); +        goto cleanup_manager; +    } + +    if (argc < 2) { +        g_print ("%s\n", g_option_context_get_help (context, TRUE, NULL)); +        goto cleanup_manager; +    } + +    camera = uca_plugin_manager_get_camera (manager, argv[argc - 1], &error, NULL); + +    if (camera == NULL) { +        g_print ("Error during initialization: %s\n", error->message); +        goto cleanup_camera; +    } + +    if (!uca_camera_parse_arg_props (camera, argv, argc - 1, &error)) { +        g_print ("Error setting properties: %s\n", error->message); +        goto cleanup_manager; +    } +    if (error != NULL) +        g_print ("Error: %s\n", error->message); + +    g_option_context_free (context); + +    serve (camera, &error); + +cleanup_camera: +    g_object_unref (camera); + +cleanup_manager: +    g_object_unref (manager); + +    return error != NULL ? 1 : 0; +} | 
