summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGeorge Kiagiadakis <george.kiagiadakis@collabora.com>2019-06-07 17:44:35 +0300
committerGeorge Kiagiadakis <george.kiagiadakis@collabora.com>2019-06-24 13:32:08 +0300
commit224712ad5f07dccb6acb19ddf8333d822c7928ce (patch)
tree397ba7656f0589b04fea3d5b7bb0e4e3b1dca4e2
parent7dff10809be5feeda6f81b942c8671cdda2e3a27 (diff)
Initial binding version
Signed-off-by: George Kiagiadakis <george.kiagiadakis@collabora.com> Change-Id: I89e493d88c7fa1309f1b2991d346fc496caa6898
-rw-r--r--CMakeLists.txt3
-rw-r--r--LICENSE20
-rw-r--r--README.md5
-rwxr-xr-xautobuild/agl/autobuild79
-rwxr-xr-xautobuild/linux/autobuild79
-rw-r--r--binding/CMakeLists.txt20
-rw-r--r--binding/audiomixer-binding.c342
-rw-r--r--binding/audiomixer.c597
-rw-r--r--binding/audiomixer.h60
-rw-r--r--conf.d/cmake/config.cmake158
-rw-r--r--conf.d/wgt/config.xml.in23
11 files changed, 1386 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..f757721
--- /dev/null
+++ b/CMakeLists.txt
@@ -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
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
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
index 0000000..db00c1a
--- /dev/null
+++ b/autobuild/agl/autobuild
@@ -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
index 0000000..db00c1a
--- /dev/null
+++ b/autobuild/linux/autobuild
@@ -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
index 0000000..587d137
--- /dev/null
+++ b/binding/CMakeLists.txt
@@ -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
index 0000000..005f5c7
--- /dev/null
+++ b/binding/audiomixer-binding.c
@@ -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
index 0000000..c4ccc12
--- /dev/null
+++ b/binding/audiomixer.c
@@ -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
index 0000000..f4e83c7
--- /dev/null
+++ b/binding/audiomixer.h
@@ -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
index 0000000..af2e4c2
--- /dev/null
+++ b/conf.d/cmake/config.cmake
@@ -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
index 0000000..6d535ba
--- /dev/null
+++ b/conf.d/wgt/config.xml.in
@@ -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>