Initial binding version 09/21709/1
authorGeorge Kiagiadakis <george.kiagiadakis@collabora.com>
Fri, 7 Jun 2019 14:44:35 +0000 (17:44 +0300)
committerGeorge Kiagiadakis <george.kiagiadakis@collabora.com>
Mon, 24 Jun 2019 10:32:08 +0000 (13:32 +0300)
Signed-off-by: George Kiagiadakis <george.kiagiadakis@collabora.com>
Change-Id: I89e493d88c7fa1309f1b2991d346fc496caa6898

CMakeLists.txt [new file with mode: 0644]
LICENSE [new file with mode: 0644]
README.md [new file with mode: 0644]
autobuild/agl/autobuild [new file with mode: 0755]
autobuild/linux/autobuild [new file with mode: 0755]
binding/CMakeLists.txt [new file with mode: 0644]
binding/audiomixer-binding.c [new file with mode: 0644]
binding/audiomixer.c [new file with mode: 0644]
binding/audiomixer.h [new file with mode: 0644]
conf.d/cmake/config.cmake [new file with mode: 0644]
conf.d/wgt/config.xml.in [new file with mode: 0644]

diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..f757721
--- /dev/null
@@ -0,0 +1,3 @@
+CMAKE_MINIMUM_REQUIRED(VERSION 3.3)
+
+include(${CMAKE_CURRENT_SOURCE_DIR}/conf.d/cmake/config.cmake)
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..71a9785
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright © 2019 Collabora Ltd.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice (including the next
+paragraph) shall be included in all copies or substantial portions of the
+Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..102189c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,5 @@
+# Audio Mixer Service
+
+Audio mixer binding service for AGL.
+
+This binding exposes PipeWire mixer controls to applications.
diff --git a/autobuild/agl/autobuild b/autobuild/agl/autobuild
new file mode 100755 (executable)
index 0000000..db00c1a
--- /dev/null
@@ -0,0 +1,79 @@
+#!/usr/bin/make -f
+# Copyright (C) 2015 - 2018 "IoT.bzh"
+# Author "Romain Forlot" <romain.forlot@iot.bzh>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+THISFILE  := $(lastword $(MAKEFILE_LIST))
+BUILD_DIR := $(abspath $(dir $(THISFILE))/../../build)
+DEST      := ${BUILD_DIR}
+
+.PHONY: all clean distclean configure build package help update
+
+all: help
+
+help:
+       @echo "List of targets available:"
+       @echo ""
+       @echo "- all"
+       @echo "- clean"
+       @echo "- distclean"
+       @echo "- configure"
+       @echo "- build: compilation, link and prepare files for package into a widget"
+       @echo "- package: output a widget file '*.wgt'"
+       @echo "- install: install in your ${CMAKE_INSTALL_DIR} directory"
+       @echo ""
+       @echo "Usage: ./autobuild/agl/autobuild package DEST=${HOME}/opt"
+       @echo "Don't use your build dir as DEST as wgt file is generated at this location"
+
+update: configure
+       @cmake --build ${BUILD_DIR} --target autobuild
+
+clean:
+       @([ -d ${BUILD_DIR} ] && make -C ${BUILD_DIR} ${CLEAN_ARGS} clean) || echo Nothing to clean
+
+distclean:
+       @rm -rf ${BUILD_DIR}
+
+configure:
+       @[ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR}
+       @[ -f ${BUILD_DIR}/Makefile ] || (cd ${BUILD_DIR} && cmake ${CONFIGURE_ARGS} ..)
+
+build: configure
+       @cmake --build ${BUILD_DIR} ${BUILD_ARGS} --target all
+
+package: build
+       @mkdir -p ${BUILD_DIR}/$@/bin
+       @mkdir -p ${BUILD_DIR}/$@/etc
+       @mkdir -p ${BUILD_DIR}/$@/lib
+       @mkdir -p ${BUILD_DIR}/$@/htdocs
+       @mkdir -p ${BUILD_DIR}/$@/var
+       @cmake --build ${BUILD_DIR} ${PACKAGE_ARGS} --target widget
+       @if [ "${DEST}" != "${BUILD_DIR}" ]; then \
+               mkdir -p ${DEST} && cp ${BUILD_DIR}/*.wgt ${DEST}; \
+       fi
+
+package-test: build
+       @mkdir -p ${BUILD_DIR}/$@/bin
+       @mkdir -p ${BUILD_DIR}/$@/etc
+       @mkdir -p ${BUILD_DIR}/$@/lib
+       @mkdir -p ${BUILD_DIR}/$@/htdocs
+       @mkdir -p ${BUILD_DIR}/$@/var
+       @cmake --build ${BUILD_DIR} ${PACKAGE_ARGS} --target widget
+       @cmake --build ${BUILD_DIR} ${PACKAGE_ARGS} --target test_widget
+       @if [ "${DEST}" != "${BUILD_DIR}" ]; then \
+               mkdir -p ${DEST} && cp ${BUILD_DIR}/*.wgt ${DEST}; \
+       fi
+
+install: build
+       @cmake --build ${BUILD_DIR} ${INSTALL_ARGS} --target install
diff --git a/autobuild/linux/autobuild b/autobuild/linux/autobuild
new file mode 100755 (executable)
index 0000000..db00c1a
--- /dev/null
@@ -0,0 +1,79 @@
+#!/usr/bin/make -f
+# Copyright (C) 2015 - 2018 "IoT.bzh"
+# Author "Romain Forlot" <romain.forlot@iot.bzh>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+THISFILE  := $(lastword $(MAKEFILE_LIST))
+BUILD_DIR := $(abspath $(dir $(THISFILE))/../../build)
+DEST      := ${BUILD_DIR}
+
+.PHONY: all clean distclean configure build package help update
+
+all: help
+
+help:
+       @echo "List of targets available:"
+       @echo ""
+       @echo "- all"
+       @echo "- clean"
+       @echo "- distclean"
+       @echo "- configure"
+       @echo "- build: compilation, link and prepare files for package into a widget"
+       @echo "- package: output a widget file '*.wgt'"
+       @echo "- install: install in your ${CMAKE_INSTALL_DIR} directory"
+       @echo ""
+       @echo "Usage: ./autobuild/agl/autobuild package DEST=${HOME}/opt"
+       @echo "Don't use your build dir as DEST as wgt file is generated at this location"
+
+update: configure
+       @cmake --build ${BUILD_DIR} --target autobuild
+
+clean:
+       @([ -d ${BUILD_DIR} ] && make -C ${BUILD_DIR} ${CLEAN_ARGS} clean) || echo Nothing to clean
+
+distclean:
+       @rm -rf ${BUILD_DIR}
+
+configure:
+       @[ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR}
+       @[ -f ${BUILD_DIR}/Makefile ] || (cd ${BUILD_DIR} && cmake ${CONFIGURE_ARGS} ..)
+
+build: configure
+       @cmake --build ${BUILD_DIR} ${BUILD_ARGS} --target all
+
+package: build
+       @mkdir -p ${BUILD_DIR}/$@/bin
+       @mkdir -p ${BUILD_DIR}/$@/etc
+       @mkdir -p ${BUILD_DIR}/$@/lib
+       @mkdir -p ${BUILD_DIR}/$@/htdocs
+       @mkdir -p ${BUILD_DIR}/$@/var
+       @cmake --build ${BUILD_DIR} ${PACKAGE_ARGS} --target widget
+       @if [ "${DEST}" != "${BUILD_DIR}" ]; then \
+               mkdir -p ${DEST} && cp ${BUILD_DIR}/*.wgt ${DEST}; \
+       fi
+
+package-test: build
+       @mkdir -p ${BUILD_DIR}/$@/bin
+       @mkdir -p ${BUILD_DIR}/$@/etc
+       @mkdir -p ${BUILD_DIR}/$@/lib
+       @mkdir -p ${BUILD_DIR}/$@/htdocs
+       @mkdir -p ${BUILD_DIR}/$@/var
+       @cmake --build ${BUILD_DIR} ${PACKAGE_ARGS} --target widget
+       @cmake --build ${BUILD_DIR} ${PACKAGE_ARGS} --target test_widget
+       @if [ "${DEST}" != "${BUILD_DIR}" ]; then \
+               mkdir -p ${DEST} && cp ${BUILD_DIR}/*.wgt ${DEST}; \
+       fi
+
+install: build
+       @cmake --build ${BUILD_DIR} ${INSTALL_ARGS} --target install
diff --git a/binding/CMakeLists.txt b/binding/CMakeLists.txt
new file mode 100644 (file)
index 0000000..587d137
--- /dev/null
@@ -0,0 +1,20 @@
+PROJECT_TARGET_ADD(audiomixer-binding)
+
+       add_definitions(-DAFB_BINDING_VERSION=3)
+       add_definitions(-DBUILDING_APPFW_BINDING)
+
+       set(audiomixer_SOURCES
+               audiomixer-binding.c
+               audiomixer.c
+       )
+
+       add_library(${TARGET_NAME} MODULE ${audiomixer_SOURCES})
+
+       SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES
+               PREFIX "libafm-"
+               LABELS "BINDING"
+               LINK_FLAGS ${BINDINGS_LINK_FLAG}
+               OUTPUT_NAME ${TARGET_NAME}
+       )
+
+       TARGET_LINK_LIBRARIES(${TARGET_NAME} ${link_libraries})
diff --git a/binding/audiomixer-binding.c b/binding/audiomixer-binding.c
new file mode 100644 (file)
index 0000000..005f5c7
--- /dev/null
@@ -0,0 +1,342 @@
+/*
+ * Copyright © 2019 Collabora Ltd.
+ *   @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <string.h>
+#include <json-c/json.h>
+#include <afb/afb-binding.h>
+#include <systemd/sd-event.h>
+#include "audiomixer.h"
+
+static struct audiomixer *audiomixer;
+static afb_event_t controls_changed;
+static afb_event_t volume_changed;
+static afb_event_t mute_changed;
+static sd_event_source *controls_changed_source;
+
+static int
+audiomixer_controls_changed_deferred(sd_event_source *s, void *data)
+{
+       afb_event_push(controls_changed, NULL);
+
+       sd_event_source_unref(controls_changed_source);
+       controls_changed_source = NULL;
+       return 0;
+}
+
+struct value_changed_data
+{
+       unsigned int change_mask;
+       struct mixer_control control;
+       sd_event_source *source;
+};
+
+static int
+audiomixer_value_changed_deferred(sd_event_source *s, void *data)
+{
+       struct value_changed_data *d = data;
+       json_object *json;
+
+       json = json_object_new_object();
+       json_object_object_add(json, "control",
+               json_object_new_string(d->control.name));
+
+       if (d->change_mask & MIXER_CONTROL_CHANGE_FLAG_VOLUME) {
+               json_object_object_add(json, "value",
+                       json_object_new_double(d->control.volume));
+               afb_event_push(volume_changed, json);
+       } else if (d->change_mask & MIXER_CONTROL_CHANGE_FLAG_MUTE) {
+               json_object_object_add(json, "value",
+                       json_object_new_int(d->control.mute));
+               afb_event_push(mute_changed, json);
+       }
+
+       sd_event_source_unref(d->source);
+       free(d);
+       return 0;
+}
+
+/* called in audiomixer's thread */
+static void
+audiomixer_controls_changed(void *data)
+{
+       sd_event *e = afb_daemon_get_event_loop();
+       sd_event_add_defer(e, &controls_changed_source,
+               audiomixer_controls_changed_deferred, NULL);
+}
+
+
+/* called in audiomixer's thread */
+static void
+audiomixer_value_changed(void *data,
+       unsigned int change_mask,
+       const struct mixer_control *control)
+{
+       sd_event *e = afb_daemon_get_event_loop();
+       struct value_changed_data *d = calloc(1, sizeof(*d));
+
+       d->change_mask = change_mask;
+       d->control = *control;
+
+       if (sd_event_add_defer(e, &d->source,
+                       audiomixer_value_changed_deferred, d) < 0)
+               free(d);
+}
+
+static const struct audiomixer_events audiomixer_events = {
+       .controls_changed = audiomixer_controls_changed,
+       .value_changed = audiomixer_value_changed,
+};
+
+static int
+cleanup(sd_event_source *s, void *data)
+{
+       audiomixer_free(audiomixer);
+       audiomixer = NULL;
+       return 0;
+}
+
+static int
+init(afb_api_t api)
+{
+       sd_event *e = afb_daemon_get_event_loop();
+
+       controls_changed = afb_api_make_event(api, "controls_changed");
+       volume_changed = afb_api_make_event(api, "volume_changed");
+       mute_changed = afb_api_make_event(api, "mute_changed");
+
+       audiomixer = audiomixer_new();
+       sd_event_add_exit(e, NULL, cleanup, NULL);
+
+       audiomixer_add_event_listener(audiomixer, &audiomixer_events, NULL);
+
+       return 0;
+}
+
+static void
+list_controls_cb(afb_req_t request)
+{
+       json_object *ret_json, *nest_json;
+       const struct mixer_control **ctls;
+       unsigned int n_controls, i;
+
+       audiomixer_lock(audiomixer);
+
+       if (audiomixer_ensure_connected(audiomixer, 3) < 0) {
+               afb_req_fail(request, "failed",
+                       "Could not connect to the PipeWire daemon");
+               goto unlock;
+       }
+
+       if (audiomixer_ensure_controls(audiomixer, 3) < 0) {
+               AFB_REQ_NOTICE(request, "No mixer controls were exposed "
+                       "in PipeWire after 3 seconds");
+       }
+
+       ctls = audiomixer_get_active_controls(audiomixer, &n_controls);
+
+       ret_json = json_object_new_array();
+       for (i = 0; i < n_controls; i++) {
+               nest_json = json_object_new_object();
+               json_object_object_add(nest_json, "control",
+                               json_object_new_string(ctls[i]->name));
+               json_object_object_add(nest_json, "volume",
+                               json_object_new_double(ctls[i]->volume));
+               json_object_object_add(nest_json, "mute",
+                               json_object_new_int(ctls[i]->mute));
+               json_object_array_add(ret_json, nest_json);
+       }
+       afb_req_success(request, ret_json, NULL);
+
+unlock:
+       audiomixer_unlock(audiomixer);
+}
+
+static void
+volume_cb(afb_req_t request)
+{
+       json_object *ret_json;
+       const char *control = afb_req_value(request, "control");
+       const char *value = afb_req_value(request, "value");
+       const struct mixer_control *ctl;
+       double volume;
+
+       audiomixer_lock(audiomixer);
+
+       if (!control) {
+               afb_req_fail(request, "failed",
+                       "Invalid arguments: missing 'control'");
+               goto unlock;
+       }
+
+       if (audiomixer_ensure_connected(audiomixer, 3) < 0) {
+               afb_req_fail(request, "failed",
+                       "Could not connect to the PipeWire daemon");
+               goto unlock;
+       }
+
+       if (audiomixer_ensure_controls(audiomixer, 3) < 0) {
+               AFB_REQ_NOTICE(request, "No mixer controls were exposed "
+                       "in PipeWire after 3 seconds");
+       }
+
+       ctl = audiomixer_find_control(audiomixer, control);
+       if (!ctl) {
+               afb_req_fail(request, "failed", "Could not find control");
+               goto unlock;
+       }
+
+       if(value) {
+               char *endptr;
+               volume = strtod(value, &endptr);
+               if (endptr == value || volume < -0.00001 || volume > 1.00001) {
+                       afb_req_fail(request, "failed",
+                               "Invalid volume value (must be between 0.0 and 1.0)");
+                       goto unlock;
+               }
+
+               audiomixer_change_volume(audiomixer, ctl, volume);
+       } else {
+               volume = ctl->volume;
+       }
+
+       ret_json = json_object_new_object();
+       json_object_object_add(ret_json, "volume", json_object_new_double(volume));
+       afb_req_success(request, ret_json, NULL);
+
+unlock:
+       audiomixer_unlock(audiomixer);
+}
+
+static void
+mute_cb(afb_req_t request)
+{
+       json_object *ret_json;
+       const char *control = afb_req_value(request, "control");
+       const char *value = afb_req_value(request, "value");
+       const struct mixer_control *ctl;
+       int mute;
+
+       audiomixer_lock(audiomixer);
+
+       if (!control) {
+               afb_req_fail(request, "failed",
+                       "Invalid arguments: missing 'control'");
+               goto unlock;
+       }
+
+       if (audiomixer_ensure_connected(audiomixer, 3) < 0) {
+               afb_req_fail(request, "failed",
+                       "Could not connect to the PipeWire daemon");
+               goto unlock;
+       }
+
+       if (audiomixer_ensure_controls(audiomixer, 3) < 0) {
+               AFB_REQ_NOTICE(request, "No mixer controls were exposed "
+                       "in PipeWire after 3 seconds");
+       }
+
+       ctl = audiomixer_find_control(audiomixer, control);
+       if (!ctl) {
+               afb_req_fail(request, "failed", "Could not find control");
+               goto unlock;
+       }
+
+       if(value) {
+               char *endptr;
+               mute = (int) strtol(value, &endptr, 10);
+               if (endptr == value || mute < 0 || mute > 1) {
+                       afb_req_fail(request, "failed",
+                               "Invalid mute value (must be integer 0 or 1)");
+                       goto unlock;
+               }
+
+               audiomixer_change_mute(audiomixer, ctl, mute);
+       } else {
+               mute = ctl->mute;
+       }
+
+       ret_json = json_object_new_object();
+       json_object_object_add(ret_json, "mute", json_object_new_int(mute));
+       afb_req_success(request, ret_json, NULL);
+
+unlock:
+       audiomixer_unlock(audiomixer);
+}
+
+static void
+subscribe_cb(afb_req_t request)
+{
+       const char *eventstr = afb_req_value(request, "event");
+       afb_event_t event;
+
+       if (!eventstr) {
+               afb_req_fail(request, "failed",
+                       "Invalid arguments: missing 'event'");
+               return;
+       }
+
+       if (!strcmp(eventstr, "controls_changed"))
+               event = controls_changed;
+       else if (!strcmp(eventstr, "volume_changed"))
+               event = volume_changed;
+       else if (!strcmp(eventstr, "mute_changed"))
+               event = mute_changed;
+       else {
+               afb_req_fail(request, "failed", "Invalid event name");
+               return;
+       }
+
+       if (afb_req_subscribe(request, event) != 0)
+               afb_req_fail(request, "failed", "Failed to subscribe to event");
+       else
+               afb_req_success(request, NULL, "Subscribed");
+}
+
+static void
+unsubscribe_cb(afb_req_t request)
+{
+       const char *eventstr = afb_req_value(request, "event");
+       afb_event_t event;
+
+       if (!eventstr) {
+               afb_req_fail(request, "failed",
+                       "Invalid arguments: missing 'event'");
+               return;
+       }
+
+       if (!strcmp(eventstr, "controls_changed"))
+               event = controls_changed;
+       else if (!strcmp(eventstr, "volume_changed"))
+               event = volume_changed;
+       else if (!strcmp(eventstr, "mute_changed"))
+               event = mute_changed;
+       else {
+               afb_req_fail(request, "failed", "Invalid event name");
+               return;
+       }
+
+       if (afb_req_unsubscribe(request, event) != 0)
+               afb_req_fail(request, "failed", "Failed to unsubscribe from event");
+       else
+               afb_req_success(request, NULL, "Unsubscribed");
+}
+
+static const afb_verb_t verbs[]= {
+       { .verb = "list_controls", .callback = list_controls_cb, .info = "List the available controls" },
+       { .verb = "volume",        .callback = volume_cb,        .info = "Get/Set volume" },
+       { .verb = "mute",          .callback = mute_cb,          .info = "Get/Set mute" },
+       { .verb = "subscribe",     .callback = subscribe_cb,     .info = "Subscribe to mixer events" },
+       { .verb = "unsubscribe",   .callback = unsubscribe_cb,   .info = "Unsubscribe from mixer events" },
+       { }
+};
+
+const afb_binding_t afbBindingV3 = {
+       .api  = "audiomixer",
+       .specification = "AudioMixer API",
+       .verbs = verbs,
+       .init = init,
+};
diff --git a/binding/audiomixer.c b/binding/audiomixer.c
new file mode 100644 (file)
index 0000000..c4ccc12
--- /dev/null
@@ -0,0 +1,597 @@
+/*
+ * Copyright © 2019 Collabora Ltd.
+ *   @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "audiomixer.h"
+#include <pipewire/pipewire.h>
+#include <pipewire/array.h>
+#include <pipewire/extensions/endpoint.h>
+
+#if !defined(BUILDING_APPFW_BINDING)
+#define debug(...) fprintf(stdout, __VA_ARGS__)
+#else
+#include <afb/afb-binding.h>
+#define debug(...) AFB_DEBUG(__VA_ARGS__)
+#endif
+
+struct audiomixer
+{
+       struct pw_thread_loop *main_loop;
+
+       struct pw_core *core;
+       struct pw_remote *remote;
+       struct spa_hook remote_listener;
+
+       struct pw_core_proxy *core_proxy;
+       struct spa_hook remote_core_listener;
+       struct pw_registry_proxy *registry_proxy;
+       struct spa_hook registry_listener;
+
+       struct pw_array endpoints;
+       struct pw_array all_mixer_controls;
+
+       const struct audiomixer_events *events;
+       void *events_data;
+};
+
+enum endpoint_state {
+       EP_STATE_INITIAL,
+       EP_STATE_COLLECT_ENUM_STREAM,
+       EP_STATE_COLLECT_ENUM_CONTROL,
+       EP_STATE_COLLECT_CONTROL,
+       EP_STATE_ACTIVE,
+};
+
+struct endpoint
+{
+       struct audiomixer *audiomixer;
+       struct pw_endpoint_proxy *proxy;
+
+       struct pw_properties *properties;
+       enum endpoint_state state;
+
+       struct spa_hook proxy_listener;
+       struct spa_hook endpoint_listener;
+
+       struct pw_array mixer_controls;
+};
+
+struct mixer_control_impl
+{
+       struct mixer_control pub;
+       struct endpoint *endpoint;
+       uint32_t stream_id;
+       uint32_t volume_control_id;
+       uint32_t mute_control_id;
+};
+
+static void
+emit_controls_changed(struct audiomixer *self)
+{
+       pw_thread_loop_signal(self->main_loop, false);
+
+       if (!self->events || !self->events->controls_changed)
+               return;
+
+       self->events->controls_changed(self->events_data);
+}
+
+static void
+emit_value_changed(struct audiomixer *self,
+       unsigned int change_mask,
+       struct mixer_control *ctl)
+{
+       if (!self->events || !self->events->value_changed)
+               return;
+
+       self->events->value_changed(self->events_data, change_mask, ctl);
+}
+
+static void
+advance_endpoint_state(struct endpoint *endpoint)
+{
+       debug("%p advance endpoint state, was:%d", endpoint, endpoint->state);
+
+       switch (endpoint->state) {
+       case EP_STATE_INITIAL:
+               endpoint->state = EP_STATE_COLLECT_ENUM_STREAM;
+               pw_endpoint_proxy_enum_params(endpoint->proxy, 0,
+                       PW_ENDPOINT_PARAM_EnumStream, 0, -1, NULL);
+               pw_proxy_sync((struct pw_proxy *) endpoint->proxy, 0);
+               break;
+       case EP_STATE_COLLECT_ENUM_STREAM:
+               endpoint->state = EP_STATE_COLLECT_ENUM_CONTROL;
+               pw_endpoint_proxy_enum_params(endpoint->proxy, 0,
+                       PW_ENDPOINT_PARAM_EnumControl, 0, -1, NULL);
+               pw_proxy_sync((struct pw_proxy *) endpoint->proxy, 0);
+               break;
+       case EP_STATE_COLLECT_ENUM_CONTROL: {
+               uint32_t ids[1] = { PW_ENDPOINT_PARAM_Control };
+
+               endpoint->state = EP_STATE_COLLECT_CONTROL;
+               pw_endpoint_proxy_subscribe_params(endpoint->proxy, ids, 1);
+               pw_proxy_sync((struct pw_proxy *) endpoint->proxy, 0);
+               break;
+       }
+       case EP_STATE_COLLECT_CONTROL: {
+               struct mixer_control_impl *ctl;
+               struct audiomixer *self = endpoint->audiomixer;
+
+               endpoint->state = EP_STATE_ACTIVE;
+               pw_array_for_each(ctl, &endpoint->mixer_controls) {
+                       pw_array_add_ptr(&self->all_mixer_controls, ctl);
+               }
+               emit_controls_changed(self);
+               break;
+       }
+       default:
+               break;
+       }
+}
+
+static void
+endpoint_param (void *object, int seq, uint32_t id,
+               uint32_t index, uint32_t next,
+               const struct spa_pod *param)
+{
+       struct endpoint *endpoint = object;
+       struct mixer_control_impl *ctl;
+       const struct spa_pod_prop *prop;
+       struct spa_pod_object *obj = (struct spa_pod_object *) param;
+
+       if (!spa_pod_is_object(param)) {
+               debug("endpoint_param: bad param - not an object");
+               return;
+       }
+
+       switch (id) {
+       case PW_ENDPOINT_PARAM_EnumStream:
+               /* verify conditions */
+               if (endpoint->state != EP_STATE_COLLECT_ENUM_STREAM) {
+                       debug("endpoint_param EnumStream: wrong state");
+                       return;
+               }
+               if (SPA_POD_OBJECT_TYPE(obj) != PW_ENDPOINT_OBJECT_ParamStream) {
+                       debug("endpoint_param EnumStream: invalid param");
+                       return;
+               }
+
+               /* create new mixer control */
+               ctl = pw_array_add(&endpoint->mixer_controls, sizeof(*ctl));
+               ctl->endpoint = endpoint;
+
+               SPA_POD_OBJECT_FOREACH(obj, prop) {
+                       switch (prop->key) {
+                       case PW_ENDPOINT_PARAM_STREAM_id:
+                               spa_pod_get_int(&prop->value, &ctl->stream_id);
+                               break;
+                       case PW_ENDPOINT_PARAM_STREAM_name:
+                               spa_pod_copy_string(&prop->value,
+                                       SPA_N_ELEMENTS(ctl->pub.name),
+                                       ctl->pub.name);
+                               break;
+                       default:
+                               break;
+                       }
+               }
+               break;
+
+       case PW_ENDPOINT_PARAM_EnumControl: {
+               uint32_t tmp_id = -1;
+               const char *name = NULL;
+
+               /* verify conditions */
+               if (endpoint->state != EP_STATE_COLLECT_ENUM_CONTROL) {
+                       debug("endpoint_param EnumControl: wrong state");
+                       return;
+               }
+               if (SPA_POD_OBJECT_TYPE(obj) != PW_ENDPOINT_OBJECT_ParamControl) {
+                       debug("endpoint_param EnumControl: invalid param");
+                       return;
+               }
+
+               /* find the mixer control */
+               prop = spa_pod_object_find_prop(obj, NULL,
+                       PW_ENDPOINT_PARAM_CONTROL_stream_id);
+               if (prop)
+                       spa_pod_get_int(&prop->value, &tmp_id);
+               else {
+                       debug("endpoint_param EnumControl: invalid control without stream");
+                       return;
+               }
+
+               pw_array_for_each(ctl, &endpoint->mixer_controls) {
+                       if (ctl->stream_id == tmp_id)
+                               break;
+               }
+
+               /* check if we reached the end of the array
+                * without finding the stream */
+               if (!pw_array_check(&endpoint->mixer_controls, ctl)) {
+                       debug("endpoint_param EnumControl: could not find "
+                               "stream id %u", tmp_id);
+                       return;
+               }
+
+               /* store the control id based on the control's name */
+               prop = spa_pod_object_find_prop(obj, NULL,
+                       PW_ENDPOINT_PARAM_CONTROL_name);
+               if (prop)
+                       spa_pod_get_string(&prop->value, &name);
+               else {
+                       debug("endpoint_param EnumControl: invalid control without name");
+                       return;
+               }
+
+               prop = spa_pod_object_find_prop(obj, NULL,
+                       PW_ENDPOINT_PARAM_CONTROL_id);
+               if (!prop) {
+                       debug("endpoint_param EnumControl: invalid control without id");
+                       return;
+               }
+
+               if (strcmp (name, "volume")) {
+                       spa_pod_get_int(&prop->value, &ctl->volume_control_id);
+                       prop = spa_pod_object_find_prop(obj, NULL,
+                               PW_ENDPOINT_PARAM_CONTROL_type);
+               } else if (strcmp (name, "mute")) {
+                       spa_pod_get_int(&prop->value, &ctl->mute_control_id);
+               }
+
+               break;
+       }
+       case PW_ENDPOINT_PARAM_Control: {
+               uint32_t tmp_id = -1;
+
+               /* verify conditions */
+               if (endpoint->state != EP_STATE_COLLECT_CONTROL ||
+                   endpoint->state != EP_STATE_ACTIVE) {
+                       debug("endpoint_param Control: wrong state");
+                       return;
+               }
+               if (SPA_POD_OBJECT_TYPE(obj) != PW_ENDPOINT_OBJECT_ParamControl) {
+                       debug("endpoint_param Control: invalid param");
+                       return;
+               }
+
+               /* match the control id and set the value */
+               prop = spa_pod_object_find_prop(obj, NULL,
+                       PW_ENDPOINT_PARAM_CONTROL_id);
+               if (prop)
+                       spa_pod_get_int(&prop->value, &tmp_id);
+               else {
+                       debug("endpoint_param Control: invalid control without id");
+                       return;
+               }
+
+               prop = spa_pod_object_find_prop(obj, NULL,
+                       PW_ENDPOINT_PARAM_CONTROL_value);
+               if (!prop) {
+                       debug("endpoint_param Control: invalid control without value");
+                       return;
+               }
+
+               pw_array_for_each(ctl, &endpoint->mixer_controls) {
+                       if (ctl->volume_control_id == tmp_id) {
+                               spa_pod_get_double(&prop->value, &ctl->pub.volume);
+
+                               if (endpoint->state == EP_STATE_ACTIVE) {
+                                       emit_value_changed(endpoint->audiomixer,
+                                               MIXER_CONTROL_CHANGE_FLAG_VOLUME,
+                                               &ctl->pub);
+                               }
+                               break;
+                       } else if (ctl->mute_control_id == tmp_id) {
+                               spa_pod_get_bool(&prop->value, &ctl->pub.mute);
+
+                               if (endpoint->state == EP_STATE_ACTIVE) {
+                                       emit_value_changed(endpoint->audiomixer,
+                                               MIXER_CONTROL_CHANGE_FLAG_MUTE,
+                                               &ctl->pub);
+                               }
+                               break;
+                       }
+               }
+
+               break;
+       }
+       default:
+               break;
+       }
+}
+
+struct pw_endpoint_proxy_events endpoint_events = {
+       PW_VERSION_ENDPOINT_PROXY_EVENTS,
+       .param = endpoint_param,
+};
+
+static void
+endpoint_proxy_destroyed(void *object)
+{
+       struct endpoint *endpoint = object;
+       struct audiomixer *self = endpoint->audiomixer;
+       struct mixer_control_impl *ctl;
+       struct mixer_control **ctlptr;
+       struct endpoint **epptr;
+
+       debug("%p endpoint destroyed", endpoint);
+
+       if (endpoint->properties)
+               pw_properties_free(endpoint->properties);
+
+       pw_array_for_each(ctl, &endpoint->mixer_controls) {
+               pw_array_for_each(ctlptr, &self->all_mixer_controls) {
+                       if (*ctlptr == &ctl->pub) {
+                               pw_array_remove(&self->all_mixer_controls, ctlptr);
+                               break;
+                       }
+               }
+       }
+       emit_controls_changed(self);
+       pw_array_clear(&endpoint->mixer_controls);
+
+       pw_array_for_each(epptr, &self->endpoints) {
+               if (*epptr == endpoint) {
+                       pw_array_remove(&self->endpoints, epptr);
+                       break;
+               }
+       }
+}
+
+static void
+endpoint_proxy_done(void *object, int seq)
+{
+       struct endpoint *endpoint = object;
+       advance_endpoint_state(endpoint);
+}
+
+struct pw_proxy_events proxy_events = {
+       PW_VERSION_PROXY_EVENTS,
+       .destroy = endpoint_proxy_destroyed,
+       .done = endpoint_proxy_done,
+};
+
+static void
+registry_event_global(void *data, uint32_t id, uint32_t parent_id,
+               uint32_t permissions, uint32_t type, uint32_t version,
+               const struct spa_dict *props)
+{
+       struct audiomixer *self = data;
+       const char *media_class;
+       struct pw_proxy *proxy;
+       struct endpoint *endpoint;
+
+       if (type != PW_TYPE_INTERFACE_Endpoint)
+               return;
+
+       /* we are only interested in mixer endpoints */
+       media_class = props ? spa_dict_lookup(props, "media.class") : NULL;
+       if (!media_class || strcmp(media_class, "Mixer/Audio") != 0)
+               return;
+
+       proxy = pw_registry_proxy_bind(self->registry_proxy,
+               id, type, PW_VERSION_ENDPOINT, sizeof(struct endpoint));
+
+       endpoint = pw_proxy_get_user_data(proxy);
+       endpoint->audiomixer = self;
+       endpoint->proxy = (struct pw_endpoint_proxy *) proxy;
+       endpoint->properties = props ? pw_properties_new_dict(props) : NULL;
+       endpoint->state = EP_STATE_INITIAL;
+       pw_array_init(&endpoint->mixer_controls, 4 * sizeof(struct mixer_control));
+
+       pw_proxy_add_listener(proxy, &endpoint->proxy_listener,
+               &proxy_events, endpoint);
+       pw_endpoint_proxy_add_listener(endpoint->proxy,
+               &endpoint->endpoint_listener,
+               &endpoint_events, endpoint);
+
+       debug("%p added endpoint: %u", endpoint, id);
+
+       pw_array_add_ptr(&self->endpoints, endpoint);
+       advance_endpoint_state(endpoint);
+}
+
+
+static const struct pw_registry_proxy_events registry_events = {
+       PW_VERSION_REGISTRY_PROXY_EVENTS,
+       .global = registry_event_global,
+};
+
+static void
+on_remote_state_changed(void *data, enum pw_remote_state old,
+               enum pw_remote_state state, const char *error)
+{
+       struct audiomixer *self = data;
+
+       if (state == PW_REMOTE_STATE_CONNECTED) {
+               self->core_proxy = pw_remote_get_core_proxy(self->remote);
+               self->registry_proxy = pw_core_proxy_get_registry(
+                       self->core_proxy,
+                       PW_TYPE_INTERFACE_Registry,
+                       PW_VERSION_REGISTRY, 0);
+               pw_registry_proxy_add_listener(self->registry_proxy,
+                       &self->registry_listener,
+                       &registry_events, self);
+       }
+
+       pw_thread_loop_signal(self->main_loop, false);
+}
+
+static const struct pw_remote_events remote_events = {
+       PW_VERSION_REMOTE_EVENTS,
+       .state_changed = on_remote_state_changed,
+};
+
+struct audiomixer *
+audiomixer_new(void)
+{
+       struct audiomixer *self;
+       struct pw_loop *loop;
+
+       pw_init(NULL, NULL);
+
+       self = calloc(1, sizeof(struct audiomixer));
+       loop = pw_loop_new(NULL);
+       self->main_loop = pw_thread_loop_new(loop, "audiomixer-loop");
+       self->core = pw_core_new(loop, NULL, 0);
+       self->remote = pw_remote_new(self->core, NULL, 0);
+       pw_array_init(&self->endpoints, 1 * sizeof(void*));
+       pw_array_init(&self->all_mixer_controls, 8 * sizeof(void*));
+
+       pw_module_load(self->core, "libpipewire-module-endpoint", NULL, NULL,
+               NULL, NULL);
+       pw_thread_loop_start(self->main_loop);
+
+       return self;
+}
+
+void
+audiomixer_free(struct audiomixer *self)
+{
+       struct pw_loop *loop;
+
+       pw_thread_loop_lock(self->main_loop);
+       self->events = NULL;
+       self->events_data = NULL;
+       pw_remote_disconnect(self->remote);
+       pw_thread_loop_unlock(self->main_loop);
+       pw_thread_loop_stop(self->main_loop);
+
+       pw_array_clear(&self->endpoints);
+       pw_array_clear(&self->all_mixer_controls);
+       pw_remote_destroy(self->remote);
+       pw_core_destroy(self->core);
+
+       loop = pw_thread_loop_get_loop(self->main_loop);
+       pw_thread_loop_destroy(self->main_loop);
+       pw_loop_destroy(loop);
+
+       free(self);
+}
+
+void
+audiomixer_lock(struct audiomixer *self)
+{
+       pw_thread_loop_lock(self->main_loop);
+}
+
+void
+audiomixer_unlock(struct audiomixer *self)
+{
+       pw_thread_loop_unlock(self->main_loop);
+}
+
+int
+audiomixer_ensure_connected(struct audiomixer *self, int timeout_sec)
+{
+       enum pw_remote_state state;
+       int res;
+
+       state = pw_remote_get_state(self->remote, NULL);
+       if (state == PW_REMOTE_STATE_CONNECTED)
+               return 0;
+
+       if ((res = pw_remote_connect(self->remote)) < 0)
+               return res;
+
+       while (true) {
+               state = pw_remote_get_state(self->remote, NULL);
+               if (state == PW_REMOTE_STATE_CONNECTED)
+                       return 0;
+               else if (state == PW_REMOTE_STATE_ERROR)
+                       return -EIO;
+
+               if (pw_thread_loop_timed_wait(self->main_loop, timeout_sec) != 0)
+                       return -ETIMEDOUT;
+       }
+}
+
+int
+audiomixer_ensure_controls(struct audiomixer *self, int timeout_sec)
+{
+       while (pw_array_get_len(&self->all_mixer_controls, void*) == 0) {
+               if (pw_thread_loop_timed_wait(self->main_loop, timeout_sec) != 0)
+                       return -ETIMEDOUT;
+       }
+       return 0;
+}
+
+const struct mixer_control **
+audiomixer_get_active_controls(struct audiomixer *self,
+       unsigned int *n_controls)
+{
+       const struct mixer_control **ret;
+
+       *n_controls = pw_array_get_len(&self->all_mixer_controls, void*);
+       ret = (const struct mixer_control **)
+               pw_array_first(&self->all_mixer_controls);
+
+       return ret;
+}
+
+const struct mixer_control *
+audiomixer_find_control(struct audiomixer *self, const char *name)
+{
+       struct mixer_control **ctlptr;
+
+       pw_array_for_each(ctlptr, &self->all_mixer_controls) {
+               if (!strcmp((*ctlptr)->name, name)) {
+                       return *ctlptr;
+               }
+       }
+       return NULL;
+}
+
+void
+audiomixer_add_event_listener(struct audiomixer *self,
+       const struct audiomixer_events *events,
+       void *data)
+{
+       self->events = events;
+       self->events_data = data;
+}
+
+void
+audiomixer_change_volume(struct audiomixer *self,
+       const struct mixer_control *control,
+       double volume)
+{
+       const struct mixer_control_impl *impl =
+               (const struct mixer_control_impl *) control;
+       struct endpoint *endpoint = impl->endpoint;
+       char buffer[1024];
+       struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, 1024);
+       struct spa_pod *param;
+
+       param = spa_pod_builder_add_object(&b,
+               PW_ENDPOINT_OBJECT_ParamControl, PW_ENDPOINT_PARAM_Control,
+               PW_ENDPOINT_PARAM_CONTROL_id, SPA_POD_Int(impl->volume_control_id),
+               PW_ENDPOINT_PARAM_CONTROL_value, SPA_POD_Double(volume),
+               NULL);
+       pw_endpoint_proxy_set_param(endpoint->proxy,
+               PW_ENDPOINT_PARAM_Control, 0, param);
+}
+
+void
+audiomixer_change_mute(struct audiomixer *self,
+       const struct mixer_control *control,
+       bool mute)
+{
+       const struct mixer_control_impl *impl =
+               (const struct mixer_control_impl *) control;
+       struct endpoint *endpoint = impl->endpoint;
+       char buffer[1024];
+       struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, 1024);
+       struct spa_pod *param;
+
+       param = spa_pod_builder_add_object(&b,
+               PW_ENDPOINT_OBJECT_ParamControl, PW_ENDPOINT_PARAM_Control,
+               PW_ENDPOINT_PARAM_CONTROL_id, SPA_POD_Int(impl->mute_control_id),
+               PW_ENDPOINT_PARAM_CONTROL_value, SPA_POD_Bool(mute),
+               NULL);
+       pw_endpoint_proxy_set_param(endpoint->proxy,
+               PW_ENDPOINT_PARAM_Control, 0, param);
+}
diff --git a/binding/audiomixer.h b/binding/audiomixer.h
new file mode 100644 (file)
index 0000000..f4e83c7
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright © 2019 Collabora Ltd.
+ *   @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <stdbool.h>
+
+struct audiomixer;
+
+struct mixer_control
+{
+       char name[32];
+       double volume;
+       bool mute;
+};
+
+struct audiomixer_events
+{
+       void (*controls_changed) (void *data);
+
+       void (*value_changed) (void *data,
+#define MIXER_CONTROL_CHANGE_FLAG_VOLUME (1<<0)
+#define MIXER_CONTROL_CHANGE_FLAG_MUTE   (1<<1)
+                               unsigned int change_mask,
+                               const struct mixer_control *control);
+};
+
+struct audiomixer * audiomixer_new(void);
+void audiomixer_free(struct audiomixer *self);
+
+/* locking is required to call any of the methods below
+ * and to access any structure maintained by audiomixer */
+void audiomixer_lock(struct audiomixer *self);
+void audiomixer_unlock(struct audiomixer *self);
+
+int audiomixer_ensure_connected(struct audiomixer *self, int timeout_sec);
+int audiomixer_ensure_controls(struct audiomixer *self, int timeout_sec);
+
+const struct mixer_control ** audiomixer_get_active_controls(
+       struct audiomixer *self,
+       unsigned int *n_controls);
+
+const struct mixer_control * audiomixer_find_control(
+       struct audiomixer *self,
+       const char *name);
+
+void audiomixer_add_event_listener(struct audiomixer *self,
+       const struct audiomixer_events *events,
+       void *data);
+
+void audiomixer_change_volume(struct audiomixer *self,
+       const struct mixer_control *control,
+       double volume);
+
+void audiomixer_change_mute(struct audiomixer *self,
+       const struct mixer_control *control,
+       bool mute);
+
diff --git a/conf.d/cmake/config.cmake b/conf.d/cmake/config.cmake
new file mode 100644 (file)
index 0000000..af2e4c2
--- /dev/null
@@ -0,0 +1,158 @@
+###########################################################################
+# Copyright 2015, 2016, 2017 IoT.bzh
+#
+# author: Fulup Ar Foll <fulup@iot.bzh>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+###########################################################################
+
+# Project Info
+# ------------------
+set(PROJECT_NAME agl-service-audiomixer)
+set(PROJECT_PRETTY_NAME "Audio mixer binding service")
+set(PROJECT_DESCRIPTION "Expose PipeWire mixer controls through the AGL Framework")
+set(PROJECT_VERSION "0.1")
+set(PROJECT_ICON "icon.png")
+set(PROJECT_AUTHOR "George Kiagiadakis")
+set(PROJECT_AUTHOR_MAIL "george.kiagiadakis@collabora.com")
+set(PROJECT_LICENSE "MIT")
+set(PROJECT_LANGUAGES,"C")
+set(API_NAME "audiomixer")
+
+# Where are stored the project configuration files
+# relative to the root project directory
+set(PROJECT_CMAKE_CONF_DIR "conf.d")
+
+# Where are stored your external libraries for your project. This is 3rd party library that you don't maintain
+# but used and must be built and linked.
+# set(PROJECT_LIBDIR "libs")
+
+# Where are stored data for your application. Pictures, static resources must be placed in that folder.
+# set(PROJECT_RESOURCES "data")
+
+# Which directories inspect to find CMakeLists.txt target files
+# set(PROJECT_SRC_DIR_PATTERN "*")
+
+# Compilation Mode (DEBUG, RELEASE)
+# ----------------------------------
+set(BUILD_TYPE "RELEASE")
+
+# Kernel selection if needed. You can choose between a
+# mandatory version to impose a minimal version.
+# Or check Kernel minimal version and just print a Warning
+# about missing features and define a preprocessor variable
+# to be used as preprocessor condition in code to disable
+# incompatibles features. Preprocessor define is named
+# KERNEL_MINIMAL_VERSION_OK.
+#
+# NOTE*** FOR NOW IT CHECKS KERNEL Yocto environment and
+# Yocto SDK Kernel version.
+# -----------------------------------------------
+#set(kernel_mandatory_version 4.8)
+
+# Compiler selection if needed. Impose a minimal version.
+# -----------------------------------------------
+set (gcc_minimal_version 4.9)
+
+# PKG_CONFIG required packages
+# -----------------------------
+set (PKG_REQUIRED_LIST
+       json-c
+       libsystemd>=222
+       afb-daemon
+       libpipewire-0.3
+)
+
+# Static constante definition
+# -----------------------------
+add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-pthread>)
+
+# Customize link option
+# -----------------------------
+list (APPEND link_libraries -pthread)
+
+# ---------------------------------------------------------------------
+set(INSTALL_PREFIX $ENV{HOME}/opt)
+
+# Optional location for config.xml.in
+# -----------------------------------
+set(WIDGET_CONFIG_TEMPLATE ${CMAKE_CURRENT_SOURCE_DIR}/conf.d/wgt/config.xml.in)
+
+# Mandatory widget Mimetype specification of the main unit
+# --------------------------------------------------------------------------
+# Choose between :
+#- text/html : HTML application,
+#      content.src designates the home page of the application
+#
+#- application/vnd.agl.native : AGL compatible native,
+#      content.src designates the relative path of the binary.
+#
+# - application/vnd.agl.service: AGL service, content.src is not used.
+#
+#- ***application/x-executable***: Native application,
+#      content.src designates the relative path of the binary.
+#      For such application, only security setup is made.
+#
+set(WIDGET_TYPE application/vnd.agl.service)
+
+# Mandatory Widget entry point file of the main unit
+# --------------------------------------------------------------
+# This is the file that will be executed, loaded,
+# at launch time by the application framework.
+#
+set(WIDGET_ENTRY_POINT lib/libafm-audiomixer-binding.so)
+
+# Print a helper message when every thing is finished
+# ----------------------------------------------------
+set(CLOSING_MESSAGE "Test with: afb-daemon --rootdir=\$\$(pwd)/package --binding=\$\$(pwd)/package/${WIDGET_ENTRY_POINT} --port=1234 --tracereq=common --token=\"1\" --verbose")
+set(PACKAGE_MESSAGE "Install widget file using in the target : afm-util install ${PROJECT_NAME}.wgt")
+
+
+
+# Optional dependencies order
+# ---------------------------
+#set(EXTRA_DEPENDENCIES_ORDER)
+
+# Optional Extra global include path
+# -----------------------------------
+#set(EXTRA_INCLUDE_DIRS)
+
+# Optional extra libraries
+# -------------------------
+#set(EXTRA_LINK_LIBRARIES)
+
+# Optional force binding installation
+# ------------------------------------
+# set(BINDINGS_INSTALL_PREFIX PrefixPath )
+
+# Optional force binding Linking flag
+# ------------------------------------
+# set(BINDINGS_LINK_FLAG LinkOptions )
+
+# Optional force package prefix generation, like widget
+# -----------------------------------------------------
+# set(PKG_PREFIX DestinationPath)
+
+# Optional Application Framework security token
+# and port use for remote debugging.
+#------------------------------------------------------------
+#set(AFB_TOKEN   ""      CACHE PATH "Default AFB_TOKEN")
+#set(AFB_REMPORT "1234" CACHE PATH "Default AFB_TOKEN")
+
+# This include is mandatory and MUST happens at the end
+# of this file, else you expose you to unexpected behavior
+#
+# This CMake module could be found at the following url:
+# https://gerrit.automotivelinux.org/gerrit/#/admin/projects/src/cmake-apps-module
+# -----------------------------------------------------------
+include(CMakeAfbTemplates)
diff --git a/conf.d/wgt/config.xml.in b/conf.d/wgt/config.xml.in
new file mode 100644 (file)
index 0000000..6d535ba
--- /dev/null
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<widget xmlns="http://www.w3.org/ns/widgets" id="@PROJECT_NAME@" version="@PROJECT_VERSION@">
+       <name>@PROJECT_NAME@</name>
+       <icon src="@PROJECT_ICON@"/>
+       <content src="@WIDGET_ENTRY_POINT@" type="@WIDGET_TYPE@"/>
+       <description>@PROJECT_DESCRIPTION@</description>
+       <author>@PROJECT_AUTHOR@ &lt;@PROJECT_AUTHOR_MAIL@&gt;</author>
+       <license>@PROJECT_LICENSE@</license>
+
+       <feature name="urn:AGL:widget:required-permission">
+               <param name="urn:AGL:permission::public:hidden" value="required" />
+               <param name="urn:AGL:permission::public:no-htdocs" value="required" />
+       </feature>
+
+       <feature name="urn:AGL:widget:provided-api">
+               <param name="audiomixer" value="ws" />
+       </feature>
+
+       <feature name="urn:AGL:widget:required-binding">
+               <param name="@WIDGET_ENTRY_POINT@" value="local" />
+       </feature>
+
+</widget>